summaryrefslogtreecommitdiffstats
path: root/comm/mailnews/base/src
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
commit6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch)
treea68f146d7fa01f0134297619fbe7e33db084e0aa /comm/mailnews/base/src
parentInitial commit. (diff)
downloadthunderbird-upstream.tar.xz
thunderbird-upstream.zip
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--comm/mailnews/base/src/ABQueryUtils.jsm159
-rw-r--r--comm/mailnews/base/src/FolderLookupService.jsm163
-rw-r--r--comm/mailnews/base/src/FolderUtils.jsm364
-rw-r--r--comm/mailnews/base/src/HeaderReader.h305
-rw-r--r--comm/mailnews/base/src/JXON.jsm159
-rw-r--r--comm/mailnews/base/src/LineReader.h188
-rw-r--r--comm/mailnews/base/src/LineReader.jsm68
-rw-r--r--comm/mailnews/base/src/MailAuthenticator.jsm468
-rw-r--r--comm/mailnews/base/src/MailChannel.sys.mjs71
-rw-r--r--comm/mailnews/base/src/MailCryptoUtils.jsm76
-rw-r--r--comm/mailnews/base/src/MailNewsDLF.cpp84
-rw-r--r--comm/mailnews/base/src/MailNewsDLF.h37
-rw-r--r--comm/mailnews/base/src/MailNotificationManager.jsm478
-rw-r--r--comm/mailnews/base/src/MailNotificationService.jsm375
-rw-r--r--comm/mailnews/base/src/MailServices.jsm169
-rw-r--r--comm/mailnews/base/src/MailStringUtils.jsm102
-rw-r--r--comm/mailnews/base/src/MailnewsLoadContextInfo.cpp51
-rw-r--r--comm/mailnews/base/src/MailnewsLoadContextInfo.h32
-rw-r--r--comm/mailnews/base/src/MailnewsMigrator.jsm352
-rw-r--r--comm/mailnews/base/src/MsgAsyncPrompter.jsm621
-rw-r--r--comm/mailnews/base/src/MsgDBCacheManager.jsm185
-rw-r--r--comm/mailnews/base/src/MsgIncomingServer.jsm1268
-rw-r--r--comm/mailnews/base/src/MsgKeySet.jsm132
-rw-r--r--comm/mailnews/base/src/MsgProtocolInfo.sys.mjs53
-rw-r--r--comm/mailnews/base/src/OAuth2.jsm364
-rw-r--r--comm/mailnews/base/src/OAuth2Module.jsm203
-rw-r--r--comm/mailnews/base/src/OAuth2Providers.jsm259
-rw-r--r--comm/mailnews/base/src/TemplateUtils.jsm90
-rw-r--r--comm/mailnews/base/src/UrlListener.cpp22
-rw-r--r--comm/mailnews/base/src/UrlListener.h72
-rw-r--r--comm/mailnews/base/src/VirtualFolderWrapper.jsm257
-rw-r--r--comm/mailnews/base/src/WinUnreadBadge.jsm246
-rw-r--r--comm/mailnews/base/src/components.conf359
-rw-r--r--comm/mailnews/base/src/converterWorker.js533
-rw-r--r--comm/mailnews/base/src/hostnameUtils.jsm366
-rw-r--r--comm/mailnews/base/src/mailstoreConverter.jsm339
-rw-r--r--comm/mailnews/base/src/moz.build154
-rw-r--r--comm/mailnews/base/src/nsCidProtocolHandler.cpp49
-rw-r--r--comm/mailnews/base/src/nsCidProtocolHandler.h25
-rw-r--r--comm/mailnews/base/src/nsCopyMessageStreamListener.cpp106
-rw-r--r--comm/mailnews/base/src/nsCopyMessageStreamListener.h29
-rw-r--r--comm/mailnews/base/src/nsImapMoveCoalescer.cpp198
-rw-r--r--comm/mailnews/base/src/nsImapMoveCoalescer.h71
-rw-r--r--comm/mailnews/base/src/nsMailAuthModule.cpp85
-rw-r--r--comm/mailnews/base/src/nsMailAuthModule.h27
-rw-r--r--comm/mailnews/base/src/nsMailChannel.cpp139
-rw-r--r--comm/mailnews/base/src/nsMailChannel.h30
-rw-r--r--comm/mailnews/base/src/nsMailDirProvider.cpp160
-rw-r--r--comm/mailnews/base/src/nsMailDirProvider.h42
-rw-r--r--comm/mailnews/base/src/nsMailDirServiceDefs.h31
-rw-r--r--comm/mailnews/base/src/nsMessenger.cpp2446
-rw-r--r--comm/mailnews/base/src/nsMessenger.h118
-rw-r--r--comm/mailnews/base/src/nsMessengerBootstrap.cpp84
-rw-r--r--comm/mailnews/base/src/nsMessengerBootstrap.h30
-rw-r--r--comm/mailnews/base/src/nsMessengerOSXIntegration.h24
-rw-r--r--comm/mailnews/base/src/nsMessengerOSXIntegration.mm63
-rw-r--r--comm/mailnews/base/src/nsMessengerUnixIntegration.cpp24
-rw-r--r--comm/mailnews/base/src/nsMessengerUnixIntegration.h22
-rw-r--r--comm/mailnews/base/src/nsMessengerWinIntegration.cpp379
-rw-r--r--comm/mailnews/base/src/nsMessengerWinIntegration.h39
-rw-r--r--comm/mailnews/base/src/nsMsgAccount.cpp413
-rw-r--r--comm/mailnews/base/src/nsMsgAccount.h34
-rw-r--r--comm/mailnews/base/src/nsMsgAccountManager.cpp3546
-rw-r--r--comm/mailnews/base/src/nsMsgAccountManager.h211
-rw-r--r--comm/mailnews/base/src/nsMsgBiffManager.cpp341
-rw-r--r--comm/mailnews/base/src/nsMsgBiffManager.h52
-rw-r--r--comm/mailnews/base/src/nsMsgCompressIStream.cpp203
-rw-r--r--comm/mailnews/base/src/nsMsgCompressIStream.h33
-rw-r--r--comm/mailnews/base/src/nsMsgCompressOStream.cpp128
-rw-r--r--comm/mailnews/base/src/nsMsgCompressOStream.h26
-rw-r--r--comm/mailnews/base/src/nsMsgContentPolicy.cpp928
-rw-r--r--comm/mailnews/base/src/nsMsgContentPolicy.h95
-rw-r--r--comm/mailnews/base/src/nsMsgCopyService.cpp587
-rw-r--r--comm/mailnews/base/src/nsMsgCopyService.h91
-rw-r--r--comm/mailnews/base/src/nsMsgDBFolder.cpp5573
-rw-r--r--comm/mailnews/base/src/nsMsgDBFolder.h366
-rw-r--r--comm/mailnews/base/src/nsMsgDBView.cpp7411
-rw-r--r--comm/mailnews/base/src/nsMsgDBView.h558
-rw-r--r--comm/mailnews/base/src/nsMsgEnumerator.cpp138
-rw-r--r--comm/mailnews/base/src/nsMsgEnumerator.h45
-rw-r--r--comm/mailnews/base/src/nsMsgFileStream.cpp190
-rw-r--r--comm/mailnews/base/src/nsMsgFileStream.h35
-rw-r--r--comm/mailnews/base/src/nsMsgFolderCache.cpp570
-rw-r--r--comm/mailnews/base/src/nsMsgFolderCache.h60
-rw-r--r--comm/mailnews/base/src/nsMsgFolderCompactor.cpp1391
-rw-r--r--comm/mailnews/base/src/nsMsgFolderCompactor.h48
-rw-r--r--comm/mailnews/base/src/nsMsgFolderNotificationService.cpp174
-rw-r--r--comm/mailnews/base/src/nsMsgFolderNotificationService.h46
-rw-r--r--comm/mailnews/base/src/nsMsgGroupThread.cpp731
-rw-r--r--comm/mailnews/base/src/nsMsgGroupThread.h88
-rw-r--r--comm/mailnews/base/src/nsMsgGroupView.cpp941
-rw-r--r--comm/mailnews/base/src/nsMsgGroupView.h78
-rw-r--r--comm/mailnews/base/src/nsMsgI18N.cpp403
-rw-r--r--comm/mailnews/base/src/nsMsgI18N.h138
-rw-r--r--comm/mailnews/base/src/nsMsgIdentity.cpp645
-rw-r--r--comm/mailnews/base/src/nsMsgIdentity.h87
-rw-r--r--comm/mailnews/base/src/nsMsgIncomingServer.cpp2142
-rw-r--r--comm/mailnews/base/src/nsMsgIncomingServer.h103
-rw-r--r--comm/mailnews/base/src/nsMsgKeySet.cpp1412
-rw-r--r--comm/mailnews/base/src/nsMsgKeySet.h108
-rw-r--r--comm/mailnews/base/src/nsMsgLineBuffer.cpp351
-rw-r--r--comm/mailnews/base/src/nsMsgLineBuffer.h123
-rw-r--r--comm/mailnews/base/src/nsMsgMailNewsUrl.cpp1070
-rw-r--r--comm/mailnews/base/src/nsMsgMailNewsUrl.h142
-rw-r--r--comm/mailnews/base/src/nsMsgMailSession.cpp671
-rw-r--r--comm/mailnews/base/src/nsMsgMailSession.h110
-rw-r--r--comm/mailnews/base/src/nsMsgOfflineManager.cpp352
-rw-r--r--comm/mailnews/base/src/nsMsgOfflineManager.h79
-rw-r--r--comm/mailnews/base/src/nsMsgProgress.cpp250
-rw-r--r--comm/mailnews/base/src/nsMsgProgress.h45
-rw-r--r--comm/mailnews/base/src/nsMsgProtocol.cpp1512
-rw-r--r--comm/mailnews/base/src/nsMsgProtocol.h263
-rw-r--r--comm/mailnews/base/src/nsMsgPurgeService.cpp496
-rw-r--r--comm/mailnews/base/src/nsMsgPurgeService.h51
-rw-r--r--comm/mailnews/base/src/nsMsgQuickSearchDBView.cpp806
-rw-r--r--comm/mailnews/base/src/nsMsgQuickSearchDBView.h99
-rw-r--r--comm/mailnews/base/src/nsMsgReadStateTxn.cpp43
-rw-r--r--comm/mailnews/base/src/nsMsgReadStateTxn.h44
-rw-r--r--comm/mailnews/base/src/nsMsgSearchDBView.cpp1344
-rw-r--r--comm/mailnews/base/src/nsMsgSearchDBView.h182
-rw-r--r--comm/mailnews/base/src/nsMsgSpecialViews.cpp163
-rw-r--r--comm/mailnews/base/src/nsMsgSpecialViews.h82
-rw-r--r--comm/mailnews/base/src/nsMsgStatusFeedback.cpp261
-rw-r--r--comm/mailnews/base/src/nsMsgStatusFeedback.h48
-rw-r--r--comm/mailnews/base/src/nsMsgTagService.cpp458
-rw-r--r--comm/mailnews/base/src/nsMsgTagService.h50
-rw-r--r--comm/mailnews/base/src/nsMsgThreadedDBView.cpp899
-rw-r--r--comm/mailnews/base/src/nsMsgThreadedDBView.h62
-rw-r--r--comm/mailnews/base/src/nsMsgTxn.cpp247
-rw-r--r--comm/mailnews/base/src/nsMsgTxn.h77
-rw-r--r--comm/mailnews/base/src/nsMsgUtils.cpp1926
-rw-r--r--comm/mailnews/base/src/nsMsgUtils.h462
-rw-r--r--comm/mailnews/base/src/nsMsgWindow.cpp327
-rw-r--r--comm/mailnews/base/src/nsMsgWindow.h52
-rw-r--r--comm/mailnews/base/src/nsMsgXFViewThread.cpp444
-rw-r--r--comm/mailnews/base/src/nsMsgXFViewThread.h51
-rw-r--r--comm/mailnews/base/src/nsMsgXFVirtualFolderDBView.cpp514
-rw-r--r--comm/mailnews/base/src/nsMsgXFVirtualFolderDBView.h70
-rw-r--r--comm/mailnews/base/src/nsNewMailnewsURI.cpp155
-rw-r--r--comm/mailnews/base/src/nsNewMailnewsURI.h15
-rw-r--r--comm/mailnews/base/src/nsQuarantinedOutputStream.cpp234
-rw-r--r--comm/mailnews/base/src/nsQuarantinedOutputStream.h72
-rw-r--r--comm/mailnews/base/src/nsSpamSettings.cpp806
-rw-r--r--comm/mailnews/base/src/nsSpamSettings.h68
-rw-r--r--comm/mailnews/base/src/nsStatusBarBiffManager.cpp254
-rw-r--r--comm/mailnews/base/src/nsStatusBarBiffManager.h37
-rw-r--r--comm/mailnews/base/src/nsStopwatch.cpp164
-rw-r--r--comm/mailnews/base/src/nsStopwatch.h51
-rw-r--r--comm/mailnews/base/src/nsSubscribableServer.cpp867
-rw-r--r--comm/mailnews/base/src/nsSubscribableServer.h59
-rw-r--r--comm/mailnews/base/src/nsUserInfo.h23
-rw-r--r--comm/mailnews/base/src/nsUserInfoMac.mm70
-rw-r--r--comm/mailnews/base/src/nsUserInfoUnix.cpp124
-rw-r--r--comm/mailnews/base/src/nsUserInfoWin.cpp99
154 files changed, 60938 insertions, 0 deletions
diff --git a/comm/mailnews/base/src/ABQueryUtils.jsm b/comm/mailnews/base/src/ABQueryUtils.jsm
new file mode 100644
index 0000000000..4944971ddf
--- /dev/null
+++ b/comm/mailnews/base/src/ABQueryUtils.jsm
@@ -0,0 +1,159 @@
+/* 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 contains helper methods for dealing with addressbook search URIs.
+ */
+
+const EXPORTED_SYMBOLS = [
+ "getSearchTokens",
+ "getModelQuery",
+ "modelQueryHasUserValue",
+ "generateQueryURI",
+ "encodeABTermValue",
+];
+
+/**
+ * Parse the multiword search string to extract individual search terms
+ * (separated on the basis of spaces) or quoted exact phrases to search
+ * against multiple fields of the addressbook cards.
+ *
+ * @param {string} aSearchString - The full search string entered by the user.
+ *
+ * @returns {Array} Array of separated search terms from the full search string.
+ */
+function getSearchTokens(aSearchString) {
+ // Trim leading and trailing whitespace and comma(s) to prevent empty search
+ // words when splitting unquoted parts of search string below.
+ let searchString = aSearchString
+ .replace(/^[,\s]+/, "")
+ .replace(/[,\s]+$/, "");
+ if (searchString == "") {
+ return [];
+ }
+
+ let quotedTerms = [];
+
+ // Split up multiple search words to create a *foo* and *bar* search against
+ // search fields, using the OR-search template from modelQuery for each word.
+ // If the search query has quoted terms like "foo bar", extract them as is.
+ let startIndex;
+ while ((startIndex = searchString.indexOf('"')) != -1) {
+ let endIndex = searchString.indexOf('"', startIndex + 1);
+ if (endIndex == -1) {
+ endIndex = searchString.length;
+ }
+
+ quotedTerms.push(searchString.substring(startIndex + 1, endIndex));
+ let query = searchString.substring(0, startIndex);
+ if (endIndex < searchString.length) {
+ query += searchString.substr(endIndex + 1);
+ }
+
+ searchString = query.trim();
+ }
+
+ let searchWords = [];
+ if (searchString.length != 0) {
+ // Split non-quoted search terms on whitespace and comma(s): Allow flexible
+ // incremental searches, and prevent false negatives for |Last, First| with
+ // |View > Show Name As > Last, First|, where comma is not found in data.
+ searchWords = quotedTerms.concat(searchString.split(/[,\s]+/));
+ } else {
+ searchWords = quotedTerms;
+ }
+
+ return searchWords;
+}
+
+/**
+ * For AB quicksearch or recipient autocomplete, get the normal or phonetic model
+ * query URL part from prefs, allowing users to customize these searches.
+ *
+ * @param {string} aBasePrefName - The full pref name of default, non-phonetic
+ * model query, e.g. mail.addr_book.quicksearchquery.format. If phonetic
+ * search is used, corresponding pref must exist:
+ * e.g. mail.addr_book.quicksearchquery.format.phonetic
+ * @returns {boolean} depending on mail.addr_book.show_phonetic_fields pref,
+ * the value of aBasePrefName or aBasePrefName + ".phonetic"
+ */
+function getModelQuery(aBasePrefName) {
+ let modelQuery = "";
+ if (
+ Services.prefs.getComplexValue(
+ "mail.addr_book.show_phonetic_fields",
+ Ci.nsIPrefLocalizedString
+ ).data == "true"
+ ) {
+ modelQuery = Services.prefs.getCharPref(aBasePrefName + ".phonetic");
+ } else {
+ modelQuery = Services.prefs.getCharPref(aBasePrefName);
+ }
+ // remove leading "?" to migrate existing customized values for mail.addr_book.quicksearchquery.format
+ // todo: could this be done in a once-off migration at install time to avoid repetitive calls?
+ if (modelQuery.startsWith("?")) {
+ modelQuery = modelQuery.slice(1);
+ }
+ return modelQuery;
+}
+
+/**
+ * Check if the currently used pref with the model query was customized by user.
+ *
+ * @param {string} aBasePrefName - The full pref name of default, non-phonetic
+ * model query, e.g. mail.addr_book.quicksearchquery.format
+ * If phonetic search is used, corresponding pref must exist:
+ * e.g. mail.addr_book.quicksearchquery.format.phonetic
+ * @returns {boolean} true or false
+ */
+function modelQueryHasUserValue(aBasePrefName) {
+ if (
+ Services.prefs.getComplexValue(
+ "mail.addr_book.show_phonetic_fields",
+ Ci.nsIPrefLocalizedString
+ ).data == "true"
+ ) {
+ return Services.prefs.prefHasUserValue(aBasePrefName + ".phonetic");
+ }
+ return Services.prefs.prefHasUserValue(aBasePrefName);
+}
+
+/*
+ * Given a database model query and a list of search tokens,
+ * return query URI.
+ *
+ * @param aModelQuery database model query
+ * @param aSearchWords an array of search tokens.
+ *
+ * @return query URI.
+ */
+function generateQueryURI(aModelQuery, aSearchWords) {
+ // If there are no search tokens, we simply return an empty string.
+ if (!aSearchWords || aSearchWords.length == 0) {
+ return "";
+ }
+
+ let queryURI = "";
+ aSearchWords.forEach(
+ searchWord =>
+ (queryURI += aModelQuery.replace(/@V/g, encodeABTermValue(searchWord)))
+ );
+
+ // queryURI has all the (or(...)) searches, link them up with (and(...)).
+ queryURI = "?(and" + queryURI + ")";
+
+ return queryURI;
+}
+
+/**
+ * Encode the string passed as value into an addressbook search term.
+ * The '(' and ')' characters are special for the addressbook
+ * search query language, but are not escaped in encodeURIComponent()
+ * so must be done manually on top of it.
+ */
+function encodeABTermValue(aString) {
+ return encodeURIComponent(aString)
+ .replace(/\(/g, "%28")
+ .replace(/\)/g, "%29");
+}
diff --git a/comm/mailnews/base/src/FolderLookupService.jsm b/comm/mailnews/base/src/FolderLookupService.jsm
new file mode 100644
index 0000000000..9530dc6e6e
--- /dev/null
+++ b/comm/mailnews/base/src/FolderLookupService.jsm
@@ -0,0 +1,163 @@
+/* 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 module implements the folder lookup service (nsIFolderLookupService).
+ */
+
+"use strict";
+
+var EXPORTED_SYMBOLS = ["FolderLookupService"];
+
+// This ensures that the service is only created once.
+var gCreated = false;
+
+/**
+ * FolderLookupService maintains an index of folders and provides
+ * lookup by folder URI.
+ *
+ * @class
+ */
+function FolderLookupService() {
+ if (gCreated) {
+ throw Components.Exception("", Cr.NS_ERROR_ALREADY_INITIALIZED);
+ }
+ this._map = new Map();
+ gCreated = true;
+}
+
+FolderLookupService.prototype = {
+ QueryInterface: ChromeUtils.generateQI(["nsIFolderLookupService"]),
+
+ /**
+ * Fetch the folder corresponding to the given URI.
+ * Will only return folders which already exist and have a parent. If this
+ * not the case then null is returned.
+ *
+ * @param {string} uri - URI of folder to get.
+ * @returns {nsIMsgFolder|null}
+ */
+ getFolderForURL(uri) {
+ let folder = this._getExisting(uri);
+
+ if (folder && !this._isValidFolder(folder)) {
+ folder = null; // no dangling folders!
+ }
+ return folder;
+ },
+
+ /**
+ * Fetch the folder corresponding to the given URI, creating it if it does
+ * not exist. If the folder is created, it will be a "dangling" folder,
+ * without a parent and not part of a normal folder hierarchy.
+ * A lot of code relies on this behaviour, but for new code this
+ * call should probably be avoided.
+ *
+ * @param {string} uri - URI of folder to get.
+ * @returns {nsIMsgFolder}
+ */
+ getOrCreateFolderForURL(uri) {
+ let folder = this._getExisting(uri);
+ if (folder) {
+ return folder;
+ }
+
+ // Create new folder.
+
+ // Check that uri has an active scheme, in case this folder is from
+ // an extension that is currently disabled or hasn't started up yet.
+ let schemeMatch = uri.match(/^([-+.\w]+):/);
+ if (!schemeMatch) {
+ return null;
+ }
+ let scheme = schemeMatch[1];
+ let contractID = "@mozilla.org/mail/folder-factory;1?name=" + scheme;
+ if (!(contractID in Cc)) {
+ console.error(
+ "getOrCreateFolderForURL: factory not registered for " + uri
+ );
+ return null;
+ }
+
+ let factory = Components.manager.getClassObject(
+ Cc[contractID],
+ Ci.nsIFactory
+ );
+ if (!factory) {
+ console.error(
+ "getOrCreateFolderForURL: failed to get factory for " + uri
+ );
+ return null;
+ }
+
+ folder = factory.createInstance(Ci.nsIMsgFolder);
+ if (folder) {
+ folder.Init(uri);
+ // Add the new folder to our map. Store a weak reference instead, so that
+ // the folder can be closed when necessary.
+ let weakRef = folder
+ .QueryInterface(Ci.nsISupportsWeakReference)
+ .GetWeakReference();
+ this._map.set(uri, weakRef);
+ }
+
+ return folder;
+ },
+
+ /**
+ * Set pretty name again from original name on all folders,
+ * typically used when locale changes.
+ */
+ setPrettyNameFromOriginalAllFolders() {
+ for (const val of this._map.values()) {
+ try {
+ let folder = val.QueryReferent(Ci.nsIMsgFolder);
+ folder.setPrettyNameFromOriginal();
+ } catch (e) {}
+ }
+ },
+
+ // "private" stuff starts here.
+
+ /**
+ * Internal helper to find a folder (which may or may not be dangling).
+ *
+ * @param {string} uri - URI of folder to look up.
+ *
+ * @returns {nsIMsgFolder|null} - The folder, if in the index, else null.
+ */
+ _getExisting(uri) {
+ let folder = null;
+ // already created?
+ if (this._map.has(uri)) {
+ try {
+ folder = this._map.get(uri).QueryReferent(Ci.nsIMsgFolder);
+ } catch (e) {
+ // The object was deleted, so we can drop it.
+ this._map.delete(uri);
+ }
+ }
+ return folder;
+ },
+
+ /**
+ * Internal helper function to test if a folder is dangling or parented.
+ * Because we can return folders that don't exist, and we may be working
+ * with a deleted folder but we're still holding on to the reference. For
+ * valid folders, one of two scenarios is true: either the folder has a parent
+ * (the deletion code clears the parent to indicate its nonvalidity), or the
+ * folder is a root folder of some server. Getting the root folder may throw
+ * an exception if we attempted to create a server that doesn't exist, so we
+ * need to guard for that error.
+ *
+ * @returns {boolean} - true if folder valid (and parented).
+ */
+ _isValidFolder(folder) {
+ try {
+ return folder.parent != null || folder.rootFolder == folder;
+ } catch (e) {
+ return false;
+ }
+ },
+};
diff --git a/comm/mailnews/base/src/FolderUtils.jsm b/comm/mailnews/base/src/FolderUtils.jsm
new file mode 100644
index 0000000000..a438aa7480
--- /dev/null
+++ b/comm/mailnews/base/src/FolderUtils.jsm
@@ -0,0 +1,364 @@
+/* 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 contains helper methods for dealing with nsIMsgFolders.
+ */
+
+const EXPORTED_SYMBOLS = ["FolderUtils"];
+
+var FolderUtils = {
+ allAccountsSorted,
+ compareAccounts,
+ folderNameCompare,
+ getFolderIcon,
+ getFolderProperties,
+ getMostRecentFolders,
+ getSpecialFolderString,
+ canRenameDeleteJunkMail,
+ isSmartTagsFolder,
+ isSmartVirtualFolder,
+};
+
+const { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+/**
+ * Returns a string representation of a folder's "special" type.
+ *
+ * @param {nsIMsgFolder} aFolder - The folder whose special type to return.
+ * @returns {string} the special type of the folder.
+ */
+function getSpecialFolderString(aFolder) {
+ let flags = aFolder.flags;
+ if (flags & Ci.nsMsgFolderFlags.Inbox) {
+ return "Inbox";
+ }
+ if (flags & Ci.nsMsgFolderFlags.Trash) {
+ return "Trash";
+ }
+ if (flags & Ci.nsMsgFolderFlags.Queue) {
+ return "Outbox";
+ }
+ if (flags & Ci.nsMsgFolderFlags.SentMail) {
+ return "Sent";
+ }
+ if (flags & Ci.nsMsgFolderFlags.Drafts) {
+ return "Drafts";
+ }
+ if (flags & Ci.nsMsgFolderFlags.Templates) {
+ return "Templates";
+ }
+ if (flags & Ci.nsMsgFolderFlags.Junk) {
+ return "Junk";
+ }
+ if (flags & Ci.nsMsgFolderFlags.Archive) {
+ return "Archive";
+ }
+ if (flags & Ci.nsMsgFolderFlags.Virtual) {
+ return "Virtual";
+ }
+ return "none";
+}
+
+/**
+ * This function is meant to be used with trees. It returns the property list
+ * for all of the common properties that css styling is based off of.
+ *
+ * @param {nsIMsgFolder} aFolder - The folder whose properties should be
+ * returned as a string.
+ * @param {boolean} aOpen - Whether the folder is open (not expanded).
+ *
+ * @returns {string} A string of the property names, delimited by space.
+ */
+function getFolderProperties(aFolder, aOpen) {
+ const nsIMsgFolder = Ci.nsIMsgFolder;
+ let properties = [];
+
+ properties.push("folderNameCol");
+
+ properties.push("serverType-" + aFolder.server.type);
+
+ // set the SpecialFolder attribute
+ properties.push("specialFolder-" + getSpecialFolderString(aFolder));
+
+ // Now set the biffState
+ switch (aFolder.biffState) {
+ case nsIMsgFolder.nsMsgBiffState_NewMail:
+ properties.push("biffState-NewMail");
+ break;
+ case nsIMsgFolder.nsMsgBiffState_NoMail:
+ properties.push("biffState-NoMail");
+ break;
+ default:
+ properties.push("biffState-UnknownMail");
+ }
+
+ properties.push("isSecure-" + aFolder.server.isSecure);
+
+ // A folder has new messages, or a closed folder or any subfolder has new messages.
+ if (
+ aFolder.hasNewMessages ||
+ (!aOpen && aFolder.hasSubFolders && aFolder.hasFolderOrSubfolderNewMessages)
+ ) {
+ properties.push("newMessages-true");
+ }
+
+ if (aFolder.isServer) {
+ properties.push("isServer-true");
+ } else {
+ // We only set this if we're not a server
+ let shallowUnread = aFolder.getNumUnread(false);
+ if (shallowUnread > 0) {
+ properties.push("hasUnreadMessages-true");
+ } else {
+ // Make sure that shallowUnread isn't negative
+ shallowUnread = 0;
+ }
+ let deepUnread = aFolder.getNumUnread(true);
+ if (deepUnread - shallowUnread > 0) {
+ properties.push("subfoldersHaveUnreadMessages-true");
+ }
+ }
+
+ properties.push("noSelect-" + aFolder.noSelect);
+ properties.push("imapShared-" + aFolder.imapShared);
+
+ return properties.join(" ");
+}
+
+/**
+ * Returns the sort order value based on the server type to be used for sorting.
+ * The servers (accounts) go in the following order:
+ * (0) default account, (1) other mail accounts, (2) Local Folders,
+ * (3) IM accounts, (4) RSS, (5) News, (9) others (no server)
+ * This ordering is encoded in the .sortOrder property of each server type.
+ *
+ * @param {nsIMsgIncomingServer} aServer -The server to get sort order for.
+ */
+function getServerSortOrder(aServer) {
+ // If there is no server sort this object to the end.
+ if (!aServer) {
+ return 999999999;
+ }
+
+ // Otherwise get the server sort order from the Account manager.
+ return MailServices.accounts.getSortOrder(aServer);
+}
+
+/**
+ * Compares the passed in accounts according to their precedence.
+ */
+function compareAccounts(aAccount1, aAccount2) {
+ return (
+ getServerSortOrder(aAccount1.incomingServer) -
+ getServerSortOrder(aAccount2.incomingServer)
+ );
+}
+
+/**
+ * Returns a list of accounts sorted by server type.
+ *
+ * @param {boolean} aExcludeIMAccounts - Remove IM accounts from the list?
+ */
+function allAccountsSorted(aExcludeIMAccounts) {
+ // This is a HACK to work around bug 41133. If we have one of the
+ // dummy "news" accounts there, that account won't have an
+ // incomingServer attached to it, and everything will blow up.
+ let accountList = MailServices.accounts.accounts.filter(
+ a => a.incomingServer
+ );
+
+ // Remove IM servers.
+ if (aExcludeIMAccounts) {
+ accountList = accountList.filter(a => a.incomingServer.type != "im");
+ }
+
+ return accountList;
+}
+
+/**
+ * Returns the most recently used/modified folders from the passed in list.
+ *
+ * @param {nsIMsgFolder[]} aFolderList - The array of folders to search
+ * for recent folders.
+ * @param {integer} aMaxHits - How many folders to return.
+ * @param {"MRMTime"|"MRUTime"} aTimeProperty - Which folder time property to
+ * use. Use "MRMTime" for most recently modified time.
+ * Use "MRUTime" for most recently used time.
+ */
+function getMostRecentFolders(aFolderList, aMaxHits, aTimeProperty) {
+ let recentFolders = [];
+ const monthOld = Math.floor((Date.now() - 31 * 24 * 60 * 60 * 1000) / 1000);
+
+ /**
+ * This sub-function will add a folder to the recentFolders array if it
+ * is among the aMaxHits most recent. If we exceed aMaxHits folders,
+ * it will pop the oldest folder, ensuring that we end up with the
+ * right number.
+ *
+ * @param {nsIMsgFolders} aFolder - The folder to check for recency.
+ */
+ let oldestTime = 0;
+ function addIfRecent(aFolder) {
+ let time = 0;
+ try {
+ time = Number(aFolder.getStringProperty(aTimeProperty)) || 0;
+ } catch (e) {}
+ if (time <= oldestTime || time < monthOld) {
+ return;
+ }
+
+ if (recentFolders.length == aMaxHits) {
+ recentFolders.sort((a, b) => a.time < b.time);
+ recentFolders.pop();
+ oldestTime = recentFolders[recentFolders.length - 1].time;
+ }
+ recentFolders.push({ folder: aFolder, time });
+ }
+
+ for (let folder of aFolderList) {
+ addIfRecent(folder);
+ }
+
+ return recentFolders.map(f => f.folder);
+}
+
+/**
+ * A locale dependent comparison function to produce a case-insensitive sort order
+ * used to sort folder names.
+ *
+ * @param {string} aString1 - First string to compare.
+ * @param {string} aString2 - Second string to compare.
+ * @returns {interger} A positive number if aString1 > aString2,
+ * negative number if aString1 > aString2, otherwise 0.
+ */
+function folderNameCompare(aString1, aString2) {
+ // TODO: improve this as described in bug 992651.
+ return aString1
+ .toLocaleLowerCase()
+ .localeCompare(aString2.toLocaleLowerCase());
+}
+
+/**
+ * Get the icon to use for this folder.
+ *
+ * @param {nsIMsgFolder} folder - The folder to get icon for.
+ * @returns {string} URL of suitable icon.
+ */
+function getFolderIcon(folder) {
+ let iconName;
+ if (folder.isServer) {
+ switch (folder.server.type) {
+ case "nntp":
+ iconName = folder.server.isSecure ? "globe-secure.svg" : "globe.svg";
+ break;
+ case "imap":
+ case "pop":
+ iconName = folder.server.isSecure ? "mail-secure.svg" : "mail.svg";
+ break;
+ case "none":
+ iconName = "folder.svg";
+ break;
+ case "rss":
+ iconName = "rss.svg";
+ break;
+ default:
+ iconName = "mail.svg";
+ break;
+ }
+ } else if (folder.server.type == "nntp") {
+ iconName = "newsletter.svg";
+ } else {
+ switch (getSpecialFolderString(folder)) {
+ case "Virtual":
+ if (isSmartTagsFolder(folder)) {
+ iconName = "tag.svg";
+ } else {
+ iconName = "folder-filter.svg";
+ }
+ break;
+ case "Junk":
+ iconName = "spam.svg";
+ break;
+ case "Templates":
+ iconName = "template.svg";
+ break;
+ case "Archive":
+ iconName = "archive.svg";
+ break;
+ case "Trash":
+ iconName = "trash.svg";
+ break;
+ case "Drafts":
+ iconName = "draft.svg";
+ break;
+ case "Outbox":
+ iconName = "outbox.svg";
+ break;
+ case "Sent":
+ iconName = "sent.svg";
+ break;
+ case "Inbox":
+ iconName = "inbox.svg";
+ break;
+ default:
+ iconName = "folder.svg";
+ break;
+ }
+ }
+
+ return `chrome://messenger/skin/icons/new/compact/${iconName}`;
+}
+
+/**
+ * Checks if `folder` is a virtual folder for the Unified Folders pane mode.
+ *
+ * @param {nsIMsgFolder} folder
+ * @returns {boolean}
+ */
+function isSmartVirtualFolder(folder) {
+ return (
+ folder.isSpecialFolder(Ci.nsMsgFolderFlags.Virtual) &&
+ folder.server.hostName == "smart mailboxes" &&
+ folder.parent?.isServer
+ );
+}
+
+/**
+ * Checks if `folder` is a virtual folder for the Tags folder pane mode.
+ *
+ * @param {nsIMsgFolder} folder
+ * @returns {boolean}
+ */
+function isSmartTagsFolder(folder) {
+ return (
+ folder.isSpecialFolder(Ci.nsMsgFolderFlags.Virtual) &&
+ folder.server.hostName == "smart mailboxes" &&
+ folder.parent?.name == "tags"
+ );
+}
+
+/**
+ * Checks if the configured junk mail can be renamed or deleted.
+ *
+ * @param {string} aFolderUri
+ */
+function canRenameDeleteJunkMail(aFolderUri) {
+ // Go through junk mail settings for all servers and see if the folder is set/used by anyone.
+ for (let server of MailServices.accounts.allServers) {
+ let settings = server.spamSettings;
+ // If junk mail control or move junk mail to folder option is disabled then
+ // allow the folder to be removed/renamed since the folder is not used in this case.
+ if (!settings.level || !settings.moveOnSpam) {
+ continue;
+ }
+ if (settings.spamFolderURI == aFolderUri) {
+ return false;
+ }
+ }
+
+ return true;
+}
diff --git a/comm/mailnews/base/src/HeaderReader.h b/comm/mailnews/base/src/HeaderReader.h
new file mode 100644
index 0000000000..da8defbbe0
--- /dev/null
+++ b/comm/mailnews/base/src/HeaderReader.h
@@ -0,0 +1,305 @@
+/* -*- 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 HeaderReader_h__
+#define HeaderReader_h__
+
+#include <algorithm>
+#include "LineReader.h"
+#include "nsMsgUtils.h"
+#include "nsString.h"
+#include "mozilla/Span.h"
+
+/**
+ * HeaderReader parses mail headers from a buffer.
+ * The input is fed in via Parse(), and a callback function is invoked for
+ * each header encountered.
+ *
+ * General goals:
+ *
+ * - Incremental. Parse() can be called multiple times as a buffer grows.
+ * - Works in-place. Headers are returned as byte ranges within the data.
+ * - Works with a partial header block (e.g. sniffing the first N bytes
+ * of a message file). It won't mistakenly emit an incomplete header.
+ * - Track exact byte offsets for values, to support rewriting headers in
+ * place. This is needed to support X-Mozilla-Status et al.
+ * - Avoids copying data where possible.
+ * - Callback is inlined.
+ * - Callback can halt processing (by returning false).
+ * - Tolerant of real-world oddness in input data (for now, we just skip
+ * lines which don't make sense).
+ *
+ * Example usage:
+ * nsCString raw = "To: Alice\r\nFrom: Bob\r\n\r\n...Message body..."_ns;
+ * auto cb = [&](HeaderReader::Header const& hdr) {
+ * printf("-> '%s':'%s'\n", hdr.Name(raw), hdr.Value(raw));
+ * return true;
+ * };
+ *
+ * HeaderReader rdr;
+ * rdr.Parse(raw, cb);
+ * // -> 'To':'Alice'
+ * // -> 'From':'Bob'
+ *
+ * See TestHeaderReader.cpp for more examples.
+ */
+class HeaderReader {
+ public:
+ /**
+ * Parse() scans an input buffer and invokes a callback for each complete
+ * header found.
+ *
+ * It can be called any number of times - it'll pick up where it left off.
+ * The idea is that the caller can accumulate data in multiple chunks and
+ * call Parse() to extract headers incrementally as they come in.
+ * It does rely on data being a single contiguous allocation, but it
+ * doesn't require the data being located in the same memory location
+ * each time. So can it can be safely used on a growable buffer.
+ *
+ * Signature of callback is:
+ * bool hdrCallback(HeaderReader::Hdr const& hdr);
+ *
+ * The callback should return true to continue parsing, or false to halt.
+ * This allows, for example, an early-out if you're scanning for one
+ * specific header and don't care about the rest.
+ *
+ * Parse() stops when one of these conditions is true:
+ * 1. The end of the header block is reached (the final blank line marker
+ * is consumed). Subsequent calls to IsComplete() will return true.
+ * 2. The callback returns false. If Parse() is called again, it will
+ * safely pick up where it left off.
+ * 3. No more headers can be read. There may be some unconsumed data
+ * returned (eg a partial line). Parse() can be safely called again
+ * when more data becomes available. It will resume from the point it
+ * reached previously.
+ *
+ * It is safe to call Parse() on a truncated header block. It will only
+ * invoke the callback for headers which are unambiguously complete.
+ *
+ * @param data - bytes containing the header block to parse.
+ * @param hdrCallback - callback to invoke for each header found
+ *
+ * @returns a span containing the unconsumed (leftover) data.
+ */
+ template <typename HeaderFn>
+ mozilla::Span<const char> Parse(mozilla::Span<const char> data,
+ HeaderFn hdrCallback);
+
+ /**
+ * Complete() returns true if the header block has been fully parsed.
+ * Further calls to Parse() will consume no more data.
+ * The blank line which separates the header block from the body is consumed.
+ */
+ bool IsComplete() const { return mFinished; }
+
+ /**
+ * Hdr holds offsets to a name/value pair within a header block.
+ * The name starts at pos.
+ * The value starts at pos+rawValOffset.
+ */
+ struct Hdr {
+ uint32_t pos{0}; // Start position of header within the block.
+ uint32_t len{0}; // Length of entire header, including final EOL.
+ uint32_t nameLen{0}; // Length of name.
+ uint32_t rawValOffset{0}; // Where the value starts, relative to pos.
+ uint32_t rawValLen{0}; // Excludes final EOL.
+ bool IsEmpty() const { return len == 0; }
+
+ /**
+ * Access the header name as a string.
+ *
+ * @param data - the data originally passed into Parse().
+ * @returns the name within data, wrapped for string access (so it is
+ * valid only as long as data is valid).
+ */
+ nsDependentCSubstring Name(mozilla::Span<const char> data) const {
+ return nsDependentCSubstring(data.Elements() + pos, nameLen);
+ }
+ /**
+ * Access the raw value as a string.
+ *
+ * @param data - the data originally passed into Parse().
+ * @returns the raw data, EOLs and all, wrapped for string access (so it
+ * is valid only as long as data is valid).
+ */
+ nsDependentCSubstring RawValue(mozilla::Span<const char> data) const {
+ return nsDependentCSubstring(data.Elements() + pos + rawValOffset,
+ rawValLen);
+ }
+ /**
+ * Decode the 'cooked' value into a string.
+ * NOTE: handles unfolding multi-line values. No attempt (yet) at dealing
+ * with comments or quoted strings...
+ *
+ * @param data - the data originally passed into Parse().
+ * @returns a new string containing the value.
+ */
+ nsCString Value(mozilla::Span<const char> data) const {
+ nsCString val(RawValue(data));
+ val.ReplaceSubstring("\r\n"_ns, ""_ns);
+ val.ReplaceSubstring("\n"_ns, ""_ns);
+ return val;
+ }
+
+ /**
+ * EOL() returns a string containing the eol characters at the end of the
+ * header. It will be "\n" or "\r\n".
+ * Calling this on an empty hdr struct is unsupported.
+ */
+ nsDependentCSubstring EOL(mozilla::Span<const char> data) const {
+ MOZ_ASSERT(len >= 2); // Empty or malformed?
+
+ uint32_t i = pos + len;
+ int n = 0;
+ if (data[i - 1] == '\n') {
+ ++n;
+ if (data[i - 2] == '\r') {
+ ++n;
+ }
+ }
+ return nsDependentCSubstring(data.Elements() + pos + len - n, n);
+ }
+ };
+
+ private:
+ // How far Parse() has gone so far.
+ uint32_t mPos{0};
+
+ // The current header we're accumulating.
+ Hdr mHdr;
+
+ // Number of EOL chars at the end of previous line (so we can strip it if the
+ // next line is folded).
+ int mEOLSize{0};
+
+ // Set when end of header block detected.
+ bool mFinished{false};
+
+ template <typename HeaderFn>
+ bool HandleLine(mozilla::Span<const char> line, HeaderFn hdrCallback);
+};
+
+// Parse() implementation.
+template <typename HeaderFn>
+mozilla::Span<const char> HeaderReader::Parse(mozilla::Span<const char> data,
+ HeaderFn hdrCallback) {
+ // If were're resuming, skip what we've already scanned.
+ auto remaining = mozilla::Span<const char>(data.cbegin() + mPos, data.cend());
+ if (mFinished) {
+ return remaining;
+ }
+ // Iterate over all the lines of our input.
+ remaining = SplitLines(remaining,
+ [this, hdrCallback](mozilla::Span<const char> line) {
+ return HandleLine(line, hdrCallback);
+ });
+
+ if (!mFinished) {
+ // We didn't get to the end of the header block, but we may still be
+ // able to finalise a previously-started header...
+ if (!mHdr.IsEmpty()) {
+ if (remaining.Length() > 0 && remaining[0] != ' ' &&
+ remaining[0] != '\t') {
+ // Next line isn't folded, so we know the header is complete.
+ mHdr.rawValLen -= mEOLSize;
+ hdrCallback(mHdr);
+ } else {
+ // Can't tell if header is complete. Rewind and try again next time.
+ mPos = mHdr.pos;
+ remaining =
+ mozilla::Span<const char>(data.cbegin() + mPos, data.cend());
+ }
+ mHdr = Hdr();
+ }
+ }
+ return remaining;
+}
+
+// Helper function - we call this on each complete line we encounter.
+template <typename HeaderFn>
+bool HeaderReader::HandleLine(mozilla::Span<const char> line,
+ HeaderFn hdrCallback) {
+ // Should never be here if we've finished.
+ MOZ_ASSERT(!mFinished);
+ // we should _never_ see empty strings.
+ MOZ_ASSERT(!line.IsEmpty());
+
+ // Find the EOL sequence (CRLF or LF).
+ auto eol = line.cend();
+ auto p = eol;
+ if (p > line.cbegin() && *(p - 1) == '\n') {
+ --eol;
+ if ((p - 1) > line.cbegin() && *(p - 2) == '\r') {
+ --eol;
+ }
+ }
+ // We should never have been called with a non-terminated line.
+ MOZ_ASSERT(eol != line.cend());
+
+ // Blank line indicates end of header block.
+ if (eol == line.cbegin()) {
+ if (!mHdr.IsEmpty()) {
+ // Emit the completed header.
+ mHdr.rawValLen -= mEOLSize;
+ hdrCallback(mHdr);
+ mHdr = Hdr();
+ }
+ mFinished = true;
+ mPos += line.Length();
+ return false; // Stop.
+ }
+
+ // A folded line?
+ // Leading space or tab indicates continuation of previous value.
+ if (line[0] == ' ' || line[0] == '\t') {
+ if (!mHdr.IsEmpty()) {
+ // Grow the existing header.
+ mHdr.len += line.Length();
+ mHdr.rawValLen += line.Length();
+ mEOLSize = line.cend() - eol;
+ } else {
+ // UHOH - a folded value but we haven't started a header...
+ // Not much we can do, so we'll just ignore the line.
+ NS_WARNING("Malformed header (bare continuation)");
+ }
+ mPos += line.Length();
+ return true; // Next line, please.
+ }
+
+ bool keepGoing = true;
+ // By now, we're expecting a "name: value" line, to start a fresh header.
+ if (!mHdr.IsEmpty()) {
+ // Flush previous header now we know it's complete.
+ mHdr.rawValLen -= mEOLSize;
+ keepGoing = hdrCallback(mHdr);
+ mHdr = Hdr();
+ }
+
+ auto colon = std::find(line.cbegin(), line.cend(), ':');
+ if (colon == line.cend()) {
+ // UHOH. We were expecting a "name: value" line, but didn't find one.
+ // Just ignore this line.
+ NS_WARNING("Malformed header (expected 'name: value')");
+ mPos += line.Length();
+ return keepGoing;
+ }
+ auto val = colon + 1;
+ if (*val == ' ' || *val == '\t') {
+ // Skip single leading whitespace.
+ ++val;
+ }
+
+ // Start filling out the new header (it may grow if folded lines come next).
+ mHdr.pos = mPos;
+ mHdr.len = line.Length();
+ mHdr.nameLen = colon - line.cbegin();
+
+ mHdr.rawValOffset = val - line.cbegin();
+ mHdr.rawValLen = line.cend() - val;
+ mEOLSize = line.cend() - eol;
+ mPos += line.Length();
+ return keepGoing;
+}
+
+#endif
diff --git a/comm/mailnews/base/src/JXON.jsm b/comm/mailnews/base/src/JXON.jsm
new file mode 100644
index 0000000000..00c1f2bb1f
--- /dev/null
+++ b/comm/mailnews/base/src/JXON.jsm
@@ -0,0 +1,159 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// This is a modification of the JXON parsers found on the page
+// <https://developer.mozilla.org/en-US/docs/JXON>
+
+var EXPORTED_SYMBOLS = ["JXON"];
+
+var JXON = new (function () {
+ const sValueProp = "value"; /* you can customize these values */
+ const sAttributesProp = "attr";
+ const sAttrPref = "@";
+ const sElementListPrefix = "$";
+ const sConflictSuffix = "_"; // used when there's a name conflict with special JXON properties
+ const aCache = [];
+ const rIsBool = /^(?:true|false)$/i;
+
+ function parseText(sValue) {
+ if (rIsBool.test(sValue)) {
+ return sValue.toLowerCase() === "true";
+ }
+ if (isFinite(sValue)) {
+ return parseFloat(sValue);
+ }
+ if (isFinite(Date.parse(sValue))) {
+ return new Date(sValue);
+ }
+ return sValue;
+ }
+
+ function EmptyTree() {}
+ EmptyTree.prototype = {
+ toString() {
+ return "null";
+ },
+ valueOf() {
+ return null;
+ },
+ };
+
+ function objectify(vValue) {
+ if (vValue === null) {
+ return new EmptyTree();
+ } else if (vValue instanceof Object) {
+ return vValue;
+ }
+ return new vValue.constructor(vValue); // What does this? copy?
+ }
+
+ function createObjTree(oParentNode, nVerb, bFreeze, bNesteAttr) {
+ const nLevelStart = aCache.length;
+ const bChildren = oParentNode.hasChildNodes();
+ const bAttributes = oParentNode.attributes && oParentNode.attributes.length;
+ const bHighVerb = Boolean(nVerb & 2);
+
+ var sProp = 0;
+ var vContent = 0;
+ var nLength = 0;
+ var sCollectedTxt = "";
+ var vResult = bHighVerb
+ ? {}
+ : /* put here the default value for empty nodes: */ true;
+
+ if (bChildren) {
+ for (
+ var oNode, nItem = 0;
+ nItem < oParentNode.childNodes.length;
+ nItem++
+ ) {
+ oNode = oParentNode.childNodes.item(nItem);
+ if (oNode.nodeType === 4) {
+ // CDATASection
+ sCollectedTxt += oNode.nodeValue;
+ } else if (oNode.nodeType === 3) {
+ // Text
+ sCollectedTxt += oNode.nodeValue;
+ } else if (oNode.nodeType === 1) {
+ // Element
+ aCache.push(oNode);
+ }
+ }
+ }
+
+ const nLevelEnd = aCache.length;
+ const vBuiltVal = parseText(sCollectedTxt);
+
+ if (!bHighVerb && (bChildren || bAttributes)) {
+ vResult = nVerb === 0 ? objectify(vBuiltVal) : {};
+ }
+
+ for (var nElId = nLevelStart; nElId < nLevelEnd; nElId++) {
+ sProp = aCache[nElId].nodeName;
+ if (sProp == sValueProp || sProp == sAttributesProp) {
+ sProp = sProp + sConflictSuffix;
+ }
+ vContent = createObjTree(aCache[nElId], nVerb, bFreeze, bNesteAttr);
+ if (!vResult.hasOwnProperty(sProp)) {
+ vResult[sProp] = vContent;
+ vResult[sElementListPrefix + sProp] = [];
+ }
+ vResult[sElementListPrefix + sProp].push(vContent);
+ nLength++;
+ }
+
+ if (bAttributes) {
+ const nAttrLen = oParentNode.attributes.length;
+ const sAPrefix = bNesteAttr ? "" : sAttrPref;
+ const oAttrParent = bNesteAttr ? {} : vResult;
+
+ for (var oAttrib, nAttrib = 0; nAttrib < nAttrLen; nLength++, nAttrib++) {
+ oAttrib = oParentNode.attributes.item(nAttrib);
+ oAttrParent[sAPrefix + oAttrib.name] = parseText(oAttrib.value);
+ }
+
+ if (bNesteAttr) {
+ if (bFreeze) {
+ Object.freeze(oAttrParent);
+ }
+ vResult[sAttributesProp] = oAttrParent;
+ nLength -= nAttrLen - 1;
+ }
+ }
+
+ if (
+ nVerb === 3 ||
+ ((nVerb === 2 || (nVerb === 1 && nLength > 0)) && sCollectedTxt)
+ ) {
+ vResult[sValueProp] = vBuiltVal;
+ } else if (!bHighVerb && nLength === 0 && sCollectedTxt) {
+ vResult = vBuiltVal;
+ }
+
+ if (bFreeze && (bHighVerb || nLength > 0)) {
+ Object.freeze(vResult);
+ }
+
+ aCache.length = nLevelStart;
+
+ return vResult;
+ }
+
+ this.build = function (
+ oXMLParent,
+ nVerbosity /* optional */,
+ bFreeze /* optional */,
+ bNesteAttributes /* optional */
+ ) {
+ const _nVerb =
+ typeof nVerbosity === "number"
+ ? nVerbosity & 3
+ : /* put here the default verbosity level: */ 1;
+ return createObjTree(
+ oXMLParent,
+ _nVerb,
+ bFreeze || false,
+ bNesteAttributes !== undefined ? bNesteAttributes : _nVerb === 3
+ );
+ };
+})();
diff --git a/comm/mailnews/base/src/LineReader.h b/comm/mailnews/base/src/LineReader.h
new file mode 100644
index 0000000000..292c7ced7c
--- /dev/null
+++ b/comm/mailnews/base/src/LineReader.h
@@ -0,0 +1,188 @@
+/* -*- 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 LineReader_h__
+#define LineReader_h__
+
+#include <algorithm>
+#include "mozilla/Span.h"
+#include "mozilla/Vector.h"
+
+/**
+ * FirstLine() returns the first line of a span.
+ * The EOL sequence (CRLF or LF) is included in the returned line.
+ * If no lines are found an empty span is returned.
+ */
+inline mozilla::Span<const char> FirstLine(
+ mozilla::Span<const char> const& data) {
+ auto eol = std::find(data.cbegin(), data.cend(), '\n');
+ if (eol == data.cend()) {
+ // no line ending found - return empty span.
+ return data.First(0);
+ }
+ ++eol;
+ return mozilla::Span<const char>(data.cbegin(), eol);
+}
+
+/**
+ * LineReader breaks up continuous character streams into lines.
+ * Data is fed in by calling Feed() as often as required, and a
+ * callback function is invoked to handle each resulting line.
+ *
+ * The resulting lines include the end-of-line char(s), except for any
+ * non-terminated final line.
+ * LF ('\n') is used as the line terminator. CRLF-terminated lines will
+ * be handled correctly - the resultant lines will include the line
+ * terminators exactly as they appear in the input data.
+ *
+ * Goals for LineReader:
+ * - Byte exact. The bytes fed in will appear _exactly_ in the callback fn.
+ * - Callback can be inlined (due to templating).
+ * - Avoid copying data if possible. The internal buffer is only used when
+ * lines are split across incoming chunks of data.
+ * - Tries to avoid heap allocation. If the internal buffer is used, it'll
+ * only allocate memory for long lines (>80 chars).
+ *
+ * Example usage:
+ *
+ * auto callback = [](mozilla::Span<const char> line) {
+ * printf("%s\n", nsCString(line).get());
+ * return true;
+ * };
+ *
+ * LineReader c;
+ * c.Feed("Line 1\r\nLine 2\r\nLine 3", callback);
+ * // -> "Line 1\r\n"
+ * // -> "Line 2\r\n"
+ * c.Feed("\r\nLeftovers.", callback);
+ * // -> "Line 3\r\n"
+ * c.Flush(callback);
+ * // -> "Leftovers."
+ *
+ * See TestLineReader.cpp for more examples.
+ */
+class LineReader {
+ public:
+ /*
+ * Feed() takes in a chunk of data to be split up into lines. You can call
+ * this as often as required to feed in all your data. Don't forget to call
+ * Flush() after the last Feed(), in case the last line has no line endings!
+ *
+ * The callback will be invoked once for each full line extracted.
+ * It should have the form:
+ * The callback is of the form:
+ * bool callback(mozilla::Span<const char> line);
+ *
+ * The data in `line` should be considered valid only until the callback
+ * returns. So if the callback wants to retain data it needs to copy it.
+ * `line` will include any EOL character(s).
+ * The callback should return true to continue processing.
+ * If the callback returns false, processing will stop, even if there is
+ * more data available.
+ */
+ template <typename LineFn>
+ void Feed(mozilla::Span<const char> data, LineFn callback) {
+ bool keepGoing = true;
+ while (!data.IsEmpty() && keepGoing) {
+ auto eol = std::find(data.cbegin(), data.cend(), '\n');
+ if (eol == data.cend()) {
+ // No LF. Just collect and wait for more.
+ // TODO: limit maximum mBuf size, to stop maliciously-crafted input
+ // OOMing us?
+ if (!mBuf.append(data.data(), data.size())) {
+ NS_ERROR("OOM!");
+ }
+ return;
+ }
+
+ // Consume everything up to and including the LF.
+ ++eol;
+ mozilla::Span<const char> line(data.cbegin(), eol);
+ data = mozilla::Span<const char>(eol, data.cend());
+
+ if (mBuf.empty()) {
+ // Pass the data through directly, no copying.
+ keepGoing = callback(line);
+ } else {
+ // Complete the line we previously started.
+ if (!mBuf.append(line.data(), line.size())) {
+ NS_ERROR("OOM!");
+ }
+ keepGoing = callback(mBuf);
+ mBuf.clear();
+ }
+ }
+ }
+
+ /*
+ * Flush() will invoke the callback with any leftover data, after the last
+ * Feed() call has completed.
+ * The line passed to the callback will be a partial line, without a final
+ * LF. If the input data has a final LF, there will be nothing to flush,
+ * and the callback will not be invoked.
+ */
+ template <typename LineFn>
+ void Flush(LineFn callback) {
+ if (!mBuf.empty()) {
+ callback(mBuf);
+ mBuf.clear();
+ }
+ }
+
+ private:
+ // Growable buffer, to collect lines which come in as multiple parts.
+ // Can handle lines up to 80 chars before needing to reallocate.
+ mozilla::Vector<char, 80> mBuf;
+};
+
+/**
+ * SplitLines() invokes a callback for every complete line it finds in the
+ * input data.
+ *
+ * The callback is of the form:
+ * bool callback(mozilla::Span<const char> line);
+ * where line is a span pointing to the range of bytes in the input data
+ * which comprises the line.
+ *
+ * If the callback returns false, processing is halted.
+ *
+ * The lines passed to the callback include end-of-line (EOL) character(s).
+ *
+ * Lines are considered terminated by '\n' (LF) but this means CRLF-delimited
+ * data is also handled correctly.
+ *
+ * This function is byte-exact: if you concatenate all the line spans, along
+ * with the unconsumed data returned at the end, you'll end up with the exact
+ * same byte sequence as the original input data.
+ *
+ * @param data - The input bytes.
+ * @param callback - The callback to invoke for each line.
+ *
+ * @returns the unconsumed data. Usually this will be empty, or an incomplete
+ * line at the end (with no EOL). However if the callback returned
+ * false, all the unused data will be returned.
+ */
+template <typename LineFn>
+mozilla::Span<const char> SplitLines(mozilla::Span<const char> data,
+ LineFn callback) {
+ while (!data.IsEmpty()) {
+ auto eol = std::find(data.cbegin(), data.cend(), '\n');
+ if (eol == data.cend()) {
+ // No LF - we're done. May or may not be some leftover data.
+ break;
+ }
+
+ // Consume everything up to and including the LF.
+ ++eol;
+ mozilla::Span<const char> line(data.cbegin(), eol);
+ data = mozilla::Span<const char>(eol, data.cend());
+
+ if (callback(line) == false) {
+ break;
+ }
+ }
+ return data;
+}
+
+#endif
diff --git a/comm/mailnews/base/src/LineReader.jsm b/comm/mailnews/base/src/LineReader.jsm
new file mode 100644
index 0000000000..2417457e3c
--- /dev/null
+++ b/comm/mailnews/base/src/LineReader.jsm
@@ -0,0 +1,68 @@
+/* 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 = ["LineReader"];
+
+/**
+ * For a single request, mail servers may return several multi-line responses. A
+ * definition of multi-line responses can be found at rfc3977#section-3.1.1.
+ *
+ * This class helps dealing with multi-line responses by:
+ * - Break up a response to lines
+ * - Join incomplete line from a previous response with the current response
+ * - Remove stuffed dot (.. at the beginning of a line)
+ * - Detect the end of the response (\r\n.\r\n)
+ */
+class LineReader {
+ processingMultiLineResponse = false;
+ _data = "";
+
+ /**
+ * Read a multi-line response, emit each line through a callback.
+ *
+ * @param {string} data - A multi-line response received from the server.
+ * @param {Function} lineCallback - A line will be passed to the callback each
+ * time.
+ * @param {Function} doneCallback - A function to be called when data is ended.
+ */
+ read(data, lineCallback, doneCallback) {
+ this._data += data;
+ if (this._data == ".\r\n" || this._data.endsWith("\r\n.\r\n")) {
+ this.processingMultiLineResponse = false;
+ this._data = this._data.slice(0, -3);
+ } else {
+ this.processingMultiLineResponse = true;
+ }
+ if (this._running) {
+ // This function can be called multiple times, but this._data should only
+ // be consumed once.
+ return;
+ }
+
+ let i = 0;
+ this._running = true;
+ while (this._data) {
+ let index = this._data.indexOf("\r\n");
+ if (index == -1) {
+ // Not enough data, save it for the next round.
+ break;
+ }
+ let line = this._data.slice(0, index + 2);
+ if (line.startsWith("..")) {
+ // Remove stuffed dot.
+ line = line.slice(1);
+ }
+ lineCallback(line);
+ this._data = this._data.slice(index + 2);
+ if (++i % 100 == 0) {
+ // Prevent blocking main process for too long.
+ Services.tm.spinEventLoopUntilEmpty();
+ }
+ }
+ this._running = false;
+ if (!this.processingMultiLineResponse && !this._data) {
+ doneCallback();
+ }
+ }
+}
diff --git a/comm/mailnews/base/src/MailAuthenticator.jsm b/comm/mailnews/base/src/MailAuthenticator.jsm
new file mode 100644
index 0000000000..cf52a88f17
--- /dev/null
+++ b/comm/mailnews/base/src/MailAuthenticator.jsm
@@ -0,0 +1,468 @@
+/* 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 = [
+ "SmtpAuthenticator",
+ "NntpAuthenticator",
+ "Pop3Authenticator",
+ "ImapAuthenticator",
+];
+
+var { MailCryptoUtils } = ChromeUtils.import(
+ "resource:///modules/MailCryptoUtils.jsm"
+);
+var { MailStringUtils } = ChromeUtils.import(
+ "resource:///modules/MailStringUtils.jsm"
+);
+
+/**
+ * A base class for interfaces when authenticating a mail connection.
+ */
+class MailAuthenticator {
+ /**
+ * Get the hostname for a connection.
+ *
+ * @returns {string}
+ */
+ get hostname() {
+ throw Components.Exception(
+ "hostname getter not implemented",
+ Cr.NS_ERROR_NOT_IMPLEMENTED
+ );
+ }
+
+ /**
+ * Get the username for a connection.
+ *
+ * @returns {string}
+ */
+ get username() {
+ throw Components.Exception(
+ "username getter not implemented",
+ Cr.NS_ERROR_NOT_IMPLEMENTED
+ );
+ }
+
+ /**
+ * Forget cached password.
+ */
+ forgetPassword() {
+ throw Components.Exception(
+ "forgetPassword not implemented",
+ Cr.NS_ERROR_NOT_IMPLEMENTED
+ );
+ }
+
+ /**
+ * Get the password for a connection.
+ *
+ * @returns {string}
+ */
+ getPassword() {
+ throw Components.Exception(
+ "getPassword not implemented",
+ Cr.NS_ERROR_NOT_IMPLEMENTED
+ );
+ }
+
+ /**
+ * Get the CRAM-MD5 auth token for a connection.
+ *
+ * @param {string} password - The password, used as HMAC-MD5 secret.
+ * @param {string} challenge - The base64 encoded server challenge.
+ * @returns {string}
+ */
+ getCramMd5Token(password, challenge) {
+ // Hash the challenge.
+ let signature = MailCryptoUtils.hmacMd5(
+ new TextEncoder().encode(password),
+ new TextEncoder().encode(atob(challenge))
+ );
+ // Get the hex form of the signature.
+ let hex = [...signature].map(x => x.toString(16).padStart(2, "0")).join("");
+ return btoa(`${this.username} ${hex}`);
+ }
+
+ /**
+ * Get the OAuth token for a connection.
+ *
+ * @returns {string}
+ */
+ async getOAuthToken() {
+ throw Components.Exception(
+ "getOAuthToken not implemented",
+ Cr.NS_ERROR_NOT_IMPLEMENTED
+ );
+ }
+
+ /**
+ * Init a nsIMailAuthModule instance for GSSAPI auth.
+ *
+ * @param {('smtp'|'imap')} protocol - The protocol name.
+ */
+ initGssapiAuth(protocol) {
+ this._authModule = Cc["@mozilla.org/mail/auth-module;1"].createInstance(
+ Ci.nsIMailAuthModule
+ );
+ this._authModule.init(
+ "sasl-gssapi", // Auth module type
+ `${protocol}@${this.hostname}`,
+ 0, // nsIAuthModule::REQ_DEFAULT
+ null, // domain
+ this.username,
+ null // password
+ );
+ }
+
+ /**
+ * Get the next token in a sequence of GSSAPI auth steps.
+ *
+ * @param {string} inToken - A base64 encoded string, usually server challenge.
+ * @returns {string}
+ */
+ getNextGssapiToken(inToken) {
+ return this._authModule.getNextToken(inToken);
+ }
+
+ /**
+ * Init a nsIMailAuthModule instance for NTLM auth.
+ */
+ initNtlmAuth() {
+ this._authModule = Cc["@mozilla.org/mail/auth-module;1"].createInstance(
+ Ci.nsIMailAuthModule
+ );
+ this._authModule.init(
+ "ntlm", // Auth module type
+ null, // Service name
+ 0, // nsIAuthModule::REQ_DEFAULT
+ null, // domain
+ this.username,
+ this.getPassword()
+ );
+ }
+
+ /**
+ * Get the next token in a sequence of NTLM auth steps.
+ *
+ * @param {string} inToken - A base64 encoded string, usually server challenge.
+ * @returns {string}
+ */
+ getNextNtlmToken(inToken) {
+ return this._authModule.getNextToken(inToken);
+ }
+
+ /**
+ * Show a dialog for authentication failure.
+ *
+ * @returns {number} - 0: Retry; 1: Cancel; 2: New password.
+ */
+ promptAuthFailed() {
+ throw Components.Exception(
+ "promptAuthFailed not implemented",
+ Cr.NS_ERROR_NOT_IMPLEMENTED
+ );
+ }
+
+ /**
+ * Show a dialog for authentication failure.
+ *
+ * @param {nsIMsgWindow} msgWindow - The associated msg window.
+ * @param {string} accountname - A user defined account name or the server hostname.
+ * @returns {number} 0: Retry; 1: Cancel; 2: New password.
+ */
+ _promptAuthFailed(msgWindow, accountname) {
+ let bundle = Services.strings.createBundle(
+ "chrome://messenger/locale/messenger.properties"
+ );
+ let message = bundle.formatStringFromName("mailServerLoginFailed2", [
+ this.hostname,
+ this.username,
+ ]);
+
+ let title = bundle.formatStringFromName(
+ "mailServerLoginFailedTitleWithAccount",
+ [accountname]
+ );
+
+ let retryButtonLabel = bundle.GetStringFromName(
+ "mailServerLoginFailedRetryButton"
+ );
+ let newPasswordButtonLabel = bundle.GetStringFromName(
+ "mailServerLoginFailedEnterNewPasswordButton"
+ );
+ let buttonFlags =
+ Ci.nsIPrompt.BUTTON_POS_0 * Ci.nsIPrompt.BUTTON_TITLE_IS_STRING +
+ Ci.nsIPrompt.BUTTON_POS_1 * Ci.nsIPrompt.BUTTON_TITLE_CANCEL +
+ Ci.nsIPrompt.BUTTON_POS_2 * Ci.nsIPrompt.BUTTON_TITLE_IS_STRING;
+ let dummyValue = { value: false };
+
+ return Services.prompt.confirmEx(
+ msgWindow?.domWindow,
+ title,
+ message,
+ buttonFlags,
+ retryButtonLabel,
+ null,
+ newPasswordButtonLabel,
+ null,
+ dummyValue
+ );
+ }
+}
+
+/**
+ * Collection of helper functions for authenticating an SMTP connection.
+ *
+ * @augments {MailAuthenticator}
+ */
+class SmtpAuthenticator extends MailAuthenticator {
+ /**
+ * @param {nsISmtpServer} server - The associated server instance.
+ */
+ constructor(server) {
+ super();
+ this._server = server;
+ }
+
+ get hostname() {
+ return this._server.hostname;
+ }
+
+ get username() {
+ return this._server.username;
+ }
+
+ forgetPassword() {
+ this._server.forgetPassword();
+ }
+
+ getPassword() {
+ if (this._server.password) {
+ return this._server.password;
+ }
+ let composeBundle = Services.strings.createBundle(
+ "chrome://messenger/locale/messengercompose/composeMsgs.properties"
+ );
+ let username = this._server.username;
+ let promptString;
+ if (username) {
+ promptString = composeBundle.formatStringFromName(
+ "smtpEnterPasswordPromptWithUsername",
+ [this._server.hostname, username]
+ );
+ } else {
+ promptString = composeBundle.formatStringFromName(
+ "smtpEnterPasswordPrompt",
+ [this._server.hostname]
+ );
+ }
+ let promptTitle = composeBundle.formatStringFromName(
+ "smtpEnterPasswordPromptTitleWithHostname",
+ [this._server.hostname]
+ );
+ return this._server.getPasswordWithUI(promptString, promptTitle);
+ }
+
+ /**
+ * Get the ByteString form of the current password.
+ *
+ * @returns {string}
+ */
+ getByteStringPassword() {
+ return MailStringUtils.stringToByteString(this.getPassword());
+ }
+
+ /**
+ * Get the PLAIN auth token for a connection.
+ *
+ * @returns {string}
+ */
+ getPlainToken() {
+ // According to rfc4616#section-2, password should be UTF-8 BinaryString
+ // before base64 encoded.
+ return btoa("\0" + this.username + "\0" + this.getByteStringPassword());
+ }
+
+ async getOAuthToken() {
+ let oauth2Module = Cc["@mozilla.org/mail/oauth2-module;1"].createInstance(
+ Ci.msgIOAuth2Module
+ );
+ if (!oauth2Module.initFromSmtp(this._server)) {
+ return Promise.reject(`initFromSmtp failed, hostname: ${this.hostname}`);
+ }
+ return new Promise((resolve, reject) => {
+ oauth2Module.connect(true, {
+ onSuccess: token => {
+ resolve(token);
+ },
+ onFailure: e => {
+ reject(e);
+ },
+ });
+ });
+ }
+
+ promptAuthFailed() {
+ return this._promptAuthFailed(
+ null,
+ this._server.description || this.hostname
+ );
+ }
+}
+
+/**
+ * Collection of helper functions for authenticating an incoming server.
+ *
+ * @augments {MailAuthenticator}
+ */
+class IncomingServerAuthenticator extends MailAuthenticator {
+ /**
+ * @param {nsIMsgIncomingServer} server - The associated server instance.
+ */
+ constructor(server) {
+ super();
+ this._server = server;
+ }
+
+ get hostname() {
+ return this._server.hostName;
+ }
+
+ get username() {
+ return this._server.username;
+ }
+
+ forgetPassword() {
+ this._server.forgetPassword();
+ }
+
+ /**
+ * Get the ByteString form of the current password.
+ *
+ * @returns {string}
+ */
+ async getByteStringPassword() {
+ return MailStringUtils.stringToByteString(await this.getPassword());
+ }
+
+ /**
+ * Get the PLAIN auth token for a connection.
+ *
+ * @returns {string}
+ */
+ async getPlainToken() {
+ // According to rfc4616#section-2, password should be UTF-8 BinaryString
+ // before base64 encoded.
+ return btoa(
+ "\0" + this.username + "\0" + (await this.getByteStringPassword())
+ );
+ }
+
+ async getOAuthToken() {
+ let oauth2Module = Cc["@mozilla.org/mail/oauth2-module;1"].createInstance(
+ Ci.msgIOAuth2Module
+ );
+ if (!oauth2Module.initFromMail(this._server)) {
+ return Promise.reject(`initFromMail failed, hostname: ${this.hostname}`);
+ }
+ return new Promise((resolve, reject) => {
+ oauth2Module.connect(true, {
+ onSuccess: token => {
+ resolve(token);
+ },
+ onFailure: e => {
+ reject(e);
+ },
+ });
+ });
+ }
+}
+
+/**
+ * Collection of helper functions for authenticating a NNTP connection.
+ *
+ * @augments {IncomingServerAuthenticator}
+ */
+class NntpAuthenticator extends IncomingServerAuthenticator {
+ /**
+ * @returns {string} - NNTP server has no userName pref, need to pass it in.
+ */
+ get username() {
+ return this._username;
+ }
+
+ set username(value) {
+ this._username = value;
+ }
+
+ promptAuthFailed() {
+ return this._promptAuthFailed(null, this._server.prettyName);
+ }
+}
+
+/**
+ * Collection of helper functions for authenticating a POP connection.
+ *
+ * @augments {IncomingServerAuthenticator}
+ */
+class Pop3Authenticator extends IncomingServerAuthenticator {
+ async getPassword() {
+ if (this._server.password) {
+ return this._server.password;
+ }
+ let composeBundle = Services.strings.createBundle(
+ "chrome://messenger/locale/localMsgs.properties"
+ );
+ let params = [this._server.username, this._server.hostName];
+ let promptString = composeBundle.formatStringFromName(
+ "pop3EnterPasswordPrompt",
+ params
+ );
+ let promptTitle = composeBundle.formatStringFromName(
+ "pop3EnterPasswordPromptTitleWithUsername",
+ [this._server.hostName]
+ );
+ return this._server.wrappedJSObject.getPasswordWithUIAsync(
+ promptString,
+ promptTitle
+ );
+ }
+
+ promptAuthFailed() {
+ return this._promptAuthFailed(null, this._server.prettyName);
+ }
+}
+
+/**
+ * Collection of helper functions for authenticating an IMAP connection.
+ *
+ * @augments {IncomingServerAuthenticator}
+ */
+class ImapAuthenticator extends IncomingServerAuthenticator {
+ async getPassword() {
+ if (this._server.password) {
+ return this._server.password;
+ }
+ let composeBundle = Services.strings.createBundle(
+ "chrome://messenger/locale/imapMsgs.properties"
+ );
+ let params = [this._server.username, this._server.hostName];
+ let promptString = composeBundle.formatStringFromName(
+ "imapEnterServerPasswordPrompt",
+ params
+ );
+ let promptTitle = composeBundle.formatStringFromName(
+ "imapEnterPasswordPromptTitleWithUsername",
+ [this._server.hostName]
+ );
+ return this._server.wrappedJSObject.getPasswordWithUIAsync(
+ promptString,
+ promptTitle
+ );
+ }
+
+ promptAuthFailed() {
+ return this._promptAuthFailed(null, this._server.prettyName);
+ }
+}
diff --git a/comm/mailnews/base/src/MailChannel.sys.mjs b/comm/mailnews/base/src/MailChannel.sys.mjs
new file mode 100644
index 0000000000..a5fbf9ee75
--- /dev/null
+++ b/comm/mailnews/base/src/MailChannel.sys.mjs
@@ -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/. */
+
+/**
+ * @see {nsIMailChannel}
+ */
+export class MailChannel {
+ _headerNames = [];
+ _headerValues = [];
+ _attachments = [];
+ _mailCharacterSet = null;
+ _progressListener = null;
+
+ addHeaderFromMIME(name, value) {
+ this._headerNames.push(name);
+ this._headerValues.push(value);
+ }
+
+ get headerNames() {
+ return this._headerNames;
+ }
+
+ get headerValues() {
+ return this._headerValues;
+ }
+
+ handleAttachmentFromMIME(contentType, url, displayName, uri, notDownloaded) {
+ let attachment = Cc["@mozilla.org/hash-property-bag;1"].createInstance(
+ Ci.nsIWritablePropertyBag2
+ );
+ attachment.setPropertyAsAUTF8String("contentType", contentType);
+ attachment.setPropertyAsAUTF8String("url", url);
+ attachment.setPropertyAsAUTF8String("displayName", displayName);
+ attachment.setPropertyAsAUTF8String("uri", uri);
+ attachment.setPropertyAsBool("notDownloaded", notDownloaded);
+ this._attachments.push(attachment);
+ }
+
+ addAttachmentFieldFromMIME(field, value) {
+ let attachment = this._attachments[this._attachments.length - 1];
+ attachment.setPropertyAsAUTF8String(field, value);
+ }
+
+ get attachments() {
+ return this._attachments.slice();
+ }
+
+ get mailCharacterSet() {
+ return this._mailCharacterSet;
+ }
+
+ set mailCharacterSet(value) {
+ let ccm = Cc["@mozilla.org/charset-converter-manager;1"].getService(
+ Ci.nsICharsetConverterManager
+ );
+ this._mailCharacterSet = ccm.getCharsetAlias(value);
+ }
+
+ imipMethod = null;
+ imipItem = null;
+ smimeHeaderSink = null;
+
+ get listener() {
+ return this._progressListener?.get();
+ }
+
+ set listener(listener) {
+ this._progressListener = Cu.getWeakReference(listener);
+ }
+}
diff --git a/comm/mailnews/base/src/MailCryptoUtils.jsm b/comm/mailnews/base/src/MailCryptoUtils.jsm
new file mode 100644
index 0000000000..6c378e6703
--- /dev/null
+++ b/comm/mailnews/base/src/MailCryptoUtils.jsm
@@ -0,0 +1,76 @@
+/* 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 = ["MailCryptoUtils"];
+
+var MailCryptoUtils = {
+ /**
+ * Converts a binary string into a Uint8Array.
+ *
+ * @param {BinaryString} str - The string to convert.
+ * @returns {Uint8Array}.
+ */
+ binaryStringToTypedArray(str) {
+ let arr = new Uint8Array(str.length);
+ for (let i = 0; i < str.length; i++) {
+ arr[i] = str.charCodeAt(i);
+ }
+ return arr;
+ },
+
+ /**
+ * The HMAC-MD5 transform works like:
+ *
+ * MD5(K XOR opad, MD5(K XOR ipad, m))
+ *
+ * where
+ * K is an n byte key
+ * ipad is the byte 0x36 repeated 64 times
+ * opad is the byte 0x5c repeated 64 times
+ * m is the message being processed
+
+ * @param {Uint8Array} key
+ * @param {Uint8Array} data
+ * @returns {Uint8Array}
+ */
+ hmacMd5(key, data) {
+ let hasher = Cc["@mozilla.org/security/hash;1"].createInstance(
+ Ci.nsICryptoHash
+ );
+ let digest;
+
+ // If key is longer than 64 bytes, reset it to MD5(key).
+ if (key.length > 64) {
+ hasher.init(Ci.nsICryptoHash.MD5);
+ hasher.update(key, key.length);
+ digest = hasher.finish(false);
+ key = this.binaryStringToTypedArray(digest);
+ }
+
+ // Generate innerPad and outerPad.
+ let innerPad = new Uint8Array(64);
+ let outerPad = new Uint8Array(64);
+ for (let i = 0; i < 64; i++) {
+ let base = key[i] || 0;
+ innerPad[i] = base ^ 0x36;
+ outerPad[i] = base ^ 0x5c;
+ }
+
+ // Perform inner MD5.
+ hasher.init(Ci.nsICryptoHash.MD5);
+ hasher.update(innerPad, 64);
+ hasher.update(data, data.length);
+ digest = hasher.finish(false);
+
+ let result = this.binaryStringToTypedArray(digest);
+
+ // Perform outer MD5.
+ hasher.init(Ci.nsICryptoHash.MD5);
+ hasher.update(outerPad, 64);
+ hasher.update(result, result.length);
+ digest = hasher.finish(false);
+
+ return this.binaryStringToTypedArray(digest);
+ },
+};
diff --git a/comm/mailnews/base/src/MailNewsDLF.cpp b/comm/mailnews/base/src/MailNewsDLF.cpp
new file mode 100644
index 0000000000..8969992414
--- /dev/null
+++ b/comm/mailnews/base/src/MailNewsDLF.cpp
@@ -0,0 +1,84 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsCOMPtr.h"
+#include "MailNewsDLF.h"
+#include "nsIChannel.h"
+#include "plstr.h"
+#include "nsString.h"
+#include "nsICategoryManager.h"
+#include "nsIServiceManager.h"
+#include "nsIStreamConverterService.h"
+#include "nsIStreamListener.h"
+#include "nsNetCID.h"
+#include "nsMsgUtils.h"
+
+namespace mozilla {
+namespace mailnews {
+NS_IMPL_ISUPPORTS(MailNewsDLF, nsIDocumentLoaderFactory)
+
+MailNewsDLF::MailNewsDLF() {}
+
+MailNewsDLF::~MailNewsDLF() {}
+
+NS_IMETHODIMP
+MailNewsDLF::CreateInstance(const char* aCommand, nsIChannel* aChannel,
+ nsILoadGroup* aLoadGroup,
+ const nsACString& aContentType,
+ nsIDocShell* aContainer, nsISupports* aExtraInfo,
+ nsIStreamListener** aDocListener,
+ nsIContentViewer** aDocViewer) {
+ nsresult rv;
+
+ bool viewSource =
+ (PL_strstr(PromiseFlatCString(aContentType).get(), "view-source") != 0);
+
+ aChannel->SetContentType(nsLiteralCString(TEXT_HTML));
+
+ // Get the HTML category
+ nsCOMPtr<nsICategoryManager> catMan(
+ do_GetService(NS_CATEGORYMANAGER_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString contractID;
+ rv = catMan->GetCategoryEntry("Gecko-Content-Viewers", TEXT_HTML, contractID);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIDocumentLoaderFactory> factory(
+ do_GetService(contractID.get(), &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIStreamListener> listener;
+
+ if (viewSource) {
+ rv = factory->CreateInstance(
+ "view-source", aChannel, aLoadGroup,
+ nsLiteralCString(TEXT_HTML "; x-view-type=view-source"), aContainer,
+ aExtraInfo, getter_AddRefs(listener), aDocViewer);
+ } else {
+ rv = factory->CreateInstance(
+ "view", aChannel, aLoadGroup, nsLiteralCString(TEXT_HTML), aContainer,
+ aExtraInfo, getter_AddRefs(listener), aDocViewer);
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIStreamConverterService> scs(
+ do_GetService(NS_STREAMCONVERTERSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return scs->AsyncConvertData(MESSAGE_RFC822, TEXT_HTML, listener, aChannel,
+ aDocListener);
+}
+
+NS_IMETHODIMP
+MailNewsDLF::CreateInstanceForDocument(nsISupports* aContainer,
+ mozilla::dom::Document* aDocument,
+ const char* aCommand,
+ nsIContentViewer** aDocViewer) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+} // namespace mailnews
+} // namespace mozilla
diff --git a/comm/mailnews/base/src/MailNewsDLF.h b/comm/mailnews/base/src/MailNewsDLF.h
new file mode 100644
index 0000000000..95455f4b56
--- /dev/null
+++ b/comm/mailnews/base/src/MailNewsDLF.h
@@ -0,0 +1,37 @@
+/* -*- 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 MailNewsDLF_h__
+#define MailNewsDLF_h__
+
+#include "nsIDocumentLoaderFactory.h"
+#include "nsMimeTypes.h"
+
+namespace mozilla {
+namespace mailnews {
+
+/*
+ * This factory is a thin wrapper around the text/html loader factory. All it
+ * does is convert message/rfc822 to text/html and delegate the rest of the
+ * work to the text/html factory.
+ */
+class MailNewsDLF : public nsIDocumentLoaderFactory {
+ public:
+ MailNewsDLF();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIDOCUMENTLOADERFACTORY
+
+ private:
+ virtual ~MailNewsDLF();
+};
+} // namespace mailnews
+} // namespace mozilla
+
+#define MAILNEWSDLF_CATEGORIES \
+ {"Gecko-Content-Viewers", MESSAGE_RFC822, \
+ "@mozilla.org/mailnews/document-loader-factory;1"},
+
+#endif
diff --git a/comm/mailnews/base/src/MailNotificationManager.jsm b/comm/mailnews/base/src/MailNotificationManager.jsm
new file mode 100644
index 0000000000..c16e37eb2f
--- /dev/null
+++ b/comm/mailnews/base/src/MailNotificationManager.jsm
@@ -0,0 +1,478 @@
+/* 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 = ["MailNotificationManager"];
+
+const { AppConstants } = ChromeUtils.importESModule(
+ "resource://gre/modules/AppConstants.sys.mjs"
+);
+const { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+const { XPCOMUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/XPCOMUtils.sys.mjs"
+);
+
+const lazy = {};
+
+XPCOMUtils.defineLazyModuleGetters(lazy, {
+ MailUtils: "resource:///modules/MailUtils.jsm",
+ WinUnreadBadge: "resource:///modules/WinUnreadBadge.jsm",
+});
+
+XPCOMUtils.defineLazyGetter(
+ lazy,
+ "l10n",
+ () => new Localization(["messenger/messenger.ftl"])
+);
+
+/**
+ * A module that listens to folder change events, and show notifications for new
+ * mails if necessary.
+ */
+class MailNotificationManager {
+ QueryInterface = ChromeUtils.generateQI([
+ "nsIObserver",
+ "nsIFolderListener",
+ "mozINewMailListener",
+ ]);
+
+ constructor() {
+ this._systemAlertAvailable = true;
+ this._unreadChatCount = 0;
+ this._unreadMailCount = 0;
+ // @type {Map<nsIMsgFolder, number>} - A map of folder and its last biff time.
+ this._folderBiffTime = new Map();
+ // @type {Set<nsIMsgFolder>} - A set of folders to show alert for.
+ this._pendingFolders = new Set();
+
+ this._logger = console.createInstance({
+ prefix: "mail.notification",
+ maxLogLevel: "Warn",
+ maxLogLevelPref: "mail.notification.loglevel",
+ });
+ this._bundle = Services.strings.createBundle(
+ "chrome://messenger/locale/messenger.properties"
+ );
+ MailServices.mailSession.AddFolderListener(
+ this,
+ Ci.nsIFolderListener.intPropertyChanged
+ );
+
+ // Ensure that OS integration is defined before we attempt to initialize the
+ // system tray icon.
+ XPCOMUtils.defineLazyGetter(this, "_osIntegration", () => {
+ try {
+ return Cc["@mozilla.org/messenger/osintegration;1"].getService(
+ Ci.nsIMessengerOSIntegration
+ );
+ } catch (e) {
+ // We don't have OS integration on all platforms.
+ return null;
+ }
+ });
+
+ if (["macosx", "win"].includes(AppConstants.platform)) {
+ // We don't have indicator for unread count on Linux yet.
+ Cc["@mozilla.org/newMailNotificationService;1"]
+ .getService(Ci.mozINewMailNotificationService)
+ .addListener(this, Ci.mozINewMailNotificationService.count);
+
+ Services.obs.addObserver(this, "unread-im-count-changed");
+ Services.obs.addObserver(this, "profile-before-change");
+ }
+
+ if (AppConstants.platform == "macosx") {
+ Services.obs.addObserver(this, "new-directed-incoming-message");
+ }
+
+ if (AppConstants.platform == "win") {
+ Services.obs.addObserver(this, "windows-refresh-badge-tray");
+ Services.prefs.addObserver("mail.biff.show_badge", this);
+ Services.prefs.addObserver("mail.biff.show_tray_icon_always", this);
+ }
+ }
+
+ observe(subject, topic, data) {
+ switch (topic) {
+ case "alertclickcallback":
+ // Display the associated message when an alert is clicked.
+ let msgHdr = Cc["@mozilla.org/messenger;1"]
+ .getService(Ci.nsIMessenger)
+ .msgHdrFromURI(data);
+ lazy.MailUtils.displayMessageInFolderTab(msgHdr, true);
+ return;
+ case "unread-im-count-changed":
+ this._logger.log(
+ `Unread chat count changed to ${this._unreadChatCount}`
+ );
+ this._unreadChatCount = parseInt(data, 10) || 0;
+ this._updateUnreadCount();
+ return;
+ case "new-directed-incoming-messenger":
+ this._animateDockIcon();
+ return;
+ case "windows-refresh-badge-tray":
+ this._updateUnreadCount();
+ return;
+ case "profile-before-change":
+ this._osIntegration?.onExit();
+ return;
+ case "newmailalert-closed":
+ // newmailalert.xhtml is closed, try to show the next queued folder.
+ this._customizedAlertShown = false;
+ this._showCustomizedAlert();
+ return;
+ case "nsPref:changed":
+ if (
+ data == "mail.biff.show_badge" ||
+ data == "mail.biff.show_tray_icon_always"
+ ) {
+ this._updateUnreadCount();
+ }
+ }
+ }
+
+ /**
+ * Following are nsIFolderListener interfaces. Do nothing about them.
+ */
+ onFolderAdded() {}
+ onMessageAdded() {}
+ onFolderRemoved() {}
+ onMessageRemoved() {}
+ onFolderPropertyChanged() {}
+ /**
+ * The only nsIFolderListener interface we care about.
+ *
+ * @see nsIFolderListener
+ */
+ onFolderIntPropertyChanged(folder, property, oldValue, newValue) {
+ if (!Services.prefs.getBoolPref("mail.biff.show_alert")) {
+ return;
+ }
+
+ this._logger.debug(
+ `onFolderIntPropertyChanged; property=${property}: ${oldValue} => ${newValue}, folder.URI=${folder.URI}`
+ );
+
+ switch (property) {
+ case "BiffState":
+ if (newValue == Ci.nsIMsgFolder.nsMsgBiffState_NewMail) {
+ // The folder argument is a root folder.
+ this._fillAlertInfo(folder);
+ }
+ break;
+ case "NewMailReceived":
+ // The folder argument is a real folder.
+ this._fillAlertInfo(folder);
+ break;
+ }
+ }
+ onFolderBoolPropertyChanged() {}
+ onFolderUnicharPropertyChanged() {}
+ onFolderPropertyFlagChanged() {}
+ onFolderEvent() {}
+
+ /**
+ * @see mozINewMailNotificationService
+ */
+ onCountChanged(count) {
+ this._logger.log(`Unread mail count changed to ${count}`);
+ this._unreadMailCount = count;
+ this._updateUnreadCount();
+ }
+
+ /**
+ * Show an alert according to the changed folder.
+ *
+ * @param {nsIMsgFolder} changedFolder - The folder that emitted the change
+ * event, can be a root folder or a real folder.
+ */
+ async _fillAlertInfo(changedFolder) {
+ let folder = this._getFirstRealFolderWithNewMail(changedFolder);
+ if (!folder) {
+ return;
+ }
+
+ let newMsgKeys = this._getNewMsgKeysNotNotified(folder);
+ let numNewMessages = newMsgKeys.length;
+ if (!numNewMessages) {
+ return;
+ }
+
+ this._logger.debug(
+ `Filling alert info; folder.URI=${folder.URI}, numNewMessages=${numNewMessages}`
+ );
+ let firstNewMsgHdr = folder.msgDatabase.getMsgHdrForKey(newMsgKeys[0]);
+
+ let title = this._getAlertTitle(folder, numNewMessages);
+ let body;
+ try {
+ body = await this._getAlertBody(folder, firstNewMsgHdr);
+ } catch (e) {
+ this._logger.error(e);
+ }
+ if (!title || !body) {
+ return;
+ }
+ this._showAlert(firstNewMsgHdr, title, body);
+ this._animateDockIcon();
+ }
+
+ /**
+ * Iterate the subfolders of changedFolder, return the first real folder with
+ * new mail.
+ *
+ * @param {nsIMsgFolder} changedFolder - The folder that emitted the change event.
+ * @returns {nsIMsgFolder} The first real folder.
+ */
+ _getFirstRealFolderWithNewMail(changedFolder) {
+ let folders = changedFolder.descendants;
+ folders.unshift(changedFolder);
+
+ for (let folder of folders) {
+ let flags = folder.flags;
+ if (
+ !(flags & Ci.nsMsgFolderFlags.Inbox) &&
+ flags & (Ci.nsMsgFolderFlags.SpecialUse | Ci.nsMsgFolderFlags.Virtual)
+ ) {
+ // Do not notify if the folder is not Inbox but one of
+ // Drafts|Trash|SentMail|Templates|Junk|Archive|Queue or Virtual.
+ continue;
+ }
+
+ if (folder.getNumNewMessages(false) > 0) {
+ return folder;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Get the title for the alert.
+ *
+ * @param {nsIMsgFolder} folder - The changed folder.
+ * @param {number} numNewMessages - The count of new messages.
+ * @returns {string} The alert title.
+ */
+ _getAlertTitle(folder, numNewMessages) {
+ return this._bundle.formatStringFromName(
+ numNewMessages == 1
+ ? "newMailNotification_message"
+ : "newMailNotification_messages",
+ [folder.server.prettyName, numNewMessages.toString()]
+ );
+ }
+
+ /**
+ * Get the body for the alert.
+ *
+ * @param {nsIMsgFolder} folder - The changed folder.
+ * @param {nsIMsgHdr} msgHdr - The nsIMsgHdr of the first new messages.
+ * @returns {string} The alert body.
+ */
+ async _getAlertBody(folder, msgHdr) {
+ await new Promise((resolve, reject) => {
+ let isAsync = folder.fetchMsgPreviewText([msgHdr.messageKey], {
+ OnStartRunningUrl() {},
+ // @see nsIUrlListener
+ OnStopRunningUrl(url, exitCode) {
+ Components.isSuccessCode(exitCode) ? resolve() : reject();
+ },
+ });
+ if (!isAsync) {
+ resolve();
+ }
+ });
+
+ let alertBody = "";
+
+ let subject = Services.prefs.getBoolPref("mail.biff.alert.show_subject")
+ ? msgHdr.mime2DecodedSubject
+ : "";
+ let author = "";
+ if (Services.prefs.getBoolPref("mail.biff.alert.show_sender")) {
+ let addressObjects = MailServices.headerParser.makeFromDisplayAddress(
+ msgHdr.mime2DecodedAuthor
+ );
+ let { name, email } = addressObjects[0] || {};
+ author = name || email;
+ }
+ if (subject && author) {
+ alertBody += this._bundle.formatStringFromName(
+ "newMailNotification_messagetitle",
+ [subject, author]
+ );
+ } else if (subject) {
+ alertBody += subject;
+ } else if (author) {
+ alertBody += author;
+ }
+ let showPreview = Services.prefs.getBoolPref(
+ "mail.biff.alert.show_preview"
+ );
+ if (showPreview) {
+ let previewLength = Services.prefs.getIntPref(
+ "mail.biff.alert.preview_length",
+ 40
+ );
+ let preview = msgHdr.getStringProperty("preview").slice(0, previewLength);
+ if (preview) {
+ alertBody += (alertBody ? "\n" : "") + preview;
+ }
+ }
+ return alertBody;
+ }
+
+ /**
+ * Show the alert.
+ *
+ * @param {nsIMsgHdr} msgHdr - The nsIMsgHdr of the first new messages.
+ * @param {string} title - The alert title.
+ * @param {string} body - The alert body.
+ */
+ _showAlert(msgHdr, title, body) {
+ let folder = msgHdr.folder;
+
+ // Try to use system alert first.
+ if (
+ Services.prefs.getBoolPref("mail.biff.use_system_alert", true) &&
+ this._systemAlertAvailable
+ ) {
+ let alertsService = Cc["@mozilla.org/system-alerts-service;1"].getService(
+ Ci.nsIAlertsService
+ );
+ let cookie = folder.generateMessageURI(msgHdr.messageKey);
+ try {
+ let alert = Cc["@mozilla.org/alert-notification;1"].createInstance(
+ Ci.nsIAlertNotification
+ );
+ alert.init(
+ cookie, // name
+ "chrome://messenger/skin/icons/new-mail-alert.png",
+ title,
+ body,
+ true, // clickable
+ cookie
+ );
+ alertsService.showAlert(alert, this);
+ return;
+ } catch (e) {
+ this._logger.error(e);
+ this._systemAlertAvailable = false;
+ }
+ }
+
+ // The use_system_alert pref is false or showAlert somehow failed, use the
+ // customized alert window.
+ this._showCustomizedAlert(folder);
+ }
+
+ /**
+ * Show a customized alert window (newmailalert.xhtml), if there is already
+ * one showing, do not show another one, because the newer one will block the
+ * older one. Instead, save the folder and newMsgKeys to this._pendingFolders.
+ *
+ * @param {nsIMsgFolder} [folder] - The folder containing new messages.
+ */
+ _showCustomizedAlert(folder) {
+ if (this._customizedAlertShown) {
+ // Queue the folder.
+ this._pendingFolders.add(folder);
+ return;
+ }
+ if (!folder) {
+ // Get the next folder from the queue.
+ folder = this._pendingFolders.keys().next().value;
+ if (!folder) {
+ return;
+ }
+ this._pendingFolders.delete(folder);
+ }
+
+ let newMsgKeys = this._getNewMsgKeysNotNotified(folder);
+ if (!newMsgKeys.length) {
+ // No NEW message in the current folder, try the next queued folder.
+ this._showCustomizedAlert();
+ return;
+ }
+
+ let args = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
+ args.appendElement(folder);
+ args.appendElement({
+ wrappedJSObject: newMsgKeys,
+ });
+ args.appendElement(this);
+ Services.ww.openWindow(
+ null,
+ "chrome://messenger/content/newmailalert.xhtml",
+ "_blank",
+ "chrome,dialog=yes,titlebar=no,popup=yes",
+ args
+ );
+ this._customizedAlertShown = true;
+ this._folderBiffTime.set(folder, Date.now());
+ }
+
+ /**
+ * Get all NEW messages from a folder that we received after last biff time.
+ *
+ * @param {nsIMsgFolder} folder - The message folder to check.
+ * @returns {number[]} An array of message keys.
+ */
+ _getNewMsgKeysNotNotified(folder) {
+ let msgDb = folder.msgDatabase;
+ let lastBiffTime = this._folderBiffTime.get(folder) || 0;
+ return msgDb
+ .getNewList()
+ .slice(-folder.getNumNewMessages(false))
+ .filter(key => {
+ let msgHdr = msgDb.getMsgHdrForKey(key);
+ return msgHdr.dateInSeconds * 1000 > lastBiffTime;
+ });
+ }
+
+ async _updateUnreadCount() {
+ if (this._updatingUnreadCount) {
+ // _updateUnreadCount can be triggered faster than we finish rendering the
+ // badge. When that happens, set a flag and return.
+ this._pendingUpdate = true;
+ return;
+ }
+ this._updatingUnreadCount = true;
+
+ this._logger.debug(
+ `Update unreadMailCount=${this._unreadMailCount}, unreadChatCount=${this._unreadChatCount}`
+ );
+ let count = this._unreadMailCount + this._unreadChatCount;
+ let tooltip = "";
+ if (AppConstants.platform == "win") {
+ if (!Services.prefs.getBoolPref("mail.biff.show_badge", true)) {
+ count = 0;
+ }
+ if (count > 0) {
+ tooltip = await lazy.l10n.formatValue("unread-messages-os-tooltip", {
+ count,
+ });
+ }
+ await lazy.WinUnreadBadge.updateUnreadCount(count, tooltip);
+ }
+ this._osIntegration?.updateUnreadCount(count, tooltip);
+
+ this._updatingUnreadCount = false;
+ if (this._pendingUpdate) {
+ // There was at least one _updateUnreadCount call while we were rendering
+ // the badge. Render one more time will ensure the badge reflects the
+ // current state.
+ this._pendingUpdate = false;
+ this._updateUnreadCount();
+ }
+ }
+
+ _animateDockIcon() {
+ if (Services.prefs.getBoolPref("mail.biff.animate_dock_icon", false)) {
+ Services.wm.getMostRecentWindow("mail:3pane")?.getAttention();
+ }
+ }
+}
diff --git a/comm/mailnews/base/src/MailNotificationService.jsm b/comm/mailnews/base/src/MailNotificationService.jsm
new file mode 100644
index 0000000000..8f2c57aad6
--- /dev/null
+++ b/comm/mailnews/base/src/MailNotificationService.jsm
@@ -0,0 +1,375 @@
+/* -*- 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/. */
+
+/**
+ * Platform-independent code to count new and unread messages and pass the
+ * information to platform-specific notification modules.
+ */
+
+var EXPORTED_SYMBOLS = ["NewMailNotificationService"];
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+/**
+ * NewMailNotificationService.
+ *
+ * @implements {mozINewMailNotificationService}
+ * @implements {nsIFolderListener}
+ * @implements {nsIObserver}
+ */
+class NewMailNotificationService {
+ QueryInterface = ChromeUtils.generateQI([
+ "nsIObserver",
+ "nsIFolderListener",
+ "mozINewMailNotificationService",
+ ]);
+
+ #unreadCount = 0;
+ #newCount = 0;
+ #listeners = [];
+ #log = null;
+
+ constructor() {
+ this.#log = console.createInstance({
+ prefix: "mail.notification",
+ maxLogLevel: "Warn",
+ maxLogLevelPref: "mail.notification.loglevel",
+ });
+
+ Services.obs.addObserver(this, "profile-before-change");
+ MailServices.mailSession.AddFolderListener(
+ this,
+ Ci.nsIFolderListener.intPropertyChanged |
+ Ci.nsIFolderListener.added |
+ Ci.nsIFolderListener.removed |
+ Ci.nsIFolderListener.propertyFlagChanged
+ );
+ if (!this.useNewCountInBadge) {
+ let total = 0;
+ for (let server of MailServices.accounts.allServers) {
+ // Don't bother counting RSS or NNTP servers
+ let type = server.type;
+ if (type == "rss" || type == "nntp") {
+ continue;
+ }
+
+ let rootFolder = server.rootFolder;
+ if (rootFolder) {
+ total += this.countUnread(rootFolder);
+ }
+ }
+ this.#unreadCount = total;
+ }
+ }
+
+ get useNewCountInBadge() {
+ return Services.prefs.getBoolPref(
+ "mail.biff.use_new_count_in_badge",
+ false
+ );
+ }
+
+ /** Setter. Used for unit tests. */
+ set unreadCount(count) {
+ this.#unreadCount = count;
+ }
+
+ observe(subject, topic, data) {
+ if (topic == "profile-before-change") {
+ try {
+ MailServices.mailSession.RemoveFolderListener(this);
+ Services.obs.removeObserver(this, "profile-before-change");
+ } catch (e) {
+ this.#log.error("Unable to deregister listeners at shutdown: " + e);
+ }
+ }
+ }
+
+ // Count all the unread messages below the given folder
+ countUnread(folder) {
+ this.#log.debug(`countUnread for ${folder.URI}`);
+ let unreadCount = 0;
+
+ let allFolders = [folder, ...folder.descendants];
+ for (let folder of allFolders) {
+ if (this.confirmShouldCount(folder)) {
+ let count = folder.getNumUnread(false);
+ this.#log.debug(`${folder.URI} has ${count} unread`);
+ if (count > 0) {
+ unreadCount += count;
+ }
+ }
+ }
+ return unreadCount;
+ }
+
+ /**
+ * Filter out special folders and then ask for observers to see if
+ * we should monitor unread messages in this folder.
+ *
+ * @param {nsIMsgFolder} aFolder - The folder we're asking about.
+ */
+ confirmShouldCount(aFolder) {
+ let shouldCount = Cc["@mozilla.org/supports-PRBool;1"].createInstance(
+ Ci.nsISupportsPRBool
+ );
+ shouldCount.data = true;
+
+ // If it's not a mail folder we don't count it by default
+ if (!(aFolder.flags & Ci.nsMsgFolderFlags.Mail)) {
+ shouldCount.data = false;
+ } else if (aFolder.server?.type == "rss") {
+ // For whatever reason, RSS folders have the 'Mail' flag.
+ shouldCount.data = false;
+ } else if (
+ aFolder.flags & Ci.nsMsgFolderFlags.SpecialUse &&
+ !(aFolder.flags & Ci.nsMsgFolderFlags.Inbox)
+ ) {
+ // It's a special folder *other than the inbox*, don't count it by default.
+ shouldCount.data = false;
+ } else if (aFolder.flags & Ci.nsMsgFolderFlags.Virtual) {
+ shouldCount.data = false;
+ } else {
+ // If we're only counting inboxes and it's not an inbox...
+ let onlyCountInboxes = Services.prefs.getBoolPref(
+ "mail.notification.count.inbox_only",
+ true
+ );
+ if (onlyCountInboxes && !(aFolder.flags & Ci.nsMsgFolderFlags.Inbox)) {
+ shouldCount.data = false;
+ }
+ }
+
+ this.#log.debug(`${aFolder.URI}: shouldCount=${shouldCount.data}`);
+ Services.obs.notifyObservers(
+ shouldCount,
+ "before-count-unread-for-folder",
+ aFolder.URI
+ );
+ return shouldCount.data;
+ }
+
+ onFolderIntPropertyChanged(folder, property, oldValue, newValue) {
+ try {
+ if (property == "FolderSize") {
+ return;
+ }
+ this.#log.trace(
+ `Changed int ${property} of ${folder.folderURL}: ${oldValue} -> ${newValue}`
+ );
+ if (property == "BiffState") {
+ this.#biffStateChanged(folder, oldValue, newValue);
+ } else if (property == "TotalUnreadMessages") {
+ this.#totalUnreadMessagesChanged(folder, oldValue, newValue);
+ } else if (property == "NewMailReceived") {
+ this.#newMailReceived(folder, oldValue, newValue);
+ }
+ } catch (error) {
+ this.#log.error("onFolderIntPropertyChanged: " + error);
+ }
+ }
+
+ #biffStateChanged(folder, oldValue, newValue) {
+ if (newValue == Ci.nsIMsgFolder.nsMsgBiffState_NewMail) {
+ if (folder.server && !folder.server.performingBiff) {
+ this.#log.debug(
+ `${folder.URI} notified, but server not performing biff`
+ );
+ return;
+ }
+
+ // Biff notifications come in for the top level of the server, we need to
+ // look for the folder that actually contains the new mail.
+
+ let allFolders = [folder, ...folder.descendants];
+
+ this.#log.debug(`${folder.URI} notified; will check subfolders`);
+ let newCount = 0;
+
+ for (let folder of allFolders) {
+ if (this.confirmShouldCount(folder)) {
+ let folderNew = folder.getNumNewMessages(false);
+ this.#log.debug(`${folder.URI}: ${folderNew} new`);
+ if (folderNew > 0) {
+ newCount += folderNew;
+ }
+ }
+ }
+ if (newCount > 0) {
+ this.#newCount += newCount;
+ this.#log.debug(`${folder.URI}: new mail count ${this.#newCount}`);
+ if (this.useNewCountInBadge) {
+ this._notifyListeners(
+ Ci.mozINewMailNotificationService.count,
+ "onCountChanged",
+ this.#newCount
+ );
+ }
+ }
+ } else if (newValue == Ci.nsIMsgFolder.nsMsgBiffState_NoMail) {
+ // Dodgy - when any folder tells us it has no mail, clear all unread mail
+ this.#newCount = 0;
+ this.#log.debug(`${folder.URI}: no new mail`);
+ if (this.useNewCountInBadge) {
+ this._notifyListeners(
+ Ci.mozINewMailNotificationService.count,
+ "onCountChanged",
+ this.#newCount
+ );
+ }
+ }
+ }
+
+ #newMailReceived(folder, oldValue, newValue) {
+ if (!this.confirmShouldCount(folder)) {
+ return;
+ }
+
+ if (!oldValue || oldValue < 0) {
+ oldValue = 0;
+ }
+ this.#newCount += newValue - oldValue;
+ this.#log.debug(`#newMailReceived ${folder.URI} - ${this.#newCount} new`);
+ if (this.useNewCountInBadge) {
+ this._notifyListeners(
+ Ci.mozINewMailNotificationService.count,
+ "onCountChanged",
+ this.#newCount
+ );
+ }
+ }
+
+ #totalUnreadMessagesChanged(folder, oldValue, newValue) {
+ if (!this.confirmShouldCount(folder)) {
+ return;
+ }
+
+ // treat "count unknown" as zero
+ if (oldValue < 0) {
+ oldValue = 0;
+ }
+ if (newValue < 0) {
+ newValue = 0;
+ }
+
+ this.#unreadCount += newValue - oldValue;
+ if (!this.useNewCountInBadge) {
+ this._notifyListeners(
+ Ci.mozINewMailNotificationService.count,
+ "onCountChanged",
+ this.#unreadCount
+ );
+ }
+ }
+
+ onFolderAdded(parentFolder, child) {
+ if (child.rootFolder == child) {
+ this.#log.trace(`Added root folder ${child.folderURL}`);
+ } else {
+ this.#log.trace(
+ `Added child folder ${child.folderURL} to ${parentFolder.folderURL}`
+ );
+ }
+ }
+
+ onMessageAdded(parentFolder, msg) {
+ if (this.confirmShouldCount(msg.folder)) {
+ this.#log.trace(`Added <${msg.messageId}> to ${msg.folder.folderURL}`);
+ }
+ }
+
+ onFolderPropertyFlagChanged(msg, property, oldFlag, newFlag) {
+ if (
+ oldFlag & Ci.nsMsgMessageFlags.New &&
+ !(newFlag & Ci.nsMsgMessageFlags.New)
+ ) {
+ this.#log.trace(
+ `<${msg.messageId}> marked read in ${msg.folder.folderURL}`
+ );
+ } else if (newFlag & Ci.nsMsgMessageFlags.New) {
+ this.#log.trace(
+ `<${msg.messageId}> marked unread in ${msg.folder.folderURL}`
+ );
+ }
+ }
+
+ onFolderRemoved(parentFolder, child) {
+ if (child.rootFolder == child) {
+ this.#log.trace(`Removed root folder ${child.folderURL}`);
+ } else {
+ this.#log.trace(
+ `Removed child folder ${child.folderURL} from ${parentFolder?.folderURL}`
+ );
+ }
+ }
+
+ onMessageRemoved(parentFolder, msg) {
+ if (!msg.isRead) {
+ this.#log.trace(
+ `Removed unread <${msg.messageId}> from ${msg.folder.folderURL}`
+ );
+ }
+ }
+
+ // Implement mozINewMailNotificationService
+
+ get messageCount() {
+ if (this.useNewCountInBadge) {
+ return this.#newCount;
+ }
+ return this.#unreadCount;
+ }
+
+ addListener(aListener, flags) {
+ for (let i = 0; i < this.#listeners.length; i++) {
+ let l = this.#listeners[i];
+ if (l.obj === aListener) {
+ l.flags = flags;
+ return;
+ }
+ }
+
+ // Ensure that first-time listeners get an accurate mail count.
+ if (flags & Ci.mozINewMailNotificationService.count) {
+ const count = this.useNewCountInBadge
+ ? this.#newCount
+ : this.#unreadCount;
+ aListener.onCountChanged(count);
+ }
+
+ // If we get here, the listener wasn't already in the list
+ this.#listeners.push({ obj: aListener, flags });
+ }
+
+ removeListener(aListener) {
+ for (let i = 0; i < this.#listeners.length; i++) {
+ let l = this.#listeners[i];
+ if (l.obj === aListener) {
+ this.#listeners.splice(i, 1);
+ return;
+ }
+ }
+ }
+
+ listenersForFlag(flag) {
+ let list = [];
+ for (let i = 0; i < this.#listeners.length; i++) {
+ let l = this.#listeners[i];
+ if (l.flags & flag) {
+ list.push(l.obj);
+ }
+ }
+ return list;
+ }
+
+ _notifyListeners(flag, func, value) {
+ let list = this.listenersForFlag(flag);
+ for (let i = 0; i < list.length; i++) {
+ list[i][func](value);
+ }
+ }
+}
diff --git a/comm/mailnews/base/src/MailServices.jsm b/comm/mailnews/base/src/MailServices.jsm
new file mode 100644
index 0000000000..f316b16bde
--- /dev/null
+++ b/comm/mailnews/base/src/MailServices.jsm
@@ -0,0 +1,169 @@
+/* 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 EXPORTED_SYMBOLS = ["MailServices"];
+
+const { XPCOMUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/XPCOMUtils.sys.mjs"
+);
+
+var MailServices = {
+ /**
+ * Gets the `nsIMsgMessageService` for a given message URI. This should have
+ * the same behaviour as `GetMessageServiceFromURI` (nsMsgUtils.cpp).
+ *
+ * @param {string} uri - The URI of a folder or message.
+ * @returns {nsIMsgMessageService}
+ */
+ messageServiceFromURI(uri) {
+ let index = uri.indexOf(":");
+ if (index == -1) {
+ throw new Components.Exception(
+ `Bad message URI: ${uri}`,
+ Cr.NS_ERROR_FAILURE
+ );
+ }
+
+ let protocol = uri.substring(0, index);
+ if (protocol == "file") {
+ protocol = "mailbox";
+ }
+ return Cc[
+ `@mozilla.org/messenger/messageservice;1?type=${protocol}`
+ ].getService(Ci.nsIMsgMessageService);
+ },
+};
+
+XPCOMUtils.defineLazyServiceGetter(
+ MailServices,
+ "mailSession",
+ "@mozilla.org/messenger/services/session;1",
+ "nsIMsgMailSession"
+);
+
+XPCOMUtils.defineLazyServiceGetter(
+ MailServices,
+ "accounts",
+ "@mozilla.org/messenger/account-manager;1",
+ "nsIMsgAccountManager"
+);
+
+XPCOMUtils.defineLazyServiceGetter(
+ MailServices,
+ "pop3",
+ "@mozilla.org/messenger/popservice;1",
+ "nsIPop3Service"
+);
+
+XPCOMUtils.defineLazyServiceGetter(
+ MailServices,
+ "imap",
+ "@mozilla.org/messenger/imapservice;1",
+ "nsIImapService"
+);
+
+XPCOMUtils.defineLazyServiceGetter(
+ MailServices,
+ "nntp",
+ "@mozilla.org/messenger/nntpservice;1",
+ "nsINntpService"
+);
+
+XPCOMUtils.defineLazyServiceGetter(
+ MailServices,
+ "smtp",
+ "@mozilla.org/messengercompose/smtp;1",
+ "nsISmtpService"
+);
+
+XPCOMUtils.defineLazyServiceGetter(
+ MailServices,
+ "compose",
+ "@mozilla.org/messengercompose;1",
+ "nsIMsgComposeService"
+);
+
+XPCOMUtils.defineLazyServiceGetter(
+ MailServices,
+ "ab",
+ "@mozilla.org/abmanager;1",
+ "nsIAbManager"
+);
+
+XPCOMUtils.defineLazyServiceGetter(
+ MailServices,
+ "copy",
+ "@mozilla.org/messenger/messagecopyservice;1",
+ "nsIMsgCopyService"
+);
+
+XPCOMUtils.defineLazyServiceGetter(
+ MailServices,
+ "mfn",
+ "@mozilla.org/messenger/msgnotificationservice;1",
+ "nsIMsgFolderNotificationService"
+);
+
+XPCOMUtils.defineLazyServiceGetter(
+ MailServices,
+ "headerParser",
+ "@mozilla.org/messenger/headerparser;1",
+ "nsIMsgHeaderParser"
+);
+
+XPCOMUtils.defineLazyServiceGetter(
+ MailServices,
+ "mimeConverter",
+ "@mozilla.org/messenger/mimeconverter;1",
+ "nsIMimeConverter"
+);
+
+XPCOMUtils.defineLazyServiceGetter(
+ MailServices,
+ "tags",
+ "@mozilla.org/messenger/tagservice;1",
+ "nsIMsgTagService"
+);
+
+XPCOMUtils.defineLazyServiceGetter(
+ MailServices,
+ "filters",
+ "@mozilla.org/messenger/services/filters;1",
+ "nsIMsgFilterService"
+);
+
+XPCOMUtils.defineLazyServiceGetter(
+ MailServices,
+ "junk",
+ "@mozilla.org/messenger/filter-plugin;1?name=bayesianfilter",
+ "nsIJunkMailPlugin"
+);
+
+XPCOMUtils.defineLazyServiceGetter(
+ MailServices,
+ "newMailNotification",
+ "@mozilla.org/newMailNotificationService;1",
+ "mozINewMailNotificationService"
+);
+
+XPCOMUtils.defineLazyServiceGetter(
+ MailServices,
+ "folderLookup",
+ "@mozilla.org/mail/folder-lookup;1",
+ "nsIFolderLookupService"
+);
+
+// Clean up all of these references at shutdown, so that they don't appear as
+// a memory leak in test logs.
+Services.obs.addObserver(
+ {
+ observe() {
+ for (let key of Object.keys(MailServices)) {
+ delete MailServices[key];
+ }
+ Services.obs.removeObserver(this, "xpcom-shutdown");
+ },
+ },
+ "xpcom-shutdown"
+);
diff --git a/comm/mailnews/base/src/MailStringUtils.jsm b/comm/mailnews/base/src/MailStringUtils.jsm
new file mode 100644
index 0000000000..c892c328e4
--- /dev/null
+++ b/comm/mailnews/base/src/MailStringUtils.jsm
@@ -0,0 +1,102 @@
+/* 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 = ["MailStringUtils"];
+
+var MailStringUtils = {
+ /**
+ * Convert a ByteString to a Uint8Array.
+ *
+ * @param {ByteString} str - The input string.
+ * @returns {Uint8Array} The output Uint8Array.
+ */
+ byteStringToUint8Array(str) {
+ let arr = new Uint8Array(str.length);
+ for (let i = 0; i < str.length; i++) {
+ arr[i] = str.charCodeAt(i);
+ }
+ return arr;
+ },
+
+ /**
+ * Convert a Uint8Array to a ByteString.
+ *
+ * @param {Uint8Array} arr - The input Uint8Array.
+ * @returns {ByteString} The output string.
+ */
+ uint8ArrayToByteString(arr) {
+ let str = "";
+ for (let i = 0; i < arr.length; i += 65536) {
+ str += String.fromCharCode.apply(null, arr.subarray(i, i + 65536));
+ }
+ return str;
+ },
+
+ /**
+ * Convert a ByteString to a string.
+ *
+ * @param {ByteString} str - The ByteString to convert.
+ * @returns {string} The converted string.
+ */
+ byteStringToString(str) {
+ return new TextDecoder().decode(this.byteStringToUint8Array(str));
+ },
+
+ /**
+ * Convert a string to a ByteString.
+ *
+ * @param {string} str - The string to convert.
+ * @returns {ByteString} The converted ByteString.
+ */
+ stringToByteString(str) {
+ return this.uint8ArrayToByteString(new TextEncoder().encode(str));
+ },
+
+ /**
+ * Detect the text encoding of a ByteString.
+ *
+ * @param {ByteString} str - The input string.
+ * @returns {string} The output charset name.
+ */
+ detectCharset(str) {
+ // Check the BOM.
+ let charset = "";
+ if (str.length >= 2) {
+ let byte0 = str.charCodeAt(0);
+ let byte1 = str.charCodeAt(1);
+ let byte2 = str.charCodeAt(2);
+ if (byte0 == 0xfe && byte1 == 0xff) {
+ charset = "UTF-16BE";
+ } else if (byte0 == 0xff && byte1 == 0xfe) {
+ charset = "UTF-16LE";
+ } else if (byte0 == 0xef && byte1 == 0xbb && byte2 == 0xbf) {
+ charset = "UTF-8";
+ }
+ }
+ if (charset) {
+ return charset;
+ }
+
+ // Use mozilla::EncodingDetector.
+ let compUtils = Cc[
+ "@mozilla.org/messengercompose/computils;1"
+ ].createInstance(Ci.nsIMsgCompUtils);
+ return compUtils.detectCharset(str);
+ },
+
+ /**
+ * Read and detect the charset of a file, then convert the file content to
+ * DOMString. If you're absolutely sure it's a UTF-8 encoded file, use
+ * IOUtils.readUTF8 instead.
+ *
+ * @param {string} path - An absolute file path.
+ * @returns {DOMString} The file content.
+ */
+ async readEncoded(path) {
+ let arr = await IOUtils.read(path);
+ let str = this.uint8ArrayToByteString(arr);
+ let charset = this.detectCharset(str);
+ return new TextDecoder(charset).decode(arr);
+ },
+};
diff --git a/comm/mailnews/base/src/MailnewsLoadContextInfo.cpp b/comm/mailnews/base/src/MailnewsLoadContextInfo.cpp
new file mode 100644
index 0000000000..2e4efc72f5
--- /dev/null
+++ b/comm/mailnews/base/src/MailnewsLoadContextInfo.cpp
@@ -0,0 +1,51 @@
+/* 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 was copied from netwerk/base/LoadContextInfo.cpp
+
+#include "MailnewsLoadContextInfo.h"
+
+#include "mozilla/dom/ToJSValue.h"
+#include "nsIChannel.h"
+#include "nsILoadContext.h"
+#include "nsIWebNavigation.h"
+#include "nsNetUtil.h"
+
+// MailnewsLoadContextInfo
+
+NS_IMPL_ISUPPORTS(MailnewsLoadContextInfo, nsILoadContextInfo)
+
+MailnewsLoadContextInfo::MailnewsLoadContextInfo(
+ bool aIsPrivate, bool aIsAnonymous,
+ mozilla::OriginAttributes aOriginAttributes)
+ : mIsPrivate(aIsPrivate),
+ mIsAnonymous(aIsAnonymous),
+ mOriginAttributes(aOriginAttributes) {
+ mOriginAttributes.SyncAttributesWithPrivateBrowsing(mIsPrivate);
+}
+
+MailnewsLoadContextInfo::~MailnewsLoadContextInfo() {}
+
+NS_IMETHODIMP MailnewsLoadContextInfo::GetIsPrivate(bool* aIsPrivate) {
+ *aIsPrivate = mIsPrivate;
+ return NS_OK;
+}
+
+NS_IMETHODIMP MailnewsLoadContextInfo::GetIsAnonymous(bool* aIsAnonymous) {
+ *aIsAnonymous = mIsAnonymous;
+ return NS_OK;
+}
+
+mozilla::OriginAttributes const*
+MailnewsLoadContextInfo::OriginAttributesPtr() {
+ return &mOriginAttributes;
+}
+
+NS_IMETHODIMP MailnewsLoadContextInfo::GetOriginAttributes(
+ JSContext* aCx, JS::MutableHandle<JS::Value> aVal) {
+ if (NS_WARN_IF(!ToJSValue(aCx, mOriginAttributes, aVal))) {
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
diff --git a/comm/mailnews/base/src/MailnewsLoadContextInfo.h b/comm/mailnews/base/src/MailnewsLoadContextInfo.h
new file mode 100644
index 0000000000..b44f7ae43b
--- /dev/null
+++ b/comm/mailnews/base/src/MailnewsLoadContextInfo.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/. */
+
+// This was copied from netwerk/base/LoadContextInfo.h
+
+#ifndef MailnewsLoadContextInfo_h__
+#define MailnewsLoadContextInfo_h__
+
+#include "nsILoadContextInfo.h"
+
+class nsIChannel;
+class nsILoadContext;
+
+class MailnewsLoadContextInfo : public nsILoadContextInfo {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSILOADCONTEXTINFO
+
+ MailnewsLoadContextInfo(bool aIsPrivate, bool aIsAnonymous,
+ mozilla::OriginAttributes aOriginAttributes);
+
+ private:
+ virtual ~MailnewsLoadContextInfo();
+
+ protected:
+ bool mIsPrivate : 1;
+ bool mIsAnonymous : 1;
+ mozilla::OriginAttributes mOriginAttributes;
+};
+
+#endif
diff --git a/comm/mailnews/base/src/MailnewsMigrator.jsm b/comm/mailnews/base/src/MailnewsMigrator.jsm
new file mode 100644
index 0000000000..bc6ad9c3ef
--- /dev/null
+++ b/comm/mailnews/base/src/MailnewsMigrator.jsm
@@ -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/. */
+
+/**
+ * Migrate profile (prefs and other files) from older versions of Mailnews to
+ * current.
+ * This should be run at startup. It migrates as needed: each migration
+ * function should be written to be a no-op when the value is already migrated
+ * or was never used in the old version.
+ */
+
+const EXPORTED_SYMBOLS = ["migrateMailnews"];
+
+const { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+const lazy = {};
+ChromeUtils.defineModuleGetter(
+ lazy,
+ "migrateServerUris",
+ "resource:///modules/MsgIncomingServer.jsm"
+);
+
+var kServerPrefVersion = 1;
+var kSmtpPrefVersion = 1;
+var kABRemoteContentPrefVersion = 1;
+
+function migrateMailnews() {
+ let migrations = [
+ migrateProfileClientid,
+ migrateServerAuthPref,
+ migrateServerAndUserName,
+ migrateABRemoteContentSettings,
+ ];
+
+ for (let fn of migrations) {
+ try {
+ fn();
+ } catch (e) {
+ console.error(e);
+ }
+ }
+}
+
+/**
+ * Creates the server specific 'CLIENTID' prefs and tries to pair up any imap
+ * services with smtp services which are using the same username and hostname.
+ */
+function migrateProfileClientid() {
+ // Comma-separated list of all account ids.
+ let accounts = Services.prefs.getCharPref("mail.accountmanager.accounts", "");
+ // Comma-separated list of all smtp servers.
+ let smtpServers = Services.prefs.getCharPref("mail.smtpservers", "");
+ // If both accounts and smtpservers are empty then there is nothing to do.
+ if (accounts.length == 0 && smtpServers.length == 0) {
+ return;
+ }
+ // A cache to allow CLIENTIDs to be stored and shared across services that
+ // share a username and hostname.
+ let clientidCache = new Map();
+ // There may be accounts but no smtpservers so check the length before
+ // trying to split the smtp servers and iterate in the loop below.
+ if (smtpServers.length > 0) {
+ // Now walk all smtp servers and generate any missing CLIENTIDS, caching
+ // all CLIENTIDS along the way to be reused for matching imap servers
+ // if possible.
+
+ // Since the length of the smtpServers string is non-zero then we can split
+ // the string by comma and iterate each entry in the comma-separated list.
+ for (let key of smtpServers.split(",")) {
+ let server = "mail.smtpserver." + key + ".";
+ if (
+ !Services.prefs.prefHasUserValue(server + "clientid") ||
+ !Services.prefs.getCharPref(server + "clientid", "")
+ ) {
+ // Always give outgoing servers a new unique CLIENTID.
+ let newClientid = Services.uuid
+ .generateUUID()
+ .toString()
+ .replace(/[{}]/g, "");
+ Services.prefs.setCharPref(server + "clientid", newClientid);
+ }
+ let username = Services.prefs.getCharPref(server + "username", "");
+ if (!username) {
+ // Not all SMTP servers require a username.
+ continue;
+ }
+
+ // Cache all CLIENTIDs from all outgoing servers to reuse them for any
+ // incoming servers which have a matching username and hostname.
+ let hostname = Services.prefs.getCharPref(server + "hostname");
+ let combinedKey;
+ try {
+ combinedKey =
+ username + "@" + Services.eTLD.getBaseDomainFromHost(hostname);
+ } catch (e) {
+ combinedKey = username + "@" + hostname;
+ }
+ clientidCache.set(
+ combinedKey,
+ Services.prefs.getCharPref(server + "clientid")
+ );
+ }
+ }
+
+ // Now walk all imap accounts and generate any missing CLIENTIDS, reusing
+ // cached CLIENTIDS if possible.
+ for (let key of accounts.split(",")) {
+ let serverKey = Services.prefs.getCharPref(
+ "mail.account." + key + ".server"
+ );
+ let server = "mail.server." + serverKey + ".";
+ // Check if this imap server needs the CLIENTID preference to be populated.
+ if (
+ !Services.prefs.prefHasUserValue(server + "clientid") ||
+ !Services.prefs.getCharPref(server + "clientid", "")
+ ) {
+ // Clientid should only be provisioned for imap accounts.
+ if (Services.prefs.getCharPref(server + "type", "") != "imap") {
+ continue;
+ }
+ // Grab username + hostname to check if a CLIENTID is cached.
+ let username = Services.prefs.getCharPref(server + "userName", "");
+ if (!username) {
+ continue;
+ }
+ let hostname = Services.prefs.getCharPref(server + "hostname");
+ let combinedKey;
+ try {
+ combinedKey =
+ username + "@" + Services.eTLD.getBaseDomainFromHost(hostname);
+ } catch (e) {
+ combinedKey = username + "@" + hostname;
+ }
+ if (!clientidCache.has(combinedKey)) {
+ // Generate a new CLIENTID if no matches were found from smtp servers.
+ let newClientid = Services.uuid
+ .generateUUID()
+ .toString()
+ .replace(/[{}]/g, "");
+ Services.prefs.setCharPref(server + "clientid", newClientid);
+ } else {
+ // Otherwise if a cached CLIENTID was found for this username + hostname
+ // then we can just use the outgoing CLIENTID which was matching.
+ Services.prefs.setCharPref(
+ server + "clientid",
+ clientidCache.get(combinedKey)
+ );
+ }
+ }
+ }
+}
+
+/**
+ * Migrates from pref useSecAuth to pref authMethod
+ */
+function migrateServerAuthPref() {
+ // comma-separated list of all accounts.
+ var accounts = Services.prefs
+ .getCharPref("mail.accountmanager.accounts")
+ .split(",");
+ for (let i = 0; i < accounts.length; i++) {
+ let accountKey = accounts[i]; // e.g. "account1"
+ if (!accountKey) {
+ continue;
+ }
+ let serverKey = Services.prefs.getCharPref(
+ "mail.account." + accountKey + ".server"
+ );
+ let server = "mail.server." + serverKey + ".";
+ if (Services.prefs.prefHasUserValue(server + "authMethod")) {
+ continue;
+ }
+ if (
+ !Services.prefs.prefHasUserValue(server + "useSecAuth") &&
+ !Services.prefs.prefHasUserValue(server + "auth_login")
+ ) {
+ continue;
+ }
+ if (Services.prefs.prefHasUserValue(server + "migrated")) {
+ continue;
+ }
+ // auth_login = false => old-style auth
+ // else: useSecAuth = true => "secure auth"
+ // else: cleartext pw
+ let auth_login = Services.prefs.getBoolPref(server + "auth_login", true);
+ // old default, default pref now removed
+ let useSecAuth = Services.prefs.getBoolPref(server + "useSecAuth", false);
+
+ if (auth_login) {
+ if (useSecAuth) {
+ Services.prefs.setIntPref(
+ server + "authMethod",
+ Ci.nsMsgAuthMethod.secure
+ );
+ } else {
+ Services.prefs.setIntPref(
+ server + "authMethod",
+ Ci.nsMsgAuthMethod.passwordCleartext
+ );
+ }
+ } else {
+ Services.prefs.setIntPref(server + "authMethod", Ci.nsMsgAuthMethod.old);
+ }
+ Services.prefs.setIntPref(server + "migrated", kServerPrefVersion);
+ }
+
+ // same again for SMTP servers
+ var smtpservers = Services.prefs.getCharPref("mail.smtpservers").split(",");
+ for (let i = 0; i < smtpservers.length; i++) {
+ if (!smtpservers[i]) {
+ continue;
+ }
+ let server = "mail.smtpserver." + smtpservers[i] + ".";
+ if (Services.prefs.prefHasUserValue(server + "authMethod")) {
+ continue;
+ }
+ if (
+ !Services.prefs.prefHasUserValue(server + "useSecAuth") &&
+ !Services.prefs.prefHasUserValue(server + "auth_method")
+ ) {
+ continue;
+ }
+ if (Services.prefs.prefHasUserValue(server + "migrated")) {
+ continue;
+ }
+ // auth_method = 0 => no auth
+ // else: useSecAuth = true => "secure auth"
+ // else: cleartext pw
+ let auth_method = Services.prefs.getIntPref(server + "auth_method", 1);
+ let useSecAuth = Services.prefs.getBoolPref(server + "useSecAuth", false);
+
+ if (auth_method) {
+ if (useSecAuth) {
+ Services.prefs.setIntPref(
+ server + "authMethod",
+ Ci.nsMsgAuthMethod.secure
+ );
+ } else {
+ Services.prefs.setIntPref(
+ server + "authMethod",
+ Ci.nsMsgAuthMethod.passwordCleartext
+ );
+ }
+ } else {
+ Services.prefs.setIntPref(server + "authMethod", Ci.nsMsgAuthMethod.none);
+ }
+ Services.prefs.setIntPref(server + "migrated", kSmtpPrefVersion);
+ }
+}
+
+/**
+ * For each mail.server.key. branch,
+ * - migrate realhostname to hostname
+ * - migrate realuserName to userName
+ */
+function migrateServerAndUserName() {
+ let branch = Services.prefs.getBranch("mail.server.");
+
+ // Collect all the server keys.
+ let keySet = new Set();
+ for (let name of branch.getChildList("")) {
+ keySet.add(name.split(".")[0]);
+ }
+ keySet.delete("default");
+
+ for (let key of keySet) {
+ let type = branch.getCharPref(`${key}.type`, "");
+ let hostname = branch.getCharPref(`${key}.hostname`, "");
+ let username = branch.getCharPref(`${key}.userName`, "");
+ let realHostname = branch.getCharPref(`${key}.realhostname`, "");
+ if (realHostname) {
+ branch.setCharPref(`${key}.hostname`, realHostname);
+ branch.clearUserPref(`${key}.realhostname`);
+ }
+ let realUsername = branch.getCharPref(`${key}.realuserName`, "");
+ if (realUsername) {
+ branch.setCharPref(`${key}.userName`, realUsername);
+ branch.clearUserPref(`${key}.realuserName`);
+ }
+ // Previously, when hostname/username changed, LoginManager and many prefs
+ // still contain the old hostname/username, try to migrate them to use the
+ // new hostname/username.
+ if (
+ ["imap", "pop3", "nntp"].includes(type) &&
+ (realHostname || realUsername)
+ ) {
+ let localStoreType = { imap: "imap", pop3: "mailbox", nntp: "news" }[
+ type
+ ];
+ lazy.migrateServerUris(
+ localStoreType,
+ hostname,
+ username,
+ realHostname || hostname,
+ realUsername || username
+ );
+ }
+ }
+}
+
+/**
+ * The address book used to contain information about whether to allow remote
+ * content for a given contact. Now we use the permission manager for that.
+ * Do a one-time migration for it.
+ */
+function migrateABRemoteContentSettings() {
+ if (Services.prefs.prefHasUserValue("mail.ab_remote_content.migrated")) {
+ return;
+ }
+
+ // Search through all of our local address books looking for a match.
+ for (let addrbook of MailServices.ab.directories) {
+ let migrateAddress = function (aEmail) {
+ let uri = Services.io.newURI(
+ "chrome://messenger/content/email=" + aEmail
+ );
+ Services.perms.addFromPrincipal(
+ Services.scriptSecurityManager.createContentPrincipal(uri, {}),
+ "image",
+ Services.perms.ALLOW_ACTION
+ );
+ };
+
+ try {
+ // If it's a read-only book, don't try to find a card as we we could never
+ // have set the AllowRemoteContent property.
+ if (addrbook.readOnly) {
+ continue;
+ }
+
+ for (let card of addrbook.childCards) {
+ if (card.getProperty("AllowRemoteContent", "0") == "0") {
+ // Not allowed for this contact.
+ continue;
+ }
+
+ for (let emailAddress of card.emailAddresses) {
+ migrateAddress(emailAddress);
+ }
+ }
+ } catch (e) {
+ console.error(e);
+ }
+ }
+
+ Services.prefs.setIntPref(
+ "mail.ab_remote_content.migrated",
+ kABRemoteContentPrefVersion
+ );
+}
diff --git a/comm/mailnews/base/src/MsgAsyncPrompter.jsm b/comm/mailnews/base/src/MsgAsyncPrompter.jsm
new file mode 100644
index 0000000000..e04e9a9418
--- /dev/null
+++ b/comm/mailnews/base/src/MsgAsyncPrompter.jsm
@@ -0,0 +1,621 @@
+/* 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 EXPORTED_SYMBOLS = ["MsgAsyncPrompter", "MsgAuthPrompt"];
+
+var { XPCOMUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/XPCOMUtils.sys.mjs"
+);
+
+const LoginInfo = Components.Constructor(
+ "@mozilla.org/login-manager/loginInfo;1",
+ "nsILoginInfo",
+ "init"
+);
+
+const lazy = {};
+
+ChromeUtils.defineESModuleGetters(lazy, {
+ Deprecated: "resource://gre/modules/Deprecated.sys.mjs",
+ PromptUtils: "resource://gre/modules/PromptUtils.sys.mjs",
+});
+
+XPCOMUtils.defineLazyGetter(lazy, "dialogsBundle", function () {
+ return Services.strings.createBundle(
+ "chrome://global/locale/commonDialogs.properties"
+ );
+});
+
+XPCOMUtils.defineLazyGetter(lazy, "passwordsBundle", function () {
+ return Services.strings.createBundle(
+ "chrome://passwordmgr/locale/passwordmgr.properties"
+ );
+});
+
+XPCOMUtils.defineLazyGetter(lazy, "brandFullName", function () {
+ return Services.strings
+ .createBundle("chrome://branding/locale/brand.properties")
+ .GetStringFromName("brandFullName");
+});
+
+function runnablePrompter(asyncPrompter, hashKey) {
+ this._asyncPrompter = asyncPrompter;
+ this._hashKey = hashKey;
+}
+
+runnablePrompter.prototype = {
+ _asyncPrompter: null,
+ _hashKey: null,
+
+ _promiseAuthPrompt(listener) {
+ return new Promise((resolve, reject) => {
+ try {
+ listener.onPromptStartAsync({ onAuthResult: resolve });
+ } catch (e) {
+ if (e.result == Cr.NS_ERROR_XPC_JSOBJECT_HAS_NO_FUNCTION_NAMED) {
+ // Fall back to onPromptStart, for add-ons compat
+ lazy.Deprecated.warning(
+ "onPromptStart has been replaced by onPromptStartAsync",
+ "https://bugzilla.mozilla.org/show_bug.cgi?id=1176399"
+ );
+ let ok = listener.onPromptStart();
+ resolve(ok);
+ } else {
+ reject(e);
+ }
+ }
+ });
+ },
+
+ async run() {
+ await Services.logins.initializationPromise;
+ this._asyncPrompter._log.debug("Running prompt for " + this._hashKey);
+ let prompter = this._asyncPrompter._pendingPrompts[this._hashKey];
+ let ok = false;
+ try {
+ ok = await this._promiseAuthPrompt(prompter.first);
+ } catch (ex) {
+ console.error("runnablePrompter:run: " + ex + "\n");
+ prompter.first.onPromptCanceled();
+ }
+
+ delete this._asyncPrompter._pendingPrompts[this._hashKey];
+
+ for (var consumer of prompter.consumers) {
+ try {
+ if (ok) {
+ consumer.onPromptAuthAvailable();
+ } else {
+ consumer.onPromptCanceled();
+ }
+ } catch (ex) {
+ // Log the error for extension devs and others to pick up.
+ console.error(
+ "runnablePrompter:run: consumer.onPrompt* reported an exception: " +
+ ex +
+ "\n"
+ );
+ }
+ }
+ this._asyncPrompter._asyncPromptInProgress--;
+
+ this._asyncPrompter._log.debug(
+ "Finished running prompter for " + this._hashKey
+ );
+ this._asyncPrompter._doAsyncAuthPrompt();
+ },
+};
+
+function MsgAsyncPrompter() {
+ this._pendingPrompts = {};
+ // By default, only log warnings to the error console
+ // You can use the preference:
+ // msgAsyncPrompter.loglevel
+ // To change this up. Values should be one of:
+ // Fatal/Error/Warn/Info/Config/Debug/Trace/All
+ this._log = console.createInstance({
+ prefix: "mail.asyncprompter",
+ maxLogLevel: "Warn",
+ maxLogLevelPref: "mail.asyncprompter.loglevel",
+ });
+}
+
+MsgAsyncPrompter.prototype = {
+ QueryInterface: ChromeUtils.generateQI(["nsIMsgAsyncPrompter"]),
+
+ _pendingPrompts: null,
+ _asyncPromptInProgress: 0,
+ _log: null,
+
+ queueAsyncAuthPrompt(aKey, aJumpQueue, aCaller) {
+ if (aKey in this._pendingPrompts) {
+ this._log.debug(
+ "Prompt bound to an existing one in the queue, key: " + aKey
+ );
+ this._pendingPrompts[aKey].consumers.push(aCaller);
+ return;
+ }
+
+ this._log.debug("Adding new prompt to the queue, key: " + aKey);
+ let asyncPrompt = {
+ first: aCaller,
+ consumers: [],
+ };
+
+ this._pendingPrompts[aKey] = asyncPrompt;
+ if (aJumpQueue) {
+ this._asyncPromptInProgress++;
+
+ this._log.debug("Forcing runnablePrompter for " + aKey);
+
+ let runnable = new runnablePrompter(this, aKey);
+ Services.tm.mainThread.dispatch(runnable, Ci.nsIThread.DISPATCH_NORMAL);
+ } else {
+ this._doAsyncAuthPrompt();
+ }
+ },
+
+ _doAsyncAuthPrompt() {
+ if (this._asyncPromptInProgress > 0) {
+ this._log.debug(
+ "_doAsyncAuthPrompt bypassed - prompt already in progress"
+ );
+ return;
+ }
+
+ // Find the first prompt key we have in the queue.
+ let hashKey = null;
+ for (hashKey in this._pendingPrompts) {
+ break;
+ }
+
+ if (!hashKey) {
+ return;
+ }
+
+ this._asyncPromptInProgress++;
+
+ this._log.debug("Dispatching runnablePrompter for " + hashKey);
+
+ let runnable = new runnablePrompter(this, hashKey);
+ Services.tm.mainThread.dispatch(runnable, Ci.nsIThread.DISPATCH_NORMAL);
+ },
+};
+
+/**
+ * An implementation of nsIAuthPrompt which is roughly the same as
+ * LoginManagerAuthPrompter was before the check box option was removed from
+ * nsIPromptService.
+ *
+ * Calls our own version of promptUsernameAndPassword/promptPassword, which
+ * directly open the prompt.
+ *
+ * @implements {nsIAuthPrompt}
+ */
+class MsgAuthPrompt {
+ QueryInterface = ChromeUtils.generateQI(["nsIAuthPrompt"]);
+
+ _getFormattedOrigin(aURI) {
+ let uri;
+ if (aURI instanceof Ci.nsIURI) {
+ uri = aURI;
+ } else {
+ uri = Services.io.newURI(aURI);
+ }
+
+ return uri.scheme + "://" + uri.displayHostPort;
+ }
+
+ _getRealmInfo(aRealmString) {
+ let httpRealm = /^.+ \(.+\)$/;
+ if (httpRealm.test(aRealmString)) {
+ return [null, null, null];
+ }
+
+ let uri = Services.io.newURI(aRealmString);
+ let pathname = "";
+
+ if (uri.pathQueryRef != "/") {
+ pathname = uri.pathQueryRef;
+ }
+
+ let formattedOrigin = this._getFormattedOrigin(uri);
+
+ return [formattedOrigin, formattedOrigin + pathname, uri.username];
+ }
+
+ _getLocalizedString(key, formatArgs) {
+ if (formatArgs) {
+ return lazy.passwordsBundle.formatStringFromName(key, formatArgs);
+ }
+ return lazy.passwordsBundle.GetStringFromName(key);
+ }
+
+ /**
+ * Wrapper around the prompt service prompt. Saving random fields here
+ * doesn't really make sense and therefore isn't implemented.
+ */
+ prompt(
+ aDialogTitle,
+ aText,
+ aPasswordRealm,
+ aSavePassword,
+ aDefaultText,
+ aResult
+ ) {
+ if (aSavePassword != Ci.nsIAuthPrompt.SAVE_PASSWORD_NEVER) {
+ throw new Components.Exception(
+ "prompt only supports SAVE_PASSWORD_NEVER",
+ Cr.NS_ERROR_NOT_IMPLEMENTED
+ );
+ }
+
+ if (aDefaultText) {
+ aResult.value = aDefaultText;
+ }
+
+ return Services.prompt.prompt(
+ this._chromeWindow,
+ aDialogTitle,
+ aText,
+ aResult,
+ null,
+ {}
+ );
+ }
+
+ /**
+ * Looks up a username and password in the database. Will prompt the user
+ * with a dialog, even if a username and password are found.
+ */
+ promptUsernameAndPassword(
+ aDialogTitle,
+ aText,
+ aPasswordRealm,
+ aSavePassword,
+ aUsername,
+ aPassword
+ ) {
+ if (aSavePassword == Ci.nsIAuthPrompt.SAVE_PASSWORD_FOR_SESSION) {
+ throw new Components.Exception(
+ "promptUsernameAndPassword doesn't support SAVE_PASSWORD_FOR_SESSION",
+ Cr.NS_ERROR_NOT_IMPLEMENTED
+ );
+ }
+
+ let checkBox = { value: false };
+ let checkBoxLabel = null;
+ let [origin, realm] = this._getRealmInfo(aPasswordRealm);
+
+ // If origin is null, we can't save this login.
+ if (origin) {
+ let canRememberLogin =
+ aSavePassword == Ci.nsIAuthPrompt.SAVE_PASSWORD_PERMANENTLY &&
+ Services.logins.getLoginSavingEnabled(origin);
+
+ // if checkBoxLabel is null, the checkbox won't be shown at all.
+ if (canRememberLogin) {
+ checkBoxLabel = this._getLocalizedString("rememberPassword");
+ }
+
+ for (let login of Services.logins.findLogins(origin, null, realm)) {
+ if (login.username == aUsername.value) {
+ checkBox.value = true;
+ aUsername.value = login.username;
+ // If the caller provided a password, prefer it.
+ if (!aPassword.value) {
+ aPassword.value = login.password;
+ }
+ }
+ }
+ }
+
+ let ok = nsIPrompt_promptUsernameAndPassword(
+ aDialogTitle,
+ aText,
+ aUsername,
+ aPassword,
+ checkBoxLabel,
+ checkBox
+ );
+
+ if (!ok || !checkBox.value || !origin) {
+ return ok;
+ }
+
+ let newLogin = new LoginInfo(
+ origin,
+ null,
+ realm,
+ aUsername.value,
+ aPassword.value
+ );
+ Services.logins.addLogin(newLogin);
+
+ return ok;
+ }
+
+ /**
+ * If a password is found in the database for the password realm, it is
+ * returned straight away without displaying a dialog.
+ *
+ * If a password is not found in the database, the user will be prompted
+ * with a dialog with a text field and ok/cancel buttons. If the user
+ * allows it, then the password will be saved in the database.
+ */
+ promptPassword(
+ aDialogTitle,
+ aText,
+ aPasswordRealm,
+ aSavePassword,
+ aPassword
+ ) {
+ if (aSavePassword == Ci.nsIAuthPrompt.SAVE_PASSWORD_FOR_SESSION) {
+ throw new Components.Exception(
+ "promptUsernameAndPassword doesn't support SAVE_PASSWORD_FOR_SESSION",
+ Cr.NS_ERROR_NOT_IMPLEMENTED
+ );
+ }
+
+ let checkBox = { value: false };
+ let checkBoxLabel = null;
+ let [origin, realm, username] = this._getRealmInfo(aPasswordRealm);
+
+ username = decodeURIComponent(username);
+
+ // If origin is null, we can't save this login.
+ if (origin) {
+ let canRememberLogin =
+ aSavePassword == Ci.nsIAuthPrompt.SAVE_PASSWORD_PERMANENTLY &&
+ Services.logins.getLoginSavingEnabled(origin);
+
+ // if checkBoxLabel is null, the checkbox won't be shown at all.
+ if (canRememberLogin) {
+ checkBoxLabel = this._getLocalizedString("rememberPassword");
+ }
+
+ if (!aPassword.value) {
+ // Look for existing logins.
+ for (let login of Services.logins.findLogins(origin, null, realm)) {
+ if (login.username == username) {
+ aPassword.value = login.password;
+ return true;
+ }
+ }
+ }
+ }
+
+ let ok = nsIPrompt_promptPassword(
+ aDialogTitle,
+ aText,
+ aPassword,
+ checkBoxLabel,
+ checkBox
+ );
+
+ if (ok && checkBox.value && origin && aPassword.value) {
+ let newLogin = new LoginInfo(
+ origin,
+ null,
+ realm,
+ username,
+ aPassword.value
+ );
+
+ Services.logins.addLogin(newLogin);
+ }
+
+ return ok;
+ }
+
+ /**
+ * Implements nsIPrompt.promptPassword as it was before the check box option
+ * was removed.
+ *
+ * Puts up a dialog with a password field and an optional, labelled checkbox.
+ *
+ * @param {string} dialogTitle - Text to appear in the title of the dialog.
+ * @param {string} text - Text to appear in the body of the dialog.
+ * @param {?object} password - Contains the default value for the password
+ * field when this method is called (null value is ok).
+ * Upon return, if the user pressed OK, then this parameter contains a
+ * newly allocated string value.
+ * Otherwise, the parameter's value is unmodified.
+ * @param {?string} checkMsg - Text to appear with the checkbox. If null,
+ * check box will not be shown.
+ * @param {?object} checkValue - Contains the initial checked state of the
+ * checkbox when this method is called and the final checked state after
+ * this method returns.
+ *
+ * @returns {boolean} true for OK, false for Cancel.
+ */
+ promptPassword2(dialogTitle, text, password, checkMsg, checkValue) {
+ return nsIPrompt_promptPassword(
+ dialogTitle,
+ text,
+ password,
+ checkMsg,
+ checkValue
+ );
+ }
+
+ /**
+ * Requests a username and a password. Implementations will commonly show a
+ * dialog with a username and password field, depending on flags also a
+ * domain field.
+ *
+ * @param {nsIChannel} channel - The channel that requires authentication.
+ * @param {number} level - One of the level constants from nsIAuthPrompt2.
+ * See there for descriptions of the levels.
+ * @param {nsIAuthInformation} authInfo - Authentication information object.
+ * The implementation should fill in this object with the information
+ * entered by the user before returning.
+ * @param {string} checkboxLabel
+ * Text to appear with the checkbox. If null, check box will not be shown.
+ * @param {object} checkValue
+ * Contains the initial checked state of the checkbox when this method
+ * is called and the final checked state after this method returns.
+ * @returns {boolean} true for OK, false for Cancel.
+ */
+ promptAuth(channel, level, authInfo, checkboxLabel, checkValue) {
+ let title = lazy.dialogsBundle.formatStringFromName(
+ "PromptUsernameAndPassword3",
+ [lazy.brandFullName]
+ );
+ let text = lazy.dialogsBundle.formatStringFromName(
+ "EnterUserPasswordFor2",
+ [`${channel.URI.scheme}://${channel.URI.host}`]
+ );
+
+ let username = { value: authInfo.username || "" };
+ let password = { value: authInfo.password || "" };
+
+ let ok = nsIPrompt_promptUsernameAndPassword(
+ title,
+ text,
+ username,
+ password,
+ checkboxLabel,
+ checkValue
+ );
+
+ if (ok) {
+ authInfo.username = username.value;
+ authInfo.password = password.value;
+ }
+
+ return ok;
+ }
+}
+
+/**
+ * @param {string} dialogTitle - Text to appear in the title of the dialog.
+ * @param {string} text - Text to appear in the body of the dialog.
+ * @param {?object} username
+ * Contains the default value for the username field when this method
+ * is called (null value is ok). Upon return, if the user pressed OK,
+ * then this parameter contains a newly allocated string value.
+ * @param {?object} password - Contains the default value for the password
+ * field when this method is called (null value is ok).
+ * Upon return, if the user pressed OK, then this parameter contains a
+ * newly allocated string value.
+ * Otherwise, the parameter's value is unmodified.
+ * @param {?string} checkMsg - Text to appear with the checkbox. If null,
+ * check box will not be shown.
+ * @param {?object} checkValue - Contains the initial checked state of the
+ * checkbox when this method is called and the final checked state after
+ * this method returns.
+ * @returns {boolean} true for OK, false for Cancel.
+ */
+function nsIPrompt_promptUsernameAndPassword(
+ dialogTitle,
+ text,
+ username,
+ password,
+ checkMsg,
+ checkValue
+) {
+ if (!dialogTitle) {
+ dialogTitle = lazy.dialogsBundle.formatStringFromName(
+ "PromptUsernameAndPassword3",
+ [lazy.brandFullName]
+ );
+ }
+
+ let args = {
+ promptType: "promptUserAndPass",
+ title: dialogTitle,
+ text,
+ user: username.value,
+ pass: password.value,
+ checkLabel: checkMsg,
+ checked: checkValue.value,
+ ok: false,
+ };
+
+ let propBag = lazy.PromptUtils.objectToPropBag(args);
+ Services.ww.openWindow(
+ Services.ww.activeWindow,
+ "chrome://global/content/commonDialog.xhtml",
+ "_blank",
+ "centerscreen,chrome,modal,titlebar",
+ propBag
+ );
+ lazy.PromptUtils.propBagToObject(propBag, args);
+
+ // Did user click Ok or Cancel?
+ let ok = args.ok;
+ if (ok) {
+ checkValue.value = args.checked;
+ username.value = args.user;
+ password.value = args.pass;
+ }
+
+ return ok;
+}
+
+/**
+ * Implements nsIPrompt.promptPassword as it was before the check box option
+ * was removed.
+ *
+ * Puts up a dialog with a password field and an optional, labelled checkbox.
+ *
+ * @param {string} dialogTitle - Text to appear in the title of the dialog.
+ * @param {string} text - Text to appear in the body of the dialog.
+ * @param {?object} password - Contains the default value for the password
+ * field when this method is called (null value is ok).
+ * Upon return, if the user pressed OK, then this parameter contains a
+ * newly allocated string value.
+ * Otherwise, the parameter's value is unmodified.
+ * @param {?string} checkMsg - Text to appear with the checkbox. If null,
+ * check box will not be shown.
+ * @param {?object} checkValue - Contains the initial checked state of the
+ * checkbox when this method is called and the final checked state after
+ * this method returns.
+ *
+ * @returns {boolean} true for OK, false for Cancel.
+ */
+function nsIPrompt_promptPassword(
+ dialogTitle,
+ text,
+ password,
+ checkMsg,
+ checkValue
+) {
+ if (!dialogTitle) {
+ dialogTitle = lazy.dialogsBundle.formatStringFromName(
+ "PromptUsernameAndPassword3",
+ [lazy.brandFullName]
+ );
+ }
+
+ let args = {
+ promptType: "promptPassword",
+ title: dialogTitle,
+ text,
+ pass: password.value,
+ checkLabel: checkMsg,
+ checked: checkValue.value,
+ ok: false,
+ };
+
+ let propBag = lazy.PromptUtils.objectToPropBag(args);
+ Services.ww.openWindow(
+ Services.ww.activeWindow,
+ "chrome://global/content/commonDialog.xhtml",
+ "_blank",
+ "centerscreen,chrome,modal,titlebar",
+ propBag
+ );
+ lazy.PromptUtils.propBagToObject(propBag, args);
+
+ // Did user click Ok or Cancel?
+ let ok = args.ok;
+ if (ok) {
+ checkValue.value = args.checked;
+ password.value = args.pass;
+ }
+
+ return ok;
+}
diff --git a/comm/mailnews/base/src/MsgDBCacheManager.jsm b/comm/mailnews/base/src/MsgDBCacheManager.jsm
new file mode 100644
index 0000000000..9506f08e5e
--- /dev/null
+++ b/comm/mailnews/base/src/MsgDBCacheManager.jsm
@@ -0,0 +1,185 @@
+/* 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 DB Cache manager
+ */
+
+/* :::::::: Constants and Helpers ::::::::::::::: */
+
+const EXPORTED_SYMBOLS = ["msgDBCacheManager"];
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+var log = console.createInstance({
+ prefix: "mailnews.database.dbcache",
+ maxLogLevel: "Warn",
+ maxLogLevelPref: "mailnews.database.dbcache.loglevel",
+});
+
+/**
+ */
+var DBCACHE_INTERVAL_DEFAULT_MS = 60000; // 1 minute
+
+/* :::::::: The Module ::::::::::::::: */
+
+var msgDBCacheManager = {
+ _initialized: false,
+
+ _msgDBCacheTimer: null,
+
+ _msgDBCacheTimerIntervalMS: DBCACHE_INTERVAL_DEFAULT_MS,
+
+ _dbService: null,
+
+ /**
+ * This is called on startup
+ */
+ init() {
+ if (this._initialized) {
+ return;
+ }
+
+ this._dbService = Cc["@mozilla.org/msgDatabase/msgDBService;1"].getService(
+ Ci.nsIMsgDBService
+ );
+
+ // we listen for "quit-application-granted" instead of
+ // "quit-application-requested" because other observers of the
+ // latter can cancel the shutdown.
+ Services.obs.addObserver(this, "quit-application-granted");
+
+ this.startPeriodicCheck();
+
+ this._initialized = true;
+ },
+
+ /* ........ Timer Callback ................*/
+
+ _dbCacheCheckTimerCallback() {
+ msgDBCacheManager.checkCachedDBs();
+ },
+
+ /* ........ Observer Notification Handler ................*/
+
+ observe(aSubject, aTopic, aData) {
+ switch (aTopic) {
+ // This is observed before any windows start unloading if something other
+ // than the last 3pane window closing requested the application be
+ // shutdown. For example, when the user quits via the file menu.
+ case "quit-application-granted":
+ Services.obs.removeObserver(this, "quit-application-granted");
+ this.stopPeriodicCheck();
+ break;
+ }
+ },
+
+ /* ........ Public API ................*/
+
+ /**
+ * Stops db cache check
+ */
+ stopPeriodicCheck() {
+ if (this._dbCacheCheckTimer) {
+ this._dbCacheCheckTimer.cancel();
+
+ delete this._dbCacheCheckTimer;
+ this._dbCacheCheckTimer = null;
+ }
+ },
+
+ /**
+ * Starts periodic db cache check
+ */
+ startPeriodicCheck() {
+ if (!this._dbCacheCheckTimer) {
+ this._dbCacheCheckTimer = Cc["@mozilla.org/timer;1"].createInstance(
+ Ci.nsITimer
+ );
+
+ this._dbCacheCheckTimer.initWithCallback(
+ this._dbCacheCheckTimerCallback,
+ this._msgDBCacheTimerIntervalMS,
+ Ci.nsITimer.TYPE_REPEATING_SLACK
+ );
+ }
+ },
+
+ /**
+ * Checks if any DBs need to be closed due to inactivity or too many of them open.
+ */
+ checkCachedDBs() {
+ let idleLimit = Services.prefs.getIntPref("mail.db.idle_limit");
+ let maxOpenDBs = Services.prefs.getIntPref("mail.db.max_open");
+
+ // db.lastUseTime below is in microseconds while Date.now and idleLimit pref
+ // is in milliseconds.
+ let closeThreshold = (Date.now() - idleLimit) * 1000;
+ let cachedDBs = this._dbService.openDBs;
+ log.info(
+ "Periodic check of cached folder databases (DBs), count=" +
+ cachedDBs.length
+ );
+ // Count databases that are already closed or get closed now due to inactivity.
+ let numClosing = 0;
+ // Count databases whose folder is open in a window.
+ let numOpenInWindow = 0;
+ let dbs = [];
+ for (let db of cachedDBs) {
+ if (!db.folder?.databaseOpen) {
+ // The DB isn't really open anymore.
+ log.debug("Skipping, DB not open for folder: " + db.folder?.name);
+ numClosing++;
+ continue;
+ }
+
+ if (MailServices.mailSession.IsFolderOpenInWindow(db.folder)) {
+ // The folder is open in a window so this DB must not be closed.
+ log.debug("Skipping, DB open in window for folder: " + db.folder.name);
+ numOpenInWindow++;
+ continue;
+ }
+
+ if (db.lastUseTime < closeThreshold) {
+ // DB open too log without activity.
+ log.debug("Closing expired DB for folder: " + db.folder.name);
+ db.folder.msgDatabase = null;
+ numClosing++;
+ continue;
+ }
+
+ // Database eligible for closing.
+ dbs.push(db);
+ }
+ log.info(
+ "DBs open in a window: " +
+ numOpenInWindow +
+ ", DBs open: " +
+ dbs.length +
+ ", DBs already closing: " +
+ numClosing
+ );
+ let dbsToClose = Math.max(
+ dbs.length - Math.max(maxOpenDBs - numOpenInWindow, 0),
+ 0
+ );
+ if (dbsToClose > 0) {
+ // Close some DBs so that we do not have more than maxOpenDBs.
+ // However, we skipped DBs for folders that are open in a window
+ // so if there are so many windows open, it may be possible for
+ // more than maxOpenDBs folders to stay open after this loop.
+ log.info("Need to close " + dbsToClose + " more DBs");
+ // Order databases by lowest lastUseTime (oldest) at the end.
+ dbs.sort((a, b) => b.lastUseTime - a.lastUseTime);
+ while (dbsToClose > 0) {
+ let db = dbs.pop();
+ log.debug("Closing DB for folder: " + db.folder.name);
+ db.folder.msgDatabase = null;
+ dbsToClose--;
+ }
+ }
+ },
+};
diff --git a/comm/mailnews/base/src/MsgIncomingServer.jsm b/comm/mailnews/base/src/MsgIncomingServer.jsm
new file mode 100644
index 0000000000..768fe9340e
--- /dev/null
+++ b/comm/mailnews/base/src/MsgIncomingServer.jsm
@@ -0,0 +1,1268 @@
+/* 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 = ["migrateServerUris", "MsgIncomingServer"];
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+/**
+ * When hostname/username changes, update the corresponding entry in
+ * nsILoginManager.
+ *
+ * @param {string} localStoreType - The store type of the current server.
+ * @param {string} oldHostname - The hostname before the change.
+ * @param {string} oldUsername - The username before the change.
+ * @param {string} newHostname - The hostname after the change.
+ * @param {string} newUsername - The username after the change.
+ */
+function migratePassword(
+ localStoreType,
+ oldHostname,
+ oldUsername,
+ newHostname,
+ newUsername
+) {
+ // When constructing nsIURI, need to wrap IPv6 address in [].
+ oldHostname = oldHostname.includes(":") ? `[${oldHostname}]` : oldHostname;
+ let oldServerUri = `${localStoreType}://${encodeURIComponent(oldHostname)}`;
+ newHostname = newHostname.includes(":") ? `[${newHostname}]` : newHostname;
+ let newServerUri = `${localStoreType}://${encodeURIComponent(newHostname)}`;
+
+ let logins = Services.logins.findLogins(oldServerUri, "", oldServerUri);
+ for (let login of logins) {
+ if (login.username == oldUsername) {
+ // If a nsILoginInfo exists for the old hostname/username, update it to
+ // use the new hostname/username.
+ let newLogin = Cc[
+ "@mozilla.org/login-manager/loginInfo;1"
+ ].createInstance(Ci.nsILoginInfo);
+ newLogin.init(
+ newServerUri,
+ null,
+ newServerUri,
+ newUsername,
+ login.password,
+ "",
+ ""
+ );
+ Services.logins.modifyLogin(login, newLogin);
+ }
+ }
+}
+
+/**
+ * When hostname/username changes, update the folder attributes in related
+ * identities.
+ *
+ * @param {string} oldServerUri - The server uri before the change.
+ * @param {string} newServerUri - The server uri after the change.
+ */
+function migrateIdentities(oldServerUri, newServerUri) {
+ for (let identity of MailServices.accounts.allIdentities) {
+ let attributes = [
+ "fcc_folder",
+ "draft_folder",
+ "archive_folder",
+ "stationery_folder",
+ ];
+ for (let attr of attributes) {
+ let folderUri = identity.getUnicharAttribute(attr);
+ if (folderUri.startsWith(oldServerUri)) {
+ identity.setUnicharAttribute(
+ attr,
+ folderUri.replace(oldServerUri, newServerUri)
+ );
+ }
+ }
+ }
+}
+
+/**
+ * When hostname/username changes, update .spamActionTargetAccount and
+ * .spamActionTargetFolder prefs.
+ *
+ * @param {string} oldServerUri - The server uri before the change.
+ * @param {string} newServerUri - The server uri after the change.
+ */
+function migrateSpamActions(oldServerUri, newServerUri) {
+ for (let server of MailServices.accounts.allServers) {
+ let targetAccount = server.getCharValue("spamActionTargetAccount");
+ let targetFolder = server.getUnicharValue("spamActionTargetFolder");
+ if (targetAccount.startsWith(oldServerUri)) {
+ server.setCharValue(
+ "spamActionTargetAccount",
+ targetAccount.replace(oldServerUri, newServerUri)
+ );
+ }
+ if (targetFolder.startsWith(oldServerUri)) {
+ server.setUnicharValue(
+ "spamActionTargetFolder",
+ targetFolder.replace(oldServerUri, newServerUri)
+ );
+ }
+ }
+}
+
+/**
+ * When hostname/username changes, update targetFolderUri in related filters
+ * to the new folder uri.
+ *
+ * @param {string} oldServerUri - The server uri before the change.
+ * @param {string} newServerUri - The server uri after the change.
+ */
+function migrateFilters(oldServerUri, newServerUri) {
+ for (let server of MailServices.accounts.allServers) {
+ let filterList;
+ try {
+ filterList = server.getFilterList(null);
+ if (!server.canHaveFilters || !filterList) {
+ continue;
+ }
+ } catch (e) {
+ continue;
+ }
+ let changed = false;
+ for (let i = 0; i < filterList.filterCount; i++) {
+ let filter = filterList.getFilterAt(i);
+ for (let action of filter.sortedActionList) {
+ let targetFolderUri;
+ try {
+ targetFolderUri = action.targetFolderUri;
+ } catch (e) {
+ continue;
+ }
+ if (targetFolderUri.startsWith(oldServerUri)) {
+ action.targetFolderUri = targetFolderUri.replace(
+ oldServerUri,
+ newServerUri
+ );
+ changed = true;
+ }
+ }
+ }
+ if (changed) {
+ filterList.saveToDefaultFile();
+ }
+ }
+}
+
+/**
+ * Migrate server uris in LoginManager and various account/folder prefs.
+ *
+ * @param {string} localStoreType - The store type of the current server.
+ * @param {string} oldHostname - The hostname before the change.
+ * @param {string} oldUsername - The username before the change.
+ * @param {string} newHostname - The hostname after the change.
+ * @param {string} newUsername - The username after the change.
+ */
+function migrateServerUris(
+ localStoreType,
+ oldHostname,
+ oldUsername,
+ newHostname,
+ newUsername
+) {
+ try {
+ migratePassword(
+ localStoreType,
+ oldHostname,
+ oldUsername,
+ newHostname,
+ newUsername
+ );
+ } catch (e) {
+ console.error(e);
+ }
+
+ let oldAuth = oldUsername ? `${encodeURIComponent(oldUsername)}@` : "";
+ let newAuth = newUsername ? `${encodeURIComponent(newUsername)}@` : "";
+ // When constructing nsIURI, need to wrap IPv6 address in [].
+ oldHostname = oldHostname.includes(":") ? `[${oldHostname}]` : oldHostname;
+ let oldServerUri = `${localStoreType}://${oldAuth}${encodeURIComponent(
+ oldHostname
+ )}`;
+ newHostname = newHostname.includes(":") ? `[${newHostname}]` : newHostname;
+ let newServerUri = `${localStoreType}://${newAuth}${encodeURIComponent(
+ newHostname
+ )}`;
+
+ try {
+ migrateIdentities(oldServerUri, newServerUri);
+ } catch (e) {
+ console.error(e);
+ }
+ try {
+ migrateSpamActions(oldServerUri, newServerUri);
+ } catch (e) {
+ console.error(e);
+ }
+ try {
+ migrateFilters(oldServerUri, newServerUri);
+ } catch (e) {
+ console.error(e);
+ }
+}
+
+/**
+ * A base class for incoming server, should not be used directly.
+ *
+ * @implements {nsIMsgIncomingServer}
+ * @implements {nsISupportsWeakReference}
+ * @implements {nsIObserver}
+ * @abstract
+ */
+class MsgIncomingServer {
+ QueryInterface = ChromeUtils.generateQI([
+ "nsIMsgIncomingServer",
+ "nsISupportsWeakReference",
+ "nsIObserver",
+ ]);
+
+ constructor() {
+ // nsIMsgIncomingServer attributes that map directly to pref values.
+ this._mapAttrsToPrefs([
+ ["Char", "type"],
+ ["Char", "clientid"],
+ ["Int", "authMethod"],
+ ["Int", "biffMinutes", "check_time"],
+ ["Int", "maxMessageSize", "max_size"],
+ ["Int", "incomingDuplicateAction", "dup_action"],
+ ["Bool", "clientidEnabled"],
+ ["Bool", "downloadOnBiff", "download_on_biff"],
+ ["Bool", "valid"],
+ ["Bool", "emptyTrashOnExit", "empty_trash_on_exit"],
+ ["Bool", "canDelete"],
+ ["Bool", "loginAtStartUp", "login_at_startup"],
+ [
+ "Bool",
+ "defaultCopiesAndFoldersPrefsToServer",
+ "allows_specialfolders_usage",
+ ],
+ ["Bool", "canCreateFoldersOnServer", "canCreateFolders"],
+ ["Bool", "canFileMessagesOnServer", "canFileMessages"],
+ ["Bool", "limitOfflineMessageSize", "limit_offline_message_size"],
+ ["Bool", "hidden"],
+ ]);
+
+ // nsIMsgIncomingServer attributes.
+ this.performingBiff = false;
+ this.accountManagerChrome = "am-main.xhtml";
+ this.biffState = Ci.nsIMsgFolder.nsMsgBiffState_Unknown;
+ this.downloadMessagesAtStartup = false;
+ this.canHaveFilters = true;
+ this.canBeDefaultServer = false;
+ this.displayStartupPage = true;
+ this.supportsDiskSpace = true;
+ this.canCompactFoldersOnServer = true;
+ this.canUndoDeleteOnServer = true;
+ this.sortOrder = 100000000;
+
+ // @type {Map<string, number>} - The key is MsgId+Subject, the value is
+ // this._hdrIndex.
+ this._knownHdrMap = new Map();
+ this._hdrIndex = 0;
+
+ Services.obs.addObserver(this, "passwordmgr-storage-changed");
+ }
+
+ /**
+ * Observe() receives notifications for all accounts, not just this server's
+ * account. So we ignore all notifications not intended for this server.
+ * When the state of the password manager changes we need to clear the
+ * this server's password from the cache in case the user just changed or
+ * removed the password or username.
+ * OAuth2 servers often automatically change the password manager's stored
+ * password (the token).
+ */
+ observe(subject, topic, data) {
+ if (topic == "passwordmgr-storage-changed") {
+ // Check that the notification is for this server and user.
+ let otherFullName = "";
+ let otherUsername = "";
+ if (subject instanceof Ci.nsILoginInfo) {
+ // The login info for a server has been removed with data being
+ // "removeLogin" or "removeAllLogins".
+ otherFullName = subject.origin;
+ otherUsername = subject.username;
+ } else if (subject instanceof Ci.nsIArray) {
+ // Probably a 2 element array containing old and new login info due to
+ // data being "modifyLogin". E.g., a user has modified the password or
+ // username in the password manager or an OAuth2 token string has
+ // automatically changed. Only need to look at names in first array
+ // element (login info before any modification) since the user might
+ // have changed the username as found in the 2nd elements. (The
+ // hostname can't be modified in the password manager.
+ otherFullName = subject.queryElementAt(0, Ci.nsISupports).origin;
+ otherUsername = subject.queryElementAt(0, Ci.nsISupports).username;
+ }
+ if (otherFullName) {
+ if (
+ otherFullName != "mailbox://" + this.hostName ||
+ otherUsername != this.username
+ ) {
+ // Not for this server; keep this server's cached password.
+ return;
+ }
+ } else if (data != "hostSavingDisabled") {
+ // "hostSavingDisabled" only occurs during test_smtpServer.js and
+ // expects the password to be removed from memory cache. Otherwise, we
+ // don't have enough information to decide to remove the cached
+ // password, so keep it.
+ return;
+ }
+ // Remove the password for this server cached in memory.
+ this.password = "";
+ }
+ }
+
+ /**
+ * Set up getters/setters for attributes that map directly to pref values.
+ *
+ * @param {string[]} attributes - An array of attributes. Each attribute is
+ * defined by its type, name and corresponding prefName.
+ */
+ _mapAttrsToPrefs(attributes) {
+ for (let [type, attrName, prefName] of attributes) {
+ prefName = prefName || attrName;
+ Object.defineProperty(this, attrName, {
+ configurable: true,
+ get: () => this[`get${type}Value`](prefName),
+ set: value => {
+ this[`set${type}Value`](prefName, value);
+ },
+ });
+ }
+ }
+
+ get key() {
+ return this._key;
+ }
+
+ set key(key) {
+ this._key = key;
+ this._prefs = Services.prefs.getBranch(`mail.server.${key}.`);
+ this._defaultPrefs = Services.prefs.getBranch("mail.server.default.");
+ }
+
+ get UID() {
+ let uid = this._prefs.getStringPref("uid", "");
+ if (uid) {
+ return uid;
+ }
+ return (this.UID = Services.uuid
+ .generateUUID()
+ .toString()
+ .substring(1, 37));
+ }
+
+ set UID(uid) {
+ if (this._prefs.prefHasUserValue("uid")) {
+ throw new Components.Exception("uid is already set", Cr.NS_ERROR_ABORT);
+ }
+ this._prefs.setStringPref("uid", uid);
+ }
+
+ get hostName() {
+ let hostname = this.getUnicharValue("hostname");
+ if (hostname.includes(":")) {
+ // Reformat the hostname if it contains a port number.
+ this.hostName = hostname;
+ return this.hostName;
+ }
+ return hostname;
+ }
+
+ set hostName(value) {
+ let oldName = this.hostName;
+ this._setHostName("hostname", value);
+
+ if (oldName && oldName != value) {
+ this.onUserOrHostNameChanged(oldName, value, true);
+ }
+ }
+
+ _setHostName(prefName, value) {
+ let [host, port] = value.split(":");
+ if (port) {
+ this.port = Number(port);
+ }
+ this.setUnicharValue(prefName, host);
+ }
+
+ get username() {
+ return this.getUnicharValue("userName");
+ }
+
+ set username(value) {
+ let oldName = this.username;
+ if (oldName && oldName != value) {
+ this.setUnicharValue("userName", value);
+ this.onUserOrHostNameChanged(oldName, value, false);
+ } else {
+ this.setUnicharValue("userName", value);
+ }
+ }
+
+ get port() {
+ let port = this.getIntValue("port");
+ if (port > 1) {
+ return port;
+ }
+
+ // If the port isn't set, use the default port based on the protocol.
+ return this.protocolInfo.getDefaultServerPort(
+ this.socketType == Ci.nsMsgSocketType.SSL
+ );
+ }
+
+ set port(value) {
+ this.setIntValue("port", value);
+ }
+
+ get protocolInfo() {
+ return Cc[
+ `@mozilla.org/messenger/protocol/info;1?type=${this.type}`
+ ].getService(Ci.nsIMsgProtocolInfo);
+ }
+
+ get socketType() {
+ try {
+ return this._prefs.getIntPref("socketType");
+ } catch (e) {
+ // socketType is set to default value. Look at isSecure setting.
+ if (this._prefs.getBoolPref("isSecure", false)) {
+ return Ci.nsMsgSocketType.SSL;
+ }
+ return this._defaultPrefs.getIntPref(
+ "socketType",
+ Ci.nsMsgSocketType.plain
+ );
+ }
+ }
+
+ set socketType(value) {
+ let wasSecure = this.isSecure;
+ this._prefs.setIntPref("socketType", value);
+ let isSecure = this.isSecure;
+ if (wasSecure != isSecure) {
+ this.rootFolder.NotifyBoolPropertyChanged(
+ "isSecure",
+ wasSecure,
+ isSecure
+ );
+ }
+ }
+
+ get isSecure() {
+ return [Ci.nsMsgSocketType.alwaysSTARTTLS, Ci.nsMsgSocketType.SSL].includes(
+ this.socketType
+ );
+ }
+
+ get serverURI() {
+ return this._getServerURI(true);
+ }
+
+ /**
+ * Get server URI in the form of localStoreType://[user@]hostname.
+ *
+ * @param {boolean} includeUsername - Whether to include the username.
+ * @returns {string}
+ */
+ _getServerURI(includeUsername) {
+ let auth =
+ includeUsername && this.username
+ ? `${encodeURIComponent(this.username)}@`
+ : "";
+ // When constructing nsIURI, need to wrap IPv6 address in [].
+ let hostname = this.hostName.includes(":")
+ ? `[${this.hostName}]`
+ : this.hostName;
+ return `${this.localStoreType}://${auth}${encodeURIComponent(hostname)}`;
+ }
+
+ get prettyName() {
+ return this.getUnicharValue("name") || this.constructedPrettyName;
+ }
+
+ set prettyName(value) {
+ this.setUnicharValue("name", value);
+ this.rootFolder.prettyName = value;
+ }
+
+ /**
+ * Construct a pretty name from username and hostname.
+ *
+ * @param {string} username - The user name.
+ * @param {string} hostname - The host name.
+ * @returns {string}
+ */
+ _constructPrettyName(username, hostname) {
+ let prefix = username ? `${username} on ` : "";
+ return `${prefix}${hostname}`;
+ }
+
+ get constructedPrettyName() {
+ return this._constructPrettyName(this.username, this.hostName);
+ }
+
+ get localPath() {
+ let localPath = this.getFileValue("directory-rel", "directory");
+ if (localPath) {
+ // If the local path has already been set, use it.
+ return localPath;
+ }
+
+ // Create the path using protocol info and hostname.
+ localPath = this.protocolInfo.defaultLocalPath;
+ if (!localPath.exists()) {
+ localPath.create(Ci.nsIFile.DIRECTORY_TYPE, 0o755);
+ }
+
+ localPath.append(this.hostName);
+ localPath.createUnique(Ci.nsIFile.DIRECTORY_TYPE, 0o755);
+
+ this.localPath = localPath;
+ return localPath;
+ }
+
+ set localPath(localPath) {
+ this.setFileValue("directory-rel", "directory", localPath);
+ }
+
+ get rootFolder() {
+ if (!this._rootFolder) {
+ this._rootFolder = MailServices.folderLookup.getOrCreateFolderForURL(
+ this.serverURI
+ );
+ }
+ return this._rootFolder;
+ }
+
+ get rootMsgFolder() {
+ return this.rootFolder;
+ }
+
+ get msgStore() {
+ if (!this._msgStore) {
+ let contractId = this.getCharValue("storeContractID");
+ if (!contractId) {
+ contractId = "@mozilla.org/msgstore/berkeleystore;1";
+ this.setCharValue("storeContractID", contractId);
+ }
+
+ // After someone starts using the pluggable store, we can no longer
+ // change the value.
+ this.setBoolValue("canChangeStoreType", false);
+
+ this._msgStore = Cc[contractId].createInstance(Ci.nsIMsgPluggableStore);
+ }
+ return this._msgStore;
+ }
+
+ get doBiff() {
+ try {
+ return this._prefs.getBoolPref("check_new_mail");
+ } catch (e) {
+ return this.protocolInfo.defaultDoBiff;
+ }
+ }
+
+ set doBiff(value) {
+ let biffManager = Cc["@mozilla.org/messenger/biffManager;1"].getService(
+ Ci.nsIMsgBiffManager
+ );
+ if (value) {
+ biffManager.addServerBiff(this);
+ } else {
+ biffManager.removeServerBiff(this);
+ }
+ this._prefs.setBoolPref("check_new_mail", value);
+ }
+
+ /**
+ * type, attribute name, pref name
+ */
+ _retentionSettingsPrefs = [
+ ["Int", "retainByPreference", "retainBy"],
+ ["Int", "numHeadersToKeep", "numHdrsToKeep"],
+ ["Int", "daysToKeepHdrs"],
+ ["Int", "daysToKeepBodies"],
+ ["Bool", "cleanupBodiesByDays", "cleanupBodies"],
+ ["Bool", "applyToFlaggedMessages"],
+ ];
+
+ get retentionSettings() {
+ let settings = Cc[
+ "@mozilla.org/msgDatabase/retentionSettings;1"
+ ].createInstance(Ci.nsIMsgRetentionSettings);
+ for (let [type, attrName, prefName] of this._retentionSettingsPrefs) {
+ prefName = prefName || attrName;
+ settings[attrName] = this[`get${type}Value`](prefName);
+ }
+ return settings;
+ }
+
+ set retentionSettings(settings) {
+ for (let [type, attrName, prefName] of this._retentionSettingsPrefs) {
+ prefName = prefName || attrName;
+ this[`set${type}Value`](prefName, settings[attrName]);
+ }
+ }
+
+ get spamSettings() {
+ if (!this.getCharValue("spamActionTargetAccount")) {
+ this.setCharValue("spamActionTargetAccount", this.serverURI);
+ }
+ if (!this._spamSettings) {
+ this._spamSettings = Cc[
+ "@mozilla.org/messenger/spamsettings;1"
+ ].createInstance(Ci.nsISpamSettings);
+ try {
+ this._spamSettings.initialize(this);
+ } catch (e) {
+ console.error(e);
+ }
+ }
+ return this._spamSettings;
+ }
+
+ get spamFilterPlugin() {
+ if (!this._spamFilterPlugin) {
+ this._spamFilterPlugin = Cc[
+ "@mozilla.org/messenger/filter-plugin;1?name=bayesianfilter"
+ ].getService(Ci.nsIMsgFilterPlugin);
+ }
+ return this._spamFilterPlugin;
+ }
+
+ get isDeferredTo() {
+ let account = MailServices.accounts.FindAccountForServer(this);
+ if (!account) {
+ return false;
+ }
+ return MailServices.accounts.allServers.some(
+ server => server.getCharValue("deferred_to_account") == account.key
+ );
+ }
+
+ get serverRequiresPasswordForBiff() {
+ return true;
+ }
+
+ /**
+ * type, attribute name, pref name
+ */
+ _downloadSettingsPrefs = [
+ ["Int", "ageLimitOfMsgsToDownload", "ageLimit"],
+ ["Bool", "downloadUnreadOnly"],
+ ["Bool", "downloadByDate"],
+ ];
+
+ get downloadSettings() {
+ if (!this._downloadSettings) {
+ this._downloadSettings = Cc[
+ "@mozilla.org/msgDatabase/downloadSettings;1"
+ ].createInstance(Ci.nsIMsgDownloadSettings);
+ for (let [type, attrName, prefName] of this._downloadSettingsPrefs) {
+ prefName = prefName || attrName;
+ this._downloadSettings[attrName] = this[`get${type}Value`](prefName);
+ }
+ }
+ return this._downloadSettings;
+ }
+
+ set downloadSettings(settings) {
+ this._downloadSettings = settings;
+ for (let [type, attrName, prefName] of this._downloadSettingsPrefs) {
+ prefName = prefName || attrName;
+ this[`set${type}Value`](prefName, settings[attrName]);
+ }
+ }
+
+ get offlineSupportLevel() {
+ const OFFLINE_SUPPORT_LEVEL_NONE = 0;
+ const OFFLINE_SUPPORT_LEVEL_UNDEFINED = -1;
+ let level = this.getIntValue("offline_support_level");
+ return level == OFFLINE_SUPPORT_LEVEL_UNDEFINED
+ ? OFFLINE_SUPPORT_LEVEL_NONE
+ : level;
+ }
+
+ set offlineSupportLevel(value) {
+ this.setIntValue("offline_support_level", value);
+ }
+
+ get filterScope() {
+ return Ci.nsMsgSearchScope.offlineMailFilter;
+ }
+
+ get searchScope() {
+ return Ci.nsMsgSearchScope.offlineMail;
+ }
+
+ get passwordPromptRequired() {
+ if (!this.serverRequiresPasswordForBiff) {
+ // If the password is not even required for biff we don't need to check
+ // any further.
+ return false;
+ }
+ if (!this.password) {
+ // If the password is empty, check to see if it is stored.
+ this.password = this._getPasswordWithoutUI();
+ }
+ if (this.password) {
+ return false;
+ }
+ return this.authMethod != Ci.nsMsgAuthMethod.OAuth2;
+ }
+
+ getCharValue(prefName) {
+ try {
+ return this._prefs.getCharPref(prefName);
+ } catch (e) {
+ return this._defaultPrefs.getCharPref(prefName, "");
+ }
+ }
+
+ setCharValue(prefName, value) {
+ let defaultValue = this._defaultPrefs.getCharPref(prefName, "");
+ if (!value || value == defaultValue) {
+ this._prefs.clearUserPref(prefName);
+ } else {
+ this._prefs.setCharPref(prefName, value);
+ }
+ }
+
+ getUnicharValue(prefName) {
+ try {
+ return this._prefs.getStringPref(prefName);
+ } catch (e) {
+ return this._defaultPrefs.getStringPref(prefName, "");
+ }
+ }
+
+ setUnicharValue(prefName, value) {
+ let defaultValue = this._defaultPrefs.getStringPref(prefName, "");
+ if (!value || value == defaultValue) {
+ this._prefs.clearUserPref(prefName);
+ } else {
+ this._prefs.setStringPref(prefName, value);
+ }
+ }
+
+ getIntValue(prefName) {
+ try {
+ return this._prefs.getIntPref(prefName);
+ } catch (e) {
+ return this._defaultPrefs.getIntPref(prefName, 0);
+ }
+ }
+
+ setIntValue(prefName, value) {
+ let defaultValue = this._defaultPrefs.getIntPref(prefName, value - 1);
+ if (defaultValue == value) {
+ this._prefs.clearUserPref(prefName);
+ } else {
+ this._prefs.setIntPref(prefName, value);
+ }
+ }
+
+ getBoolValue(prefName) {
+ try {
+ return this._prefs.getBoolPref(prefName);
+ } catch (e) {
+ return this._defaultPrefs.getBoolPref(prefName, false);
+ }
+ }
+
+ setBoolValue(prefName, value) {
+ let defaultValue = this._defaultPrefs.getBoolPref(prefName, !value);
+ if (defaultValue == value) {
+ this._prefs.clearUserPref(prefName);
+ } else {
+ this._prefs.setBoolPref(prefName, value);
+ }
+ }
+
+ getFileValue(relPrefName, absPrefName) {
+ try {
+ let file = this._prefs.getComplexValue(
+ relPrefName,
+ Ci.nsIRelativeFilePref
+ ).file;
+ file.normalize();
+ return file;
+ } catch (e) {
+ try {
+ let file = this._prefs.getComplexValue(absPrefName, Ci.nsIFile);
+ this._prefs.setComplexValue(relPrefName, Ci.nsIRelativeFilePref, {
+ QueryInterface: ChromeUtils.generateQI(["nsIRelativeFilePref"]),
+ file,
+ relativeToKey: "ProfD",
+ });
+ return file;
+ } catch (e) {
+ return null;
+ }
+ }
+ }
+
+ setFileValue(relPrefName, absPrefName, file) {
+ this._prefs.setComplexValue(relPrefName, Ci.nsIRelativeFilePref, {
+ QueryInterface: ChromeUtils.generateQI(["nsIRelativeFilePref"]),
+ file,
+ relativeToKey: "ProfD",
+ });
+ this._prefs.setComplexValue(absPrefName, Ci.nsIFile, file);
+ }
+
+ onUserOrHostNameChanged(oldValue, newValue, hostnameChanged) {
+ migrateServerUris(
+ this.localStoreType,
+ hostnameChanged ? oldValue : this.hostName,
+ hostnameChanged ? this.username : oldValue,
+ this.hostName,
+ this.username
+ );
+ this._spamSettings = null;
+
+ // Clear the clientid because the user or host have changed.
+ this.clientid = "";
+
+ let atIndex = newValue.indexOf("@");
+ if (!this.prettyName || (!hostnameChanged && atIndex != -1)) {
+ // If new username contains @ then better not update the pretty name.
+ return;
+ }
+
+ atIndex = this.prettyName.indexOf("@");
+ if (
+ !hostnameChanged &&
+ atIndex != -1 &&
+ oldValue == this.prettyName.slice(0, atIndex)
+ ) {
+ // If username changed and the pretty name has the old username before @,
+ // update to the new username.
+ this.prettyName = newValue + this.prettyName.slice(atIndex);
+ } else if (
+ hostnameChanged &&
+ oldValue == this.prettyName.slice(atIndex + 1)
+ ) {
+ // If hostname changed and the pretty name has the old hostname after @,
+ // update to the new hostname.
+ this.prettyName = this.prettyName.slice(0, atIndex + 1) + newValue;
+ } else {
+ // Set the `name` pref anyway, to make tests happy.
+ // eslint-disable-next-line no-self-assign
+ this.prettyName = this.prettyName;
+ }
+ }
+
+ /**
+ * Try to get the password from nsILoginManager.
+ *
+ * @returns {string}
+ */
+ _getPasswordWithoutUI() {
+ let serverURI = this._getServerURI();
+ let logins = Services.logins.findLogins(serverURI, "", serverURI);
+ for (let login of logins) {
+ if (login.username == this.username) {
+ return login.password;
+ }
+ }
+ return null;
+ }
+
+ getPasswordWithUI(promptMessage, promptTitle) {
+ let password = this._getPasswordWithoutUI();
+ if (password) {
+ this.password = password;
+ return this.password;
+ }
+ let outUsername = {};
+ let outPassword = {};
+ let ok;
+ let authPrompt;
+ try {
+ // This prompt has a checkbox for saving password.
+ authPrompt = Cc["@mozilla.org/messenger/msgAuthPrompt;1"].getService(
+ Ci.nsIAuthPrompt
+ );
+ } catch (e) {
+ // Often happens in tests. This prompt has no checkbox for saving password.
+ authPrompt = Services.ww.getNewAuthPrompter(null);
+ }
+ if (this.username) {
+ ok = authPrompt.promptPassword(
+ promptTitle,
+ promptMessage,
+ this.serverURI,
+ Ci.nsIAuthPrompt.SAVE_PASSWORD_PERMANENTLY,
+ outPassword
+ );
+ } else {
+ ok = authPrompt.promptUsernameAndPassword(
+ promptTitle,
+ promptMessage,
+ this.serverURI,
+ Ci.nsIAuthPrompt.SAVE_PASSWORD_PERMANENTLY,
+ outUsername,
+ outPassword
+ );
+ }
+ if (ok) {
+ if (outUsername.value) {
+ this.username = outUsername.value;
+ }
+ this.password = outPassword.value;
+ } else {
+ throw Components.Exception("Password dialog canceled", Cr.NS_ERROR_ABORT);
+ }
+ return this.password;
+ }
+
+ forgetPassword() {
+ let serverURI = this._getServerURI();
+ let logins = Services.logins.findLogins(serverURI, "", serverURI);
+ for (let login of logins) {
+ if (login.username == this.username) {
+ Services.logins.removeLogin(login);
+ }
+ }
+ this.password = "";
+ }
+
+ forgetSessionPassword() {
+ this.password = "";
+ }
+
+ closeCachedConnections() {}
+
+ shutdown() {
+ this.closeCachedConnections();
+
+ if (this._filterList) {
+ this._filterList.logStream = null;
+ this._filterList = null;
+ }
+ if (this._spamSettings) {
+ this._spamSettings.logStream = null;
+ this._spamSettings = null;
+ }
+ }
+
+ getFilterList(msgWindow) {
+ if (!this._filterList) {
+ if (!this.rootFolder.filePath.path) {
+ // Happens in tests.
+ return null;
+ }
+ let filterFile = this.rootFolder.filePath.clone();
+ filterFile.append("msgFilterRules.dat");
+ try {
+ this._filterList = MailServices.filters.OpenFilterList(
+ filterFile,
+ this.rootFolder,
+ msgWindow
+ );
+ } catch (e) {
+ console.error(e);
+ const NS_ERROR_FILE_FS_CORRUPTED = 0x80520016;
+ if (e.result == NS_ERROR_FILE_FS_CORRUPTED && filterFile.exists()) {
+ // OpenFilterList will create a new one next time.
+ filterFile.renameTo(filterFile.parent, "msgFilterRules.dat.orig");
+ }
+ }
+ }
+ return this._filterList;
+ }
+
+ setFilterList(value) {
+ this._filterList = value;
+ }
+
+ getEditableFilterList(msgWindow) {
+ if (!this._editableFilterList) {
+ return this.getFilterList(msgWindow);
+ }
+ return this._editableFilterList;
+ }
+
+ setEditableFilterList(value) {
+ this._editableFilterList = value;
+ }
+
+ setDefaultLocalPath(value) {
+ this.protocolInfo.setDefaultLocalPath(value);
+ }
+
+ getNewMessages(folder, msgWindow, urlListener) {
+ folder.getNewMessages(msgWindow, urlListener);
+ }
+
+ writeToFolderCache(folderCache) {
+ this.rootFolder.writeToFolderCache(folderCache, true);
+ }
+
+ clearAllValues() {
+ for (let prefName of this._prefs.getChildList("")) {
+ this._prefs.clearUserPref(prefName);
+ }
+ }
+
+ removeFiles() {
+ if (this.getCharValue("deferred_to_account") || this.isDeferredTo) {
+ throw Components.Exception(
+ "Should not remove files for a deferred account",
+ Cr.NS_ERROR_FAILURE
+ );
+ }
+ this.localPath.remove(true);
+ }
+
+ getMsgFolderFromURI(folder, uri) {
+ try {
+ return this.rootMsgFolder.getChildWithURI(uri, true, true) || folder;
+ } catch (e) {
+ return folder;
+ }
+ }
+
+ isNewHdrDuplicate(newHdr) {
+ // If the message has been partially downloaded, the message should not
+ // be considered a duplicated message. See bug 714090.
+ if (newHdr.flags & Ci.nsMsgMessageFlags.Partial) {
+ return false;
+ }
+
+ if (!newHdr.subject || !newHdr.messageId) {
+ return false;
+ }
+
+ let key = `${newHdr.messageId}${newHdr.subject}`;
+ if (this._knownHdrMap.get(key)) {
+ return true;
+ }
+
+ this._knownHdrMap.set(key, ++this._hdrIndex);
+
+ const MAX_SIZE = 500;
+ if (this._knownHdrMap.size > MAX_SIZE) {
+ // Release the oldest half of downloaded hdrs.
+ for (let [k, v] of this._knownHdrMap) {
+ if (v < this._hdrIndex - MAX_SIZE / 2) {
+ this._knownHdrMap.delete(k);
+ } else if (this._knownHdrMap.size <= MAX_SIZE / 2) {
+ break;
+ }
+ }
+ }
+ return false;
+ }
+
+ equals(server) {
+ return this.key == server.key;
+ }
+
+ _configureTemporaryReturnReceiptsFilter(filterList) {
+ let identity = MailServices.accounts.getFirstIdentityForServer(this);
+ if (!identity) {
+ return;
+ }
+ let incorp = Ci.nsIMsgMdnGenerator.eIncorporateInbox;
+ if (identity.getBoolAttribute("use_custom_prefs")) {
+ incorp = this.getIntValue("incorporate_return_receipt");
+ } else {
+ incorp = Services.prefs.getIntPref("mail.incorporate.return_receipt");
+ }
+
+ let enable = incorp == Ci.nsIMsgMdnGenerator.eIncorporateSent;
+
+ const FILTER_NAME = "mozilla-temporary-internal-MDN-receipt-filter";
+ let filter = filterList.getFilterNamed(FILTER_NAME);
+
+ if (filter) {
+ filter.enabled = enable;
+ return;
+ } else if (!enable || !identity.fccFolder) {
+ return;
+ }
+
+ filter = filterList.createFilter(FILTER_NAME);
+ if (!filter) {
+ return;
+ }
+
+ filter.enabled = true;
+ filter.temporary = true;
+
+ let term = filter.createTerm();
+ let value = term.value;
+ value.attrib = Ci.nsMsgSearchAttrib.OtherHeader + 1;
+ value.str = "multipart/report";
+ term.attrib = Ci.nsMsgSearchAttrib.OtherHeader + 1;
+ term.op = Ci.nsMsgSearchOp.Contains;
+ term.booleanAnd = true;
+ term.arbitraryHeader = "Content-Type";
+ term.value = value;
+ filter.appendTerm(term);
+
+ term = filter.createTerm();
+ value = term.value;
+ value.attrib = Ci.nsMsgSearchAttrib.OtherHeader + 1;
+ value.str = "disposition-notification";
+ term.attrib = Ci.nsMsgSearchAttrib.OtherHeader + 1;
+ term.op = Ci.nsMsgSearchOp.Contains;
+ term.booleanAnd = true;
+ term.arbitraryHeader = "Content-Type";
+ term.value = value;
+ filter.appendTerm(term);
+
+ let action = filter.createAction();
+ action.type = Ci.nsMsgFilterAction.MoveToFolder;
+ action.targetFolderUri = identity.fccFolder;
+ filter.appendAction(action);
+ filterList.insertFilterAt(0, filter);
+ }
+
+ _configureTemporaryServerSpamFilters(filterList) {
+ let spamSettings = this.spamSettings;
+ if (!spamSettings.useServerFilter) {
+ return;
+ }
+ let serverFilterName = spamSettings.serverFilterName;
+ let serverFilterTrustFlags = spamSettings.serverFilterTrustFlags;
+ if (!serverFilterName || !serverFilterName) {
+ return;
+ }
+
+ // Check if filters have been setup already.
+ let yesFilterName = `${serverFilterName}Yes`;
+ let noFilterName = `${serverFilterName}No`;
+ let filter = filterList.getFilterNamed(yesFilterName);
+ if (!filter) {
+ filter = filterList.getFilterNamed(noFilterName);
+ }
+ if (filter) {
+ return;
+ }
+
+ let serverFilterList = MailServices.filters.OpenFilterList(
+ spamSettings.serverFilterFile,
+ null,
+ null
+ );
+ filter = serverFilterList.getFilterNamed(yesFilterName);
+ if (filter && serverFilterTrustFlags & Ci.nsISpamSettings.TRUST_POSITIVES) {
+ filter.temporary = true;
+ // Check if we're supposed to move junk mail to junk folder; if so, add
+ // filter action to do so.
+ let searchTerms = filter.searchTerms;
+ if (searchTerms.length) {
+ searchTerms[0].beginsGrouping = true;
+ searchTerms.at(-1).endsGrouping = true;
+ }
+
+ // Create a new term, checking if the user set junk status. The term will
+ // search for junkscoreorigin != "user".
+ let term = filter.createTerm();
+ term.attrib = Ci.nsMsgSearchAttrib.JunkScoreOrigin;
+ term.op = Ci.nsMsgSearchOp.Isnt;
+ term.booleanAnd = true;
+ let value = term.value;
+ value.attrib = Ci.nsMsgSearchAttrib.JunkScoreOrigin;
+ value.str = "user";
+ term.value = value;
+ filter.appendTerm(term);
+
+ if (spamSettings.moveOnSpam) {
+ let spamFolderURI = spamSettings.spamFolderURI;
+ if (spamFolderURI) {
+ let action = filter.createAction();
+ action.type = Ci.nsMsgFilterAction.MoveToFolder;
+ action.targetFolderUri = spamFolderURI;
+ filter.appendAction(action);
+ }
+ }
+
+ if (spamSettings.markAsReadOnSpam) {
+ let action = filter.createAction();
+ action.type = Ci.nsMsgFilterAction.MarkRead;
+ filter.appendAction(action);
+ }
+ filterList.insertFilterAt(0, filter);
+ }
+
+ filter = serverFilterList.getFilterNamed(noFilterName);
+ if (filter && serverFilterTrustFlags & Ci.nsISpamSettings.TRUST_NEGATIVES) {
+ filter.temporary = true;
+ filterList.insertFilterAt(0, filter);
+ }
+ }
+
+ configureTemporaryFilters(filterList) {
+ this._configureTemporaryReturnReceiptsFilter(filterList);
+ this._configureTemporaryServerSpamFilters(filterList);
+ }
+
+ clearTemporaryReturnReceiptsFilter() {
+ if (!this._filterList) {
+ return;
+ }
+ let filter = this._filterList.getFilterNamed(
+ "mozilla-temporary-internal-MDN-receipt-filter"
+ );
+ if (filter) {
+ this._filterList.removeFilter(filter);
+ }
+ }
+
+ getForcePropertyEmpty(name) {
+ return this.getCharValue(`${name}.empty`) == "true";
+ }
+
+ setForcePropertyEmpty(name, value) {
+ return this.setCharValue(`${name}.empty`, value ? "true" : "");
+ }
+
+ performExpand(msgWindow) {}
+
+ get wrappedJSObject() {
+ return this;
+ }
+
+ _passwordPromise = null;
+
+ /**
+ * Show a password prompt. If a prompt is currently shown, just wait for it.
+ *
+ * @param {string} message - The text inside the prompt.
+ * @param {string} title - The title of the prompt.
+ */
+ async getPasswordWithUIAsync(message, title) {
+ if (this._passwordPromise) {
+ await this._passwordPromise;
+ return this.password;
+ }
+ let deferred = {};
+ this._passwordPromise = new Promise((resolve, reject) => {
+ deferred.resolve = resolve;
+ deferred.reject = reject;
+ });
+ try {
+ this.getPasswordWithUI(message, title);
+ } catch (e) {
+ deferred.reject(e);
+ throw e;
+ } finally {
+ this._passwordPromise = null;
+ }
+ deferred.resolve();
+ return this.password;
+ }
+}
diff --git a/comm/mailnews/base/src/MsgKeySet.jsm b/comm/mailnews/base/src/MsgKeySet.jsm
new file mode 100644
index 0000000000..bbbd580ba9
--- /dev/null
+++ b/comm/mailnews/base/src/MsgKeySet.jsm
@@ -0,0 +1,132 @@
+/* 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 = ["MsgKeySet"];
+
+/**
+ * A structure to represent a set of articles. This is usually for lines from
+ * the newsrc, which have article lists like
+ *
+ * 1-29627,29635,29658,32861-32863
+ *
+ * so the data has these properties:
+ *
+ * - strictly increasing
+ * - large subsequences of monotonically increasing ranges
+ * - gaps in the set are usually small, but not always
+ * - consecutive ranges tend to be large
+ */
+class MsgKeySet {
+ /**
+ * @param {string} [str] - The raw string to represent a set of articles.
+ */
+ constructor(str) {
+ // An array of tuples, each tuple contains the start and end value of a sub
+ // range.
+ // @type {Array<[number, number]>}
+ this._ranges = str
+ ? str.split(",").map(part => {
+ let [start, end] = part.split("-");
+ return [+start, +end || +start];
+ })
+ : [];
+ }
+
+ /**
+ * Add a value to the set.
+ *
+ * @param {number} value - The value to add.
+ */
+ add(value) {
+ this.addRange(value, value);
+ }
+
+ /**
+ * Add a range to the set.
+ *
+ * @param {number} low - The smallest value of the range.
+ * @param {number} high - The largest value of the range.
+ */
+ addRange(low, high) {
+ let index = 0;
+ for (let [start] of this._ranges) {
+ if (start > low) {
+ break;
+ }
+ index++;
+ }
+ this._ranges.splice(index, 0, [low, high]);
+ this._rebuild();
+ }
+
+ /**
+ * Check if a value is in the set.
+ *
+ * @param {number} value - The value to check.
+ * @returns {boolean}
+ */
+ has(value) {
+ return this._ranges.some(([start, end]) =>
+ end ? start <= value && value <= end : start == value
+ );
+ }
+
+ /**
+ * Get the last range that is in the input range, but not in the key set.
+ *
+ * @param {number} low - The smallest value of the input range.
+ * @param {number} high - The largest value of the input range.
+ * @returns {number[]} - Array of lenght two with [low, high].
+ */
+ getLastMissingRange(low, high) {
+ let length = this._ranges.length;
+ for (let i = length - 1; i >= 0; i--) {
+ let [start, end] = this._ranges[i];
+ if (end < high) {
+ return [Math.max(low, end + 1), high];
+ } else if (low < start && high > start) {
+ high = start - 1;
+ } else {
+ return [];
+ }
+ }
+ return [low, high];
+ }
+
+ /**
+ * Get the string representation of the key set.
+ *
+ * @returns {string}
+ */
+ toString() {
+ return this._ranges
+ .map(([start, end]) => (start == end ? start : `${start}-${end}`))
+ .join(",");
+ }
+
+ /**
+ * Sub ranges may become overlapped after some operations. This method merges
+ * them if needed.
+ */
+ _rebuild() {
+ if (this._ranges.length < 2) {
+ return;
+ }
+ let newRanges = [];
+ let [cursorStart, cursorEnd] = this._ranges[0];
+ for (let [start, end] of this._ranges.slice(1)) {
+ if (cursorEnd < start - 1) {
+ // No overlap between the two ranges.
+ newRanges.push([cursorStart, cursorEnd]);
+ cursorStart = start;
+ cursorEnd = end;
+ } else {
+ // Overlapped, merge them.
+ cursorEnd = end;
+ }
+ }
+ newRanges.push([cursorStart, cursorEnd]);
+ this._ranges = newRanges;
+ }
+}
diff --git a/comm/mailnews/base/src/MsgProtocolInfo.sys.mjs b/comm/mailnews/base/src/MsgProtocolInfo.sys.mjs
new file mode 100644
index 0000000000..7c9088e12e
--- /dev/null
+++ b/comm/mailnews/base/src/MsgProtocolInfo.sys.mjs
@@ -0,0 +1,53 @@
+/* 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/. */
+
+/**
+ * @see {nsIMsgProtocolInfo}
+ */
+export class MsgProtocolInfo {
+ get defaultLocalPath() {
+ let file = this._getFileValue(this.RELATIVE_PREF, this.ABSOLUTE_PREF);
+ if (!file) {
+ file = Services.dirsvc.get(this.DIR_SERVICE_PROP, Ci.nsIFile);
+ this._setFileValue(this.RELATIVE_PREF, this.ABSOLUTE_PREF, file);
+ }
+ if (!file.exists()) {
+ file.createUnique(Ci.nsIFile.DIRECTORY_TYPE, 0o775);
+ }
+ file.normalize();
+ return file;
+ }
+
+ set defaultLocalPath(value) {
+ this._setFileValue(this.RELATIVE_PREF, this.ABSOLUTE_PREF, value);
+ }
+
+ _getFileValue(relPrefName, absPrefName) {
+ try {
+ return Services.prefs.getComplexValue(relPrefName, Ci.nsIRelativeFilePref)
+ .file;
+ } catch (e) {
+ try {
+ let file = Services.prefs.getComplexValue(absPrefName, Ci.nsIFile);
+ Services.prefs.setComplexValue(relPrefName, Ci.nsIRelativeFilePref, {
+ QueryInterface: ChromeUtils.generateQI(["nsIRelativeFilePref"]),
+ file,
+ relativeToKey: "ProfD",
+ });
+ return file;
+ } catch (e) {
+ return null;
+ }
+ }
+ }
+
+ _setFileValue(relPrefName, absPrefName, file) {
+ Services.prefs.setComplexValue(relPrefName, Ci.nsIRelativeFilePref, {
+ QueryInterface: ChromeUtils.generateQI(["nsIRelativeFilePref"]),
+ file,
+ relativeToKey: "ProfD",
+ });
+ Services.prefs.setComplexValue(absPrefName, Ci.nsIFile, file);
+ }
+}
diff --git a/comm/mailnews/base/src/OAuth2.jsm b/comm/mailnews/base/src/OAuth2.jsm
new file mode 100644
index 0000000000..c5148d41a7
--- /dev/null
+++ b/comm/mailnews/base/src/OAuth2.jsm
@@ -0,0 +1,364 @@
+/* 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/. */
+
+/**
+ * Provides OAuth 2.0 authentication.
+ *
+ * @see RFC 6749
+ */
+var EXPORTED_SYMBOLS = ["OAuth2"];
+
+var { CryptoUtils } = ChromeUtils.importESModule(
+ "resource://services-crypto/utils.sys.mjs"
+);
+
+// Only allow one connecting window per endpoint.
+var gConnecting = {};
+
+/**
+ * Constructor for the OAuth2 object.
+ *
+ * @class
+ * @param {?string} scope - The scope as specified by RFC 6749 Section 3.3.
+ * Will not be included in the requests if falsy.
+ * @param {object} issuerDetails
+ * @param {string} issuerDetails.authorizationEndpoint - The authorization
+ * endpoint as defined by RFC 6749 Section 3.1.
+ * @param {string} issuerDetails.clientId - The client_id as specified by RFC
+ * 6749 Section 2.3.1.
+ * @param {string} issuerDetails.clientSecret - The client_secret as specified
+ * in RFC 6749 section 2.3.1. Will not be included in the requests if null.
+ * @param {boolean} issuerDetails.usePKCE - Whether to use PKCE as specified
+ * in RFC 7636 during the oauth registration process
+ * @param {string} issuerDetails.redirectionEndpoint - The redirect_uri as
+ * specified by RFC 6749 section 3.1.2.
+ * @param {string} issuerDetails.tokenEndpoint - The token endpoint as defined
+ * by RFC 6749 Section 3.2.
+ */
+function OAuth2(scope, issuerDetails) {
+ this.scope = scope;
+ this.authorizationEndpoint = issuerDetails.authorizationEndpoint;
+ this.clientId = issuerDetails.clientId;
+ this.consumerSecret = issuerDetails.clientSecret || null;
+ this.usePKCE = issuerDetails.usePKCE;
+ this.redirectionEndpoint =
+ issuerDetails.redirectionEndpoint || "http://localhost";
+ this.tokenEndpoint = issuerDetails.tokenEndpoint;
+
+ this.extraAuthParams = [];
+
+ this.log = console.createInstance({
+ prefix: "mailnews.oauth",
+ maxLogLevel: "Warn",
+ maxLogLevelPref: "mailnews.oauth.loglevel",
+ });
+}
+
+OAuth2.prototype = {
+ clientId: null,
+ consumerSecret: null,
+ requestWindowURI: "chrome://messenger/content/browserRequest.xhtml",
+ requestWindowFeatures: "chrome,centerscreen,width=980,height=750",
+ requestWindowTitle: "",
+ scope: null,
+ usePKCE: false,
+ codeChallenge: null,
+
+ accessToken: null,
+ refreshToken: null,
+ tokenExpires: 0,
+
+ connect(aSuccess, aFailure, aWithUI, aRefresh) {
+ this.connectSuccessCallback = aSuccess;
+ this.connectFailureCallback = aFailure;
+
+ if (this.accessToken && !this.tokenExpired && !aRefresh) {
+ aSuccess();
+ } else if (this.refreshToken) {
+ this.requestAccessToken(this.refreshToken, true);
+ } else {
+ if (!aWithUI) {
+ aFailure('{ "error": "auth_noui" }');
+ return;
+ }
+ if (gConnecting[this.authorizationEndpoint]) {
+ aFailure("Window already open");
+ return;
+ }
+ this.requestAuthorization();
+ }
+ },
+
+ /**
+ * True if the token has expired, or will expire within the grace time.
+ */
+ get tokenExpired() {
+ // 30 seconds to allow for network inefficiency, clock drift, etc.
+ const OAUTH_GRACE_TIME_MS = 30 * 1000;
+ return this.tokenExpires - OAUTH_GRACE_TIME_MS < Date.now();
+ },
+
+ requestAuthorization() {
+ let params = new URLSearchParams({
+ response_type: "code",
+ client_id: this.clientId,
+ redirect_uri: this.redirectionEndpoint,
+ });
+
+ // The scope is optional.
+ if (this.scope) {
+ params.append("scope", this.scope);
+ }
+
+ // See rfc7636
+ if (this.usePKCE) {
+ // Convert base64 to base64url (rfc4648#section-5)
+ const to_b64url = b =>
+ b.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
+
+ params.append("code_challenge_method", "S256");
+
+ // rfc7636#section-4.1
+ // code_verifier = high-entropy cryptographic random STRING ... with a minimum
+ // length of 43 characters and a maximum length of 128 characters.
+ const code_verifier = to_b64url(
+ btoa(CryptoUtils.generateRandomBytesLegacy(64))
+ );
+ this.codeVerifier = code_verifier;
+
+ // rfc7636#section-4.2
+ // code_challenge = BASE64URL-ENCODE(SHA256(ASCII(code_verifier)))
+ const code_challenge = to_b64url(CryptoUtils.sha256Base64(code_verifier));
+ params.append("code_challenge", code_challenge);
+ }
+
+ for (let [name, value] of this.extraAuthParams) {
+ params.append(name, value);
+ }
+
+ let authEndpointURI = this.authorizationEndpoint + "?" + params.toString();
+ this.log.info(
+ "Interacting with the resource owner to obtain an authorization grant " +
+ "from the authorization endpoint: " +
+ authEndpointURI
+ );
+
+ this._browserRequest = {
+ account: this,
+ url: authEndpointURI,
+ _active: true,
+ iconURI: "",
+ cancelled() {
+ if (!this._active) {
+ return;
+ }
+
+ this.account.finishAuthorizationRequest();
+ this.account.onAuthorizationFailed(
+ Cr.NS_ERROR_ABORT,
+ '{ "error": "cancelled"}'
+ );
+ },
+
+ loaded(aWindow, aWebProgress) {
+ if (!this._active) {
+ return;
+ }
+
+ this._listener = {
+ window: aWindow,
+ webProgress: aWebProgress,
+ _parent: this.account,
+
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIWebProgressListener",
+ "nsISupportsWeakReference",
+ ]),
+
+ _cleanUp() {
+ this.webProgress.removeProgressListener(this);
+ this.window.close();
+ delete this.window;
+ },
+
+ _checkForRedirect(url) {
+ if (!url.startsWith(this._parent.redirectionEndpoint)) {
+ return;
+ }
+
+ this._parent.finishAuthorizationRequest();
+ this._parent.onAuthorizationReceived(url);
+ },
+
+ onStateChange(aWebProgress, aRequest, aStateFlags, aStatus) {
+ const wpl = Ci.nsIWebProgressListener;
+ if (aStateFlags & (wpl.STATE_START | wpl.STATE_IS_NETWORK)) {
+ let channel = aRequest.QueryInterface(Ci.nsIChannel);
+ this._checkForRedirect(channel.URI.spec);
+ }
+ },
+ onLocationChange(aWebProgress, aRequest, aLocation) {
+ this._checkForRedirect(aLocation.spec);
+ },
+ onProgressChange() {},
+ onStatusChange() {},
+ onSecurityChange() {},
+ };
+ aWebProgress.addProgressListener(
+ this._listener,
+ Ci.nsIWebProgress.NOTIFY_ALL
+ );
+ aWindow.document.title = this.account.requestWindowTitle;
+ },
+ };
+
+ const windowPrivacy = Services.prefs.getBoolPref(
+ "mailnews.oauth.usePrivateBrowser",
+ false
+ )
+ ? "private"
+ : "non-private";
+ const windowFeatures = `${this.requestWindowFeatures},${windowPrivacy}`;
+
+ this.wrappedJSObject = this._browserRequest;
+ gConnecting[this.authorizationEndpoint] = true;
+ Services.ww.openWindow(
+ null,
+ this.requestWindowURI,
+ null,
+ windowFeatures,
+ this
+ );
+ },
+ finishAuthorizationRequest() {
+ gConnecting[this.authorizationEndpoint] = false;
+ if (!("_browserRequest" in this)) {
+ return;
+ }
+
+ this._browserRequest._active = false;
+ if ("_listener" in this._browserRequest) {
+ this._browserRequest._listener._cleanUp();
+ }
+ delete this._browserRequest;
+ },
+
+ /**
+ * @param {string} aURL - Redirection URI with additional parameters.
+ */
+ onAuthorizationReceived(aURL) {
+ this.log.info("OAuth2 authorization response received: url=" + aURL);
+ const url = new URL(aURL);
+ if (url.searchParams.has("code")) {
+ // @see RFC 6749 section 4.1.2: Authorization Response
+ this.requestAccessToken(url.searchParams.get("code"), false);
+ } else {
+ // @see RFC 6749 section 4.1.2.1: Error Response
+ if (url.searchParams.has("error")) {
+ let error = url.searchParams.get("error");
+ let errorDescription = url.searchParams.get("error_description") || "";
+ if (error == "invalid_scope") {
+ errorDescription += ` Invalid scope: ${this.scope}.`;
+ }
+ if (url.searchParams.has("error_uri")) {
+ errorDescription += ` See ${url.searchParams.get("error_uri")}.`;
+ }
+ this.log.error(`Authorization error [${error}]: ${errorDescription}`);
+ }
+ this.onAuthorizationFailed(null, aURL);
+ }
+ },
+
+ onAuthorizationFailed(aError, aData) {
+ this.connectFailureCallback(aData);
+ },
+
+ /**
+ * Request a new access token, or refresh an existing one.
+ *
+ * @param {string} aCode - The token issued to the client.
+ * @param {boolean} aRefresh - Whether it's a refresh of a token or not.
+ */
+ requestAccessToken(aCode, aRefresh) {
+ // @see RFC 6749 section 4.1.3. Access Token Request
+ // @see RFC 6749 section 6. Refreshing an Access Token
+
+ let data = new URLSearchParams();
+ data.append("client_id", this.clientId);
+ if (this.consumerSecret !== null) {
+ // Section 2.3.1. of RFC 6749 states that empty secrets MAY be omitted
+ // by the client. This OAuth implementation delegates this decision to
+ // the caller: If the secret is null, it will be omitted.
+ data.append("client_secret", this.consumerSecret);
+ }
+
+ if (aRefresh) {
+ this.log.info(
+ `Making a refresh request to the token endpoint: ${this.tokenEndpoint}`
+ );
+ data.append("grant_type", "refresh_token");
+ data.append("refresh_token", aCode);
+ } else {
+ this.log.info(
+ `Making access token request to the token endpoint: ${this.tokenEndpoint}`
+ );
+ data.append("grant_type", "authorization_code");
+ data.append("code", aCode);
+ data.append("redirect_uri", this.redirectionEndpoint);
+ if (this.usePKCE) {
+ data.append("code_verifier", this.codeVerifier);
+ }
+ }
+
+ fetch(this.tokenEndpoint, {
+ method: "POST",
+ cache: "no-cache",
+ body: data,
+ })
+ .then(response => response.json())
+ .then(result => {
+ let resultStr = JSON.stringify(result, null, 2);
+ if ("error" in result) {
+ // RFC 6749 section 5.2. Error Response
+ let err = result.error;
+ if ("error_description" in result) {
+ err += "; " + result.error_description;
+ }
+ if ("error_uri" in result) {
+ err += "; " + result.error_uri;
+ }
+ this.log.warn(`Error response from the authorization server: ${err}`);
+ this.log.info(`Error response details: ${resultStr}`);
+
+ // Typically in production this would be {"error": "invalid_grant"}.
+ // That is, the token expired or was revoked (user changed password?).
+ // Reset the tokens we have and call success so that the auth flow
+ // will be re-triggered.
+ this.accessToken = null;
+ this.refreshToken = null;
+ this.connectSuccessCallback();
+ return;
+ }
+
+ // RFC 6749 section 5.1. Successful Response
+ this.log.info(
+ `Successful response from the authorization server: ${resultStr}`
+ );
+ this.accessToken = result.access_token;
+ if ("refresh_token" in result) {
+ this.refreshToken = result.refresh_token;
+ }
+ if ("expires_in" in result) {
+ this.tokenExpires = new Date().getTime() + result.expires_in * 1000;
+ } else {
+ this.tokenExpires = Number.MAX_VALUE;
+ }
+ this.connectSuccessCallback();
+ })
+ .catch(err => {
+ this.log.info(`Connection to authorization server failed: ${err}`);
+ this.connectFailureCallback(err);
+ });
+ },
+};
diff --git a/comm/mailnews/base/src/OAuth2Module.jsm b/comm/mailnews/base/src/OAuth2Module.jsm
new file mode 100644
index 0000000000..79826779c4
--- /dev/null
+++ b/comm/mailnews/base/src/OAuth2Module.jsm
@@ -0,0 +1,203 @@
+/* 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";
+
+var EXPORTED_SYMBOLS = ["OAuth2Module"];
+
+var { OAuth2 } = ChromeUtils.import("resource:///modules/OAuth2.jsm");
+var { OAuth2Providers } = ChromeUtils.import(
+ "resource:///modules/OAuth2Providers.jsm"
+);
+
+/**
+ * OAuth2Module is the glue layer that gives XPCOM access to an OAuth2
+ * bearer token it can use to authenticate in SASL steps.
+ * It also takes care of persising the refreshToken for later usage.
+ *
+ * @implements {msgIOAuth2Module}
+ */
+function OAuth2Module() {}
+OAuth2Module.prototype = {
+ QueryInterface: ChromeUtils.generateQI(["msgIOAuth2Module"]),
+
+ initFromSmtp(aServer) {
+ return this._initPrefs(
+ "mail.smtpserver." + aServer.key + ".",
+ aServer.username,
+ aServer.hostname
+ );
+ },
+ initFromMail(aServer) {
+ return this._initPrefs(
+ "mail.server." + aServer.key + ".",
+ aServer.username,
+ aServer.hostName
+ );
+ },
+ initFromABDirectory(aDirectory, aHostname) {
+ this._initPrefs(
+ aDirectory.dirPrefId + ".",
+ aDirectory.getStringValue("carddav.username", "") || aDirectory.UID,
+ aHostname
+ );
+ },
+ _initPrefs(root, aUsername, aHostname) {
+ let issuer = Services.prefs.getStringPref(root + "oauth2.issuer", null);
+ let scope = Services.prefs.getStringPref(root + "oauth2.scope", null);
+
+ let details = OAuth2Providers.getHostnameDetails(aHostname);
+ if (
+ details &&
+ (details[0] != issuer ||
+ !scope?.split(" ").every(s => details[1].split(" ").includes(s)))
+ ) {
+ // Found in the list of hardcoded providers. Use the hardcoded values.
+ // But only if what we had wasn't a narrower scope of current
+ // defaults. Updating scope would cause re-authorization.
+ [issuer, scope] = details;
+ // Store them for the future, can be useful once we support
+ // dynamic registration.
+ Services.prefs.setStringPref(root + "oauth2.issuer", issuer);
+ Services.prefs.setStringPref(root + "oauth2.scope", scope);
+ }
+ if (!issuer || !scope) {
+ // We need these properties for OAuth2 support.
+ return false;
+ }
+
+ // Find the app key we need for the OAuth2 string. Eventually, this should
+ // be using dynamic client registration, but there are no current
+ // implementations that we can test this with.
+ const issuerDetails = OAuth2Providers.getIssuerDetails(issuer);
+ if (!issuerDetails.clientId) {
+ return false;
+ }
+
+ // Username is needed to generate the XOAUTH2 string.
+ this._username = aUsername;
+ // loginOrigin is needed to save the refresh token in the password manager.
+ this._loginOrigin = "oauth://" + issuer;
+ // We use the scope to indicate realm when storing in the password manager.
+ this._scope = scope;
+
+ // Define the OAuth property and store it.
+ this._oauth = new OAuth2(scope, issuerDetails);
+
+ // Try hinting the username...
+ this._oauth.extraAuthParams = [["login_hint", aUsername]];
+
+ // Set the window title to something more useful than "Unnamed"
+ this._oauth.requestWindowTitle = Services.strings
+ .createBundle("chrome://messenger/locale/messenger.properties")
+ .formatStringFromName("oauth2WindowTitle", [aUsername, aHostname]);
+
+ // This stores the refresh token in the login manager.
+ Object.defineProperty(this._oauth, "refreshToken", {
+ get: () => this.refreshToken,
+ set: token => {
+ this.refreshToken = token;
+ },
+ });
+
+ return true;
+ },
+
+ get refreshToken() {
+ for (let login of Services.logins.findLogins(this._loginOrigin, null, "")) {
+ if (
+ login.username == this._username &&
+ (login.httpRealm == this._scope ||
+ login.httpRealm.split(" ").includes(this._scope))
+ ) {
+ return login.password;
+ }
+ }
+ return "";
+ },
+ set refreshToken(token) {
+ // Check if we already have a login with this username, and modify the
+ // password on that, if we do.
+ let logins = Services.logins.findLogins(
+ this._loginOrigin,
+ null,
+ this._scope
+ );
+ for (let login of logins) {
+ if (login.username == this._username) {
+ if (token) {
+ if (token != login.password) {
+ let propBag = Cc["@mozilla.org/hash-property-bag;1"].createInstance(
+ Ci.nsIWritablePropertyBag
+ );
+ propBag.setProperty("password", token);
+ Services.logins.modifyLogin(login, propBag);
+ }
+ } else {
+ Services.logins.removeLogin(login);
+ }
+ return;
+ }
+ }
+
+ // Unless the token is null, we need to create and fill in a new login
+ if (token) {
+ let login = Cc["@mozilla.org/login-manager/loginInfo;1"].createInstance(
+ Ci.nsILoginInfo
+ );
+ login.init(
+ this._loginOrigin,
+ null,
+ this._scope,
+ this._username,
+ token,
+ "",
+ ""
+ );
+ Services.logins.addLogin(login);
+ }
+ },
+
+ connect(aWithUI, aListener) {
+ let oauth = this._oauth;
+ let promptlistener = {
+ onPromptStartAsync(callback) {
+ this.onPromptAuthAvailable(callback);
+ },
+
+ onPromptAuthAvailable: callback => {
+ oauth.connect(
+ () => {
+ aListener.onSuccess(
+ btoa(
+ `user=${this._username}\x01auth=Bearer ${oauth.accessToken}\x01\x01`
+ )
+ );
+ if (callback) {
+ callback.onAuthResult(true);
+ }
+ },
+ () => {
+ aListener.onFailure(Cr.NS_ERROR_ABORT);
+ if (callback) {
+ callback.onAuthResult(false);
+ }
+ },
+ aWithUI,
+ false
+ );
+ },
+ onPromptCanceled() {
+ aListener.onFailure(Cr.NS_ERROR_ABORT);
+ },
+ onPromptStart() {},
+ };
+
+ let asyncprompter = Cc[
+ "@mozilla.org/messenger/msgAsyncPrompter;1"
+ ].getService(Ci.nsIMsgAsyncPrompter);
+ let promptkey = this._loginOrigin + "/" + this._username;
+ asyncprompter.queueAsyncAuthPrompt(promptkey, false, promptlistener);
+ },
+};
diff --git a/comm/mailnews/base/src/OAuth2Providers.jsm b/comm/mailnews/base/src/OAuth2Providers.jsm
new file mode 100644
index 0000000000..86300b2ead
--- /dev/null
+++ b/comm/mailnews/base/src/OAuth2Providers.jsm
@@ -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/. */
+
+/**
+ * Details of supported OAuth2 Providers.
+ */
+var EXPORTED_SYMBOLS = ["OAuth2Providers"];
+
+// When we add a Google mail account, ask for address book and calendar scopes
+// as well. Then we can add an address book or calendar without asking again.
+//
+// Don't ask for all the scopes when adding an address book or calendar
+// independently of the mail set-up process. If a mail account already exists,
+// we already have a token, and if it doesn't the user is likely to be setting
+// up an address book/calendar without wanting mail.
+const GOOGLE_SCOPES =
+ "https://mail.google.com/ https://www.googleapis.com/auth/carddav https://www.googleapis.com/auth/calendar";
+const FASTMAIL_SCOPES =
+ "https://www.fastmail.com/dev/protocol-imap https://www.fastmail.com/dev/protocol-pop https://www.fastmail.com/dev/protocol-smtp https://www.fastmail.com/dev/protocol-carddav https://www.fastmail.com/dev/protocol-caldav";
+const COMCAST_SCOPES = "https://email.comcast.net/ profile openid";
+
+/**
+ * Map of hostnames to [issuer, scope].
+ */
+var kHostnames = new Map([
+ ["imap.googlemail.com", ["accounts.google.com", GOOGLE_SCOPES]],
+ ["smtp.googlemail.com", ["accounts.google.com", GOOGLE_SCOPES]],
+ ["pop.googlemail.com", ["accounts.google.com", GOOGLE_SCOPES]],
+ ["imap.gmail.com", ["accounts.google.com", GOOGLE_SCOPES]],
+ ["smtp.gmail.com", ["accounts.google.com", GOOGLE_SCOPES]],
+ ["pop.gmail.com", ["accounts.google.com", GOOGLE_SCOPES]],
+ [
+ "www.googleapis.com",
+ ["accounts.google.com", "https://www.googleapis.com/auth/carddav"],
+ ],
+
+ ["imap.mail.ru", ["o2.mail.ru", "mail.imap"]],
+ ["smtp.mail.ru", ["o2.mail.ru", "mail.imap"]],
+
+ ["imap.yandex.com", ["oauth.yandex.com", "mail:imap_full"]],
+ ["smtp.yandex.com", ["oauth.yandex.com", "mail:smtp"]],
+
+ ["imap.mail.yahoo.com", ["login.yahoo.com", "mail-w"]],
+ ["pop.mail.yahoo.com", ["login.yahoo.com", "mail-w"]],
+ ["smtp.mail.yahoo.com", ["login.yahoo.com", "mail-w"]],
+
+ ["imap.aol.com", ["login.aol.com", "mail-w"]],
+ ["pop.aol.com", ["login.aol.com", "mail-w"]],
+ ["smtp.aol.com", ["login.aol.com", "mail-w"]],
+
+ [
+ "outlook.office365.com",
+ [
+ "login.microsoftonline.com",
+ "https://outlook.office.com/IMAP.AccessAsUser.All https://outlook.office.com/POP.AccessAsUser.All https://outlook.office.com/SMTP.Send offline_access",
+ ],
+ ],
+ [
+ "smtp.office365.com",
+ [
+ "login.microsoftonline.com",
+ "https://outlook.office.com/IMAP.AccessAsUser.All https://outlook.office.com/POP.AccessAsUser.All https://outlook.office.com/SMTP.Send offline_access",
+ ],
+ ],
+
+ ["imap.fastmail.com", ["www.fastmail.com", FASTMAIL_SCOPES]],
+ ["pop.fastmail.com", ["www.fastmail.com", FASTMAIL_SCOPES]],
+ ["smtp.fastmail.com", ["www.fastmail.com", FASTMAIL_SCOPES]],
+ [
+ "carddav.fastmail.com",
+ ["www.fastmail.com", "https://www.fastmail.com/dev/protocol-carddav"],
+ ],
+
+ ["imap.comcast.net", ["comcast.net", COMCAST_SCOPES]],
+ ["pop.comcast.net", ["comcast.net", COMCAST_SCOPES]],
+ ["smtp.comcast.net", ["comcast.net", COMCAST_SCOPES]],
+
+ // For testing purposes.
+ ["mochi.test", ["mochi.test", "test_scope"]],
+]);
+
+/**
+ * Map of issuers to clientId, clientSecret, authorizationEndpoint, tokenEndpoint,
+ * and usePKCE (RFC7636).
+ * Issuer is a unique string for the organization that a Thunderbird account
+ * was registered at.
+ *
+ * For the moment these details are hard-coded, since dynamic client
+ * registration is not yet supported. Don't copy these values for your
+ * own application - register one for yourself! This code (and possibly even the
+ * registration itself) will disappear when this is switched to dynamic
+ * client registration.
+ */
+var kIssuers = new Map([
+ [
+ "accounts.google.com",
+ {
+ clientId:
+ "406964657835-aq8lmia8j95dhl1a2bvharmfk3t1hgqj.apps.googleusercontent.com",
+ clientSecret: "kSmqreRr0qwBWJgbf5Y-PjSU",
+ authorizationEndpoint: "https://accounts.google.com/o/oauth2/auth",
+ tokenEndpoint: "https://www.googleapis.com/oauth2/v3/token",
+ },
+ ],
+ [
+ "o2.mail.ru",
+ {
+ clientId: "thunderbird",
+ clientSecret: "I0dCAXrcaNFujaaY",
+ authorizationEndpoint: "https://o2.mail.ru/login",
+ tokenEndpoint: "https://o2.mail.ru/token",
+ },
+ ],
+ [
+ "oauth.yandex.com",
+ {
+ clientId: "2a00bba7374047a6ab79666485ffce31",
+ clientSecret: "3ded85b4ec574c2187a55dc49d361280",
+ authorizationEndpoint: "https://oauth.yandex.com/authorize",
+ tokenEndpoint: "https://oauth.yandex.com/token",
+ },
+ ],
+ [
+ "login.yahoo.com",
+ {
+ clientId:
+ "dj0yJmk9NUtCTWFMNVpTaVJmJmQ9WVdrOVJ6UjVTa2xJTXpRbWNHbzlNQS0tJnM9Y29uc3VtZXJzZWNyZXQmeD0yYw--",
+ clientSecret: "f2de6a30ae123cdbc258c15e0812799010d589cc",
+ authorizationEndpoint: "https://api.login.yahoo.com/oauth2/request_auth",
+ tokenEndpoint: "https://api.login.yahoo.com/oauth2/get_token",
+ },
+ ],
+ [
+ "login.aol.com",
+ {
+ clientId:
+ "dj0yJmk9OXRHc1FqZHRQYzVvJmQ9WVdrOU1UQnJOR0pvTjJrbWNHbzlNQS0tJnM9Y29uc3VtZXJzZWNyZXQmeD02NQ--",
+ clientSecret: "79c1c11991d148ddd02a919000d69879942fc278",
+ authorizationEndpoint: "https://api.login.aol.com/oauth2/request_auth",
+ tokenEndpoint: "https://api.login.aol.com/oauth2/get_token",
+ },
+ ],
+
+ [
+ "login.microsoftonline.com",
+ {
+ clientId: "9e5f94bc-e8a4-4e73-b8be-63364c29d753", // Application (client) ID
+ // https://docs.microsoft.com/en-us/azure/active-directory/develop/active-directory-v2-protocols#endpoints
+ authorizationEndpoint:
+ "https://login.microsoftonline.com/common/oauth2/v2.0/authorize",
+ tokenEndpoint:
+ "https://login.microsoftonline.com/common/oauth2/v2.0/token",
+ redirectionEndpoint: "https://localhost",
+ },
+ ],
+
+ [
+ "www.fastmail.com",
+ {
+ clientId: "35f141ae",
+ authorizationEndpoint: "https://api.fastmail.com/oauth/authorize",
+ tokenEndpoint: "https://api.fastmail.com/oauth/refresh",
+ usePKCE: true,
+ },
+ ],
+
+ [
+ "comcast.net",
+ {
+ clientId: "thunderbird-oauth",
+ clientSecret: "fc5d0a314549bb3d059e0cec751fa4bd40a9cc7b",
+ authorizationEndpoint: "https://oauth.xfinity.com/oauth/authorize",
+ tokenEndpoint: "https://oauth.xfinity.com/oauth/token",
+ usePKCE: true,
+ },
+ ],
+
+ // For testing purposes.
+ [
+ "mochi.test",
+ {
+ clientId: "test_client_id",
+ clientSecret: "test_secret",
+ authorizationEndpoint:
+ "http://mochi.test:8888/browser/comm/mail/components/addrbook/test/browser/data/redirect_auto.sjs",
+ tokenEndpoint:
+ "http://mochi.test:8888/browser/comm/mail/components/addrbook/test/browser/data/token.sjs",
+ // I don't know why, but tests refuse to work with a plain HTTP endpoint
+ // (the request is redirected to HTTPS, which we're not listening to).
+ // Just use an HTTPS endpoint.
+ redirectionEndpoint: "https://localhost",
+ },
+ ],
+]);
+
+/**
+ * OAuth2Providers: Methods to lookup OAuth2 parameters for supported OAuth2
+ * providers.
+ */
+var OAuth2Providers = {
+ /**
+ * Map a hostname to the relevant issuer and scope.
+ *
+ * @param {string} hostname - The hostname of the server. For example
+ * "imap.googlemail.com".
+ *
+ * @returns {Array} An array containing [issuer, scope] for the hostname, or
+ * undefined if not found.
+ * - issuer is a string representing the organization
+ * - scope is an OAuth2 parameter describing the required access level
+ */
+ getHostnameDetails(hostname) {
+ // During CardDAV SRV autodiscovery, rfc6764#section-6 says:
+ //
+ // * The client will need to make authenticated HTTP requests to
+ // the service. Typically, a "user identifier" is required for
+ // some form of user/password authentication. When a user
+ // identifier is required, clients MUST first use the "mailbox"
+ //
+ // However macOS Contacts does not do this and just uses the "localpart"
+ // instead. To work around this bug, during SRV autodiscovery Fastmail
+ // returns SRV records of the form '0 1 443 d[0-9]+.carddav.fastmail.com.'
+ // which encodes the internal domainid of the queried SRV domain in the
+ // sub-domain of the Target (rfc2782) of the SRV result. This can
+ // then be extracted from the Host header on each DAV request, the
+ // original domain looked up and attached to the "localpart" to create
+ // a full "mailbox", allowing autodiscovery to just work for usernames
+ // in any domain including self hosted domains.
+ //
+ // So for this hostname -> issuer/scope lookup to work, we need to
+ // look not just at the hostname, but also any sub-domains of this
+ // hostname.
+ while (hostname.includes(".")) {
+ let foundHost = kHostnames.get(hostname);
+ if (foundHost) {
+ return foundHost;
+ }
+ hostname = hostname.replace(/^[^.]*[.]/, "");
+ }
+ return undefined;
+ },
+
+ /**
+ * Map an issuer to OAuth2 account details.
+ *
+ * @param {string} issuer - The organization issuing OAuth2 parameters, e.g.
+ * "accounts.google.com".
+ *
+ * @returns {Array} An array containing [clientId, clientSecret, authorizationEndpoint, tokenEndpoint].
+ * clientId and clientSecret are strings representing the account registered
+ * for Thunderbird with the organization.
+ * authorizationEndpoint and tokenEndpoint are url strings representing
+ * endpoints to access OAuth2 authentication.
+ */
+ getIssuerDetails(issuer) {
+ return kIssuers.get(issuer);
+ },
+};
diff --git a/comm/mailnews/base/src/TemplateUtils.jsm b/comm/mailnews/base/src/TemplateUtils.jsm
new file mode 100644
index 0000000000..fca56fca67
--- /dev/null
+++ b/comm/mailnews/base/src/TemplateUtils.jsm
@@ -0,0 +1,90 @@
+/* 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 EXPORTED_SYMBOLS = ["PluralStringFormatter", "makeFriendlyDateAgo"];
+
+var { PluralForm } = ChromeUtils.importESModule(
+ "resource://gre/modules/PluralForm.sys.mjs"
+);
+
+function PluralStringFormatter(aBundleURI) {
+ this._bundle = Services.strings.createBundle(aBundleURI);
+}
+
+PluralStringFormatter.prototype = {
+ get(aStringName, aReplacements, aPluralCount) {
+ let str = this._bundle.GetStringFromName(aStringName);
+ if (aPluralCount !== undefined) {
+ str = PluralForm.get(aPluralCount, str);
+ }
+ if (aReplacements !== undefined) {
+ for (let i = 0; i < aReplacements.length; i++) {
+ str = str.replace("#" + (i + 1), aReplacements[i]);
+ }
+ }
+ return str;
+ },
+};
+
+var gTemplateUtilsStrings = new PluralStringFormatter(
+ "chrome://messenger/locale/templateUtils.properties"
+);
+
+const _dateFormatter = new Services.intl.DateTimeFormat(undefined, {
+ dateStyle: "short",
+});
+const _dayMonthFormatter = new Services.intl.DateTimeFormat(undefined, {
+ month: "long",
+ day: "numeric",
+});
+const _timeFormatter = new Services.intl.DateTimeFormat(undefined, {
+ timeStyle: "short",
+});
+const _weekdayFormatter = new Services.intl.DateTimeFormat(undefined, {
+ weekday: "long",
+});
+
+/**
+ * Helper function to generate a localized "friendly" representation of
+ * time relative to the present. If the time input is "today", it returns
+ * a string corresponding to just the time. If it's yesterday, it returns
+ * "yesterday" (localized). If it's in the last week, it returns the day
+ * of the week. If it's before that, it returns the date.
+ *
+ * @param {Date} time - The time (better be in the past!)
+ * @returns {string} A "human-friendly" representation of that time
+ * relative to now.
+ */
+function makeFriendlyDateAgo(time) {
+ // TODO: use Intl.RelativeTimeFormat instead.
+ // Figure out when today begins
+ let now = new Date();
+ let today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
+
+ // Get the end time to display
+ let end = time;
+
+ // Figure out if the end time is from today, yesterday,
+ // this week, etc.
+ let dateTime;
+ let kDayInMsecs = 24 * 60 * 60 * 1000;
+ let k6DaysInMsecs = 6 * kDayInMsecs;
+ if (end >= today) {
+ // activity finished after today started, show the time
+ dateTime = _timeFormatter.format(end);
+ } else if (today - end < kDayInMsecs) {
+ // activity finished after yesterday started, show yesterday
+ dateTime = gTemplateUtilsStrings.get("yesterday");
+ } else if (today - end < k6DaysInMsecs) {
+ // activity finished after last week started, show day of week
+ dateTime = _weekdayFormatter.format(end);
+ } else if (now.getFullYear() == end.getFullYear()) {
+ // activity must have been from some time ago.. show month/day
+ dateTime = _dayMonthFormatter.format(end);
+ } else {
+ // not this year, so show full date format
+ dateTime = _dateFormatter.format(end);
+ }
+ return dateTime;
+}
diff --git a/comm/mailnews/base/src/UrlListener.cpp b/comm/mailnews/base/src/UrlListener.cpp
new file mode 100644
index 0000000000..ce2ef4904a
--- /dev/null
+++ b/comm/mailnews/base/src/UrlListener.cpp
@@ -0,0 +1,22 @@
+/* -*- 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 "UrlListener.h"
+
+NS_IMPL_ISUPPORTS(UrlListener, nsIUrlListener)
+
+NS_IMETHODIMP UrlListener::OnStartRunningUrl(nsIURI* url) {
+ if (!mStartFn) {
+ return NS_OK;
+ }
+ return mStartFn(url);
+}
+
+NS_IMETHODIMP UrlListener::OnStopRunningUrl(nsIURI* url, nsresult exitCode) {
+ if (!mStopFn) {
+ return NS_OK;
+ }
+ return mStopFn(url, exitCode);
+}
diff --git a/comm/mailnews/base/src/UrlListener.h b/comm/mailnews/base/src/UrlListener.h
new file mode 100644
index 0000000000..22a0597854
--- /dev/null
+++ b/comm/mailnews/base/src/UrlListener.h
@@ -0,0 +1,72 @@
+/* -*- 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 UrlListener_h__
+#define UrlListener_h__
+
+#include <functional> // For std::function.
+#include "nsIUrlListener.h"
+class nsIURI;
+
+/**
+ * UrlListener is a small nsIUrlListener implementation which allows
+ * callable objects (including lambdas) to be plugged in instead of deriving
+ * your own nsIUrlListener.
+ *
+ * The aim is to encourage more readable code by allowing the start/stop
+ * notifications of a long-running operation to be handled near to where the
+ * operation was initiated.
+ *
+ * A contrived example:
+ *
+ * void Kick() {
+ * UrlListener* listener = new UrlListener;
+ * listener->mStopFn = [](nsIURI* url, nsresult status) -> nsresult {
+ * // Note that we may get here waaaaaaay after Kick() has returned...
+ * printf("LongRunningOperation is finished.\n");
+ * return NS_OK;
+ * };
+ * thingService.startLongRunningOperation(listener);
+ * //...continue doing other stuff while operation is ongoing...
+ * }
+ *
+ * Traditionally, c-c code has tended to use multiple inheritance to add
+ * listener callbacks to the class of the object initiating the operation.
+ * This has a couple of undesirable side effects:
+ *
+ * 1) It separates out the onStopRunningUrl handling into some other
+ * part of the code, which makes the order of things much harder to follow.
+ * 2) Often the same onStopRunningUrl handler will be used for many different
+ * kinds of operations (see nsImapMailFolder::OnStopRunningUrl(), for
+ * example).
+ * 3) It exposes implementation details as part of the public interface
+ * e.g see all the listener types nsMsgDBFolder derives from to implement
+ * it's internals. That's all just confusing noise that shouldn't be seen
+ * from outside the class.
+ *
+ * Just as PromiseTestUtils.jsm brings the Javascript side up from callback
+ * hell to async lovelyness, this can be used to raise the C++ side from
+ * callback-somewhere-else-maybe-in-this-class-but-who-can-really-tell hell
+ * up to normal callback hell :-)
+ *
+ */
+class UrlListener : public nsIUrlListener {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIURLLISTENER
+
+ UrlListener() {}
+ /**
+ * mStartFn and mStopFn are the OnStartRunning() and OnStopRunningUrl()
+ * handlers. It's fine for them to be null (often you'll only need mStopFn).
+ */
+ std::function<nsresult(nsIURI*)> mStartFn;
+ std::function<nsresult(nsIURI*, nsresult)> mStopFn;
+
+ protected:
+ virtual ~UrlListener() {}
+};
+
+#endif // UrlListener_h__
diff --git a/comm/mailnews/base/src/VirtualFolderWrapper.jsm b/comm/mailnews/base/src/VirtualFolderWrapper.jsm
new file mode 100644
index 0000000000..6fc86c8d1e
--- /dev/null
+++ b/comm/mailnews/base/src/VirtualFolderWrapper.jsm
@@ -0,0 +1,257 @@
+/* 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/. */
+
+/*
+ * Wrap everything about virtual folders.
+ */
+
+const EXPORTED_SYMBOLS = ["VirtualFolderHelper"];
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+var VirtualFolderHelper = {
+ /**
+ * Create a new virtual folder (an actual nsIMsgFolder that did not previously
+ * exist), wrapping it in a VirtualFolderWrapper, and returning that wrapper.
+ *
+ * If the call to addSubfolder fails (and therefore throws), we will NOT catch
+ * it.
+ *
+ * @param {string} aFolderName - The name of the new folder to create.
+ * @param {nsIMsgFolder} aParentFolder - The folder in which to create the
+ * search folder.
+ * @param {nsIMsgFolder[]} aSearchFolders A list of nsIMsgFolders that you
+ * want to use as the sources for the virtual folder OR a string that is
+ * the already '|' delimited list of folder URIs to use.
+ * @param {nsIMsgSearchTerms[]} aSearchTerms - The search terms to
+ * use for the virtual folder.
+ * @param {boolean} aOnlineSearch Should the search attempt to use the
+ * server's search capabilities when possible and appropriate?
+ * @returns {VirtualFolderWrapper} The VirtualFolderWrapper wrapping the
+ * newly created folder. You would probably only want this for its
+ * virtualFolder attribute which has the nsIMsgFolder we created.
+ * Be careful about accessing any of the other attributes, as they will
+ * bring its message database back to life.
+ */
+ createNewVirtualFolder(
+ aFolderName,
+ aParentFolder,
+ aSearchFolders,
+ aSearchTerms,
+ aOnlineSearch
+ ) {
+ let msgFolder = aParentFolder.addSubfolder(aFolderName);
+ msgFolder.prettyName = aFolderName;
+ msgFolder.setFlag(Ci.nsMsgFolderFlags.Virtual);
+
+ let wrappedVirt = new VirtualFolderWrapper(msgFolder);
+ wrappedVirt.searchTerms = aSearchTerms;
+ wrappedVirt.searchFolders = aSearchFolders;
+ wrappedVirt.onlineSearch = aOnlineSearch;
+
+ let msgDatabase = msgFolder.msgDatabase;
+ msgDatabase.summaryValid = true;
+ msgDatabase.close(true);
+
+ aParentFolder.notifyFolderAdded(msgFolder);
+ MailServices.accounts.saveVirtualFolders();
+
+ return wrappedVirt;
+ },
+
+ /**
+ * Given an existing nsIMsgFolder that is a virtual folder, wrap it into a
+ * VirtualFolderWrapper.
+ *
+ * @param {nsIMsgFolder} aMsgFolder - The folder to use.
+ */
+ wrapVirtualFolder(aMsgFolder) {
+ return new VirtualFolderWrapper(aMsgFolder);
+ },
+};
+
+/**
+ * Abstracts dealing with the properties of a virtual folder that differentiate
+ * it from a non-virtual folder. A virtual folder is an odd duck. When
+ * holding an nsIMsgFolder that is a virtual folder, it is distinguished by
+ * the virtual flag and a number of properties that tell us the string
+ * representation of its search, the folders it searches over, and whether we
+ * use online searching or not.
+ * Virtual folders and their defining attributes are loaded from
+ * virtualFolders.dat (in the profile directory) by the account manager at
+ * startup, (re-)creating them if need be. It also saves them back to the
+ * file at shutdown. The most important thing the account manager does is to
+ * create VirtualFolderChangeListener instances that are registered with the
+ * message database service. This means that if one of the databases for the
+ * folders that the virtual folder includes is opened for some reason (for
+ * example, new messages are added to the folder because of a filter or they
+ * are delivered there), the virtual folder gets a chance to know about this
+ * and update the virtual folder's "cache" of information, such as the message
+ * counts or the presence of the message in the folder.
+ * The odd part is that a lot of the virtual folder logic also happens as a
+ * result of the nsMsgDBView subclasses being told the search query and the
+ * underlying folders. This makes for an odd collaboration of UI and backend
+ * logic.
+ *
+ * Justification for this class: Virtual folders aren't all that complex, but
+ * they are complex enough that we don't want to have the same code duplicated
+ * all over the place. We also don't want to have a loose assembly of global
+ * functions for working with them. So here we are.
+ *
+ * Important! Accessing any of our attributes results in the message database
+ * being loaded so that we can access the dBFolderInfo associated with the
+ * database. The message database is not automatically forgotten by the
+ * folder, which can lead to an (effective) memory leak. Please make sure
+ * that you are playing your part in not leaking memory by only using the
+ * wrapper when you have a serious need to access the database, and by
+ * forcing the folder to forget about the database when you are done by
+ * setting the database to null (unless you know with confidence someone else
+ * definitely wants the database around and will clean it up.)
+ *
+ * @param {nsIMsgFolder} aVirtualFolder - Folder to wrap.
+ */
+function VirtualFolderWrapper(aVirtualFolder) {
+ this.virtualFolder = aVirtualFolder;
+}
+VirtualFolderWrapper.prototype = {
+ /**
+ * @returns {nsIMsgFolders[]} The list of nsIMsgFolders that this virtual
+ * folder is a search over.
+ */
+ get searchFolders() {
+ return this.dbFolderInfo
+ .getCharProperty("searchFolderUri")
+ .split("|")
+ .sort() // Put folders in URI order so a parent is always before a child.
+ .map(uri => MailServices.folderLookup.getOrCreateFolderForURL(uri))
+ .filter(Boolean);
+ },
+ /**
+ * Set the search folders that back this virtual folder.
+ *
+ * @param {string|nsIMsgFolder[]} aFolders - Either a "|"-delimited string of
+ * folder URIs or a list of folders.
+ */
+ set searchFolders(aFolders) {
+ if (typeof aFolders == "string") {
+ this.dbFolderInfo.setCharProperty("searchFolderUri", aFolders);
+ } else {
+ let uris = aFolders.map(folder => folder.URI);
+ this.dbFolderInfo.setCharProperty("searchFolderUri", uris.join("|"));
+ }
+ Services.obs.notifyObservers(this.virtualFolder, "search-folders-changed");
+ },
+
+ /**
+ * @returns {string} a "|"-delimited string containing the URIs of the folders
+ * that back this virtual folder.
+ */
+ get searchFolderURIs() {
+ return this.dbFolderInfo.getCharProperty("searchFolderUri");
+ },
+
+ /**
+ * @returns {nsIMsgSearchTerm[]} The list of search terms that define this
+ * virtual folder.
+ */
+ get searchTerms() {
+ return this.searchTermsSession.searchTerms;
+ },
+ /**
+ * @returns {nsIMsgFilterList} A newly created filter with the search terms
+ * loaded into it that define this virtual folder. The filter is apparently
+ * useful as an nsIMsgSearchSession stand-in to some code.
+ */
+ get searchTermsSession() {
+ // Temporary means it doesn't get exposed to the UI and doesn't get saved to
+ // disk. Which is good, because this is just a trick to parse the string
+ // into search terms.
+ let filterList = MailServices.filters.getTempFilterList(this.virtualFolder);
+ let tempFilter = filterList.createFilter("temp");
+ filterList.parseCondition(tempFilter, this.searchString);
+ return tempFilter;
+ },
+
+ /**
+ * Set the search string for this virtual folder to the stringified version of
+ * the provided list of nsIMsgSearchTerm search terms. If you already have
+ * a strinigified version of the search constraint, just set |searchString|
+ * directly.
+ *
+ * @param {string[]} aTerms - a list of search terms
+ */
+ set searchTerms(aTerms) {
+ let condition = "";
+ for (let term of aTerms) {
+ if (condition) {
+ condition += " ";
+ }
+ if (term.matchAll) {
+ condition = "ALL";
+ break;
+ }
+ condition += term.booleanAnd ? "AND (" : "OR (";
+ condition += term.termAsString + ")";
+ }
+ this.searchString = condition;
+ },
+
+ /**
+ * @returns {string} the set of search terms that define this virtual folder
+ * as a string. You may prefer to use |searchTerms| which converts them
+ * into a list of nsIMsgSearchTerms instead.
+ */
+ get searchString() {
+ return this.dbFolderInfo.getCharProperty("searchStr");
+ },
+ /**
+ * Set the search that defines this virtual folder from a string. If you have
+ * a list of nsIMsgSearchTerms, you should use |searchTerms| instead.
+ *
+ * @param {string} aSearchString
+ */
+ set searchString(aSearchString) {
+ this.dbFolderInfo.setCharProperty("searchStr", aSearchString);
+ },
+
+ /**
+ * @returns {boolean} whether the virtual folder is configured for online search.
+ */
+ get onlineSearch() {
+ return this.dbFolderInfo.getBooleanProperty("searchOnline", false);
+ },
+ /**
+ * Set whether the virtual folder is configured for online search.
+ *
+ * @param {boolean} aOnlineSearch
+ */
+ set onlineSearch(aOnlineSearch) {
+ this.dbFolderInfo.setBooleanProperty("searchOnline", aOnlineSearch);
+ },
+
+ /**
+ * @returns {?nsIDBFolderInfo} The dBFolderInfo associated with the virtual
+ * folder directly. Maybe null. Will cause the message database to be
+ * opened, which may have memory bloat/leak ramifications, so make sure
+ * the folder's database was already going to be opened anyways or that you
+ * call |cleanUpMessageDatabase|.
+ */
+ get dbFolderInfo() {
+ let msgDatabase = this.virtualFolder.msgDatabase;
+ return msgDatabase && msgDatabase.dBFolderInfo;
+ },
+
+ /**
+ * Avoid memory bloat by making the virtual folder forget about its database.
+ * If the database is actually in use (read: someone is keeping it alive by
+ * having references to it from places other than the nsIMsgFolder), the
+ * folder will be able to re-establish the reference for minimal cost.
+ */
+ cleanUpMessageDatabase() {
+ this.virtualFolder.msgDatabase.close(true);
+ this.virtualFolder.msgDatabase = null;
+ },
+};
diff --git a/comm/mailnews/base/src/WinUnreadBadge.jsm b/comm/mailnews/base/src/WinUnreadBadge.jsm
new file mode 100644
index 0000000000..819d8c5719
--- /dev/null
+++ b/comm/mailnews/base/src/WinUnreadBadge.jsm
@@ -0,0 +1,246 @@
+/* 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/.
+ *
+ * Based on https://github.com/bstreiff/unread-badge.
+ *
+ * Copyright (c) 2013-2020 Brandon Streiff
+ */
+
+const EXPORTED_SYMBOLS = ["WinUnreadBadge"];
+
+const { XPCOMUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/XPCOMUtils.sys.mjs"
+);
+
+const lazy = {};
+
+XPCOMUtils.defineLazyModuleGetters(lazy, {
+ NetUtil: "resource://gre/modules/NetUtil.jsm",
+});
+
+XPCOMUtils.defineLazyServiceGetters(lazy, {
+ imgTools: ["@mozilla.org/image/tools;1", "imgITools"],
+ taskbar: ["@mozilla.org/windows-taskbar;1", "nsIWinTaskbar"],
+});
+
+/**
+ * Get an imgIContainer instance from a canvas element.
+ *
+ * @param {HTMLCanvasElement} canvas - The canvas element.
+ * @param {number} width - The width of the canvas to use.
+ * @param {number} height - The height of the canvas to use.
+ * @returns {imgIContainer}
+ */
+function getCanvasAsImgContainer(canvas, width, height) {
+ let imageData = canvas.getContext("2d").getImageData(0, 0, width, height);
+
+ // Create an imgIEncoder so we can turn the image data into a PNG stream.
+ let imgEncoder = Cc["@mozilla.org/image/encoder;2?type=image/png"].getService(
+ Ci.imgIEncoder
+ );
+ imgEncoder.initFromData(
+ imageData.data,
+ imageData.data.length,
+ imageData.width,
+ imageData.height,
+ imageData.width * 4,
+ imgEncoder.INPUT_FORMAT_RGBA,
+ ""
+ );
+
+ // Now turn the PNG stream into an imgIContainer.
+ let imgBuffer = lazy.NetUtil.readInputStreamToString(
+ imgEncoder,
+ imgEncoder.available()
+ );
+ let iconImage = lazy.imgTools.decodeImageFromBuffer(
+ imgBuffer,
+ imgBuffer.length,
+ "image/png"
+ );
+
+ // Close the PNG stream.
+ imgEncoder.close();
+ return iconImage;
+}
+
+/**
+ * Draw text centered in the middle of a CanvasRenderingContext2D.
+ *
+ * @param {CanvasRenderingContext2D} cxt - The canvas context to operate on.
+ * @param {string} text - The text to draw.
+ */
+function drawUnreadCountText(cxt, text) {
+ cxt.save();
+
+ let imageSize = cxt.canvas.width;
+
+ // Use smaller fonts for longer text to try and squeeze it in.
+ let fontSize = imageSize * (0.95 - 0.15 * text.length);
+
+ cxt.font = "500 " + fontSize + "px Calibri";
+ cxt.fillStyle = "#ffffff";
+ cxt.textAlign = "center";
+
+ // TODO: There isn't a textBaseline for accurate vertical centering ('middle' is the
+ // middle of the 'em block', and digits extend higher than 'm'), and the Mozilla core
+ // does not currently support computation of ascenders and descenters in measureText().
+ // So, we just assume that the font is 70% of the 'px' height we requested, then
+ // compute where the baseline ought to be located.
+ let approximateHeight = fontSize * 0.7;
+
+ cxt.textBaseline = "alphabetic";
+ cxt.fillText(
+ text,
+ imageSize / 2,
+ imageSize - (imageSize - approximateHeight) / 2
+ );
+
+ cxt.restore();
+}
+
+/**
+ * Create a flat badge, as is the Windows 8/10 style.
+ *
+ * @param {HTMLCanvasElement} canvas - The canvas element to draw the badge.
+ * @param {string} text - The text to draw in the badge.
+ */
+function createModernBadgeStyle(canvas, text) {
+ let cxt = canvas.getContext("2d");
+ let iconSize = canvas.width;
+
+ // Draw the background.
+ cxt.save();
+ // Solid color first.
+ cxt.fillStyle = "#ff0039";
+ cxt.shadowOffsetX = 0;
+ cxt.shadowOffsetY = 0;
+ cxt.shadowColor = "rgba(0,0,0,0.7)";
+ cxt.shadowBlur = iconSize / 10;
+ cxt.beginPath();
+ cxt.arc(iconSize / 2, iconSize / 2, iconSize / 2.25, 0, Math.PI * 2, true);
+ cxt.fill();
+ cxt.clip();
+ cxt.closePath();
+ cxt.restore();
+
+ drawUnreadCountText(cxt, text);
+}
+
+/**
+ * Downsample by 4X with simple averaging.
+ *
+ * Drawing at 4X and then downscaling like this gives us better results than
+ * using either CanvasRenderingContext2D.drawImage() to resize or letting
+ * the Windows taskbar service handle the resize, both of which seem to just
+ * give us a simple point resize.
+ *
+ * @param {Window} window - The DOM window.
+ * @param {HTMLCanvasElement} canvas - The input canvas element to resize.
+ * @returns {HTMLCanvasElement} The resized canvas element.
+ */
+function downsampleBy4X(window, canvas) {
+ let resizedCanvas = window.document.createElement("canvas");
+ resizedCanvas.width = resizedCanvas.height = canvas.width / 4;
+ resizedCanvas.style.width = resizedCanvas.style.height =
+ resizedCanvas.width + "px";
+
+ let source = canvas
+ .getContext("2d")
+ .getImageData(0, 0, canvas.width, canvas.height);
+ let downsampled = resizedCanvas
+ .getContext("2d")
+ .createImageData(resizedCanvas.width, resizedCanvas.height);
+
+ for (let y = 0; y < resizedCanvas.height; ++y) {
+ for (let x = 0; x < resizedCanvas.width; ++x) {
+ let r = 0,
+ g = 0,
+ b = 0,
+ a = 0;
+ let index;
+
+ for (let i = 0; i < 4; ++i) {
+ for (let j = 0; j < 4; ++j) {
+ index = ((y * 4 + i) * source.width + (x * 4 + j)) * 4;
+ r += source.data[index];
+ g += source.data[index + 1];
+ b += source.data[index + 2];
+ a += source.data[index + 3];
+ }
+ }
+
+ index = (y * downsampled.width + x) * 4;
+ downsampled.data[index] = Math.round(r / 16);
+ downsampled.data[index + 1] = Math.round(g / 16);
+ downsampled.data[index + 2] = Math.round(b / 16);
+ downsampled.data[index + 3] = Math.round(a / 16);
+ }
+ }
+
+ resizedCanvas.getContext("2d").putImageData(downsampled, 0, 0);
+
+ return resizedCanvas;
+}
+
+/**
+ * A module to manage the unread badge icon on Windows.
+ */
+var WinUnreadBadge = {
+ /**
+ * Keeping an instance of nsITaskbarOverlayIconController alive
+ * to show a taskbar icon after the updateUnreadCount method exits.
+ */
+ _controller: null,
+
+ /**
+ * Update the unread badge.
+ *
+ * @param {number} unreadCount - Unread message count.
+ * @param {number} unreadTooltip - Unread message count tooltip.
+ */
+ async updateUnreadCount(unreadCount, unreadTooltip) {
+ let window = Services.wm.getMostRecentBrowserWindow();
+ if (!window) {
+ return;
+ }
+ if (!this._controller) {
+ this._controller = lazy.taskbar.getOverlayIconController(window.docShell);
+ }
+ if (unreadCount == 0) {
+ // Remove the badge if no unread.
+ this._controller.setOverlayIcon(null, "");
+ return;
+ }
+
+ // Draw the badge in a canvas.
+ let smallIconSize = Cc["@mozilla.org/windows-ui-utils;1"].getService(
+ Ci.nsIWindowsUIUtils
+ ).systemSmallIconSize;
+ let iconSize = Math.floor(
+ (window.windowUtils.displayDPI / 96) * smallIconSize
+ );
+ let iconSize4X = iconSize * 4;
+ let badge = window.document.createElement("canvas");
+ badge.width = badge.height = iconSize4X;
+ badge.style.width = badge.style.height = badge.width + "px";
+
+ createModernBadgeStyle(
+ badge,
+ unreadCount < 100 ? unreadCount.toString() : "99+"
+ );
+
+ badge = downsampleBy4X(window, badge);
+ let icon = getCanvasAsImgContainer(badge, iconSize, iconSize);
+ // Purge image from cache to force encodeImage() to not be lazy
+ icon.requestDiscard();
+ // Side effect of encodeImage() is that it decodes original image
+ lazy.imgTools.encodeImage(icon, "image/png");
+ // Somehow this is needed to prevent NS_ERROR_NOT_AVAILABLE error in
+ // setOverlayIcon.
+ await new Promise(resolve => window.setTimeout(resolve));
+
+ this._controller.setOverlayIcon(icon, unreadTooltip);
+ },
+};
diff --git a/comm/mailnews/base/src/components.conf b/comm/mailnews/base/src/components.conf
new file mode 100644
index 0000000000..776148e132
--- /dev/null
+++ b/comm/mailnews/base/src/components.conf
@@ -0,0 +1,359 @@
+# -*- 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": "{a30be08c-afc8-4fed-9af7-79778a23db23}",
+ "contract_ids": ["@mozilla.org/mail/folder-lookup;1"],
+ "jsm": "resource:///modules/FolderLookupService.jsm",
+ "constructor": "FolderLookupService",
+ },
+ {
+ "cid": "{49b04761-23dd-45d7-903d-619418a4d319}",
+ "contract_ids": ["@mozilla.org/messenger/msgAsyncPrompter;1"],
+ "jsm": "resource:///modules/MsgAsyncPrompter.jsm",
+ "constructor": "MsgAsyncPrompter",
+ },
+ {
+ "cid": "{26ebf3a7-3e52-4b7a-a89b-fa7c0b7506f9}",
+ "contract_ids": ["@mozilla.org/messenger/msgAuthPrompt;1"],
+ "jsm": "resource:///modules/MsgAsyncPrompter.jsm",
+ "constructor": "MsgAuthPrompt",
+ },
+ {
+ "cid": "{b63d8e4c-bf60-439b-be0e-7c9f67291042}",
+ "contract_ids": ["@mozilla.org/mail/oauth2-module;1"],
+ "jsm": "resource:///modules/OAuth2Module.jsm",
+ "constructor": "OAuth2Module",
+ },
+ {
+ "cid": "{740880e6-e299-4165-b82f-df1dcab3ae22}",
+ "contract_ids": ["@mozilla.org/newMailNotificationService;1"],
+ "jsm": "resource:///modules/MailNotificationService.jsm",
+ "constructor": "NewMailNotificationService",
+ "name": "MailNotification",
+ "interfaces": ["mozINewMailNotificationService"],
+ },
+ {
+ "cid": "{37246055-3596-4bfa-911f-3d2977e8d284}",
+ "contract_ids": ["@mozilla.org/mail/auth-module;1"],
+ "type": "nsMailAuthModule",
+ "headers": ["/comm/mailnews/base/src/nsMailAuthModule.h"],
+ },
+ {
+ "cid": "{e9aef539-29db-4936-9fdc-40ba11c70cb3}",
+ "contract_ids": ["@mozilla.org/mail/notification-manager;1"],
+ "jsm": "resource:///modules/MailNotificationManager.jsm",
+ "constructor": "MailNotificationManager",
+ },
+ {
+ "cid": "{4a85a5d0-cddd-11d2-b7f6-00805f05ffa5}",
+ "contract_ids": [
+ "@mozilla.org/appshell/component/messenger;1",
+ "@mozilla.org/messenger/windowservice;1",
+ ],
+ "type": "nsMessengerBootstrap",
+ "headers": ["/comm/mailnews/base/src/nsMessengerBootstrap.h"],
+ },
+ {
+ "cid": "{d5124441-d59e-11d2-806a-006080128c4e}",
+ "contract_ids": ["@mozilla.org/messenger/services/session;1"],
+ "type": "nsMsgMailSession",
+ "init_method": "Init",
+ "headers": ["/comm/mailnews/base/src/nsMsgMailSession.h"],
+ "name": "MailSession",
+ "interfaces": ["nsIMsgMailSession"],
+ },
+ {
+ "cid": "{f436a174-e2c0-4955-9afe-e3feb68aee56}",
+ "contract_ids": ["@mozilla.org/messenger;1"],
+ "type": "nsMessenger",
+ "headers": ["/comm/mailnews/base/src/nsMessenger.h"],
+ },
+ {
+ "cid": "{d2876e50-e62c-11d2-b7fc-00805f05ffa5}",
+ "contract_ids": ["@mozilla.org/messenger/account-manager;1"],
+ "type": "nsMsgAccountManager",
+ "init_method": "Init",
+ "headers": ["/comm/mailnews/base/src/nsMsgAccountManager.h"],
+ "name": "AccountManager",
+ "interfaces": ["nsIMsgAccountManager"],
+ },
+ {
+ "cid": "{68b25510-e641-11d2-b7fc-00805f05ffa5}",
+ "contract_ids": ["@mozilla.org/messenger/account;1"],
+ "type": "nsMsgAccount",
+ "headers": ["/comm/mailnews/base/src/nsMsgAccount.h"],
+ },
+ {
+ "cid": "{8fbf6ac0-ebcc-11d2-b7fc-00805f05ffa5}",
+ "contract_ids": ["@mozilla.org/messenger/identity;1"],
+ "type": "nsMsgIdentity",
+ "headers": ["/comm/mailnews/base/src/nsMsgIdentity.h"],
+ },
+ {
+ "cid": "{4a374e7e-190f-11d3-8a88-0060b0fc04d2}",
+ "contract_ids": ["@mozilla.org/messenger/biffManager;1"],
+ "type": "nsMsgBiffManager",
+ "init_method": "Init",
+ "headers": ["/comm/mailnews/base/src/nsMsgBiffManager.h"],
+ },
+ {
+ "cid": "{a687b474-afd8-418f-8ad9-f362202ae9a9}",
+ "contract_ids": ["@mozilla.org/messenger/purgeService;1"],
+ "type": "nsMsgPurgeService",
+ "headers": ["/comm/mailnews/base/src/nsMsgPurgeService.h"],
+ },
+ {
+ "cid": "{7f9a9fb0-4161-11d4-9876-00c04fa0d2a6}",
+ "contract_ids": ["@mozilla.org/messenger/statusBarBiffManager;1"],
+ "type": "nsStatusBarBiffManager",
+ "init_method": "Init",
+ "headers": ["/comm/mailnews/base/src/nsStatusBarBiffManager.h"],
+ },
+ {
+ "cid": "{7741daed-2125-11d3-8a90-0060b0fc04d2}",
+ "contract_ids": ["@mozilla.org/messenger/copymessagestreamlistener;1"],
+ "type": "nsCopyMessageStreamListener",
+ "headers": ["/comm/mailnews/base/src/nsCopyMessageStreamListener.h"],
+ },
+ {
+ "cid": "{c766e666-29bd-11d3-afb3-001083002da8}",
+ "contract_ids": ["@mozilla.org/messenger/messagecopyservice;1"],
+ "type": "nsMsgCopyService",
+ "headers": ["/comm/mailnews/base/src/nsMsgCopyService.h"],
+ "name": "Copy",
+ "interfaces": ["nsIMsgCopyService"],
+ },
+ {
+ "cid": "{bcdca970-3b22-11d3-8d76-0080f58a6617}",
+ "contract_ids": ["@mozilla.org/messenger/msgFolderCache;1"],
+ "type": "nsMsgFolderCache",
+ "headers": ["/comm/mailnews/base/src/nsMsgFolderCache.h"],
+ },
+ {
+ "cid": "{bd85a417-5433-11d3-8ac5-0060b0fc04d2}",
+ "contract_ids": ["@mozilla.org/messenger/statusfeedback;1"],
+ "type": "nsMsgStatusFeedback",
+ "headers": ["/comm/mailnews/base/src/nsMsgStatusFeedback.h"],
+ },
+ {
+ "cid": "{bb460dff-8bf0-11d3-8afe-0060b0fc04d2}",
+ "contract_ids": ["@mozilla.org/messenger/msgwindow;1"],
+ "type": "nsMsgWindow",
+ "init_method": "Init",
+ "headers": ["/comm/mailnews/base/src/nsMsgWindow.h"],
+ },
+ {
+ "cid": "{8510876a-1dd2-11b2-8253-91f71b348a25}",
+ "contract_ids": ["@mozilla.org/messenger/subscribableserver;1"],
+ "type": "nsSubscribableServer",
+ "init_method": "Init",
+ "headers": ["/comm/mailnews/base/src/nsSubscribableServer.h"],
+ },
+ {
+ "cid": "{56c4c2ac-fe4a-4528-aa78-f8fb579b029c}",
+ "contract_ids": ["@mozilla.org/messenger/foldercompactor;1"],
+ "type": "nsMsgFolderCompactor",
+ "headers": ["/comm/mailnews/base/src/nsMsgFolderCompactor.h"],
+ },
+ {
+ "cid": "{52f860e0-1dd2-11b2-aa72-bb751981bd00}",
+ "contract_ids": ["@mozilla.org/messenger/msgdbview;1?type=threaded"],
+ "type": "nsMsgThreadedDBView",
+ "headers": ["/comm/mailnews/base/src/nsMsgThreadedDBView.h"],
+ },
+ {
+ "cid": "{ca79a00e-010d-11d5-a5be-0060b0fc04b7}",
+ "contract_ids": ["@mozilla.org/messenger/msgdbview;1?type=threadswithunread"],
+ "type": "nsMsgThreadsWithUnreadDBView",
+ "headers": ["/comm/mailnews/base/src/nsMsgSpecialViews.h"],
+ },
+ {
+ "cid": "{597e1ffe-0123-11d5-a5be-0060b0fc04b7}",
+ "contract_ids": [
+ "@mozilla.org/messenger/msgdbview;1?type=watchedthreadswithunread"
+ ],
+ "type": "nsMsgWatchedThreadsWithUnreadDBView",
+ "headers": ["/comm/mailnews/base/src/nsMsgSpecialViews.h"],
+ },
+ {
+ "cid": "{aeac118c-0823-11d5-a5bf-0060b0fc04b7}",
+ "contract_ids": ["@mozilla.org/messenger/msgdbview;1?type=search"],
+ "type": "nsMsgSearchDBView",
+ "headers": ["/comm/mailnews/base/src/nsMsgSearchDBView.h"],
+ },
+ {
+ "cid": "{2dd9d0fe-b609-11d6-bacc-00108335748d}",
+ "contract_ids": ["@mozilla.org/messenger/msgdbview;1?type=quicksearch"],
+ "type": "nsMsgQuickSearchDBView",
+ "headers": ["/comm/mailnews/base/src/nsMsgQuickSearchDBView.h"],
+ },
+ {
+ "cid": "{2af6e050-04f6-495a-8387-86b0aeb1863c}",
+ "contract_ids": ["@mozilla.org/messenger/msgdbview;1?type=xfvf"],
+ "type": "nsMsgXFVirtualFolderDBView",
+ "headers": ["/comm/mailnews/base/src/nsMsgXFVirtualFolderDBView.h"],
+ },
+ {
+ "cid": "{e4603d6c-0a74-47c5-b69e-2f8876990304}",
+ "contract_ids": ["@mozilla.org/messenger/msgdbview;1?type=group"],
+ "type": "nsMsgGroupView",
+ "headers": ["/comm/mailnews/base/src/nsMsgGroupView.h"],
+ },
+ {
+ "cid": "{bcf6afbe-7d4f-11ec-9092-eb4fed0a5aaa}",
+ "contract_ids": ["@mozilla.org/msgDBView/msgDBViewService;1"],
+ "type": "nsMsgDBViewService",
+ "headers": ["/comm/mailnews/base/src/nsMsgDBView.h"],
+ "name": "DBView",
+ "interfaces": ["nsIMsgDBViewService"],
+ },
+ {
+ "cid": "{ac6c518a-09b2-11d5-a5bf-0060b0fc04b7}",
+ "contract_ids": ["@mozilla.org/messenger/offline-manager;1"],
+ "type": "nsMsgOfflineManager",
+ "headers": ["/comm/mailnews/base/src/nsMsgOfflineManager.h"],
+ },
+ {
+ "cid": "{9f4dd201-3b1f-11d5-9daa-c345c9453d3c}",
+ "contract_ids": ["@mozilla.org/messenger/progress;1"],
+ "type": "nsMsgProgress",
+ "headers": ["/comm/mailnews/base/src/nsMsgProgress.h"],
+ },
+ {
+ "cid": "{ce6038ae-e5e0-4372-9cff-2a6633333b2b}",
+ "contract_ids": ["@mozilla.org/messenger/spamsettings;1"],
+ "type": "nsSpamSettings",
+ "headers": ["/comm/mailnews/base/src/nsSpamSettings.h"],
+ },
+ {
+ "cid": "{b3db9392-1b15-48ba-a136-0cc3db13d87b}",
+ "contract_ids": ["@mozilla.org/network/protocol;1?name=cid"],
+ "type": "nsCidProtocolHandler",
+ "headers": ["/comm/mailnews/base/src/nsCidProtocolHandler.h"],
+ "protocol_config": {
+ "scheme": "cid",
+ "flags": [
+ "URI_DANGEROUS_TO_LOAD"
+ ],
+ },
+ },
+ {
+ "cid": "{b897da55-8256-4cf5-892b-32e77bc7c50b}",
+ "contract_ids": ["@mozilla.org/messenger/tagservice;1"],
+ "type": "nsMsgTagService",
+ "headers": ["/comm/mailnews/base/src/nsMsgTagService.h"],
+ "name": "Tag",
+ "interfaces": ["nsIMsgTagService"],
+ },
+ {
+ "cid": "{0c8ec907-49c7-49bc-8bdf-b16e29bd6c47}",
+ "contract_ids": ["@mozilla.org/msgFolder/msgFolderService;1"],
+ "type": "nsMsgFolderService",
+ "headers": ["/comm/mailnews/base/src/nsMsgDBFolder.h"],
+ "name": "Folder",
+ "interfaces": ["nsIMsgFolderService"],
+ },
+ {
+ "cid": "{f1f7cbcd-d5e3-45a0-aa2d-cecf1a95ab03}",
+ "contract_ids": ["@mozilla.org/messenger/msgnotificationservice;1"],
+ "type": "nsMsgFolderNotificationService",
+ "headers": ["/comm/mailnews/base/src/nsMsgFolderNotificationService.h"],
+ "name": "FolderNotification",
+ "interfaces": ["nsIMsgFolderNotificationService"],
+ },
+ {
+ "cid": "{dbfcfdf0-4489-4faa-8122-190fd1efa16c}",
+ "contract_ids": ["@mozilla.org/messenger/content-policy;1"],
+ "type": "nsMsgContentPolicy",
+ "init_method": "Init",
+ "headers": ["/comm/mailnews/base/src/nsMsgContentPolicy.h"],
+ "categories": {"content-policy": "@mozilla.org/messenger/content-policy;1"},
+ },
+ {
+ "cid": "{483c8abb-ecf9-48a3-a394-2c604b603bd5}",
+ "contract_ids": ["@mozilla.org/messenger/msgshutdownservice;1"],
+ "type": "nsMsgShutdownService",
+ "headers": ["/comm/mailnews/base/src/nsMsgMailSession.h"],
+ },
+ {
+ "cid": "{03f9bb53-a680-4349-8de9-d26864d9ffd9}",
+ "contract_ids": ["@mozilla.org/mail/dir-provider;1"],
+ "type": "nsMailDirProvider",
+ "headers": ["/comm/mailnews/base/src/nsMailDirProvider.h"],
+ "categories": {"xpcom-directory-providers": "mail-directory-provider"},
+ },
+ {
+ "cid": "{6ef7eafd-72d0-4c56-9409-67e16d0f255b}",
+ "contract_ids": ["@mozilla.org/stopwatch;1"],
+ "type": "nsStopwatch",
+ "headers": ["/comm/mailnews/base/src/nsStopwatch.h"],
+ },
+ {
+ "cid": "{de0f34a9-a87f-4f4c-b978-6187db187b90}",
+ "contract_ids": ["@mozilla.org/mailnews/document-loader-factory;1"],
+ "type": "mailnews::MailNewsDLF",
+ "headers": ["/comm/mailnews/base/src/MailNewsDLF.h"],
+ "categories": {"Gecko-Content-Viewers": "message/rfc822"},
+ },
+ {
+ "cid": "{bf88b48c-fd8e-40b4-ba36-c7c3ad6d8ac9}",
+ "contract_ids": ["@mozilla.org/embedcomp/base-command-controller;1"],
+ "type": "nsBaseCommandController",
+ "headers": ["/dom/commandhandler/nsBaseCommandController.h"],
+ },
+ {
+ "cid": "{9c8f9601-801a-11d2-98ba-00805f297d89}",
+ "contract_ids": ["@mozilla.org/transactionmanager;1"],
+ "type": "TransactionManager",
+ "headers": ["/editor/txmgr/TransactionManager.h"],
+ },
+ {
+ "cid": "{14c13684-1dd2-11b2-9463-bb10ba742554}",
+ "contract_ids": ["@mozilla.org/userinfo;1"],
+ "type": "nsUserInfo",
+ "headers": ["/comm/mailnews/base/src/nsUserInfo.h"],
+ },
+ # The XPCOM registration for nsSyncStreamListener was moved to comm-central
+ # in bug 1501718. In bug 1800606 all uses were removed from comm-central.
+ # The registration is maintained for some MailExtension Experiments.
+ # Use of this service is discouraged due to its synchronous processing and
+ # probable deprecation in the future. Instead use asynchronous code like in
+ # `get_msg_source()` in ComposeHelpers.jsm.
+ {
+ "cid": "{439400d3-6f23-43db-8b06-8aafe1869bd8}",
+ "contract_ids": ["@mozilla.org/network/sync-stream-listener;1"],
+ "constructor": "SyncStreamListenerCreate",
+ "headers": ["/comm/mailnews/base/src/nsMsgUtils.h"],
+ },
+]
+
+if buildconfig.substs["OS_ARCH"] == "Darwin":
+ Classes += [
+ {
+ "cid": "{746b28a5-d239-4719-b1a2-cf8093332ae3}",
+ "contract_ids": ["@mozilla.org/messenger/osintegration;1"],
+ "type": "nsMessengerOSXIntegration",
+ "headers": ["/comm/mailnews/base/src/nsMessengerOSXIntegration.h"],
+ },
+ ]
+
+ Categories = {
+ "app-startup": {
+ "OS Integration": "@mozilla.org/messenger/osintegration;1",
+ }
+ }
+
+if buildconfig.substs["OS_ARCH"] == "WINNT":
+ Classes += [
+ {
+ "cid": "{a74dd1d6-2ec4-4985-98f3-f69e18d20811}",
+ "contract_ids": ["@mozilla.org/messenger/osintegration;1"],
+ "type": "nsMessengerWinIntegration",
+ "headers": ["/comm/mailnews/base/src/nsMessengerWinIntegration.h"],
+ },
+ ]
diff --git a/comm/mailnews/base/src/converterWorker.js b/comm/mailnews/base/src/converterWorker.js
new file mode 100644
index 0000000000..188476c1e1
--- /dev/null
+++ b/comm/mailnews/base/src/converterWorker.js
@@ -0,0 +1,533 @@
+/* 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/. */
+
+/* eslint-env mozilla/chrome-worker, node */
+
+/**
+ * This worker will perform mbox<->maildir conversions on a tree of
+ * directories. It operates purely at the filesystem level.
+ *
+ * The initial message data should pass in these params to control
+ * the conversion:
+ *
+ * srcType - source mailstore type ('mbox' or 'maildir')
+ * destType - destination mailstore type ('maildir' or 'mbox')
+ * srcRoot - root path of source (eg ".../ImapMail/imap.example.com")
+ * destRoot - root path of destination (eg "/tmp/imap.example.com-maildir")
+ *
+ * The conversion is non-destructive - srcRoot will be left untouched.
+ *
+ * The worker will post progress messages back to the main thread of
+ * the form:
+ *
+ * {"msg": "progress", "val": val, "total": total}
+ *
+ * Where `val` is the current progress, out of `total`.
+ * The units used for val and total are undefined.
+ *
+ * When the conversion is complete, before exiting, the worker sends a
+ * message of the form:
+ *
+ * {"msg": "success"}
+ *
+ * Errors are posted back to the main thread via the standard
+ * "error" event.
+ *
+ */
+
+/**
+ * Merge all the messages in a maildir into a single mbox file.
+ *
+ * @param {string} maildir - Path to the source maildir.
+ * @param {string} mboxFilename - Path of the mbox file to create.
+ * @param {Function(number)} progressFn - Function to be invoked regularly with
+ * progress updates. Param is number of
+ * "units" processed since last update.
+ */
+async function maildirToMBox(maildir, mboxFilename, progressFn) {
+ // Helper to format dates
+ // eg "Thu Jan 18 12:34:56 2018"
+ let fmtUTC = function (d) {
+ const dayNames = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
+ const monthNames = [
+ "Jan",
+ "Feb",
+ "Mar",
+ "Apr",
+ "May",
+ "Jun",
+ "Jul",
+ "Aug",
+ "Sep",
+ "Oct",
+ "Nov",
+ "Dec",
+ ];
+ return (
+ dayNames[d.getUTCDay()] +
+ " " +
+ monthNames[d.getUTCMonth()] +
+ " " +
+ d.getUTCDate().toString().padStart(2) +
+ " " +
+ d.getUTCHours().toString().padStart(2, "0") +
+ ":" +
+ d.getUTCMinutes().toString().padStart(2, "0") +
+ ":" +
+ d.getUTCSeconds().toString().padStart(2, "0") +
+ " " +
+ d.getUTCFullYear()
+ );
+ };
+
+ // Initialize mbox file
+ await IOUtils.write(mboxFilename, new Uint8Array(), {
+ mode: "create",
+ });
+
+ // Iterate over all the message files in "cur".
+ let curPath = PathUtils.join(maildir, "cur");
+ let paths = await IOUtils.getChildren(curPath);
+ let files = await Promise.all(
+ paths.map(async path => {
+ let stat = await IOUtils.stat(path);
+ return {
+ path,
+ creationDate: stat.creationTime,
+ };
+ })
+ );
+ // We write out the mbox messages ordered by creation time.
+ // Not ideal, but best we can do without parsing message.
+ files.sort(function (a, b) {
+ return a.creationDate - b.creationDate;
+ });
+
+ for (let ent of files) {
+ let raw = await IOUtils.read(ent.path);
+ // Old converter had a bug where maildir messages included the
+ // leading "From " marker, so we need to cope with any
+ // cases of this left in the wild.
+ if (String.fromCharCode.apply(null, raw.slice(0, 5)) != "From ") {
+ // Write the separator line.
+ // Technically, timestamp should be the reception time of the
+ // message, but we don't really want to have to parse the
+ // message here and nothing is likely to rely on it.
+ let sepLine = "From - " + fmtUTC(new Date()) + "\n";
+ await IOUtils.writeUTF8(mboxFilename, sepLine, {
+ mode: "append",
+ });
+ }
+
+ await IOUtils.write(mboxFilename, raw, {
+ mode: "append",
+ });
+ // Maildir progress is one per message.
+ progressFn(1);
+ }
+}
+
+/**
+ * Split an mbox file up into a maildir.
+ *
+ * @param {string} mboxPath - Path of the mbox file to split.
+ * @param {string} maildirPath - Path of the maildir to create.
+ * @param {Function(number)} progressFn - Function to be invoked regularly with
+ * progress updates. One parameter is
+ * passed - the number of "cost units"
+ * since the previous update.
+ */
+async function mboxToMaildir(mboxPath, maildirPath, progressFn) {
+ // Create the maildir structure.
+ await IOUtils.makeDirectory(maildirPath);
+ let curDirPath = PathUtils.join(maildirPath, "cur");
+ let tmpDirPath = PathUtils.join(maildirPath, "tmp");
+ await IOUtils.makeDirectory(curDirPath);
+ await IOUtils.makeDirectory(tmpDirPath);
+
+ const CHUNK_SIZE = 1000000;
+ // SAFE_MARGIN is how much to keep back between chunks in order to
+ // cope with separator lines which might span chunks.
+ const SAFE_MARGIN = 100;
+
+ // A regexp to match mbox separator lines. Separator lines in the wild can
+ // have all sorts of forms, for example:
+ //
+ // "From "
+ // "From MAILER-DAEMON Fri Jul 8 12:08:34 2011"
+ // "From - Mon Jul 11 12:08:34 2011"
+ // "From bob@example.com Fri Jul 8 12:08:34 2011"
+ //
+ // So we accept any line beginning with "From " and ignore the rest of it.
+ //
+ // We also require a message header on the next line, in order
+ // to better cope with unescaped "From " lines in the message body.
+ // note: the first subexpression matches the separator line, so
+ // that it can be removed from the input.
+ let sepRE = /^(From (?:.*?)\r?\n)[\x21-\x7E]+:/gm;
+
+ // Use timestamp as starting name for output messages, incrementing
+ // by one for each.
+ let ident = Date.now();
+
+ /**
+ * Helper. Convert a string into a Uint8Array, using no encoding. The low
+ * byte of each 16 bit character will be used, the high byte discarded.
+ *
+ * @param {string} str - Input string with chars in 0-255 range.
+ * @returns {Uint8Array} The output bytes.
+ */
+ let stringToBytes = function (str) {
+ var bytes = new Uint8Array(str.length);
+ for (let i = 0; i < str.length; i++) {
+ bytes[i] = str.charCodeAt(i);
+ }
+ return bytes;
+ };
+
+ /**
+ * Helper. Convert a Uint8Array directly into a string, using each byte
+ * directly as a character code. So all characters in the resulting string
+ * will range from 0 to 255, even though they are 16 bit values.
+ *
+ * @param {Uint8Array} bytes - The bytes to convert.
+ * @returns {string} The byte values in string form.
+ */
+ let bytesToString = function (bytes) {
+ return bytes.reduce(function (str, b) {
+ return str + String.fromCharCode(b);
+ }, "");
+ };
+
+ let outPath;
+
+ /**
+ * Helper. Write out a block of bytes to the current message file, starting
+ * a new file if required.
+ *
+ * @param {string} str - The bytes to append (as chars in range 0-255).
+ */
+ let writeToMsg = async function (str) {
+ let mode = "append";
+ if (!outPath) {
+ outPath = PathUtils.join(curDirPath, ident.toString() + ".eml");
+ ident += 1;
+ mode = "create";
+ }
+ // We know that str is really raw 8-bit data, not UTF-16. So we can
+ // discard the upper byte and just keep the low byte of each char.
+ let raw = stringToBytes(str);
+ await IOUtils.write(outPath, raw, { mode });
+ // For mbox->maildir conversion, progress is measured in bytes.
+ progressFn(raw.byteLength);
+ };
+
+ let buf = "";
+ let eof = false;
+ let offset = 0;
+ while (!eof) {
+ let rawBytes = await IOUtils.read(mboxPath, {
+ offset,
+ maxBytes: CHUNK_SIZE,
+ });
+ // We're using JavaScript strings (which hold 16bit characters) to store
+ // 8 bit data. This sucks, but is faster than trying to operate directly
+ // upon Uint8Arrays. A lot of work goes into optimising JavaScript strings.
+ buf += bytesToString(rawBytes);
+ offset += rawBytes.byteLength;
+ eof = rawBytes.byteLength < CHUNK_SIZE;
+
+ let pos = 0;
+ sepRE.lastIndex = 0; // start at beginning of buf
+ let m = null;
+ while ((m = sepRE.exec(buf)) !== null) {
+ // Output everything up to the line separator.
+ if (m.index > pos) {
+ await writeToMsg(buf.substring(pos, m.index));
+ }
+ pos = m.index;
+ pos += m[1].length; // skip the "From " line
+ // Reset the current message file path if any.
+ if (outPath) {
+ outPath = null;
+ }
+ }
+
+ // Deal with whatever is left in the buffer.
+ let endPos = buf.length;
+ if (!eof) {
+ // Keep back enough to cope with separator lines crossing
+ // chunk boundaries.
+ endPos -= SAFE_MARGIN;
+ if (endPos < pos) {
+ endPos = pos;
+ }
+ }
+
+ if (endPos > pos) {
+ await writeToMsg(buf.substring(pos, endPos));
+ }
+ buf = buf.substring(endPos);
+ }
+}
+
+/**
+ * Check if directory is a subfolder directory.
+ *
+ * @param {string} name - Name of directory to check.
+ * @returns {boolean} - true if subfolder.
+ */
+function isSBD(name) {
+ return name.substr(-4) == ".sbd";
+}
+
+/**
+ * Check if file is a type which should be copied verbatim as part of a
+ * conversion.
+ * See also: nsMsgLocalStoreUtils::nsShouldIgnoreFile().
+ *
+ * @param {string} name - Name of file to check.
+ * @returns {boolean} - true if file should be copied verbatim.
+ */
+function isFileToCopy(name) {
+ let ext4 = name.substr(-4);
+ // Database and config files.
+ if (ext4 == ".msf" || ext4 == ".dat") {
+ return true;
+ }
+ // Summary files.
+ if (ext4 == ".snm" || ext4 == ".toc") {
+ return true;
+ }
+ // A few files we know might be lurking there.
+ const SPECIAL_FILES = [
+ "filterlog.html",
+ "junklog.html",
+ "feeds.json",
+ "feeds.json.tmp",
+ "feeds.json.backup",
+ "feeds.json.corrupt",
+ "feeditems.json",
+ "feeditems.json.tmp",
+ "feeditems.json.backup",
+ "feeditems.json.corrupt",
+ "mailfilt.log",
+ "filters.js",
+ ];
+ if (SPECIAL_FILES.includes(name)) {
+ return true;
+ }
+ return false;
+}
+
+/**
+ * Check if file is an mbox.
+ * (actually we can't really tell if it's an mbox or not just from the name.
+ * we just assume it is, if it's not .msf or .dat).
+ *
+ * @param {string} name - Name of file to check.
+ * @returns {boolean} - true if file is an mbox
+ */
+function isMBoxName(name) {
+ // If it's not a "special" file, assume it's mbox.
+ return !isFileToCopy(name);
+}
+
+/**
+ * Check if directory is a maildir (by looking for a "cur" subdir).
+ *
+ * @param {string} dir - Path of directory to check.
+ * @returns {Promise<boolean>} - true if directory is a maildir.
+ */
+async function isMaildir(dir) {
+ try {
+ let cur = PathUtils.join(dir, "cur");
+ let fi = await IOUtils.stat(cur);
+ return fi.type === "directory";
+ } catch (ex) {
+ if (ex instanceof DOMException && ex.name === "NotFoundError") {
+ // "cur" does not exist - not a maildir.
+ return false;
+ }
+ throw ex; // Other error.
+ }
+}
+
+/**
+ * Count the number of messages in the "cur" dir of maildir.
+ *
+ * @param {string} maildir - Path of maildir.
+ * @returns {Promise<number>} - number of messages found.
+ */
+async function countMaildirMsgs(maildir) {
+ let cur = PathUtils.join(maildir, "cur");
+ let paths = await IOUtils.getChildren(cur);
+ return paths.length;
+}
+
+/**
+ * Recursively calculate the 'cost' of a hierarchy of maildir folders.
+ * This is the figure used for progress updates.
+ * For maildir, cost is 1 per message.
+ *
+ * @param {string} srcPath - Path of root dir containing maildirs.
+ * @returns {Promise<number>} - calculated conversion cost.
+ */
+async function calcMaildirCost(srcPath) {
+ let cost = 0;
+ for (let path of await IOUtils.getChildren(srcPath)) {
+ let stat = await IOUtils.stat(path);
+ if (stat.type === "directory") {
+ let name = PathUtils.filename(path);
+ if (isSBD(name)) {
+ // Recurse into subfolder.
+ cost += await calcMaildirCost(path);
+ } else if (await isMaildir(path)) {
+ // Looks like a maildir. Cost is number of messages.
+ cost += await countMaildirMsgs(path);
+ }
+ }
+ }
+ return cost;
+}
+
+/**
+ * Recursively calculate the 'cost' of a hierarchy of mbox folders.
+ * This is the figure used for progress updates.
+ * For mbox, cost is the total byte size of data. This avoids the need to
+ * parse the mbox files to count the number of messages.
+ * Note that this byte count cost is not 100% accurate because it includes
+ * the "From " lines which are not written into the maildir files. But it's
+ * definitely close enough to give good user feedback.
+ *
+ * @param {string} srcPath - Path of root dir containing maildirs.
+ * @returns {Promise<number>} - calculated conversion cost.
+ */
+async function calcMBoxCost(srcPath) {
+ let cost = 0;
+ for (const path of await IOUtils.getChildren(srcPath)) {
+ let stat = await IOUtils.stat(path);
+ let name = PathUtils.filename(path);
+ if (stat.type === "directory") {
+ if (isSBD(name)) {
+ // Recurse into .sbd subfolder.
+ cost += await calcMBoxCost(path);
+ }
+ } else if (isMBoxName(name)) {
+ cost += stat.size;
+ }
+ }
+ return cost;
+}
+
+/**
+ * Recursively convert a tree of mbox-based folders to maildirs.
+ *
+ * @param {string} srcPath - Root path containing mboxes.
+ * @param {string} destPath - Where to create destination root.
+ * @param {Function(number)} progressFn - Function to be invoked regularly with
+ * progress updates (called with number of
+ * cost "units" since last update)
+ */
+async function convertTreeMBoxToMaildir(srcPath, destPath, progressFn) {
+ await IOUtils.makeDirectory(destPath);
+
+ for (const path of await IOUtils.getChildren(srcPath)) {
+ let name = PathUtils.filename(path);
+ let dest = PathUtils.join(destPath, name);
+ let stat = await IOUtils.stat(path);
+ if (stat.type === "directory") {
+ if (isSBD(name)) {
+ // Recurse into .sbd subfolder.
+ await convertTreeMBoxToMaildir(path, dest, progressFn);
+ }
+ } else if (isFileToCopy(name)) {
+ await IOUtils.copy(path, dest);
+ } else if (isMBoxName(name)) {
+ // It's an mbox. Convert it.
+ await mboxToMaildir(path, dest, progressFn);
+ }
+ }
+}
+
+/**
+ * Recursively convert a tree of maildir-based folders to mbox.
+ *
+ * @param {string} srcPath - Root path containing maildirs.
+ * @param {string} destPath - Where to create destination root.
+ * @param {Function(number)} progressFn - Function to be invoked regularly with
+ * progress updates (called with number of
+ * cost "units" since last update)
+ */
+async function convertTreeMaildirToMBox(srcPath, destPath, progressFn) {
+ await IOUtils.makeDirectory(destPath);
+
+ for (let path of await IOUtils.getChildren(srcPath)) {
+ let name = PathUtils.filename(path);
+ let dest = PathUtils.join(destPath, name);
+ let stat = await IOUtils.stat(path);
+ if (stat.type === "directory") {
+ if (isSBD(name)) {
+ // Recurse into .sbd subfolder.
+ await convertTreeMaildirToMBox(path, dest, progressFn);
+ } else if (await isMaildir(path)) {
+ // It's a maildir - convert it.
+ await maildirToMBox(path, dest, progressFn);
+ }
+ } else if (isFileToCopy(name)) {
+ await IOUtils.copy(path, dest);
+ }
+ }
+}
+
+// propagate unhandled rejections to the error handler on the main thread
+self.addEventListener("unhandledrejection", function (error) {
+ throw error.reason;
+});
+
+self.addEventListener("message", function (e) {
+ // Unpack the request params from the main thread.
+ let srcType = e.data.srcType;
+ let destType = e.data.destType;
+ let srcRoot = e.data.srcRoot;
+ let destRoot = e.data.destRoot;
+ // destRoot will be a temporary dir, so if it all goes pear-shaped
+ // we can just bail out without cleaning up.
+
+ // Configure the conversion.
+ let costFn = null;
+ let convertFn = null;
+ if (srcType == "maildir" && destType == "mbox") {
+ costFn = calcMaildirCost;
+ convertFn = convertTreeMaildirToMBox;
+ } else if (srcType == "mbox" && destType == "maildir") {
+ costFn = calcMBoxCost;
+ convertFn = convertTreeMBoxToMaildir;
+ } else {
+ throw new Error(`Unsupported conversion: ${srcType} => ${destType}`);
+ }
+
+ // Go!
+ costFn(srcRoot).then(totalCost => {
+ let v = 0;
+ let progressFn = function (n) {
+ v += n;
+ self.postMessage({ msg: "progress", val: v, total: totalCost });
+ };
+ convertFn(srcRoot, destRoot, progressFn).then(() => {
+ // We fake a final progress update, with exactly 100% completed.
+ // Our byte-counting on mbox->maildir conversion will fall slightly short:
+ // The total is estimated from the mbox filesize, but progress is tracked
+ // by counting bytes as they are written out - and the mbox "From " lines
+ // are _not_ written out to the maildir files.
+ // This is still accurate enough to provide progress to the user, but we
+ // don't want the GUI left showing "progress 97% - conversion complete!"
+ // or anything silly like that.
+ self.postMessage({ msg: "progress", val: totalCost, total: totalCost });
+
+ // Let the main thread know we succeeded.
+ self.postMessage({ msg: "success" });
+ });
+ });
+});
diff --git a/comm/mailnews/base/src/hostnameUtils.jsm b/comm/mailnews/base/src/hostnameUtils.jsm
new file mode 100644
index 0000000000..e80c210b2e
--- /dev/null
+++ b/comm/mailnews/base/src/hostnameUtils.jsm
@@ -0,0 +1,366 @@
+/* -*- 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/. */
+
+/**
+ * Generic shared utility code for checking of IP and hostname validity.
+ */
+
+const EXPORTED_SYMBOLS = [
+ "isLegalHostNameOrIP",
+ "isLegalHostName",
+ "isLegalIPv4Address",
+ "isLegalIPv6Address",
+ "isLegalIPAddress",
+ "isLegalLocalIPAddress",
+ "cleanUpHostName",
+ "kMinPort",
+ "kMaxPort",
+];
+
+var kMinPort = 1;
+var kMaxPort = 65535;
+
+/**
+ * Check if aHostName is an IP address or a valid hostname.
+ *
+ * @param {string} aHostName - The string to check for validity.
+ * @param {boolean} aAllowExtendedIPFormats - Allow hex/octal formats in addition to decimal.
+ * @returns {?string} Unobscured host name if aHostName is valid.
+ * Returns null if it's not.
+ */
+function isLegalHostNameOrIP(aHostName, aAllowExtendedIPFormats) {
+ /*
+ RFC 1123:
+ Whenever a user inputs the identity of an Internet host, it SHOULD
+ be possible to enter either (1) a host domain name or (2) an IP
+ address in dotted-decimal ("#.#.#.#") form. The host SHOULD check
+ the string syntactically for a dotted-decimal number before
+ looking it up in the Domain Name System.
+ */
+
+ return (
+ isLegalIPAddress(aHostName, aAllowExtendedIPFormats) ||
+ isLegalHostName(aHostName)
+ );
+}
+
+/**
+ * Check if aHostName is a valid hostname.
+ *
+ * @returns {?string} The host name if it is valid. Returns null if it's not.
+ */
+function isLegalHostName(aHostName) {
+ /*
+ RFC 952:
+ A "name" (Net, Host, Gateway, or Domain name) is a text string up
+ to 24 characters drawn from the alphabet (A-Z), digits (0-9), minus
+ sign (-), and period (.). Note that periods are only allowed when
+ they serve to delimit components of "domain style names". (See
+ RFC-921, "Domain Name System Implementation Schedule", for
+ background). No blank or space characters are permitted as part of a
+ name. No distinction is made between upper and lower case. The first
+ character must be an alpha character. The last character must not be
+ a minus sign or period.
+
+ RFC 1123:
+ The syntax of a legal Internet host name was specified in RFC-952
+ [DNS:4]. One aspect of host name syntax is hereby changed: the
+ restriction on the first character is relaxed to allow either a
+ letter or a digit. Host software MUST support this more liberal
+ syntax.
+
+ Host software MUST handle host names of up to 63 characters and
+ SHOULD handle host names of up to 255 characters.
+
+ RFC 1034:
+ Relative names are either taken relative to a well known origin, or to a
+ list of domains used as a search list. Relative names appear mostly at
+ the user interface, where their interpretation varies from
+ implementation to implementation, and in master files, where they are
+ relative to a single origin domain name. The most common interpretation
+ uses the root "." as either the single origin or as one of the members
+ of the search list, so a multi-label relative name is often one where
+ the trailing dot has been omitted to save typing.
+
+ Since a complete domain name ends with the root label, this leads to
+ a printed form which ends in a dot.
+ */
+
+ const hostPattern =
+ /^(([a-z0-9]|[a-z0-9][a-z0-9\-]{0,61}[a-z0-9])\.)*([a-z0-9]|[a-z0-9][a-z0-9\-]{0,61}[a-z0-9])\.?$/i;
+ return aHostName.length <= 255 && hostPattern.test(aHostName)
+ ? aHostName
+ : null;
+}
+
+/**
+ * Check if aHostName is a valid IPv4 address.
+ *
+ * @param {string} aHostName - The string to check for validity.
+ * @param {boolean} aAllowExtendedIPFormats - If false, only IPv4 addresses
+ * in the common decimal format (4 components, each up to 255)
+ * will be accepted, no hex/octal formats.
+ * @returns {string} Unobscured canonicalized address if aHostName is an
+ * IPv4 address. Returns null if it's not.
+ */
+function isLegalIPv4Address(aHostName, aAllowExtendedIPFormats) {
+ // Scammers frequently obscure the IP address by encoding each component as
+ // decimal, octal, hex or in some cases a mix match of each. There can even
+ // be less than 4 components where the last number covers the missing components.
+ // See the test at mailnews/base/test/unit/test_hostnameUtils.js for possible
+ // combinations.
+
+ if (!aHostName) {
+ return null;
+ }
+
+ // Break the IP address down into individual components.
+ let ipComponents = aHostName.split(".");
+ let componentCount = ipComponents.length;
+ if (componentCount > 4 || (componentCount < 4 && !aAllowExtendedIPFormats)) {
+ return null;
+ }
+
+ /**
+ * Checks validity of an IP address component.
+ *
+ * @param {string} aValue - The component string.
+ * @param {integer} aWidth - How many components does this string cover.
+ * @returns {integer|null} The value of the component in decimal if it is valid.
+ * Returns null if it's not.
+ */
+ const kPowersOf256 = [1, 256, 65536, 16777216, 4294967296];
+ function isLegalIPv4Component(aValue, aWidth) {
+ let component;
+ // Is the component decimal?
+ if (/^(0|([1-9][0-9]{0,9}))$/.test(aValue)) {
+ component = parseInt(aValue, 10);
+ } else if (aAllowExtendedIPFormats) {
+ // Is the component octal?
+ if (/^(0[0-7]{1,12})$/.test(aValue)) {
+ component = parseInt(aValue, 8);
+ } else if (/^(0x[0-9a-f]{1,8})$/i.test(aValue)) {
+ // The component is hex.
+ component = parseInt(aValue, 16);
+ } else {
+ return null;
+ }
+ } else {
+ return null;
+ }
+
+ // Make sure the component in not larger than the expected maximum.
+ if (component >= kPowersOf256[aWidth]) {
+ return null;
+ }
+
+ return component;
+ }
+
+ for (let i = 0; i < componentCount; i++) {
+ // If we are on the last supplied component but we do not have 4,
+ // the last one covers the remaining ones.
+ let componentWidth = i == componentCount - 1 ? 4 - i : 1;
+ let componentValue = isLegalIPv4Component(ipComponents[i], componentWidth);
+ if (componentValue == null) {
+ return null;
+ }
+
+ // If we have a component spanning multiple ones, split it.
+ for (let j = 0; j < componentWidth; j++) {
+ ipComponents[i + j] =
+ (componentValue >> ((componentWidth - 1 - j) * 8)) & 255;
+ }
+ }
+
+ // First component of zero is not valid.
+ if (ipComponents[0] == 0) {
+ return null;
+ }
+
+ return ipComponents.join(".");
+}
+
+/**
+ * Check if aHostName is a valid IPv6 address.
+ *
+ * @param {string} aHostName - The string to check for validity.
+ * @returns {string} Unobscured canonicalized address if aHostName is an
+ * IPv6 address. Returns null if it's not.
+ */
+function isLegalIPv6Address(aHostName) {
+ if (!aHostName) {
+ return null;
+ }
+
+ // Break the IP address down into individual components.
+ let ipComponents = aHostName.toLowerCase().split(":");
+
+ // Make sure there are at least 3 components.
+ if (ipComponents.length < 3) {
+ return null;
+ }
+
+ let ipLength = ipComponents.length - 1;
+
+ // Take care if the last part is written in decimal using dots as separators.
+ let lastPart = isLegalIPv4Address(ipComponents[ipLength], false);
+ if (lastPart) {
+ let lastPartComponents = lastPart.split(".");
+ // Convert it into standard IPv6 components.
+ ipComponents[ipLength] = (
+ (lastPartComponents[0] << 8) |
+ lastPartComponents[1]
+ ).toString(16);
+ ipComponents[ipLength + 1] = (
+ (lastPartComponents[2] << 8) |
+ lastPartComponents[3]
+ ).toString(16);
+ }
+
+ // Make sure that there is only one empty component.
+ let emptyIndex;
+ for (let i = 1; i < ipComponents.length - 1; i++) {
+ if (ipComponents[i] == "") {
+ // If we already found an empty component return null.
+ if (emptyIndex) {
+ return null;
+ }
+
+ emptyIndex = i;
+ }
+ }
+
+ // If we found an empty component, extend it.
+ if (emptyIndex) {
+ ipComponents[emptyIndex] = 0;
+
+ // Add components so we have a total of 8.
+ for (let count = ipComponents.length; count < 8; count++) {
+ ipComponents.splice(emptyIndex, 0, 0);
+ }
+ }
+
+ // Make sure there are 8 components.
+ if (ipComponents.length != 8) {
+ return null;
+ }
+
+ // Format all components to 4 character hex value.
+ for (let i = 0; i < ipComponents.length; i++) {
+ if (ipComponents[i] == "") {
+ ipComponents[i] = 0;
+ }
+
+ // Make sure the component is a number and it isn't larger than 0xffff.
+ if (/^[0-9a-f]{1,4}$/.test(ipComponents[i])) {
+ ipComponents[i] = parseInt(ipComponents[i], 16);
+ if (isNaN(ipComponents[i]) || ipComponents[i] > 0xffff) {
+ return null;
+ }
+ } else {
+ return null;
+ }
+
+ // Pad the component with 0:s.
+ ipComponents[i] = ("0000" + ipComponents[i].toString(16)).substr(-4);
+ }
+
+ // TODO: support Zone indices in Link-local addresses? Currently they are rejected.
+ // http://en.wikipedia.org/wiki/IPv6_address#Link-local_addresses_and_zone_indices
+
+ let hostName = ipComponents.join(":");
+ // Treat 0000:0000:0000:0000:0000:0000:0000:0000 as an invalid IPv6 address.
+ return hostName != "0000:0000:0000:0000:0000:0000:0000:0000"
+ ? hostName
+ : null;
+}
+
+/**
+ * Check if aHostName is a valid IP address (IPv4 or IPv6).
+ *
+ * @param {string} aHostName - The string to check for validity.
+ * @param {boolean} aAllowExtendedIPFormats - Allow hex/octal formats in
+ * addition to decimal.
+ * @returns {?string} Unobscured canonicalized IPv4 or IPv6 address if it is
+ * valid, otherwise null.
+ */
+function isLegalIPAddress(aHostName, aAllowExtendedIPFormats) {
+ return (
+ isLegalIPv4Address(aHostName, aAllowExtendedIPFormats) ||
+ isLegalIPv6Address(aHostName)
+ );
+}
+
+/**
+ * Check if aIPAddress is a local or private IP address.
+ * Note: if the passed in address is not in canonical (unobscured form),
+ * the result may be wrong.
+ *
+ * @param {string} aIPAddress - A valid IP address literal in canonical
+ * (unobscured) form.
+ * @returns {boolean} frue if it is a local/private IPv4 or IPv6 address.
+ */
+function isLegalLocalIPAddress(aIPAddress) {
+ // IPv4 address?
+ let ipComponents = aIPAddress.split(".");
+ if (ipComponents.length == 4) {
+ // Check if it's a local or private IPv4 address.
+ return (
+ ipComponents[0] == 10 ||
+ ipComponents[0] == 127 || // loopback address
+ (ipComponents[0] == 192 && ipComponents[1] == 168) ||
+ (ipComponents[0] == 169 && ipComponents[1] == 254) ||
+ (ipComponents[0] == 172 && ipComponents[1] >= 16 && ipComponents[1] < 32)
+ );
+ }
+
+ // IPv6 address?
+ ipComponents = aIPAddress.split(":");
+ if (ipComponents.length == 8) {
+ // ::1/128 - localhost
+ if (
+ ipComponents[0] == "0000" &&
+ ipComponents[1] == "0000" &&
+ ipComponents[2] == "0000" &&
+ ipComponents[3] == "0000" &&
+ ipComponents[4] == "0000" &&
+ ipComponents[5] == "0000" &&
+ ipComponents[6] == "0000" &&
+ ipComponents[7] == "0001"
+ ) {
+ return true;
+ }
+
+ // fe80::/10 - link local addresses
+ if (ipComponents[0] == "fe80") {
+ return true;
+ }
+
+ // fc00::/7 - unique local addresses
+ if (
+ ipComponents[0].startsWith("fc") || // usage has not been defined yet
+ ipComponents[0].startsWith("fd")
+ ) {
+ return true;
+ }
+
+ return false;
+ }
+
+ return false;
+}
+
+/**
+ * Clean up the hostname or IP. Usually used to sanitize a value input by the user.
+ * It is usually applied before we know if the hostname is even valid.
+ *
+ * @param {string} aHostName - The hostname or IP string to clean up.
+ */
+function cleanUpHostName(aHostName) {
+ // TODO: Bug 235312: if UTF8 string was input, convert to punycode using convertUTF8toACE()
+ // but bug 563172 needs resolving first.
+ return aHostName.trim();
+}
diff --git a/comm/mailnews/base/src/mailstoreConverter.jsm b/comm/mailnews/base/src/mailstoreConverter.jsm
new file mode 100644
index 0000000000..6e5be5ebe1
--- /dev/null
+++ b/comm/mailnews/base/src/mailstoreConverter.jsm
@@ -0,0 +1,339 @@
+/* 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 EXPORTED_SYMBOLS = ["convertMailStoreTo", "terminateWorkers"];
+
+const { FileUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/FileUtils.sys.mjs"
+);
+
+let log = console.createInstance({
+ prefix: "mail.mailstoreconverter",
+ maxLogLevel: "Warn",
+ maxLogLevelPref: "mail.mailstoreconverter.loglevel",
+});
+
+let gConverterWorker = null;
+
+/**
+ * Sets a server to use a different type of mailstore, converting
+ * all the existing data.
+ *
+ * @param {string} aMailstoreContractId - XPCOM id of new mailstore type.
+ * @param {nsIMsgServer} aServer - server to migrate.
+ * @param {?Element} aEventTarget - If set, element to send progress events.
+ *
+ * @returns {Promise<string>} - Resolves with a string containing the new root
+ * directory for the migrated server.
+ * Rejects with an error message.
+ */
+function convertMailStoreTo(aMailstoreContractId, aServer, aEventTarget) {
+ let accountRootFolder = aServer.rootFolder.filePath;
+
+ let srcType = null;
+ let destType = null;
+ if (aMailstoreContractId == "@mozilla.org/msgstore/maildirstore;1") {
+ srcType = "maildir";
+ destType = "mbox";
+ } else {
+ srcType = "mbox";
+ destType = "maildir";
+ }
+
+ // Go offline before conversion, so there aren't messages coming in during
+ // the process.
+ Services.io.offline = true;
+ let destDir = createTmpConverterFolder(
+ accountRootFolder,
+ aMailstoreContractId
+ );
+
+ // Return a promise that will complete once the worker is done.
+ return new Promise(function (resolve, reject) {
+ let worker = new ChromeWorker("resource:///modules/converterWorker.js");
+ gConverterWorker = worker;
+
+ // Helper to log error, clean up and reject with error message.
+ let bailout = function (errmsg) {
+ log.error("bailing out (" + errmsg + ")");
+ // Cleanup.
+ log.info("Trying to remove converter folder: " + destDir.path);
+ destDir.remove(true);
+ reject(errmsg);
+ };
+
+ // Handle exceptions thrown by the worker thread.
+ worker.addEventListener("error", function (e) {
+ // (e is type ErrorEvent)
+
+ // if we're lucky, the error will contain location info
+ if (e.filename && e.lineno) {
+ bailout(e.filename + ":" + e.lineno + ": " + e.message);
+ } else {
+ bailout(e.message);
+ }
+ });
+
+ // Handle updates from the worker thread.
+ worker.addEventListener("message", function (e) {
+ let response = e.data;
+ // log.debug("WORKER SAYS: " + JSON.stringify(response) + "\n");
+ if (response.msg == "progress") {
+ let val = response.val;
+ let total = response.total;
+
+ // Send the percentage completion to the GUI.
+ // XXX TODO: should probably check elapsed time, and throttle
+ // the events to avoid spending all our time drawing!
+ let ev = new Event("progress");
+ ev.detail = parseInt((val / total) * 100);
+ if (aEventTarget) {
+ aEventTarget.dispatchEvent(ev);
+ }
+ }
+ if (response.msg == "success") {
+ // If we receive this, the worker has completed, without errors.
+ let storeTypeIDs = {
+ mbox: "@mozilla.org/msgstore/berkeleystore;1",
+ maildir: "@mozilla.org/msgstore/maildirstore;1",
+ };
+ let newStoreTypeID = storeTypeIDs[destType];
+
+ try {
+ let finalRoot = installNewRoot(aServer, destDir, newStoreTypeID);
+ log.info(
+ "Conversion complete. Converted dir installed as: " + finalRoot
+ );
+ resolve(finalRoot);
+ } catch (e) {
+ bailout("installNewRoot() failed");
+ }
+ }
+ });
+
+ // Kick off the worker.
+ worker.postMessage({
+ srcType,
+ destType,
+ srcRoot: accountRootFolder.path,
+ destRoot: destDir.path,
+ });
+ });
+}
+
+/**
+ * Checks if Converter folder exists in tmp dir, removes it and creates a new
+ * "Converter" folder.
+ *
+ * @param {nsIFile} aFolder - account root folder.
+ * @param {string} aMailstoreContractId - XPCOM id of dest mailstore type
+ *
+ * @returns {nsIFile} - the new tmp directory to use as converter dest.
+ */
+function createTmpConverterFolder(aFolder, aMailstoreContractId) {
+ let tmpDir = FileUtils.getDir("TmpD", [], false);
+ let tmpFolder;
+ switch (aMailstoreContractId) {
+ case "@mozilla.org/msgstore/maildirstore;1": {
+ if (aFolder.leafName.substr(-8) == "-maildir") {
+ tmpFolder = new FileUtils.File(
+ PathUtils.join(
+ tmpDir.path,
+ aFolder.leafName.substr(0, aFolder.leafName.length - 8) + "-mbox"
+ )
+ );
+ } else {
+ tmpFolder = new FileUtils.File(
+ PathUtils.join(tmpDir.path, aFolder.leafName + "-mbox")
+ );
+ }
+
+ if (tmpFolder.exists()) {
+ log.info(
+ "Temporary Converter folder " +
+ tmpFolder.path +
+ " exists in tmp dir. Removing it"
+ );
+ tmpFolder.remove(true);
+ }
+ return FileUtils.getDir("TmpD", [tmpFolder.leafName], true);
+ }
+
+ case "@mozilla.org/msgstore/berkeleystore;1": {
+ if (aFolder.leafName.substr(-5) == "-mbox") {
+ tmpFolder = new FileUtils.File(
+ PathUtils.join(
+ tmpDir.path,
+ aFolder.leafName.substr(0, aFolder.leafName.length - 5) + "-maildir"
+ )
+ );
+ } else {
+ tmpFolder = new FileUtils.File(
+ PathUtils.join(tmpDir.path, aFolder.leafName + "-maildir")
+ );
+ }
+
+ if (tmpFolder.exists()) {
+ log.info(
+ "Temporary Converter folder " +
+ tmpFolder.path +
+ "exists in tmp dir. Removing it"
+ );
+ tmpFolder.remove(true);
+ }
+ return FileUtils.getDir("TmpD", [tmpFolder.leafName], true);
+ }
+
+ default: {
+ throw new Error(
+ "Unexpected mailstoreContractId: " + aMailstoreContractId
+ );
+ }
+ }
+}
+
+/**
+ * Switch server over to use the newly-converted directory tree.
+ * Moves the converted directory into an appropriate place for the server.
+ *
+ * @param {nsIMsgServer} server - server to migrate.
+ * @param {string} dir - dir of converted mailstore to install
+ * (will be moved by this function).
+ * @param {string} newStoreTypeID - XPCOM id of new mailstore type.
+ * @returns {string} new location of dir.
+ */
+function installNewRoot(server, dir, newStoreTypeID) {
+ let accountRootFolder = server.rootFolder.filePath;
+
+ // Migration is complete, get path of parent of account root
+ // folder into "parentPath" check if Converter folder already
+ // exists in "parentPath". If yes, remove it.
+ let lastSlash = accountRootFolder.path.lastIndexOf("/");
+ let parentPath = accountRootFolder.parent.path;
+ log.info("Path to parent folder of account root folder: " + parentPath);
+
+ let parent = new FileUtils.File(parentPath);
+ log.info("Path to parent folder of account root folder: " + parent.path);
+
+ let converterFolder = new FileUtils.File(
+ PathUtils.join(parent.path, dir.leafName)
+ );
+ if (converterFolder.exists()) {
+ log.info(
+ "Converter folder exists in " +
+ parentPath +
+ ". Removing already existing folder"
+ );
+ converterFolder.remove(true);
+ }
+
+ // Move Converter folder into the parent of account root folder.
+ try {
+ dir.moveTo(parent, dir.leafName);
+ // {nsIFile} new account root folder.
+ log.info("Path to new account root folder: " + converterFolder.path);
+ } catch (e) {
+ // Cleanup.
+ log.error(e);
+ log.error("Trying to remove converter folder: " + converterFolder.path);
+ converterFolder.remove(true);
+ throw e;
+ }
+
+ // If the account is imap then copy the msf file for the original
+ // root folder and rename the copy with the name of the new root
+ // folder.
+ if (server.type != "pop3" && server.type != "none") {
+ let converterFolderMsf = new FileUtils.File(
+ PathUtils.join(parent.path, dir.leafName + ".msf")
+ );
+ if (converterFolderMsf.exists()) {
+ converterFolderMsf.remove(true);
+ }
+
+ let oldRootFolderMsf = new FileUtils.File(
+ PathUtils.join(parent.path, accountRootFolder.leafName + ".msf")
+ );
+ if (oldRootFolderMsf.exists()) {
+ oldRootFolderMsf.copyTo(parent, converterFolderMsf.leafName);
+ }
+ }
+
+ if (server.type == "nntp") {
+ let converterFolderNewsrc = new FileUtils.File(
+ PathUtils.join(parent.path, "newsrc-" + dir.leafName)
+ );
+ if (converterFolderNewsrc.exists()) {
+ converterFolderNewsrc.remove(true);
+ }
+ let oldNewsrc = new FileUtils.File(
+ PathUtils.join(parent.path, "newsrc-" + accountRootFolder.leafName)
+ );
+ if (oldNewsrc.exists()) {
+ oldNewsrc.copyTo(parent, converterFolderNewsrc.leafName);
+ }
+ }
+
+ server.rootFolder.filePath = converterFolder;
+ server.localPath = converterFolder;
+ log.info("Path to account root folder: " + server.rootFolder.filePath.path);
+
+ // Set various preferences.
+ let p1 = "mail.server." + server.key + ".directory";
+ let p2 = "mail.server." + server.key + ".directory-rel";
+ let p3 = "mail.server." + server.key + ".newsrc.file";
+ let p4 = "mail.server." + server.key + ".newsrc.file-rel";
+ let p5 = "mail.server." + server.key + ".storeContractID";
+
+ Services.prefs.setCharPref(p1, converterFolder.path);
+ log.info(p1 + ": " + converterFolder.path);
+
+ // The directory-rel pref is of the form "[ProfD]Mail/pop.gmail.com
+ // " (pop accounts) or "[ProfD]ImapMail/imap.gmail.com" (imap
+ // accounts) ie the last slash "/" is followed by the root folder
+ // name. So, replace the old root folder name that follows the last
+ // slash with the new root folder name to set the correct value of
+ // directory-rel pref.
+ let directoryRel = Services.prefs.getCharPref(p2);
+ lastSlash = directoryRel.lastIndexOf("/");
+ directoryRel =
+ directoryRel.slice(0, lastSlash) + "/" + converterFolder.leafName;
+ Services.prefs.setCharPref(p2, directoryRel);
+ log.info(p2 + ": " + directoryRel);
+
+ if (server.type == "nntp") {
+ let newNewsrc = FileUtils.File(
+ PathUtils.join(parent.path, "newsrc-" + converterFolder.leafName)
+ );
+ Services.prefs.setCharPref(p3, newNewsrc.path);
+
+ // The newsrc.file-rel pref is of the form "[ProfD]News/newsrc-
+ // news.mozilla.org" ie the last slash "/" is followed by the
+ // newsrc file name. So, replace the old newsrc file name that
+ // follows the last slash with the new newsrc file name to set
+ // the correct value of newsrc.file-rel pref.
+ let newsrcRel = Services.prefs.getCharPref(p4);
+ lastSlash = newsrcRel.lastIndexOf("/");
+ newsrcRel = newsrcRel.slice(0, lastSlash) + "/" + newNewsrc.leafName;
+ Services.prefs.setCharPref(p4, newsrcRel);
+ log.info(p4 + ": " + newsrcRel);
+ }
+
+ Services.prefs.setCharPref(p5, newStoreTypeID);
+
+ Services.prefs.savePrefFile(null);
+
+ return converterFolder.path;
+}
+
+/**
+ * Terminate any workers involved in the conversion process.
+ */
+function terminateWorkers() {
+ // We're only using a single worker right now.
+ if (gConverterWorker !== null) {
+ gConverterWorker.terminate();
+ gConverterWorker = null;
+ }
+}
diff --git a/comm/mailnews/base/src/moz.build b/comm/mailnews/base/src/moz.build
new file mode 100644
index 0000000000..8255f50c67
--- /dev/null
+++ b/comm/mailnews/base/src/moz.build
@@ -0,0 +1,154 @@
+# 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/.
+
+EXPORTS += [
+ "HeaderReader.h",
+ "LineReader.h",
+ "nsImapMoveCoalescer.h",
+ "nsMailAuthModule.h",
+ "nsMailChannel.h",
+ "nsMailDirServiceDefs.h",
+ "nsMsgCompressIStream.h",
+ "nsMsgCompressOStream.h",
+ "nsMsgDBFolder.h",
+ "nsMsgEnumerator.h",
+ "nsMsgI18N.h",
+ "nsMsgIdentity.h",
+ "nsMsgIncomingServer.h",
+ "nsMsgKeySet.h",
+ "nsMsgLineBuffer.h",
+ "nsMsgMailNewsUrl.h",
+ "nsMsgProtocol.h",
+ "nsMsgReadStateTxn.h",
+ "nsMsgTxn.h",
+ "nsMsgUtils.h",
+ "nsNewMailnewsURI.h",
+ "nsQuarantinedOutputStream.h",
+ "UrlListener.h",
+]
+
+SOURCES += [
+ "MailNewsDLF.cpp",
+ "MailnewsLoadContextInfo.cpp",
+ "nsCidProtocolHandler.cpp",
+ "nsCopyMessageStreamListener.cpp",
+ "nsImapMoveCoalescer.cpp",
+ "nsMailAuthModule.cpp",
+ "nsMailChannel.cpp",
+ "nsMailDirProvider.cpp",
+ "nsMessenger.cpp",
+ "nsMessengerBootstrap.cpp",
+ "nsMsgAccount.cpp",
+ "nsMsgAccountManager.cpp",
+ "nsMsgBiffManager.cpp",
+ "nsMsgCompressIStream.cpp",
+ "nsMsgCompressOStream.cpp",
+ "nsMsgContentPolicy.cpp",
+ "nsMsgCopyService.cpp",
+ "nsMsgDBFolder.cpp",
+ "nsMsgDBView.cpp",
+ "nsMsgEnumerator.cpp",
+ "nsMsgFileStream.cpp",
+ "nsMsgFolderCache.cpp",
+ "nsMsgFolderCompactor.cpp",
+ "nsMsgFolderNotificationService.cpp",
+ "nsMsgGroupThread.cpp",
+ "nsMsgGroupView.cpp",
+ "nsMsgI18N.cpp",
+ "nsMsgIdentity.cpp",
+ "nsMsgIncomingServer.cpp",
+ "nsMsgKeySet.cpp",
+ "nsMsgLineBuffer.cpp",
+ "nsMsgMailNewsUrl.cpp",
+ "nsMsgMailSession.cpp",
+ "nsMsgOfflineManager.cpp",
+ "nsMsgProgress.cpp",
+ "nsMsgProtocol.cpp",
+ "nsMsgPurgeService.cpp",
+ "nsMsgQuickSearchDBView.cpp",
+ "nsMsgReadStateTxn.cpp",
+ "nsMsgSearchDBView.cpp",
+ "nsMsgSpecialViews.cpp",
+ "nsMsgStatusFeedback.cpp",
+ "nsMsgTagService.cpp",
+ "nsMsgThreadedDBView.cpp",
+ "nsMsgTxn.cpp",
+ "nsMsgUtils.cpp",
+ "nsMsgWindow.cpp",
+ "nsMsgXFViewThread.cpp",
+ "nsMsgXFVirtualFolderDBView.cpp",
+ "nsNewMailnewsURI.cpp",
+ "nsQuarantinedOutputStream.cpp",
+ "nsSpamSettings.cpp",
+ "nsStatusBarBiffManager.cpp",
+ "nsStopwatch.cpp",
+ "nsSubscribableServer.cpp",
+ "UrlListener.cpp",
+]
+
+if CONFIG["OS_ARCH"] == "WINNT":
+ SOURCES += [
+ "nsMessengerWinIntegration.cpp",
+ # This file cannot be built in unified mode because of name clashes with Windows headers.
+ "nsUserInfoWin.cpp",
+ ]
+elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "gtk":
+ SOURCES += [
+ "nsMessengerUnixIntegration.cpp",
+ "nsUserInfoUnix.cpp",
+ ]
+elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "cocoa":
+ SOURCES += [
+ "nsMessengerOSXIntegration.mm",
+ "nsUserInfoMac.mm",
+ ]
+
+EXTRA_JS_MODULES += [
+ "ABQueryUtils.jsm",
+ "converterWorker.js",
+ "FolderLookupService.jsm",
+ "FolderUtils.jsm",
+ "hostnameUtils.jsm",
+ "JXON.jsm",
+ "LineReader.jsm",
+ "MailAuthenticator.jsm",
+ "MailChannel.sys.mjs",
+ "MailCryptoUtils.jsm",
+ "MailnewsMigrator.jsm",
+ "MailNotificationManager.jsm",
+ "MailNotificationService.jsm",
+ "MailServices.jsm",
+ "mailstoreConverter.jsm",
+ "MailStringUtils.jsm",
+ "MsgAsyncPrompter.jsm",
+ "MsgDBCacheManager.jsm",
+ "MsgIncomingServer.jsm",
+ "MsgKeySet.jsm",
+ "MsgProtocolInfo.sys.mjs",
+ "OAuth2.jsm",
+ "OAuth2Module.jsm",
+ "OAuth2Providers.jsm",
+ "TemplateUtils.jsm",
+ "VirtualFolderWrapper.jsm",
+ "WinUnreadBadge.jsm",
+]
+
+USE_LIBS += [
+ "jsoncpp",
+]
+
+LOCAL_INCLUDES += [
+ "/dom/base",
+ "/netwerk/base",
+ "/toolkit/components/jsoncpp/include",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "mail"
+
+XPCOM_MANIFESTS += [
+ "components.conf",
+]
diff --git a/comm/mailnews/base/src/nsCidProtocolHandler.cpp b/comm/mailnews/base/src/nsCidProtocolHandler.cpp
new file mode 100644
index 0000000000..50f14bd782
--- /dev/null
+++ b/comm/mailnews/base/src/nsCidProtocolHandler.cpp
@@ -0,0 +1,49 @@
+/* -*- 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 "nsCidProtocolHandler.h"
+#include "nsString.h"
+#include "nsIURI.h"
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
+#include "nsComponentManagerUtils.h"
+
+nsCidProtocolHandler::nsCidProtocolHandler() {}
+
+nsCidProtocolHandler::~nsCidProtocolHandler() {}
+
+NS_IMPL_ISUPPORTS(nsCidProtocolHandler, nsIProtocolHandler)
+
+NS_IMETHODIMP nsCidProtocolHandler::GetScheme(nsACString& aScheme) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsresult nsCidProtocolHandler::NewURI(const nsACString& aSpec,
+ const char* aOriginCharset,
+ nsIURI* aBaseURI, nsIURI** _retval) {
+ // the right fix is to use the baseSpec (or aBaseUri)
+ // and specify the cid, and then fix mime
+ // to handle that, like it does with "...&part=1.2"
+ // for now, do about blank to prevent spam
+ // from popping up annoying alerts about not implementing the cid
+ // protocol
+ nsCOMPtr<nsIURI> url;
+ nsresult rv = NS_NewURI(getter_AddRefs(url), "about:blank");
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ url.forget(_retval);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsCidProtocolHandler::NewChannel(nsIURI* aURI,
+ nsILoadInfo* aLoadInfo,
+ nsIChannel** _retval) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsCidProtocolHandler::AllowPort(int32_t port, const char* scheme,
+ bool* _retval) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
diff --git a/comm/mailnews/base/src/nsCidProtocolHandler.h b/comm/mailnews/base/src/nsCidProtocolHandler.h
new file mode 100644
index 0000000000..9d84812ee7
--- /dev/null
+++ b/comm/mailnews/base/src/nsCidProtocolHandler.h
@@ -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/. */
+
+#ifndef nsCidProtocolHandler_h__
+#define nsCidProtocolHandler_h__
+
+#include "nsCOMPtr.h"
+#include "nsIProtocolHandler.h"
+
+class nsCidProtocolHandler : public nsIProtocolHandler {
+ public:
+ nsCidProtocolHandler();
+ static nsresult NewURI(const nsACString& aSpec, const char* aOriginCharset,
+ nsIURI* aBaseURI, nsIURI** _retval);
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIPROTOCOLHANDLER
+
+ private:
+ virtual ~nsCidProtocolHandler();
+};
+
+#endif /* nsCidProtocolHandler_h__ */
diff --git a/comm/mailnews/base/src/nsCopyMessageStreamListener.cpp b/comm/mailnews/base/src/nsCopyMessageStreamListener.cpp
new file mode 100644
index 0000000000..343c27ac54
--- /dev/null
+++ b/comm/mailnews/base/src/nsCopyMessageStreamListener.cpp
@@ -0,0 +1,106 @@
+/* -*- 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 "nsCopyMessageStreamListener.h"
+#include "nsIMsgMailNewsUrl.h"
+#include "nsIMsgImapMailFolder.h"
+#include "nsIChannel.h"
+#include "nsIURI.h"
+
+NS_IMPL_ISUPPORTS(nsCopyMessageStreamListener, nsIStreamListener,
+ nsIRequestObserver, nsICopyMessageStreamListener)
+
+nsCopyMessageStreamListener::nsCopyMessageStreamListener() {}
+
+nsCopyMessageStreamListener::~nsCopyMessageStreamListener() {
+ // All member variables are nsCOMPtr's.
+}
+
+NS_IMETHODIMP nsCopyMessageStreamListener::Init(
+ nsICopyMessageListener* destination) {
+ mDestination = destination;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsCopyMessageStreamListener::StartMessage() {
+ if (mDestination) {
+ return mDestination->StartMessage();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsCopyMessageStreamListener::EndMessage(nsMsgKey key) {
+ if (mDestination) {
+ return mDestination->EndMessage(key);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsCopyMessageStreamListener::OnDataAvailable(
+ nsIRequest* /* request */, nsIInputStream* aIStream, uint64_t sourceOffset,
+ uint32_t aLength) {
+ return mDestination->CopyData(aIStream, aLength);
+}
+
+NS_IMETHODIMP nsCopyMessageStreamListener::OnStartRequest(nsIRequest* request) {
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIURI> uri;
+
+ // We know the request is an nsIChannel we can get a URI from, but this is
+ // probably bad form. See Bug 1528662.
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(request, &rv);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "error QI nsIRequest to nsIChannel failed");
+ if (NS_SUCCEEDED(rv)) rv = channel->GetURI(getter_AddRefs(uri));
+ if (NS_SUCCEEDED(rv)) rv = mDestination->BeginCopy();
+
+ NS_ENSURE_SUCCESS(rv, rv);
+ return rv;
+}
+
+NS_IMETHODIMP nsCopyMessageStreamListener::EndCopy(nsIURI* uri,
+ nsresult status) {
+ nsresult rv;
+ bool copySucceeded = (status == NS_BINDING_SUCCEEDED);
+ rv = mDestination->EndCopy(copySucceeded);
+ // If this is a move and we finished the copy, delete the old message.
+ bool moveMessage = false;
+
+ nsCOMPtr<nsIMsgMailNewsUrl> mailURL(do_QueryInterface(uri));
+ if (mailURL) rv = mailURL->IsUrlType(nsIMsgMailNewsUrl::eMove, &moveMessage);
+
+ if (NS_FAILED(rv)) moveMessage = false;
+
+ // OK, this is wrong if we're moving to an imap folder, for example. This
+ // really says that we were able to pull the message from the source, NOT that
+ // we were able to put it in the destination!
+ if (moveMessage) {
+ // don't do this if we're moving to an imap folder - that's handled
+ // elsewhere.
+ nsCOMPtr<nsIMsgImapMailFolder> destImap = do_QueryInterface(mDestination);
+ // if the destination is a local folder, it will handle the delete from the
+ // source in EndMove
+ if (!destImap) rv = mDestination->EndMove(copySucceeded);
+ }
+ // Even if the above actions failed we probably still want to return NS_OK.
+ // There should probably be some error dialog if either the copy or delete
+ // failed.
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsCopyMessageStreamListener::OnStopRequest(nsIRequest* request,
+ nsresult aStatus) {
+ nsresult rv;
+ // We know the request is an nsIChannel we can get a URI from, but this is
+ // probably bad form. See Bug 1528662.
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(request, &rv);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "error QI nsIRequest to nsIChannel failed");
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIURI> uri;
+ rv = channel->GetURI(getter_AddRefs(uri));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return EndCopy(uri, aStatus);
+}
diff --git a/comm/mailnews/base/src/nsCopyMessageStreamListener.h b/comm/mailnews/base/src/nsCopyMessageStreamListener.h
new file mode 100644
index 0000000000..509dcef019
--- /dev/null
+++ b/comm/mailnews/base/src/nsCopyMessageStreamListener.h
@@ -0,0 +1,29 @@
+/* -*- 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 NSCOPYMESSAGESTREAMLISTENER_H
+#define NSCOPYMESSAGESTREAMLISTENER_H
+
+#include "nsICopyMessageStreamListener.h"
+#include "nsIStreamListener.h"
+#include "nsICopyMessageListener.h"
+#include "nsCOMPtr.h"
+
+class nsCopyMessageStreamListener : public nsIStreamListener,
+ public nsICopyMessageStreamListener {
+ public:
+ nsCopyMessageStreamListener();
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSICOPYMESSAGESTREAMLISTENER
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+
+ protected:
+ virtual ~nsCopyMessageStreamListener();
+ nsCOMPtr<nsICopyMessageListener> mDestination;
+};
+
+#endif
diff --git a/comm/mailnews/base/src/nsImapMoveCoalescer.cpp b/comm/mailnews/base/src/nsImapMoveCoalescer.cpp
new file mode 100644
index 0000000000..160f204a61
--- /dev/null
+++ b/comm/mailnews/base/src/nsImapMoveCoalescer.cpp
@@ -0,0 +1,198 @@
+/* -*- 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 "msgCore.h"
+#include "nsImapMoveCoalescer.h"
+#include "nsIImapService.h"
+#include "nsIMsgCopyService.h"
+#include "nsIMsgFolder.h" // TO include biffState enum. Change to bool later...
+#include "nsMsgFolderFlags.h"
+#include "nsIMsgHdr.h"
+#include "nsIMsgImapMailFolder.h"
+#include "nsThreadUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "mozilla/ArrayUtils.h"
+
+NS_IMPL_ISUPPORTS(nsImapMoveCoalescer, nsIUrlListener)
+
+nsImapMoveCoalescer::nsImapMoveCoalescer(nsIMsgFolder* sourceFolder,
+ nsIMsgWindow* msgWindow) {
+ m_sourceFolder = sourceFolder;
+ m_msgWindow = msgWindow;
+ m_hasPendingMoves = false;
+}
+
+nsImapMoveCoalescer::~nsImapMoveCoalescer() {}
+
+nsresult nsImapMoveCoalescer::AddMove(nsIMsgFolder* folder, nsMsgKey key) {
+ m_hasPendingMoves = true;
+ int32_t folderIndex = m_destFolders.IndexOf(folder);
+ nsTArray<nsMsgKey>* keysToAdd = nullptr;
+
+ if (folderIndex >= 0)
+ keysToAdd = &(m_sourceKeyArrays[folderIndex]);
+ else {
+ m_destFolders.AppendObject(folder);
+ keysToAdd = m_sourceKeyArrays.AppendElement();
+ if (!keysToAdd) return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ if (!keysToAdd->Contains(key)) keysToAdd->AppendElement(key);
+
+ return NS_OK;
+}
+
+nsresult nsImapMoveCoalescer::PlaybackMoves(
+ bool doNewMailNotification /* = false */) {
+ int32_t numFolders = m_destFolders.Count();
+ // Nothing to do, so don't change the member variables.
+ if (numFolders == 0) return NS_OK;
+
+ nsresult rv = NS_OK;
+ m_hasPendingMoves = false;
+ m_doNewMailNotification = doNewMailNotification;
+ m_outstandingMoves = 0;
+
+ for (int32_t i = 0; i < numFolders; ++i) {
+ // XXX TODO
+ // JUNK MAIL RELATED
+ // is this the right place to make sure dest folder exists
+ // (and has proper flags?), before we start copying?
+ nsCOMPtr<nsIMsgFolder> destFolder(m_destFolders[i]);
+ nsTArray<nsMsgKey>& keysToAdd = m_sourceKeyArrays[i];
+ int32_t numNewMessages = 0;
+ int32_t numKeysToAdd = keysToAdd.Length();
+ if (numKeysToAdd == 0) continue;
+
+ nsTArray<RefPtr<nsIMsgDBHdr>> messages(keysToAdd.Length());
+ for (uint32_t keyIndex = 0; keyIndex < keysToAdd.Length(); keyIndex++) {
+ nsCOMPtr<nsIMsgDBHdr> mailHdr = nullptr;
+ rv = m_sourceFolder->GetMessageHeader(keysToAdd.ElementAt(keyIndex),
+ getter_AddRefs(mailHdr));
+ if (NS_SUCCEEDED(rv) && mailHdr) {
+ messages.AppendElement(mailHdr);
+ bool isRead = false;
+ mailHdr->GetIsRead(&isRead);
+ if (!isRead) numNewMessages++;
+ }
+ }
+ uint32_t destFlags;
+ destFolder->GetFlags(&destFlags);
+ if (!(destFlags &
+ nsMsgFolderFlags::Junk)) // don't set has new on junk folder
+ {
+ destFolder->SetNumNewMessages(numNewMessages);
+ if (numNewMessages > 0) destFolder->SetHasNewMessages(true);
+ }
+ // adjust the new message count on the source folder
+ int32_t oldNewMessageCount = 0;
+ m_sourceFolder->GetNumNewMessages(false, &oldNewMessageCount);
+ if (oldNewMessageCount >= numKeysToAdd)
+ oldNewMessageCount -= numKeysToAdd;
+ else
+ oldNewMessageCount = 0;
+
+ m_sourceFolder->SetNumNewMessages(oldNewMessageCount);
+
+ keysToAdd.Clear();
+ nsCOMPtr<nsIMsgCopyService> copySvc =
+ do_GetService("@mozilla.org/messenger/messagecopyservice;1");
+ if (copySvc) {
+ nsCOMPtr<nsIMsgCopyServiceListener> listener;
+ if (m_doNewMailNotification) {
+ nsMoveCoalescerCopyListener* copyListener =
+ new nsMoveCoalescerCopyListener(this, destFolder);
+ if (copyListener) listener = copyListener;
+ }
+ rv = copySvc->CopyMessages(m_sourceFolder, messages, destFolder, true,
+ listener, m_msgWindow, false /*allowUndo*/);
+ if (NS_SUCCEEDED(rv)) m_outstandingMoves++;
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsImapMoveCoalescer::OnStartRunningUrl(nsIURI* aUrl) {
+ NS_ASSERTION(aUrl, "just a sanity check");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapMoveCoalescer::OnStopRunningUrl(nsIURI* aUrl, nsresult aExitCode) {
+ m_outstandingMoves--;
+ if (m_doNewMailNotification && !m_outstandingMoves) {
+ nsCOMPtr<nsIMsgImapMailFolder> imapFolder =
+ do_QueryInterface(m_sourceFolder);
+ if (imapFolder) imapFolder->NotifyIfNewMail();
+ }
+ return NS_OK;
+}
+
+nsTArray<nsMsgKey>* nsImapMoveCoalescer::GetKeyBucket(uint32_t keyArrayIndex) {
+ NS_ASSERTION(keyArrayIndex < MOZ_ARRAY_LENGTH(m_keyBuckets), "invalid index");
+
+ return keyArrayIndex < mozilla::ArrayLength(m_keyBuckets)
+ ? &(m_keyBuckets[keyArrayIndex])
+ : nullptr;
+}
+
+NS_IMPL_ISUPPORTS(nsMoveCoalescerCopyListener, nsIMsgCopyServiceListener)
+
+nsMoveCoalescerCopyListener::nsMoveCoalescerCopyListener(
+ nsImapMoveCoalescer* coalescer, nsIMsgFolder* destFolder) {
+ m_destFolder = destFolder;
+ m_coalescer = coalescer;
+}
+
+nsMoveCoalescerCopyListener::~nsMoveCoalescerCopyListener() {}
+
+NS_IMETHODIMP nsMoveCoalescerCopyListener::OnStartCopy() {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/* void OnProgress (in uint32_t aProgress, in uint32_t aProgressMax); */
+NS_IMETHODIMP nsMoveCoalescerCopyListener::OnProgress(uint32_t aProgress,
+ uint32_t aProgressMax) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/* void SetMessageKey (in uint32_t aKey); */
+NS_IMETHODIMP nsMoveCoalescerCopyListener::SetMessageKey(uint32_t aKey) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/* void GetMessageId (in nsACString aMessageId); */
+NS_IMETHODIMP nsMoveCoalescerCopyListener::GetMessageId(nsACString& messageId) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/* void OnStopCopy (in nsresult aStatus); */
+NS_IMETHODIMP nsMoveCoalescerCopyListener::OnStopCopy(nsresult aStatus) {
+ nsresult rv = NS_OK;
+ if (NS_SUCCEEDED(aStatus)) {
+ // if the dest folder is imap, update it.
+ nsCOMPtr<nsIMsgImapMailFolder> imapFolder = do_QueryInterface(m_destFolder);
+ if (imapFolder) {
+ uint32_t folderFlags;
+ m_destFolder->GetFlags(&folderFlags);
+ if (!(folderFlags & (nsMsgFolderFlags::Junk | nsMsgFolderFlags::Trash))) {
+ nsCOMPtr<nsIImapService> imapService =
+ do_GetService("@mozilla.org/messenger/imapservice;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIURI> url;
+ rv = imapService->SelectFolder(m_destFolder, m_coalescer, nullptr,
+ getter_AddRefs(url));
+ }
+ } else // give junk filters a chance to run on new msgs in destination
+ // local folder
+ {
+ bool filtersRun;
+ m_destFolder->CallFilterPlugins(nullptr, &filtersRun);
+ }
+ }
+ return rv;
+}
diff --git a/comm/mailnews/base/src/nsImapMoveCoalescer.h b/comm/mailnews/base/src/nsImapMoveCoalescer.h
new file mode 100644
index 0000000000..5471c4bd88
--- /dev/null
+++ b/comm/mailnews/base/src/nsImapMoveCoalescer.h
@@ -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/. */
+
+#ifndef _nsImapMoveCoalescer_H
+#define _nsImapMoveCoalescer_H
+
+#include "msgCore.h"
+#include "nsCOMArray.h"
+#include "nsIMsgWindow.h"
+#include "nsCOMPtr.h"
+#include "MailNewsTypes.h"
+#include "nsTArray.h"
+#include "nsIUrlListener.h"
+#include "nsIMsgCopyServiceListener.h"
+
+// imap move coalescer class - in order to keep nsImapMailFolder from growing
+// like Topsy Logically, we want to keep track of an nsTArray<nsMsgKey> per
+// nsIMsgFolder, and then be able to retrieve them one by one and play back the
+// moves. This utility class will be used by both the filter code and the
+// offline playback code, to avoid multiple moves to the same folder.
+
+class nsImapMoveCoalescer : public nsIUrlListener {
+ public:
+ friend class nsMoveCoalescerCopyListener;
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIURLLISTENER
+
+ nsImapMoveCoalescer(nsIMsgFolder* sourceFolder, nsIMsgWindow* msgWindow);
+
+ nsresult AddMove(nsIMsgFolder* folder, nsMsgKey key);
+ nsresult PlaybackMoves(bool doNewMailNotification = false);
+ // this lets the caller store keys in an arbitrary number of buckets. If the
+ // bucket for the passed in index doesn't exist, it will get created.
+ nsTArray<nsMsgKey>* GetKeyBucket(uint32_t keyArrayIndex);
+ nsIMsgWindow* GetMsgWindow() { return m_msgWindow; }
+ bool HasPendingMoves() { return m_hasPendingMoves; }
+
+ protected:
+ virtual ~nsImapMoveCoalescer();
+ // m_sourceKeyArrays and m_destFolders are parallel arrays.
+ nsTArray<nsTArray<nsMsgKey> > m_sourceKeyArrays;
+ nsCOMArray<nsIMsgFolder> m_destFolders;
+ nsCOMPtr<nsIMsgWindow> m_msgWindow;
+ nsCOMPtr<nsIMsgFolder> m_sourceFolder;
+ bool m_doNewMailNotification;
+ bool m_hasPendingMoves;
+ nsTArray<nsMsgKey> m_keyBuckets[2];
+ int32_t m_outstandingMoves;
+};
+
+class nsMoveCoalescerCopyListener final : public nsIMsgCopyServiceListener {
+ public:
+ nsMoveCoalescerCopyListener(nsImapMoveCoalescer* coalescer,
+ nsIMsgFolder* destFolder);
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMSGCOPYSERVICELISTENER
+
+ nsCOMPtr<nsIMsgFolder> m_destFolder;
+
+ nsImapMoveCoalescer* m_coalescer;
+ // when we get OnStopCopy, update the folder. When we've finished all the
+ // copies, send the biff notification.
+
+ private:
+ ~nsMoveCoalescerCopyListener();
+};
+
+#endif // _nsImapMoveCoalescer_H
diff --git a/comm/mailnews/base/src/nsMailAuthModule.cpp b/comm/mailnews/base/src/nsMailAuthModule.cpp
new file mode 100644
index 0000000000..69089737df
--- /dev/null
+++ b/comm/mailnews/base/src/nsMailAuthModule.cpp
@@ -0,0 +1,85 @@
+/* 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 "nsIAuthModule.h"
+#include "nsIMailAuthModule.h"
+#include "nsMailAuthModule.h"
+#include "nsString.h"
+#include "plbase64.h"
+
+NS_IMPL_ISUPPORTS(nsMailAuthModule, nsIMailAuthModule)
+
+nsMailAuthModule::nsMailAuthModule() {}
+
+nsMailAuthModule::~nsMailAuthModule() {}
+
+/**
+ * A simple wrap of CreateInstance and Init of nsIAuthModule.
+ */
+NS_IMETHODIMP
+nsMailAuthModule::Init(const char* type, const nsACString& serviceName,
+ uint32_t serviceFlags, const nsAString& domain,
+ const nsAString& username, const nsAString& password) {
+ mAuthModule = nsIAuthModule::CreateInstance(type);
+ return mAuthModule->Init(serviceName, serviceFlags, domain, username,
+ password);
+}
+
+/**
+ * A wrap of nsIAuthModule::GetNextToken with two extra processings:
+ * 1. inToken is base64 decoded then passed to nsIAuthModule::GetNextToken.
+ * 2. The out value of nsIAuthModule::GetNextToken is base64 encoded then
+ * assigned to outToken.
+ */
+NS_IMETHODIMP
+nsMailAuthModule::GetNextToken(const nsACString& inToken,
+ nsACString& outToken) {
+ nsresult rv;
+ void *inBuf, *outBuf;
+ uint32_t inBufLen = 0, outBufLen = 0;
+ uint32_t len = inToken.Length();
+ if (len > 0) {
+ // Decode into the input buffer.
+ inBufLen = (len * 3) / 4; // sufficient size (see plbase64.h)
+ inBuf = moz_xmalloc(inBufLen);
+
+ // Strip off any padding (see bug 230351).
+ char* challenge = ToNewCString(inToken);
+ while (challenge[len - 1] == '=') len--;
+
+ // We need to know the exact length of the decoded string to give to
+ // the GSSAPI libraries. But NSPR's base64 routine doesn't seem capable
+ // of telling us that. So, we figure it out for ourselves.
+
+ // For every 4 characters, add 3 to the destination
+ // If there are 3 remaining, add 2
+ // If there are 2 remaining, add 1
+ // 1 remaining is an error
+ inBufLen =
+ (len / 4) * 3 + ((len % 4 == 3) ? 2 : 0) + ((len % 4 == 2) ? 1 : 0);
+ PL_Base64Decode(challenge, len, (char*)inBuf);
+ free(challenge);
+ } else {
+ inBufLen = 0;
+ inBuf = NULL;
+ }
+
+ rv = mAuthModule->GetNextToken(inBuf, inBufLen, &outBuf, &outBufLen);
+ free(inBuf);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // It's not an error if outBuf is empty, return an empty string as reply to
+ // the server.
+ if (outBuf) {
+ char* base64Str = PL_Base64Encode((char*)outBuf, outBufLen, nullptr);
+ if (base64Str) {
+ outToken.Adopt(base64Str);
+ } else {
+ rv = NS_ERROR_OUT_OF_MEMORY;
+ }
+ free(outBuf);
+ }
+
+ return rv;
+}
diff --git a/comm/mailnews/base/src/nsMailAuthModule.h b/comm/mailnews/base/src/nsMailAuthModule.h
new file mode 100644
index 0000000000..e6bca30623
--- /dev/null
+++ b/comm/mailnews/base/src/nsMailAuthModule.h
@@ -0,0 +1,27 @@
+/* -*- 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 nsMailAuthModule_h__
+#define nsMailAuthModule_h__
+
+#include "nsCOMPtr.h"
+#include "nsIAuthModule.h"
+#include "nsIMailAuthModule.h"
+
+class nsMailAuthModule : public nsIMailAuthModule {
+ public:
+ nsMailAuthModule();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMAILAUTHMODULE
+
+ protected:
+ virtual ~nsMailAuthModule();
+
+ private:
+ nsCOMPtr<nsIAuthModule> mAuthModule;
+};
+
+#endif /* nsMailAuthModule_h__ */
diff --git a/comm/mailnews/base/src/nsMailChannel.cpp b/comm/mailnews/base/src/nsMailChannel.cpp
new file mode 100644
index 0000000000..1e427718e0
--- /dev/null
+++ b/comm/mailnews/base/src/nsMailChannel.cpp
@@ -0,0 +1,139 @@
+/* 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 "nsMailChannel.h"
+#include "nsHashPropertyBag.h"
+#include "nsServiceManagerUtils.h"
+#include "nsICharsetConverterManager.h"
+
+NS_IMETHODIMP
+nsMailChannel::AddHeaderFromMIME(const nsACString& name,
+ const nsACString& value) {
+ mHeaderNames.AppendElement(name);
+ mHeaderValues.AppendElement(value);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMailChannel::GetHeaderNames(nsTArray<nsCString>& aHeaderNames) {
+ aHeaderNames = mHeaderNames.Clone();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMailChannel::GetHeaderValues(nsTArray<nsCString>& aHeaderValues) {
+ aHeaderValues = mHeaderValues.Clone();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMailChannel::HandleAttachmentFromMIME(const nsACString& contentType,
+ const nsACString& url,
+ const nsACString& displayName,
+ const nsACString& uri,
+ bool notDownloaded) {
+ RefPtr<nsIWritablePropertyBag2> attachment = new nsHashPropertyBag();
+ attachment->SetPropertyAsAUTF8String(u"contentType"_ns, contentType);
+ attachment->SetPropertyAsAUTF8String(u"url"_ns, url);
+ attachment->SetPropertyAsAUTF8String(u"displayName"_ns, displayName);
+ attachment->SetPropertyAsAUTF8String(u"uri"_ns, uri);
+ attachment->SetPropertyAsBool(u"notDownloaded"_ns, notDownloaded);
+ mAttachments.AppendElement(attachment);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMailChannel::AddAttachmentFieldFromMIME(const nsACString& field,
+ const nsACString& value) {
+ nsIWritablePropertyBag2* attachment = mAttachments.LastElement();
+ attachment->SetPropertyAsAUTF8String(NS_ConvertUTF8toUTF16(nsCString(field)),
+ value);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMailChannel::GetAttachments(
+ nsTArray<RefPtr<nsIPropertyBag2> >& aAttachments) {
+ aAttachments.Clear();
+ for (nsIWritablePropertyBag2* attachment : mAttachments) {
+ aAttachments.AppendElement(static_cast<nsIPropertyBag2*>(attachment));
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMailChannel::GetMailCharacterSet(nsACString& aMailCharacterSet) {
+ aMailCharacterSet = mMailCharacterSet;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMailChannel::SetMailCharacterSet(const nsACString& aMailCharacterSet) {
+ mMailCharacterSet = aMailCharacterSet;
+
+ // Convert to a canonical charset name instead of using the charset name from
+ // the message header as is. This is needed for charset menu item to have a
+ // check mark correctly.
+ nsresult rv;
+ nsCOMPtr<nsICharsetConverterManager> ccm =
+ do_GetService(NS_CHARSETCONVERTERMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return ccm->GetCharsetAlias(PromiseFlatCString(aMailCharacterSet).get(),
+ mMailCharacterSet);
+}
+
+NS_IMETHODIMP
+nsMailChannel::GetImipMethod(nsACString& aImipMethod) {
+ aImipMethod = mImipMethod;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMailChannel::SetImipMethod(const nsACString& aImipMethod) {
+ mImipMethod = aImipMethod;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMailChannel::GetImipItem(calIItipItem** aImipItem) {
+ NS_IF_ADDREF(*aImipItem = mImipItem);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMailChannel::SetImipItem(calIItipItem* aImipItem) {
+ mImipItem = aImipItem;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMailChannel::GetSmimeHeaderSink(nsIMsgSMIMEHeaderSink** aSmimeHeaderSink) {
+ NS_IF_ADDREF(*aSmimeHeaderSink = mSmimeHeaderSink);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMailChannel::SetSmimeHeaderSink(nsIMsgSMIMEHeaderSink* aSmimeHeaderSink) {
+ mSmimeHeaderSink = aSmimeHeaderSink;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMailChannel::GetListener(nsIMailProgressListener** aListener) {
+ nsCOMPtr<nsIMailProgressListener> listener = do_QueryReferent(mListener);
+ if (listener) {
+ NS_IF_ADDREF(*aListener = listener);
+ } else {
+ *aListener = nullptr;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMailChannel::SetListener(nsIMailProgressListener* aListener) {
+ nsresult rv;
+ mListener = do_GetWeakReference(aListener, &rv);
+ return rv;
+}
diff --git a/comm/mailnews/base/src/nsMailChannel.h b/comm/mailnews/base/src/nsMailChannel.h
new file mode 100644
index 0000000000..91cf59705a
--- /dev/null
+++ b/comm/mailnews/base/src/nsMailChannel.h
@@ -0,0 +1,30 @@
+/* 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 nsMailChannel_h__
+#define nsMailChannel_h__
+
+#include "nsIMailChannel.h"
+#include "nsIWritablePropertyBag2.h"
+#include "nsTArray.h"
+#include "nsTString.h"
+#include "calIItipItem.h"
+#include "nsIWeakReferenceUtils.h"
+
+class nsMailChannel : public nsIMailChannel {
+ public:
+ NS_DECL_NSIMAILCHANNEL
+
+ protected:
+ nsTArray<nsCString> mHeaderNames;
+ nsTArray<nsCString> mHeaderValues;
+ nsTArray<RefPtr<nsIWritablePropertyBag2>> mAttachments;
+ nsCString mMailCharacterSet;
+ nsCString mImipMethod;
+ nsCOMPtr<calIItipItem> mImipItem;
+ nsCOMPtr<nsIMsgSMIMEHeaderSink> mSmimeHeaderSink;
+ nsWeakPtr mListener;
+};
+
+#endif /* nsMailChannel_h__ */
diff --git a/comm/mailnews/base/src/nsMailDirProvider.cpp b/comm/mailnews/base/src/nsMailDirProvider.cpp
new file mode 100644
index 0000000000..5d092203cf
--- /dev/null
+++ b/comm/mailnews/base/src/nsMailDirProvider.cpp
@@ -0,0 +1,160 @@
+/* -*- 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 "nsMailDirProvider.h"
+#include "nsMailDirServiceDefs.h"
+#include "nsXULAppAPI.h"
+#include "nsCOMArray.h"
+#include "nsEnumeratorUtils.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsIChromeRegistry.h"
+#include "nsICategoryManager.h"
+#include "nsServiceManagerUtils.h"
+#include "nsDirectoryServiceUtils.h"
+
+#define MAIL_DIR_50_NAME "Mail"
+#define IMAP_MAIL_DIR_50_NAME "ImapMail"
+#define NEWS_DIR_50_NAME "News"
+
+nsresult nsMailDirProvider::EnsureDirectory(nsIFile* aDirectory) {
+ bool exists;
+ nsresult rv = aDirectory->Exists(&exists);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!exists) rv = aDirectory->Create(nsIFile::DIRECTORY_TYPE, 0700);
+
+ return rv;
+}
+
+NS_IMPL_ISUPPORTS(nsMailDirProvider, nsIDirectoryServiceProvider,
+ nsIDirectoryServiceProvider2)
+
+NS_IMETHODIMP
+nsMailDirProvider::GetFile(const char* aKey, bool* aPersist,
+ nsIFile** aResult) {
+ // NOTE: This function can be reentrant through the NS_GetSpecialDirectory
+ // call, so be careful not to cause infinite recursion.
+ // i.e. the check for supported files must come first.
+ const char* leafName = nullptr;
+ bool isDirectory = true;
+
+ if (!strcmp(aKey, NS_APP_MAIL_50_DIR)) {
+ leafName = MAIL_DIR_50_NAME;
+ } else if (!strcmp(aKey, NS_APP_IMAP_MAIL_50_DIR)) {
+ leafName = IMAP_MAIL_DIR_50_NAME;
+ } else if (!strcmp(aKey, NS_APP_NEWS_50_DIR)) {
+ leafName = NEWS_DIR_50_NAME;
+ } else if (!strcmp(aKey, NS_APP_MESSENGER_FOLDER_CACHE_50_FILE)) {
+ isDirectory = false;
+ leafName = "folderCache.json";
+ } else if (!strcmp(aKey, NS_APP_MESSENGER_LEGACY_FOLDER_CACHE_50_FILE)) {
+ isDirectory = false;
+ leafName = "panacea.dat";
+ } else {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIFile> parentDir;
+ nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
+ getter_AddRefs(parentDir));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIFile> file;
+ rv = parentDir->Clone(getter_AddRefs(file));
+ if (NS_FAILED(rv)) return rv;
+
+ nsDependentCString leafStr(leafName);
+ rv = file->AppendNative(leafStr);
+ if (NS_FAILED(rv)) return rv;
+
+ bool exists;
+ if (isDirectory && NS_SUCCEEDED(file->Exists(&exists)) && !exists)
+ rv = EnsureDirectory(file);
+
+ *aPersist = true;
+ file.forget(aResult);
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMailDirProvider::GetFiles(const char* aKey, nsISimpleEnumerator** aResult) {
+ if (strcmp(aKey, ISP_DIRECTORY_LIST) != 0) return NS_ERROR_FAILURE;
+
+ // The list of isp directories includes the isp directory
+ // in the current process dir (i.e. <path to thunderbird.exe>\isp and
+ // <path to thunderbird.exe>\isp\locale
+ // and isp and isp\locale for each active extension
+
+ nsCOMPtr<nsIProperties> dirSvc =
+ do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID);
+ if (!dirSvc) return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIFile> currentProcessDir;
+ nsresult rv = dirSvc->Get(NS_XPCOM_CURRENT_PROCESS_DIR, NS_GET_IID(nsIFile),
+ getter_AddRefs(currentProcessDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsISimpleEnumerator> directoryEnumerator;
+ rv = NS_NewSingletonEnumerator(getter_AddRefs(directoryEnumerator),
+ currentProcessDir);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ADDREF(*aResult = new AppendingEnumerator(directoryEnumerator));
+ return NS_SUCCESS_AGGREGATE_RESULT;
+}
+
+NS_IMETHODIMP
+nsMailDirProvider::AppendingEnumerator::HasMoreElements(bool* aResult) {
+ *aResult = mNext || mNextWithLocale ? true : false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMailDirProvider::AppendingEnumerator::GetNext(nsISupports** aResult) {
+ // Set the return value to the next directory we want to enumerate over
+ if (aResult) NS_ADDREF(*aResult = mNext);
+
+ if (mNextWithLocale) {
+ mNext = mNextWithLocale;
+ mNextWithLocale = nullptr;
+ return NS_OK;
+ }
+
+ mNext = nullptr;
+
+ // Ignore all errors
+
+ bool more;
+ while (NS_SUCCEEDED(mBase->HasMoreElements(&more)) && more) {
+ nsCOMPtr<nsISupports> nextbasesupp;
+ mBase->GetNext(getter_AddRefs(nextbasesupp));
+
+ nsCOMPtr<nsIFile> nextbase(do_QueryInterface(nextbasesupp));
+ if (!nextbase) continue;
+
+ nextbase->Clone(getter_AddRefs(mNext));
+ if (!mNext) continue;
+
+ mNext->AppendNative("isp"_ns);
+ bool exists;
+ nsresult rv = mNext->Exists(&exists);
+ if (NS_SUCCEEDED(rv) && exists) {
+ break;
+ }
+
+ mNext = nullptr;
+ }
+
+ return NS_OK;
+}
+
+nsMailDirProvider::AppendingEnumerator::AppendingEnumerator(
+ nsISimpleEnumerator* aBase)
+ : mBase(aBase) {
+ // Initialize mNext to begin
+ GetNext(nullptr);
+}
diff --git a/comm/mailnews/base/src/nsMailDirProvider.h b/comm/mailnews/base/src/nsMailDirProvider.h
new file mode 100644
index 0000000000..e67c87a9e3
--- /dev/null
+++ b/comm/mailnews/base/src/nsMailDirProvider.h
@@ -0,0 +1,42 @@
+/* -*- 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 nsMailDirProvider_h__
+#define nsMailDirProvider_h__
+
+#include "nsIDirectoryService.h"
+#include "nsSimpleEnumerator.h"
+#include "nsString.h"
+#include "nsCOMPtr.h"
+#include "nsIFile.h"
+
+class nsMailDirProvider final : public nsIDirectoryServiceProvider2 {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIDIRECTORYSERVICEPROVIDER
+ NS_DECL_NSIDIRECTORYSERVICEPROVIDER2
+
+ private:
+ ~nsMailDirProvider() {}
+
+ nsresult EnsureDirectory(nsIFile* aDirectory);
+
+ class AppendingEnumerator final : public nsSimpleEnumerator {
+ public:
+ const nsID& DefaultInterface() override { return NS_GET_IID(nsIFile); }
+
+ NS_DECL_NSISIMPLEENUMERATOR
+
+ explicit AppendingEnumerator(nsISimpleEnumerator* aBase);
+
+ private:
+ ~AppendingEnumerator() override = default;
+ nsCOMPtr<nsISimpleEnumerator> mBase;
+ nsCOMPtr<nsIFile> mNext;
+ nsCOMPtr<nsIFile> mNextWithLocale;
+ };
+};
+
+#endif // nsMailDirProvider_h__
diff --git a/comm/mailnews/base/src/nsMailDirServiceDefs.h b/comm/mailnews/base/src/nsMailDirServiceDefs.h
new file mode 100644
index 0000000000..3753c96782
--- /dev/null
+++ b/comm/mailnews/base/src/nsMailDirServiceDefs.h
@@ -0,0 +1,31 @@
+/* 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 nsMailDirectoryServiceDefs_h___
+#define nsMailDirectoryServiceDefs_h___
+
+//=============================================================================
+//
+// Defines property names for directories available from the mail-specific
+// nsMailDirProvider.
+//
+// System and XPCOM properties are defined in nsDirectoryServiceDefs.h.
+// General application properties are defined in nsAppDirectoryServiceDefs.h.
+//
+//=============================================================================
+
+// ----------------------------------------------------------------------------
+// Files and directories that exist on a per-profile basis.
+// ----------------------------------------------------------------------------
+
+#define NS_APP_MAIL_50_DIR "MailD"
+#define NS_APP_IMAP_MAIL_50_DIR "IMapMD"
+#define NS_APP_NEWS_50_DIR "NewsD"
+
+#define NS_APP_MESSENGER_LEGACY_FOLDER_CACHE_50_FILE "MLFCaF"
+#define NS_APP_MESSENGER_FOLDER_CACHE_50_FILE "MFCaF"
+
+#define ISP_DIRECTORY_LIST "ISPDL"
+
+#endif
diff --git a/comm/mailnews/base/src/nsMessenger.cpp b/comm/mailnews/base/src/nsMessenger.cpp
new file mode 100644
index 0000000000..7fc5c05934
--- /dev/null
+++ b/comm/mailnews/base/src/nsMessenger.cpp
@@ -0,0 +1,2446 @@
+/* -*- 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 "prsystem.h"
+
+#include "nsMessenger.h"
+
+// xpcom
+#include "nsIComponentManager.h"
+#include "nsIServiceManager.h"
+#include "nsIStringStream.h"
+#include "nsLocalFile.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsQuickSort.h"
+#include "nsNativeCharsetUtils.h"
+#include "mozilla/Path.h"
+#include "mozilla/Components.h"
+#include "mozilla/dom/LoadURIOptionsBinding.h"
+
+// necko
+#include "nsMimeTypes.h"
+#include "nsIURL.h"
+#include "nsIPrompt.h"
+#include "nsIStreamListener.h"
+#include "nsIStreamConverterService.h"
+#include "nsNetUtil.h"
+#include "nsIFileURL.h"
+#include "nsIMIMEInfo.h"
+
+// gecko
+#include "nsLayoutCID.h"
+#include "nsIContentViewer.h"
+
+/* for access to docshell */
+#include "nsPIDOMWindow.h"
+#include "nsIDocShell.h"
+#include "nsIDocShellTreeItem.h"
+#include "nsIWebNavigation.h"
+#include "nsContentUtils.h"
+#include "nsDocShellLoadState.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/XULFrameElement.h"
+#include "nsFrameLoader.h"
+#include "mozilla/dom/Document.h"
+
+// mail
+#include "nsIMsgMailNewsUrl.h"
+#include "nsIMsgAccountManager.h"
+#include "nsIMsgMailSession.h"
+#include "nsIMailboxUrl.h"
+#include "nsIMsgFolder.h"
+#include "nsMsgMessageFlags.h"
+#include "nsIMsgIncomingServer.h"
+
+#include "nsIMsgMessageService.h"
+
+#include "nsIMsgHdr.h"
+// compose
+#include "nsNativeCharsetUtils.h"
+
+// draft/folders/sendlater/etc
+#include "nsIMsgCopyService.h"
+#include "nsIMsgCopyServiceListener.h"
+#include "nsIUrlListener.h"
+#include "UrlListener.h"
+
+// undo
+#include "nsITransaction.h"
+#include "nsMsgTxn.h"
+
+// charset conversions
+#include "nsIMimeConverter.h"
+
+// Save As
+#include "nsIStringBundle.h"
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+#include "nsCExternalHandlerService.h"
+#include "nsIExternalProtocolService.h"
+#include "nsIMIMEService.h"
+#include "nsITransfer.h"
+
+#define MESSENGER_SAVE_DIR_PREF_NAME "messenger.save.dir"
+#define MIMETYPE_DELETED "text/x-moz-deleted"
+#define ATTACHMENT_PERMISSION 00664
+
+//
+// Convert an nsString buffer to plain text...
+//
+#include "nsMsgUtils.h"
+#include "nsCharsetSource.h"
+#include "nsIChannel.h"
+#include "nsIOutputStream.h"
+#include "nsIPrincipal.h"
+
+#include "mozilla/dom/BrowserParent.h"
+#include "mozilla/dom/CanonicalBrowsingContext.h"
+
+#include "mozilla/NullPrincipal.h"
+#include "mozilla/dom/RemoteType.h"
+#include "nsQueryObject.h"
+#include "mozilla/JSONStringWriteFuncs.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+static void ConvertAndSanitizeFileName(const nsACString& displayName,
+ nsString& aResult) {
+ nsCString unescapedName;
+
+ /* we need to convert the UTF-8 fileName to platform specific character set.
+ The display name is in UTF-8 because it has been escaped from JS
+ */
+ MsgUnescapeString(displayName, 0, unescapedName);
+ CopyUTF8toUTF16(unescapedName, aResult);
+
+ // replace platform specific path separator and illegale characters to avoid
+ // any confusion
+ aResult.ReplaceChar(u"" FILE_PATH_SEPARATOR FILE_ILLEGAL_CHARACTERS, u'-');
+}
+
+// ***************************************************
+// jefft - this is a rather obscured class serves for Save Message As File,
+// Save Message As Template, and Save Attachment to a file
+// It's used to save out a single item. If multiple items are to be saved,
+// a nsSaveAllAttachmentsState should be set, which holds a list of items.
+//
+class nsSaveAllAttachmentsState;
+
+class nsSaveMsgListener : public nsIUrlListener,
+ public nsIMsgCopyServiceListener,
+ public nsIStreamListener,
+ public nsICancelable {
+ using PathChar = mozilla::filesystem::Path::value_type;
+
+ public:
+ nsSaveMsgListener(nsIFile* file, nsMessenger* aMessenger,
+ nsIUrlListener* aListener);
+
+ NS_DECL_ISUPPORTS
+
+ NS_DECL_NSIURLLISTENER
+ NS_DECL_NSIMSGCOPYSERVICELISTENER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSICANCELABLE
+
+ nsCOMPtr<nsIFile> m_file;
+ nsCOMPtr<nsIOutputStream> m_outputStream;
+ char m_dataBuffer[FILE_IO_BUFFER_SIZE];
+ nsCOMPtr<nsIChannel> m_channel;
+ nsCString m_templateUri;
+ RefPtr<nsMessenger> m_messenger;
+ nsSaveAllAttachmentsState* m_saveAllAttachmentsState;
+
+ // rhp: For character set handling
+ bool m_doCharsetConversion;
+ nsString m_charset;
+ enum { eUnknown, ePlainText, eHTML } m_outputFormat;
+ nsCString m_msgBuffer;
+
+ nsCString m_contentType; // used only when saving attachment
+
+ nsCOMPtr<nsITransfer> mTransfer;
+ nsCOMPtr<nsIUrlListener> mListener;
+ nsCOMPtr<nsIURI> mListenerUri;
+ int64_t mProgress;
+ int64_t mMaxProgress;
+ bool mCanceled;
+ bool mInitialized;
+ bool mUrlHasStopped;
+ bool mRequestHasStopped;
+ nsresult InitializeDownload(nsIRequest* aRequest);
+
+ private:
+ virtual ~nsSaveMsgListener();
+};
+
+// This helper class holds a list of attachments to be saved and (optionally)
+// detached. It's used by nsSaveMsgListener (which only sticks around for a
+// single item, then passes the nsSaveAllAttachmentsState along to the next
+// SaveAttachment() call).
+class nsSaveAllAttachmentsState {
+ using PathChar = mozilla::filesystem::Path::value_type;
+
+ public:
+ nsSaveAllAttachmentsState(const nsTArray<nsCString>& contentTypeArray,
+ const nsTArray<nsCString>& urlArray,
+ const nsTArray<nsCString>& displayNameArray,
+ const nsTArray<nsCString>& messageUriArray,
+ const PathChar* directoryName,
+ bool detachingAttachments,
+ nsIUrlListener* overallListener);
+ virtual ~nsSaveAllAttachmentsState();
+
+ uint32_t m_count;
+ uint32_t m_curIndex;
+ PathChar* m_directoryName;
+ nsTArray<nsCString> m_contentTypeArray;
+ nsTArray<nsCString> m_urlArray;
+ nsTArray<nsCString> m_displayNameArray;
+ nsTArray<nsCString> m_messageUriArray;
+ bool m_detachingAttachments;
+ // The listener to invoke when all the items have been saved.
+ nsCOMPtr<nsIUrlListener> m_overallListener;
+ // if detaching, do without warning? Will create unique files instead of
+ // prompting if duplicate files exist.
+ bool m_withoutWarning;
+ // if detaching first, remember where we saved to.
+ nsTArray<nsCString> m_savedFiles;
+};
+
+//
+// nsMessenger
+//
+nsMessenger::nsMessenger() {}
+
+nsMessenger::~nsMessenger() {}
+
+NS_IMPL_ISUPPORTS(nsMessenger, nsIMessenger, nsISupportsWeakReference)
+
+NS_IMETHODIMP nsMessenger::SetWindow(mozIDOMWindowProxy* aWin,
+ nsIMsgWindow* aMsgWindow) {
+ nsresult rv;
+
+ nsCOMPtr<nsIMsgMailSession> mailSession =
+ do_GetService("@mozilla.org/messenger/services/session;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (aWin) {
+ aMsgWindow->GetTransactionManager(getter_AddRefs(mTxnMgr));
+ mMsgWindow = aMsgWindow;
+ mWindow = aWin;
+
+ NS_ENSURE_TRUE(aWin, NS_ERROR_FAILURE);
+ nsCOMPtr<nsPIDOMWindowOuter> win = nsPIDOMWindowOuter::From(aWin);
+ mDocShell = win->GetDocShell();
+ } else {
+ mWindow = nullptr;
+ mDocShell = nullptr;
+ }
+
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(nsMessenger::nsFilePickerShownCallback,
+ nsIFilePickerShownCallback)
+nsMessenger::nsFilePickerShownCallback::nsFilePickerShownCallback() {
+ mResult = nsIFilePicker::returnOK;
+ mPickerDone = false;
+}
+
+NS_IMETHODIMP
+nsMessenger::nsFilePickerShownCallback::Done(
+ nsIFilePicker::ResultCode aResult) {
+ mResult = aResult;
+ mPickerDone = true;
+ return NS_OK;
+}
+
+nsresult nsMessenger::ShowPicker(nsIFilePicker* aPicker,
+ nsIFilePicker::ResultCode* aResult) {
+ nsCOMPtr<nsIFilePickerShownCallback> callback =
+ new nsMessenger::nsFilePickerShownCallback();
+ nsFilePickerShownCallback* cb =
+ static_cast<nsFilePickerShownCallback*>(callback.get());
+
+ nsresult rv;
+ rv = aPicker->Open(callback);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Spin the event loop until the callback was called.
+ nsCOMPtr<nsIThread> thread(do_GetCurrentThread());
+ while (!cb->mPickerDone) {
+ NS_ProcessNextEvent(thread, true);
+ }
+
+ *aResult = cb->mResult;
+ return NS_OK;
+}
+
+nsresult nsMessenger::PromptIfFileExists(nsIFile* file) {
+ nsresult rv = NS_ERROR_FAILURE;
+ bool exists;
+ file->Exists(&exists);
+ if (!exists) return NS_OK;
+
+ nsCOMPtr<nsIPrompt> dialog(do_GetInterface(mDocShell));
+ if (!dialog) return rv;
+ nsAutoString path;
+ bool dialogResult = false;
+ nsString errorMessage;
+
+ file->GetPath(path);
+ AutoTArray<nsString, 1> pathFormatStrings = {path};
+
+ if (!mStringBundle) {
+ rv = InitStringBundle();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ rv = mStringBundle->FormatStringFromName("fileExists", pathFormatStrings,
+ errorMessage);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = dialog->Confirm(nullptr, errorMessage.get(), &dialogResult);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (dialogResult) return NS_OK; // user says okay to replace
+
+ // if we don't re-init the path for redisplay the picker will
+ // show the full path, not just the file name
+ nsCOMPtr<nsIFile> currentFile =
+ do_CreateInstance("@mozilla.org/file/local;1");
+ if (!currentFile) return NS_ERROR_FAILURE;
+
+ rv = currentFile->InitWithPath(path);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString leafName;
+ currentFile->GetLeafName(leafName);
+ if (!leafName.IsEmpty())
+ path.Assign(leafName); // path should be a copy of leafName
+
+ nsCOMPtr<nsIFilePicker> filePicker =
+ do_CreateInstance("@mozilla.org/filepicker;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsString saveAttachmentStr;
+ GetString(u"SaveAttachment"_ns, saveAttachmentStr);
+ filePicker->Init(mWindow, saveAttachmentStr, nsIFilePicker::modeSave);
+ filePicker->SetDefaultString(path);
+ filePicker->AppendFilters(nsIFilePicker::filterAll);
+
+ nsCOMPtr<nsIFile> lastSaveDir;
+ rv = GetLastSaveDirectory(getter_AddRefs(lastSaveDir));
+ if (NS_SUCCEEDED(rv) && lastSaveDir) {
+ filePicker->SetDisplayDirectory(lastSaveDir);
+ }
+
+ nsIFilePicker::ResultCode dialogReturn;
+ rv = ShowPicker(filePicker, &dialogReturn);
+ if (NS_FAILED(rv) || dialogReturn == nsIFilePicker::returnCancel) {
+ // XXX todo
+ // don't overload the return value like this
+ // change this function to have an out boolean
+ // that we check to see if the user cancelled
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIFile> localFile;
+
+ rv = filePicker->GetFile(getter_AddRefs(localFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = SetLastSaveDirectory(localFile);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // reset the file to point to the new path
+ return file->InitWithFile(localFile);
+}
+
+NS_IMETHODIMP nsMessenger::SaveAttachmentToFile(nsIFile* aFile,
+ const nsACString& aURL,
+ const nsACString& aMessageUri,
+ const nsACString& aContentType,
+ nsIUrlListener* aListener) {
+ return SaveAttachment(aFile, aURL, aMessageUri, aContentType, nullptr,
+ aListener);
+}
+
+NS_IMETHODIMP
+nsMessenger::DetachAttachmentsWOPrompts(
+ nsIFile* aDestFolder, const nsTArray<nsCString>& aContentTypeArray,
+ const nsTArray<nsCString>& aUrlArray,
+ const nsTArray<nsCString>& aDisplayNameArray,
+ const nsTArray<nsCString>& aMessageUriArray, nsIUrlListener* aListener) {
+ NS_ENSURE_ARG_POINTER(aDestFolder);
+ MOZ_ASSERT(aContentTypeArray.Length() == aUrlArray.Length() &&
+ aUrlArray.Length() == aDisplayNameArray.Length() &&
+ aDisplayNameArray.Length() == aMessageUriArray.Length());
+
+ if (!aContentTypeArray.Length()) return NS_OK;
+ nsCOMPtr<nsIFile> attachmentDestination;
+ nsresult rv = aDestFolder->Clone(getter_AddRefs(attachmentDestination));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ PathString path = attachmentDestination->NativePath();
+
+ nsAutoString unescapedFileName;
+ ConvertAndSanitizeFileName(aDisplayNameArray[0], unescapedFileName);
+ rv = attachmentDestination->Append(unescapedFileName);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = attachmentDestination->CreateUnique(nsIFile::NORMAL_FILE_TYPE,
+ ATTACHMENT_PERMISSION);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Set up to detach the attachments once they've been saved out.
+ // NOTE: nsSaveAllAttachmentsState has a detach option, but I'd like to
+ // phase it out, so we set up a listener to call DetachAttachments()
+ // instead.
+ UrlListener* listener = new UrlListener;
+ nsSaveAllAttachmentsState* saveState = new nsSaveAllAttachmentsState(
+ aContentTypeArray, aUrlArray, aDisplayNameArray, aMessageUriArray,
+ path.get(),
+ false, // detach = false
+ listener);
+
+ // Note: saveState is kept in existence by SaveAttachment() until after
+ // the last item is saved.
+ listener->mStopFn = [saveState, self = RefPtr<nsMessenger>(this),
+ originalListener = nsCOMPtr<nsIUrlListener>(aListener)](
+ nsIURI* url, nsresult status) -> nsresult {
+ if (NS_SUCCEEDED(status)) {
+ status = self->DetachAttachments(
+ saveState->m_contentTypeArray, saveState->m_urlArray,
+ saveState->m_displayNameArray, saveState->m_messageUriArray,
+ &saveState->m_savedFiles, originalListener,
+ saveState->m_withoutWarning);
+ }
+ if (NS_FAILED(status) && originalListener) {
+ return originalListener->OnStopRunningUrl(nullptr, status);
+ }
+ return NS_OK;
+ };
+
+ // This method is used in filters, where we don't want to warn
+ saveState->m_withoutWarning = true;
+
+ rv = SaveAttachment(attachmentDestination, aUrlArray[0], aMessageUriArray[0],
+ aContentTypeArray[0], saveState, nullptr);
+ return rv;
+}
+
+// Internal helper for Saving attachments.
+// It handles a single attachment, but multiple attachments can be saved
+// by passing in an nsSaveAllAttachmentsState. In this case, SaveAttachment()
+// will be called for each attachment, and the saveState keeps track of which
+// one we're up to.
+//
+// aListener is invoked to cover this single attachment save.
+// If a saveState is used, it can also contain a nsIUrlListener which
+// will be invoked when _all_ the saves are complete.
+//
+// SaveAttachment() takes ownership of the saveState passed in.
+// If SaveAttachment() fails, then
+// saveState->m_overallListener->OnStopRunningUrl()
+// will be invoked and saveState itself will be deleted.
+//
+// Even though SaveAttachment() takes ownership of saveState,
+// nsSaveMsgListener is responsible for finally deleting it when the
+// last save operation successfully completes.
+//
+// Yes, this is convoluted. Bug 1788159 covers simplifying all this stuff.
+nsresult nsMessenger::SaveAttachment(nsIFile* aFile, const nsACString& aURL,
+ const nsACString& aMessageUri,
+ const nsACString& aContentType,
+ nsSaveAllAttachmentsState* saveState,
+ nsIUrlListener* aListener) {
+ nsCOMPtr<nsIMsgMessageService> messageService;
+ nsCOMPtr<nsIMsgMessageFetchPartService> fetchService;
+ nsAutoCString urlString;
+ nsAutoCString fullMessageUri(aMessageUri);
+
+ nsresult rv = NS_OK;
+
+ // This instance will be held onto by the listeners, and will be released once
+ // the transfer has been completed.
+ RefPtr<nsSaveMsgListener> saveListener(
+ new nsSaveMsgListener(aFile, this, aListener));
+
+ saveListener->m_contentType = aContentType;
+ if (saveState) {
+ if (saveState->m_overallListener && saveState->m_curIndex == 0) {
+ // This is the first item, so tell the caller we're starting.
+ saveState->m_overallListener->OnStartRunningUrl(nullptr);
+ }
+ saveListener->m_saveAllAttachmentsState = saveState;
+ // Record the resultant file:// URL for each saved attachment as we go
+ // along. It'll be used later if we want to also detach them from the email.
+ // Placeholder text will be inserted into the email to replace the
+ // removed attachment pointing at it's final resting place.
+ nsCOMPtr<nsIURI> outputURI;
+ rv = NS_NewFileURI(getter_AddRefs(outputURI), aFile);
+ if (NS_SUCCEEDED(rv)) {
+ nsAutoCString fileUriSpec;
+ rv = outputURI->GetSpec(fileUriSpec);
+ if NS_SUCCEEDED (rv) {
+ saveState->m_savedFiles.AppendElement(fileUriSpec);
+ }
+ }
+ }
+
+ nsCOMPtr<nsIURI> URL;
+ if (NS_SUCCEEDED(rv)) {
+ urlString = aURL;
+ // strip out ?type=application/x-message-display because it confuses libmime
+
+ int32_t typeIndex = urlString.Find("?type=application/x-message-display");
+ if (typeIndex != kNotFound) {
+ urlString.Cut(typeIndex,
+ sizeof("?type=application/x-message-display") - 1);
+ // we also need to replace the next '&' with '?'
+ int32_t firstPartIndex = urlString.FindChar('&');
+ if (firstPartIndex != kNotFound) urlString.SetCharAt('?', firstPartIndex);
+ }
+
+ urlString.ReplaceSubstring("/;section", "?section");
+ rv = NS_NewURI(getter_AddRefs(URL), urlString);
+ }
+
+ if (NS_SUCCEEDED(rv)) {
+ rv = GetMessageServiceFromURI(aMessageUri, getter_AddRefs(messageService));
+ if (NS_SUCCEEDED(rv)) {
+ fetchService = do_QueryInterface(messageService);
+ // if the message service has a fetch part service then we know we can
+ // fetch mime parts...
+ if (fetchService) {
+ int32_t partPos = urlString.FindChar('?');
+ if (partPos == kNotFound) return NS_ERROR_FAILURE;
+ fullMessageUri.Append(Substring(urlString, partPos));
+ }
+
+ nsCOMPtr<nsIStreamListener> convertedListener;
+ saveListener->QueryInterface(NS_GET_IID(nsIStreamListener),
+ getter_AddRefs(convertedListener));
+
+ nsCOMPtr<nsIURI> dummyNull;
+ if (fetchService)
+ rv = fetchService->FetchMimePart(URL, fullMessageUri, convertedListener,
+ mMsgWindow, saveListener,
+ getter_AddRefs(dummyNull));
+ else
+ rv = messageService->LoadMessage(fullMessageUri, convertedListener,
+ mMsgWindow, nullptr, false);
+ } // if we got a message service
+ } // if we created a url
+
+ if (NS_FAILED(rv)) {
+ if (saveState) {
+ // If we had a listener, make sure it sees the failure!
+ if (saveState->m_overallListener) {
+ saveState->m_overallListener->OnStopRunningUrl(nullptr, rv);
+ }
+ // Ugh. Ownership is all over the place here!
+ // Usually nsSaveMsgListener is responsible for cleaning up
+ // nsSaveAllAttachmentsState... but we're not getting
+ // that far, so have to clean it up here!
+ delete saveState;
+ saveListener->m_saveAllAttachmentsState = nullptr;
+ }
+ Alert("saveAttachmentFailed");
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMessenger::SaveAttachmentToFolder(const nsACString& contentType,
+ const nsACString& url,
+ const nsACString& displayName,
+ const nsACString& messageUri,
+ nsIFile* aDestFolder, nsIFile** aOutFile) {
+ NS_ENSURE_ARG_POINTER(aDestFolder);
+ nsresult rv;
+
+ nsCOMPtr<nsIFile> attachmentDestination;
+ rv = aDestFolder->Clone(getter_AddRefs(attachmentDestination));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsString unescapedFileName;
+ ConvertAndSanitizeFileName(displayName, unescapedFileName);
+ rv = attachmentDestination->Append(unescapedFileName);
+ NS_ENSURE_SUCCESS(rv, rv);
+#ifdef XP_MACOSX
+ rv = attachmentDestination->CreateUnique(nsIFile::NORMAL_FILE_TYPE,
+ ATTACHMENT_PERMISSION);
+ NS_ENSURE_SUCCESS(rv, rv);
+#endif
+
+ rv = SaveAttachment(attachmentDestination, url, messageUri, contentType,
+ nullptr, nullptr);
+ attachmentDestination.forget(aOutFile);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMessenger::SaveAttachment(const nsACString& aContentType,
+ const nsACString& aURL,
+ const nsACString& aDisplayName,
+ const nsACString& aMessageUri,
+ bool aIsExternalAttachment) {
+ return SaveOneAttachment(aContentType, aURL, aDisplayName, aMessageUri,
+ false);
+}
+
+nsresult nsMessenger::SaveOneAttachment(const nsACString& aContentType,
+ const nsACString& aURL,
+ const nsACString& aDisplayName,
+ const nsACString& aMessageUri,
+ bool detaching) {
+ nsresult rv = NS_ERROR_OUT_OF_MEMORY;
+ nsCOMPtr<nsIFilePicker> filePicker =
+ do_CreateInstance("@mozilla.org/filepicker;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsIFilePicker::ResultCode dialogResult;
+ nsCOMPtr<nsIFile> localFile;
+ nsCOMPtr<nsIFile> lastSaveDir;
+ nsCString filePath;
+ nsString saveAttachmentStr;
+ nsString defaultDisplayString;
+ ConvertAndSanitizeFileName(aDisplayName, defaultDisplayString);
+
+ if (detaching) {
+ GetString(u"DetachAttachment"_ns, saveAttachmentStr);
+ } else {
+ GetString(u"SaveAttachment"_ns, saveAttachmentStr);
+ }
+ filePicker->Init(mWindow, saveAttachmentStr, nsIFilePicker::modeSave);
+ filePicker->SetDefaultString(defaultDisplayString);
+
+ // Check if the attachment file name has an extension (which must not
+ // contain spaces) and set it as the default extension for the attachment.
+ int32_t extensionIndex = defaultDisplayString.RFindChar('.');
+ if (extensionIndex > 0 &&
+ defaultDisplayString.FindChar(' ', extensionIndex) == kNotFound) {
+ nsString extension;
+ extension = Substring(defaultDisplayString, extensionIndex + 1);
+ filePicker->SetDefaultExtension(extension);
+ if (!mStringBundle) {
+ rv = InitStringBundle();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ nsString filterName;
+ AutoTArray<nsString, 1> extensionParam = {extension};
+ rv = mStringBundle->FormatStringFromName("saveAsType", extensionParam,
+ filterName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ extension.InsertLiteral(u"*.", 0);
+ filePicker->AppendFilter(filterName, extension);
+ }
+
+ filePicker->AppendFilters(nsIFilePicker::filterAll);
+
+ rv = GetLastSaveDirectory(getter_AddRefs(lastSaveDir));
+ if (NS_SUCCEEDED(rv) && lastSaveDir)
+ filePicker->SetDisplayDirectory(lastSaveDir);
+
+ rv = ShowPicker(filePicker, &dialogResult);
+ if (NS_FAILED(rv) || dialogResult == nsIFilePicker::returnCancel) return rv;
+
+ rv = filePicker->GetFile(getter_AddRefs(localFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ SetLastSaveDirectory(localFile);
+
+ PathString dirName = localFile->NativePath();
+
+ AutoTArray<nsCString, 1> contentTypeArray = {
+ PromiseFlatCString(aContentType)};
+ AutoTArray<nsCString, 1> urlArray = {PromiseFlatCString(aURL)};
+ AutoTArray<nsCString, 1> displayNameArray = {
+ PromiseFlatCString(aDisplayName)};
+ AutoTArray<nsCString, 1> messageUriArray = {PromiseFlatCString(aMessageUri)};
+ nsSaveAllAttachmentsState* saveState = new nsSaveAllAttachmentsState(
+ contentTypeArray, urlArray, displayNameArray, messageUriArray,
+ dirName.get(), detaching, nullptr);
+
+ // SaveAttachment takes ownership of saveState.
+ return SaveAttachment(localFile, aURL, aMessageUri, aContentType, saveState,
+ nullptr);
+}
+
+NS_IMETHODIMP
+nsMessenger::SaveAllAttachments(const nsTArray<nsCString>& contentTypeArray,
+ const nsTArray<nsCString>& urlArray,
+ const nsTArray<nsCString>& displayNameArray,
+ const nsTArray<nsCString>& messageUriArray) {
+ uint32_t len = contentTypeArray.Length();
+ NS_ENSURE_TRUE(urlArray.Length() == len, NS_ERROR_INVALID_ARG);
+ NS_ENSURE_TRUE(displayNameArray.Length() == len, NS_ERROR_INVALID_ARG);
+ NS_ENSURE_TRUE(messageUriArray.Length() == len, NS_ERROR_INVALID_ARG);
+ if (len == 0) {
+ return NS_OK;
+ }
+ return SaveAllAttachments(contentTypeArray, urlArray, displayNameArray,
+ messageUriArray, false);
+}
+
+nsresult nsMessenger::SaveAllAttachments(
+ const nsTArray<nsCString>& contentTypeArray,
+ const nsTArray<nsCString>& urlArray,
+ const nsTArray<nsCString>& displayNameArray,
+ const nsTArray<nsCString>& messageUriArray, bool detaching) {
+ nsresult rv = NS_ERROR_OUT_OF_MEMORY;
+ nsCOMPtr<nsIFilePicker> filePicker =
+ do_CreateInstance("@mozilla.org/filepicker;1", &rv);
+ nsCOMPtr<nsIFile> localFile;
+ nsCOMPtr<nsIFile> lastSaveDir;
+ nsIFilePicker::ResultCode dialogResult;
+ nsString saveAttachmentStr;
+
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (detaching) {
+ GetString(u"DetachAllAttachments"_ns, saveAttachmentStr);
+ } else {
+ GetString(u"SaveAllAttachments"_ns, saveAttachmentStr);
+ }
+ filePicker->Init(mWindow, saveAttachmentStr, nsIFilePicker::modeGetFolder);
+
+ rv = GetLastSaveDirectory(getter_AddRefs(lastSaveDir));
+ if (NS_SUCCEEDED(rv) && lastSaveDir)
+ filePicker->SetDisplayDirectory(lastSaveDir);
+
+ rv = ShowPicker(filePicker, &dialogResult);
+ if (NS_FAILED(rv) || dialogResult == nsIFilePicker::returnCancel) return rv;
+
+ rv = filePicker->GetFile(getter_AddRefs(localFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = SetLastSaveDirectory(localFile);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ PathString dirName = localFile->NativePath();
+
+ nsString unescapedName;
+ ConvertAndSanitizeFileName(displayNameArray[0], unescapedName);
+ rv = localFile->Append(unescapedName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = PromptIfFileExists(localFile);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsSaveAllAttachmentsState* saveState = new nsSaveAllAttachmentsState(
+ contentTypeArray, urlArray, displayNameArray, messageUriArray,
+ dirName.get(), detaching, nullptr);
+ // SaveAttachment takes ownership of saveState.
+ rv = SaveAttachment(localFile, urlArray[0], messageUriArray[0],
+ contentTypeArray[0], saveState, nullptr);
+ return rv;
+}
+
+enum MESSENGER_SAVEAS_FILE_TYPE {
+ EML_FILE_TYPE = 0,
+ HTML_FILE_TYPE = 1,
+ TEXT_FILE_TYPE = 2,
+ ANY_FILE_TYPE = 3
+};
+#define HTML_FILE_EXTENSION ".htm"
+#define HTML_FILE_EXTENSION2 ".html"
+#define TEXT_FILE_EXTENSION ".txt"
+
+/**
+ * Adjust the file name, removing characters from the middle of the name if
+ * the name would otherwise be too long - too long for what file systems
+ * usually support.
+ */
+nsresult nsMessenger::AdjustFileIfNameTooLong(nsIFile* aFile) {
+ NS_ENSURE_ARG_POINTER(aFile);
+ nsAutoString path;
+ nsresult rv = aFile->GetPath(path);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // Most common file systems have a max filename length of 255. On windows, the
+ // total path length is (at least for all practical purposees) limited to 255.
+ // Let's just don't allow paths longer than that elsewhere either for
+ // simplicity.
+ uint32_t MAX = 255;
+ if (path.Length() > MAX) {
+ nsAutoString leafName;
+ rv = aFile->GetLeafName(leafName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t pathLengthUpToLeaf = path.Length() - leafName.Length();
+ if (pathLengthUpToLeaf >= MAX - 8) { // want at least 8 chars for name
+ return NS_ERROR_FILE_NAME_TOO_LONG;
+ }
+ uint32_t x = MAX - pathLengthUpToLeaf; // x = max leaf size
+ nsAutoString truncatedLeaf;
+ truncatedLeaf.Append(Substring(leafName, 0, x / 2));
+ truncatedLeaf.AppendLiteral("...");
+ truncatedLeaf.Append(
+ Substring(leafName, leafName.Length() - x / 2 + 3, leafName.Length()));
+ rv = aFile->SetLeafName(truncatedLeaf);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMessenger::SaveAs(const nsACString& aURI, bool aAsFile,
+ nsIMsgIdentity* aIdentity, const nsAString& aMsgFilename,
+ bool aBypassFilePicker) {
+ nsCOMPtr<nsIMsgMessageService> messageService;
+ nsCOMPtr<nsIUrlListener> urlListener;
+ RefPtr<nsSaveMsgListener> saveListener;
+ nsCOMPtr<nsIStreamListener> convertedListener;
+ int32_t saveAsFileType = EML_FILE_TYPE;
+
+ nsresult rv = GetMessageServiceFromURI(aURI, getter_AddRefs(messageService));
+ if (NS_FAILED(rv)) goto done;
+
+ if (aAsFile) {
+ nsCOMPtr<nsIFile> saveAsFile;
+ // show the file picker if BypassFilePicker is not specified (null) or false
+ if (!aBypassFilePicker) {
+ rv = GetSaveAsFile(aMsgFilename, &saveAsFileType,
+ getter_AddRefs(saveAsFile));
+ // A null saveAsFile means that the user canceled the save as
+ if (NS_FAILED(rv) || !saveAsFile) goto done;
+ } else {
+ saveAsFile = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
+ rv = saveAsFile->InitWithPath(aMsgFilename);
+ if (NS_FAILED(rv)) goto done;
+ if (StringEndsWith(aMsgFilename,
+ NS_LITERAL_STRING_FROM_CSTRING(TEXT_FILE_EXTENSION),
+ nsCaseInsensitiveStringComparator))
+ saveAsFileType = TEXT_FILE_TYPE;
+ else if ((StringEndsWith(
+ aMsgFilename,
+ NS_LITERAL_STRING_FROM_CSTRING(HTML_FILE_EXTENSION),
+ nsCaseInsensitiveStringComparator)) ||
+ (StringEndsWith(
+ aMsgFilename,
+ NS_LITERAL_STRING_FROM_CSTRING(HTML_FILE_EXTENSION2),
+ nsCaseInsensitiveStringComparator)))
+ saveAsFileType = HTML_FILE_TYPE;
+ else
+ saveAsFileType = EML_FILE_TYPE;
+ }
+
+ rv = AdjustFileIfNameTooLong(saveAsFile);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = PromptIfFileExists(saveAsFile);
+ if (NS_FAILED(rv)) {
+ goto done;
+ }
+
+ // After saveListener goes out of scope, the listener will be owned by
+ // whoever the listener is registered with, usually a URL.
+ saveListener = new nsSaveMsgListener(saveAsFile, this, nullptr);
+ rv = saveListener->QueryInterface(NS_GET_IID(nsIUrlListener),
+ getter_AddRefs(urlListener));
+ if (NS_FAILED(rv)) goto done;
+
+ if (saveAsFileType == EML_FILE_TYPE) {
+ nsCOMPtr<nsIURI> dummyNull;
+ rv = messageService->SaveMessageToDisk(
+ aURI, saveAsFile, false, urlListener, getter_AddRefs(dummyNull), true,
+ mMsgWindow);
+ } else {
+ nsAutoCString urlString(aURI);
+
+ // we can't go RFC822 to TXT until bug #1775 is fixed
+ // so until then, do the HTML to TXT conversion in
+ // nsSaveMsgListener::OnStopRequest(), see ConvertBufToPlainText()
+ //
+ // Setup the URL for a "Save As..." Operation...
+ // For now, if this is a save as TEXT operation, then do
+ // a "printing" operation
+ if (saveAsFileType == TEXT_FILE_TYPE) {
+ saveListener->m_outputFormat = nsSaveMsgListener::ePlainText;
+ saveListener->m_doCharsetConversion = true;
+ urlString.AppendLiteral("?header=print");
+ } else {
+ saveListener->m_outputFormat = nsSaveMsgListener::eHTML;
+ saveListener->m_doCharsetConversion = false;
+ urlString.AppendLiteral("?header=saveas");
+ }
+
+ nsCOMPtr<nsIURI> url;
+ rv = NS_NewURI(getter_AddRefs(url), urlString);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "NS_NewURI failed");
+ if (NS_FAILED(rv)) goto done;
+
+ nsCOMPtr<nsIPrincipal> nullPrincipal =
+ NullPrincipal::CreateWithoutOriginAttributes();
+
+ saveListener->m_channel = nullptr;
+ rv = NS_NewInputStreamChannel(
+ getter_AddRefs(saveListener->m_channel), url, nullptr, nullPrincipal,
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "NS_NewChannel failed");
+ if (NS_FAILED(rv)) goto done;
+
+ nsCOMPtr<nsIStreamConverterService> streamConverterService =
+ do_GetService("@mozilla.org/streamConverters;1");
+ nsCOMPtr<nsISupports> channelSupport =
+ do_QueryInterface(saveListener->m_channel);
+
+ // we can't go RFC822 to TXT until bug #1775 is fixed
+ // so until then, do the HTML to TXT conversion in
+ // nsSaveMsgListener::OnStopRequest(), see ConvertBufToPlainText()
+ rv = streamConverterService->AsyncConvertData(
+ MESSAGE_RFC822, TEXT_HTML, saveListener, channelSupport,
+ getter_AddRefs(convertedListener));
+ NS_ASSERTION(NS_SUCCEEDED(rv), "AsyncConvertData failed");
+ if (NS_FAILED(rv)) goto done;
+
+ rv = messageService->LoadMessage(urlString, convertedListener, mMsgWindow,
+ nullptr, false);
+ }
+ } else {
+ // ** save as Template
+ nsCOMPtr<nsIFile> tmpFile;
+ nsresult rv = GetSpecialDirectoryWithFileName(NS_OS_TEMP_DIR, "nsmail.tmp",
+ getter_AddRefs(tmpFile));
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // For temp file, we should use restrictive 00600 instead of
+ // ATTACHMENT_PERMISSION
+ rv = tmpFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 00600);
+ if (NS_FAILED(rv)) goto done;
+
+ // The saveListener is owned by whoever we ultimately register the
+ // listener with, generally a URL.
+ saveListener = new nsSaveMsgListener(tmpFile, this, nullptr);
+
+ if (aIdentity)
+ rv = aIdentity->GetStationeryFolder(saveListener->m_templateUri);
+ if (NS_FAILED(rv)) goto done;
+
+ bool needDummyHeader =
+ StringBeginsWith(saveListener->m_templateUri, "mailbox://"_ns);
+ bool canonicalLineEnding =
+ StringBeginsWith(saveListener->m_templateUri, "imap://"_ns);
+
+ rv = saveListener->QueryInterface(NS_GET_IID(nsIUrlListener),
+ getter_AddRefs(urlListener));
+ if (NS_FAILED(rv)) goto done;
+
+ nsCOMPtr<nsIURI> dummyNull;
+ rv = messageService->SaveMessageToDisk(
+ aURI, tmpFile, needDummyHeader, urlListener, getter_AddRefs(dummyNull),
+ canonicalLineEnding, mMsgWindow);
+ }
+
+done:
+ if (NS_FAILED(rv)) {
+ Alert("saveMessageFailed");
+ }
+ return rv;
+}
+
+nsresult nsMessenger::GetSaveAsFile(const nsAString& aMsgFilename,
+ int32_t* aSaveAsFileType,
+ nsIFile** aSaveAsFile) {
+ nsresult rv;
+ nsCOMPtr<nsIFilePicker> filePicker =
+ do_CreateInstance("@mozilla.org/filepicker;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsString saveMailAsStr;
+ GetString(u"SaveMailAs"_ns, saveMailAsStr);
+ filePicker->Init(mWindow, saveMailAsStr, nsIFilePicker::modeSave);
+
+ // if we have a non-null filename use it, otherwise use default save message
+ // one
+ if (aMsgFilename.IsEmpty()) {
+ nsString saveMsgStr;
+ GetString(u"defaultSaveMessageAsFileName"_ns, saveMsgStr);
+ filePicker->SetDefaultString(saveMsgStr);
+ } else {
+ filePicker->SetDefaultString(aMsgFilename);
+ }
+
+ // because we will be using GetFilterIndex()
+ // we must call AppendFilters() one at a time,
+ // in MESSENGER_SAVEAS_FILE_TYPE order
+ nsString emlFilesStr;
+ GetString(u"EMLFiles"_ns, emlFilesStr);
+ filePicker->AppendFilter(emlFilesStr, u"*.eml"_ns);
+ filePicker->AppendFilters(nsIFilePicker::filterHTML);
+ filePicker->AppendFilters(nsIFilePicker::filterText);
+ filePicker->AppendFilters(nsIFilePicker::filterAll);
+
+ // Save as the "All Files" file type by default. We want to save as .eml by
+ // default, but the filepickers on some platforms don't switch extensions
+ // based on the file type selected (bug 508597).
+ filePicker->SetFilterIndex(ANY_FILE_TYPE);
+ // Yes, this is fine even if we ultimately save as HTML or text. On Windows,
+ // this actually is a boolean telling the file picker to automatically add
+ // the correct extension depending on the filter. On Mac or Linux this is a
+ // no-op.
+ filePicker->SetDefaultExtension(u"eml"_ns);
+
+ nsIFilePicker::ResultCode dialogResult;
+
+ nsCOMPtr<nsIFile> lastSaveDir;
+ rv = GetLastSaveDirectory(getter_AddRefs(lastSaveDir));
+ if (NS_SUCCEEDED(rv) && lastSaveDir)
+ filePicker->SetDisplayDirectory(lastSaveDir);
+
+ nsCOMPtr<nsIFile> localFile;
+ rv = ShowPicker(filePicker, &dialogResult);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (dialogResult == nsIFilePicker::returnCancel) {
+ // We'll indicate this by setting the outparam to null.
+ *aSaveAsFile = nullptr;
+ return NS_OK;
+ }
+
+ rv = filePicker->GetFile(getter_AddRefs(localFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = SetLastSaveDirectory(localFile);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t selectedSaveAsFileType;
+ rv = filePicker->GetFilterIndex(&selectedSaveAsFileType);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // If All Files was selected, look at the extension
+ if (selectedSaveAsFileType == ANY_FILE_TYPE) {
+ nsAutoString fileName;
+ rv = localFile->GetLeafName(fileName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (StringEndsWith(fileName,
+ NS_LITERAL_STRING_FROM_CSTRING(HTML_FILE_EXTENSION),
+ nsCaseInsensitiveStringComparator) ||
+ StringEndsWith(fileName,
+ NS_LITERAL_STRING_FROM_CSTRING(HTML_FILE_EXTENSION2),
+ nsCaseInsensitiveStringComparator))
+ *aSaveAsFileType = HTML_FILE_TYPE;
+ else if (StringEndsWith(fileName,
+ NS_LITERAL_STRING_FROM_CSTRING(TEXT_FILE_EXTENSION),
+ nsCaseInsensitiveStringComparator))
+ *aSaveAsFileType = TEXT_FILE_TYPE;
+ else
+ // The default is .eml
+ *aSaveAsFileType = EML_FILE_TYPE;
+ } else {
+ *aSaveAsFileType = selectedSaveAsFileType;
+ }
+
+ if (dialogResult == nsIFilePicker::returnReplace) {
+ // be extra safe and only delete when the file is really a file
+ bool isFile;
+ rv = localFile->IsFile(&isFile);
+ if (NS_SUCCEEDED(rv) && isFile) {
+ rv = localFile->Remove(false /* recursive delete */);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ // We failed, or this isn't a file. We can't do anything about it.
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ *aSaveAsFile = nullptr;
+ localFile.forget(aSaveAsFile);
+ return NS_OK;
+}
+
+/**
+ * Show a Save All dialog allowing the user to pick which folder to save
+ * messages to.
+ * @param [out] aSaveDir directory to save to. Will be null on cancel.
+ */
+nsresult nsMessenger::GetSaveToDir(nsIFile** aSaveDir) {
+ nsresult rv;
+ nsCOMPtr<nsIFilePicker> filePicker =
+ do_CreateInstance("@mozilla.org/filepicker;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsString chooseFolderStr;
+ GetString(u"ChooseFolder"_ns, chooseFolderStr);
+ filePicker->Init(mWindow, chooseFolderStr, nsIFilePicker::modeGetFolder);
+
+ nsCOMPtr<nsIFile> lastSaveDir;
+ rv = GetLastSaveDirectory(getter_AddRefs(lastSaveDir));
+ if (NS_SUCCEEDED(rv) && lastSaveDir)
+ filePicker->SetDisplayDirectory(lastSaveDir);
+
+ nsIFilePicker::ResultCode dialogResult;
+ rv = ShowPicker(filePicker, &dialogResult);
+ if (NS_FAILED(rv) || dialogResult == nsIFilePicker::returnCancel) {
+ // We'll indicate this by setting the outparam to null.
+ *aSaveDir = nullptr;
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIFile> dir;
+ rv = filePicker->GetFile(getter_AddRefs(dir));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = SetLastSaveDirectory(dir);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *aSaveDir = nullptr;
+ dir.forget(aSaveDir);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMessenger::SaveMessages(const nsTArray<nsString>& aFilenameArray,
+ const nsTArray<nsCString>& aMessageUriArray) {
+ MOZ_ASSERT(aFilenameArray.Length() == aMessageUriArray.Length());
+
+ nsresult rv;
+
+ nsCOMPtr<nsIFile> saveDir;
+ rv = GetSaveToDir(getter_AddRefs(saveDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!saveDir) // A null saveDir means that the user canceled the save.
+ return NS_OK;
+
+ for (uint32_t i = 0; i < aFilenameArray.Length(); i++) {
+ nsCOMPtr<nsIFile> saveToFile =
+ do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = saveToFile->InitWithFile(saveDir);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = saveToFile->Append(aFilenameArray[i]);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = AdjustFileIfNameTooLong(saveToFile);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = PromptIfFileExists(saveToFile);
+ if (NS_FAILED(rv)) continue;
+
+ nsCOMPtr<nsIMsgMessageService> messageService;
+ nsCOMPtr<nsIUrlListener> urlListener;
+
+ rv = GetMessageServiceFromURI(aMessageUriArray[i],
+ getter_AddRefs(messageService));
+ if (NS_FAILED(rv)) {
+ Alert("saveMessageFailed");
+ return rv;
+ }
+
+ RefPtr<nsSaveMsgListener> saveListener =
+ new nsSaveMsgListener(saveToFile, this, nullptr);
+
+ rv = saveListener->QueryInterface(NS_GET_IID(nsIUrlListener),
+ getter_AddRefs(urlListener));
+ if (NS_FAILED(rv)) {
+ Alert("saveMessageFailed");
+ return rv;
+ }
+
+ // Ok, now save the message.
+ nsCOMPtr<nsIURI> dummyNull;
+ rv = messageService->SaveMessageToDisk(
+ aMessageUriArray[i], saveToFile, false, urlListener,
+ getter_AddRefs(dummyNull), true, mMsgWindow);
+ if (NS_FAILED(rv)) {
+ Alert("saveMessageFailed");
+ return rv;
+ }
+ }
+ return rv;
+}
+
+nsresult nsMessenger::Alert(const char* stringName) {
+ nsresult rv = NS_OK;
+
+ if (mDocShell) {
+ nsCOMPtr<nsIPrompt> dialog(do_GetInterface(mDocShell));
+
+ if (dialog) {
+ nsString alertStr;
+ GetString(NS_ConvertASCIItoUTF16(stringName), alertStr);
+ rv = dialog->Alert(nullptr, alertStr.get());
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMessenger::MsgHdrFromURI(const nsACString& aUri, nsIMsgDBHdr** aMsgHdr) {
+ NS_ENSURE_ARG_POINTER(aMsgHdr);
+ nsCOMPtr<nsIMsgMessageService> msgService;
+ nsresult rv;
+
+ rv = GetMessageServiceFromURI(aUri, getter_AddRefs(msgService));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return msgService->MessageURIToMsgHdr(aUri, aMsgHdr);
+}
+
+NS_IMETHODIMP nsMessenger::GetUndoTransactionType(uint32_t* txnType) {
+ NS_ENSURE_TRUE(txnType && mTxnMgr, NS_ERROR_NULL_POINTER);
+
+ nsresult rv;
+ *txnType = nsMessenger::eUnknown;
+ nsCOMPtr<nsITransaction> txn;
+ rv = mTxnMgr->PeekUndoStack(getter_AddRefs(txn));
+ if (NS_SUCCEEDED(rv) && txn) {
+ nsCOMPtr<nsIPropertyBag2> propertyBag = do_QueryInterface(txn, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return propertyBag->GetPropertyAsUint32(u"type"_ns, txnType);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsMessenger::CanUndo(bool* bValue) {
+ NS_ENSURE_TRUE(bValue && mTxnMgr, NS_ERROR_NULL_POINTER);
+
+ nsresult rv;
+ *bValue = false;
+ int32_t count = 0;
+ rv = mTxnMgr->GetNumberOfUndoItems(&count);
+ if (NS_SUCCEEDED(rv) && count > 0) *bValue = true;
+ return rv;
+}
+
+NS_IMETHODIMP nsMessenger::GetRedoTransactionType(uint32_t* txnType) {
+ NS_ENSURE_TRUE(txnType && mTxnMgr, NS_ERROR_NULL_POINTER);
+
+ nsresult rv;
+ *txnType = nsMessenger::eUnknown;
+ nsCOMPtr<nsITransaction> txn;
+ rv = mTxnMgr->PeekRedoStack(getter_AddRefs(txn));
+ if (NS_SUCCEEDED(rv) && txn) {
+ nsCOMPtr<nsIPropertyBag2> propertyBag = do_QueryInterface(txn, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return propertyBag->GetPropertyAsUint32(u"type"_ns, txnType);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsMessenger::CanRedo(bool* bValue) {
+ NS_ENSURE_TRUE(bValue && mTxnMgr, NS_ERROR_NULL_POINTER);
+
+ nsresult rv;
+ *bValue = false;
+ int32_t count = 0;
+ rv = mTxnMgr->GetNumberOfRedoItems(&count);
+ if (NS_SUCCEEDED(rv) && count > 0) *bValue = true;
+ return rv;
+}
+
+MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHODIMP
+nsMessenger::Undo(nsIMsgWindow* msgWindow) {
+ nsresult rv = NS_OK;
+ if (mTxnMgr) {
+ int32_t numTxn = 0;
+ rv = mTxnMgr->GetNumberOfUndoItems(&numTxn);
+ if (NS_SUCCEEDED(rv) && numTxn > 0) {
+ nsCOMPtr<nsITransaction> txn;
+ rv = mTxnMgr->PeekUndoStack(getter_AddRefs(txn));
+ if (NS_SUCCEEDED(rv) && txn) {
+ static_cast<nsMsgTxn*>(static_cast<nsITransaction*>(txn.get()))
+ ->SetMsgWindow(msgWindow);
+ }
+ nsCOMPtr<nsITransactionManager> txnMgr = mTxnMgr;
+ txnMgr->UndoTransaction();
+ }
+ }
+ return rv;
+}
+
+MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHODIMP
+nsMessenger::Redo(nsIMsgWindow* msgWindow) {
+ nsresult rv = NS_OK;
+ if (mTxnMgr) {
+ int32_t numTxn = 0;
+ rv = mTxnMgr->GetNumberOfRedoItems(&numTxn);
+ if (NS_SUCCEEDED(rv) && numTxn > 0) {
+ nsCOMPtr<nsITransaction> txn;
+ rv = mTxnMgr->PeekRedoStack(getter_AddRefs(txn));
+ if (NS_SUCCEEDED(rv) && txn) {
+ static_cast<nsMsgTxn*>(static_cast<nsITransaction*>(txn.get()))
+ ->SetMsgWindow(msgWindow);
+ }
+ nsCOMPtr<nsITransactionManager> txnMgr = mTxnMgr;
+ txnMgr->RedoTransaction();
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMessenger::GetTransactionManager(nsITransactionManager** aTxnMgr) {
+ NS_ENSURE_TRUE(mTxnMgr && aTxnMgr, NS_ERROR_NULL_POINTER);
+ NS_ADDREF(*aTxnMgr = mTxnMgr);
+ return NS_OK;
+}
+
+nsSaveMsgListener::nsSaveMsgListener(nsIFile* aFile, nsMessenger* aMessenger,
+ nsIUrlListener* aListener) {
+ m_file = aFile;
+ m_messenger = aMessenger;
+ mListener = aListener;
+ mUrlHasStopped = false;
+ mRequestHasStopped = false;
+
+ // rhp: for charset handling
+ m_doCharsetConversion = false;
+ m_saveAllAttachmentsState = nullptr;
+ mProgress = 0;
+ mMaxProgress = -1;
+ mCanceled = false;
+ m_outputFormat = eUnknown;
+ mInitialized = false;
+}
+
+nsSaveMsgListener::~nsSaveMsgListener() {}
+
+//
+// nsISupports
+//
+NS_IMPL_ISUPPORTS(nsSaveMsgListener, nsIUrlListener, nsIMsgCopyServiceListener,
+ nsIStreamListener, nsIRequestObserver, nsICancelable)
+
+NS_IMETHODIMP
+nsSaveMsgListener::Cancel(nsresult status) {
+ mCanceled = true;
+ return NS_OK;
+}
+
+//
+// nsIUrlListener
+//
+NS_IMETHODIMP
+nsSaveMsgListener::OnStartRunningUrl(nsIURI* url) {
+ if (mListener) mListener->OnStartRunningUrl(url);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSaveMsgListener::OnStopRunningUrl(nsIURI* url, nsresult exitCode) {
+ nsresult rv = exitCode;
+ mUrlHasStopped = true;
+
+ // ** save as template goes here
+ if (!m_templateUri.IsEmpty()) {
+ nsCOMPtr<nsIMsgFolder> templateFolder;
+ rv = GetOrCreateFolder(m_templateUri, getter_AddRefs(templateFolder));
+ if (NS_FAILED(rv)) goto done;
+ nsCOMPtr<nsIMsgCopyService> copyService =
+ do_GetService("@mozilla.org/messenger/messagecopyservice;1");
+ if (copyService) {
+ nsCOMPtr<nsIFile> clone;
+ m_file->Clone(getter_AddRefs(clone));
+ rv = copyService->CopyFileMessage(clone, templateFolder, nullptr, true,
+ nsMsgMessageFlags::Read, EmptyCString(),
+ this, nullptr);
+ // Clear this so we don't end up in a loop if OnStopRunningUrl gets
+ // called again.
+ m_templateUri.Truncate();
+ }
+ } else if (m_outputStream && mRequestHasStopped) {
+ m_outputStream->Close();
+ m_outputStream = nullptr;
+ }
+
+done:
+ if (NS_FAILED(rv)) {
+ if (m_file) m_file->Remove(false);
+ if (m_messenger) m_messenger->Alert("saveMessageFailed");
+ }
+
+ if (mRequestHasStopped && mListener)
+ mListener->OnStopRunningUrl(url, exitCode);
+ else
+ mListenerUri = url;
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsSaveMsgListener::OnStartCopy(void) { return NS_OK; }
+
+NS_IMETHODIMP
+nsSaveMsgListener::OnProgress(uint32_t aProgress, uint32_t aProgressMax) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSaveMsgListener::SetMessageKey(nsMsgKey aKey) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsSaveMsgListener::GetMessageId(nsACString& aMessageId) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsSaveMsgListener::OnStopCopy(nsresult aStatus) {
+ if (m_file) m_file->Remove(false);
+ return aStatus;
+}
+
+// initializes the progress window if we are going to show one
+// and for OSX, sets creator flags on the output file
+nsresult nsSaveMsgListener::InitializeDownload(nsIRequest* aRequest) {
+ nsresult rv = NS_OK;
+
+ mInitialized = true;
+ nsCOMPtr<nsIChannel> channel(do_QueryInterface(aRequest));
+
+ if (!channel) return rv;
+
+ // Get the max progress from the URL if we haven't already got it.
+ if (mMaxProgress == -1) {
+ nsCOMPtr<nsIURI> uri;
+ channel->GetURI(getter_AddRefs(uri));
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl(do_QueryInterface(uri));
+ if (mailnewsUrl) mailnewsUrl->GetMaxProgress(&mMaxProgress);
+ }
+
+ if (!m_contentType.IsEmpty()) {
+ nsCOMPtr<nsIMIMEService> mimeService(
+ do_GetService(NS_MIMESERVICE_CONTRACTID));
+ nsCOMPtr<nsIMIMEInfo> mimeinfo;
+
+ mimeService->GetFromTypeAndExtension(m_contentType, EmptyCString(),
+ getter_AddRefs(mimeinfo));
+
+ // create a download progress window
+
+ // Set saveToDisk explicitly to avoid launching the saved file.
+ // See
+ // https://hg.mozilla.org/mozilla-central/file/814a6f071472/toolkit/components/jsdownloads/src/DownloadLegacy.js#l164
+ mimeinfo->SetPreferredAction(nsIHandlerInfo::saveToDisk);
+
+ // When we don't allow warnings, also don't show progress, as this
+ // is an environment (typically filters) where we don't want
+ // interruption.
+ bool allowProgress = true;
+ if (m_saveAllAttachmentsState)
+ allowProgress = !m_saveAllAttachmentsState->m_withoutWarning;
+ if (allowProgress) {
+ nsCOMPtr<nsITransfer> tr = do_CreateInstance(NS_TRANSFER_CONTRACTID, &rv);
+ if (tr && m_file) {
+ PRTime timeDownloadStarted = PR_Now();
+
+ nsCOMPtr<nsIURI> outputURI;
+ NS_NewFileURI(getter_AddRefs(outputURI), m_file);
+
+ nsCOMPtr<nsIURI> url;
+ channel->GetURI(getter_AddRefs(url));
+ rv = tr->Init(url, nullptr, outputURI, EmptyString(), mimeinfo,
+ timeDownloadStarted, nullptr, this, false,
+ nsITransfer::DOWNLOAD_ACCEPTABLE, nullptr, false);
+
+ // now store the web progresslistener
+ mTransfer = tr;
+ }
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsSaveMsgListener::OnStartRequest(nsIRequest* request) {
+ if (m_file)
+ MsgNewBufferedFileOutputStream(getter_AddRefs(m_outputStream), m_file, -1,
+ ATTACHMENT_PERMISSION);
+ if (!m_outputStream) {
+ mCanceled = true;
+ if (m_messenger) m_messenger->Alert("saveAttachmentFailed");
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSaveMsgListener::OnStopRequest(nsIRequest* request, nsresult status) {
+ nsresult rv = NS_OK;
+ mRequestHasStopped = true;
+
+ // rhp: If we are doing the charset conversion magic, this is different
+ // processing, otherwise, its just business as usual.
+ // If we need text/plain, then we need to convert the HTML and then convert
+ // to the systems charset.
+ if (m_doCharsetConversion && m_outputStream) {
+ // For HTML, code is emitted immediately in OnDataAvailable.
+ MOZ_ASSERT(m_outputFormat == ePlainText,
+ "For HTML, m_doCharsetConversion shouldn't be set");
+ NS_ConvertUTF8toUTF16 utf16Buffer(m_msgBuffer);
+ ConvertBufToPlainText(utf16Buffer, false, false, false);
+
+ nsCString outCString;
+ // NS_CopyUnicodeToNative() doesn't return an error, so we have no choice
+ // but to always use UTF-8.
+ CopyUTF16toUTF8(utf16Buffer, outCString);
+ uint32_t writeCount;
+ rv = m_outputStream->Write(outCString.get(), outCString.Length(),
+ &writeCount);
+ if (outCString.Length() != writeCount) rv = NS_ERROR_FAILURE;
+ }
+
+ if (m_outputStream) {
+ m_outputStream->Close();
+ m_outputStream = nullptr;
+ }
+
+ // Are there more attachments to deal with?
+ nsSaveAllAttachmentsState* state = m_saveAllAttachmentsState;
+ if (state) {
+ state->m_curIndex++;
+ if (!mCanceled && state->m_curIndex < state->m_count) {
+ // Yes, start on the next attachment.
+ uint32_t i = state->m_curIndex;
+ nsString unescapedName;
+ RefPtr<nsLocalFile> localFile =
+ new nsLocalFile(nsTDependentString<PathChar>(state->m_directoryName));
+ if (localFile->NativePath().IsEmpty()) {
+ rv = NS_ERROR_FAILURE;
+ goto done;
+ }
+
+ ConvertAndSanitizeFileName(state->m_displayNameArray[i], unescapedName);
+ rv = localFile->Append(unescapedName);
+ if (NS_FAILED(rv)) goto done;
+
+ // When we are running with no warnings (typically filters and other
+ // automatic uses), then don't prompt for duplicates, but create a unique
+ // file instead.
+ if (!state->m_withoutWarning) {
+ rv = m_messenger->PromptIfFileExists(localFile);
+ if (NS_FAILED(rv)) goto done;
+ } else {
+ rv = localFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE,
+ ATTACHMENT_PERMISSION);
+ if (NS_FAILED(rv)) goto done;
+ }
+ // Start the next attachment saving.
+ // NOTE: null listener passed in on subsequent saves! The original
+ // listener will already have been invoked.
+ // See Bug 1789565
+ rv = m_messenger->SaveAttachment(
+ localFile, state->m_urlArray[i], state->m_messageUriArray[i],
+ state->m_contentTypeArray[i], state, nullptr);
+ if (NS_FAILED(rv)) {
+ // If SaveAttachment() fails, state will have been deleted, and
+ // m_overallListener->OnStopRunningUrl() will have been called.
+ state = nullptr;
+ m_saveAllAttachmentsState = nullptr;
+ }
+ done:
+ if (NS_FAILED(rv) && state) {
+ if (state->m_overallListener) {
+ state->m_overallListener->OnStopRunningUrl(nullptr, rv);
+ }
+ delete state;
+ m_saveAllAttachmentsState = nullptr;
+ }
+ } else {
+ // All attachments have been saved.
+ if (state->m_overallListener) {
+ state->m_overallListener->OnStopRunningUrl(
+ nullptr, mCanceled ? NS_ERROR_FAILURE : NS_OK);
+ }
+ // Check if we're supposed to be detaching attachments after saving them.
+ if (state->m_detachingAttachments && !mCanceled) {
+ m_messenger->DetachAttachments(
+ state->m_contentTypeArray, state->m_urlArray,
+ state->m_displayNameArray, state->m_messageUriArray,
+ &state->m_savedFiles, nullptr, state->m_withoutWarning);
+ }
+ delete m_saveAllAttachmentsState;
+ m_saveAllAttachmentsState = nullptr;
+ }
+ }
+
+ if (mTransfer) {
+ mTransfer->OnProgressChange64(nullptr, nullptr, mMaxProgress, mMaxProgress,
+ mMaxProgress, mMaxProgress);
+ mTransfer->OnStateChange(nullptr, nullptr,
+ nsIWebProgressListener::STATE_STOP |
+ nsIWebProgressListener::STATE_IS_NETWORK,
+ NS_OK);
+ mTransfer = nullptr; // break any circular dependencies between the
+ // progress dialog and use
+ }
+
+ if (mUrlHasStopped && mListener)
+ mListener->OnStopRunningUrl(mListenerUri, rv);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSaveMsgListener::OnDataAvailable(nsIRequest* request,
+ nsIInputStream* inStream, uint64_t srcOffset,
+ uint32_t count) {
+ nsresult rv = NS_ERROR_FAILURE;
+ // first, check to see if we've been canceled....
+ if (mCanceled) // then go cancel our underlying channel too
+ return request->Cancel(NS_BINDING_ABORTED);
+
+ if (!mInitialized) InitializeDownload(request);
+
+ if (m_outputStream) {
+ mProgress += count;
+ uint64_t available;
+ uint32_t readCount, maxReadCount = sizeof(m_dataBuffer);
+ uint32_t writeCount;
+ rv = inStream->Available(&available);
+ while (NS_SUCCEEDED(rv) && available) {
+ if (maxReadCount > available) maxReadCount = (uint32_t)available;
+ rv = inStream->Read(m_dataBuffer, maxReadCount, &readCount);
+
+ // rhp:
+ // Ok, now we do one of two things. If we are sending out HTML, then
+ // just write it to the HTML stream as it comes along...but if this is
+ // a save as TEXT operation, we need to buffer this up for conversion
+ // when we are done. When the stream converter for HTML-TEXT gets in
+ // place, this magic can go away.
+ //
+ if (NS_SUCCEEDED(rv)) {
+ if ((m_doCharsetConversion) && (m_outputFormat == ePlainText))
+ m_msgBuffer.Append(Substring(m_dataBuffer, m_dataBuffer + readCount));
+ else
+ rv = m_outputStream->Write(m_dataBuffer, readCount, &writeCount);
+
+ available -= readCount;
+ }
+ }
+
+ if (NS_SUCCEEDED(rv) && mTransfer) // Send progress notification.
+ mTransfer->OnProgressChange64(nullptr, request, mProgress, mMaxProgress,
+ mProgress, mMaxProgress);
+ }
+ return rv;
+}
+
+#define MESSENGER_STRING_URL "chrome://messenger/locale/messenger.properties"
+
+nsresult nsMessenger::InitStringBundle() {
+ if (mStringBundle) return NS_OK;
+
+ const char propertyURL[] = MESSENGER_STRING_URL;
+ nsCOMPtr<nsIStringBundleService> sBundleService =
+ mozilla::components::StringBundle::Service();
+ NS_ENSURE_TRUE(sBundleService, NS_ERROR_UNEXPECTED);
+ return sBundleService->CreateBundle(propertyURL,
+ getter_AddRefs(mStringBundle));
+}
+
+void nsMessenger::GetString(const nsString& aStringName, nsString& aValue) {
+ nsresult rv;
+ aValue.Truncate();
+
+ if (!mStringBundle) InitStringBundle();
+
+ if (mStringBundle)
+ rv = mStringBundle->GetStringFromName(
+ NS_ConvertUTF16toUTF8(aStringName).get(), aValue);
+ else
+ rv = NS_ERROR_FAILURE;
+
+ if (NS_FAILED(rv) || aValue.IsEmpty()) aValue = aStringName;
+ return;
+}
+
+nsSaveAllAttachmentsState::nsSaveAllAttachmentsState(
+ const nsTArray<nsCString>& contentTypeArray,
+ const nsTArray<nsCString>& urlArray,
+ const nsTArray<nsCString>& displayNameArray,
+ const nsTArray<nsCString>& messageUriArray, const PathChar* dirName,
+ bool detachingAttachments, nsIUrlListener* overallListener)
+ : m_contentTypeArray(contentTypeArray.Clone()),
+ m_urlArray(urlArray.Clone()),
+ m_displayNameArray(displayNameArray.Clone()),
+ m_messageUriArray(messageUriArray.Clone()),
+ m_detachingAttachments(detachingAttachments),
+ m_overallListener(overallListener),
+ m_withoutWarning(false) {
+ m_count = contentTypeArray.Length();
+ m_curIndex = 0;
+ m_directoryName = NS_xstrdup(dirName);
+}
+
+nsSaveAllAttachmentsState::~nsSaveAllAttachmentsState() {
+ free(m_directoryName);
+}
+
+nsresult nsMessenger::GetLastSaveDirectory(nsIFile** aLastSaveDir) {
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> prefBranch =
+ do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // this can fail, and it will, on the first time we call it, as there is no
+ // default for this pref.
+ nsCOMPtr<nsIFile> localFile;
+ rv = prefBranch->GetComplexValue(MESSENGER_SAVE_DIR_PREF_NAME,
+ NS_GET_IID(nsIFile),
+ getter_AddRefs(localFile));
+ if (NS_SUCCEEDED(rv)) localFile.forget(aLastSaveDir);
+ return rv;
+}
+
+nsresult nsMessenger::SetLastSaveDirectory(nsIFile* aLocalFile) {
+ NS_ENSURE_ARG_POINTER(aLocalFile);
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> prefBranch =
+ do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // if the file is a directory, just use it for the last dir chosen
+ // otherwise, use the parent of the file as the last dir chosen.
+ // IsDirectory() will return error on saving a file, as the
+ // file doesn't exist yet.
+ bool isDirectory;
+ rv = aLocalFile->IsDirectory(&isDirectory);
+ if (NS_SUCCEEDED(rv) && isDirectory) {
+ rv = prefBranch->SetComplexValue(MESSENGER_SAVE_DIR_PREF_NAME,
+ NS_GET_IID(nsIFile), aLocalFile);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ nsCOMPtr<nsIFile> parent;
+ rv = aLocalFile->GetParent(getter_AddRefs(parent));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = prefBranch->SetComplexValue(MESSENGER_SAVE_DIR_PREF_NAME,
+ NS_GET_IID(nsIFile), parent);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMessenger::FormatFileSize(uint64_t aSize, bool aUseKB,
+ nsAString& aFormattedSize) {
+ return ::FormatFileSize(aSize, aUseKB, aFormattedSize);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Detach/Delete Attachments
+///////////////////////////////////////////////////////////////////////////////
+
+static const char* GetAttachmentPartId(const char* aAttachmentUrl) {
+ static const char partIdPrefix[] = "part=";
+ const char* partId = PL_strstr(aAttachmentUrl, partIdPrefix);
+ return partId ? (partId + sizeof(partIdPrefix) - 1) : nullptr;
+}
+
+static int CompareAttachmentPartId(const char* aAttachUrlLeft,
+ const char* aAttachUrlRight) {
+ // part ids are numbers separated by periods, like "1.2.3.4".
+ // we sort by doing a numerical comparison on each item in turn. e.g. "1.4" <
+ // "1.25" shorter entries come before longer entries. e.g. "1.4" < "1.4.1.2"
+ // return values:
+ // -2 left is a parent of right
+ // -1 left is less than right
+ // 0 left == right
+ // 1 right is greater than left
+ // 2 right is a parent of left
+
+ const char* partIdLeft = GetAttachmentPartId(aAttachUrlLeft);
+ const char* partIdRight = GetAttachmentPartId(aAttachUrlRight);
+
+ // for detached attachments the URL does not contain any "part=xx"
+ if (!partIdLeft) partIdLeft = "0";
+
+ if (!partIdRight) partIdRight = "0";
+
+ long idLeft, idRight;
+ do {
+ MOZ_ASSERT(partIdLeft && IS_DIGIT(*partIdLeft),
+ "Invalid character in part id string");
+ MOZ_ASSERT(partIdRight && IS_DIGIT(*partIdRight),
+ "Invalid character in part id string");
+
+ // if the part numbers are different then the numerically smaller one is
+ // first
+ char* fixConstLoss;
+ idLeft = strtol(partIdLeft, &fixConstLoss, 10);
+ partIdLeft = fixConstLoss;
+ idRight = strtol(partIdRight, &fixConstLoss, 10);
+ partIdRight = fixConstLoss;
+ if (idLeft != idRight) return idLeft < idRight ? -1 : 1;
+
+ // if one part id is complete but the other isn't, then the shortest one
+ // is first (parents before children)
+ if (*partIdLeft != *partIdRight) return *partIdRight ? -2 : 2;
+
+ // if both part ids are complete (*partIdLeft == *partIdRight now) then
+ // they are equal
+ if (!*partIdLeft) return 0;
+
+ MOZ_ASSERT(*partIdLeft == '.', "Invalid character in part id string");
+ MOZ_ASSERT(*partIdRight == '.', "Invalid character in part id string");
+
+ ++partIdLeft;
+ ++partIdRight;
+ } while (true);
+}
+
+// ------------------------------------
+
+// struct on purpose -> show that we don't ever want a vtable
+struct msgAttachment {
+ msgAttachment(const nsACString& aContentType, const nsACString& aUrl,
+ const nsACString& aDisplayName, const nsACString& aMessageUri)
+ : mContentType(aContentType),
+ mUrl(aUrl),
+ mDisplayName(aDisplayName),
+ mMessageUri(aMessageUri) {}
+
+ nsCString mContentType;
+ nsCString mUrl;
+ nsCString mDisplayName;
+ nsCString mMessageUri;
+};
+
+// ------------------------------------
+
+class nsAttachmentState {
+ public:
+ nsAttachmentState();
+ nsresult Init(const nsTArray<nsCString>& aContentTypeArray,
+ const nsTArray<nsCString>& aUrlArray,
+ const nsTArray<nsCString>& aDisplayNameArray,
+ const nsTArray<nsCString>& aMessageUriArray);
+ nsresult PrepareForAttachmentDelete();
+
+ private:
+ static int CompareAttachmentsByPartId(const void* aLeft, const void* aRight);
+
+ public:
+ uint32_t mCurIndex;
+ nsTArray<msgAttachment> mAttachmentArray;
+};
+
+nsAttachmentState::nsAttachmentState() : mCurIndex(0) {}
+
+nsresult nsAttachmentState::Init(const nsTArray<nsCString>& aContentTypeArray,
+ const nsTArray<nsCString>& aUrlArray,
+ const nsTArray<nsCString>& aDisplayNameArray,
+ const nsTArray<nsCString>& aMessageUriArray) {
+ MOZ_ASSERT(aContentTypeArray.Length() > 0);
+ MOZ_ASSERT(aContentTypeArray.Length() == aUrlArray.Length() &&
+ aUrlArray.Length() == aDisplayNameArray.Length() &&
+ aDisplayNameArray.Length() == aMessageUriArray.Length());
+
+ uint32_t count = aContentTypeArray.Length();
+ mCurIndex = 0;
+ mAttachmentArray.Clear();
+ mAttachmentArray.SetCapacity(count);
+
+ for (uint32_t u = 0; u < count; ++u) {
+ mAttachmentArray.AppendElement(
+ msgAttachment(aContentTypeArray[u], aUrlArray[u], aDisplayNameArray[u],
+ aMessageUriArray[u]));
+ }
+
+ return NS_OK;
+}
+
+nsresult nsAttachmentState::PrepareForAttachmentDelete() {
+ // this must be called before any processing
+ if (mCurIndex != 0) return NS_ERROR_FAILURE;
+
+ // this prepares the attachment list for use in deletion. In order to prepare,
+ // we sort the attachments in numerical ascending order on their part id,
+ // remove all duplicates and remove any subparts which will be removed
+ // automatically by the removal of the parent.
+ //
+ // e.g. the attachment list processing (showing only part ids)
+ // before: 1.11, 1.3, 1.2, 1.2.1.3, 1.4.1.2
+ // sorted: 1.2, 1.2.1.3, 1.3, 1.4.1.2, 1.11
+ // after: 1.2, 1.3, 1.4.1.2, 1.11
+
+ // sort
+ qsort(mAttachmentArray.Elements(), mAttachmentArray.Length(),
+ sizeof(msgAttachment), CompareAttachmentsByPartId);
+
+ // remove duplicates and sub-items
+ int nCompare;
+ for (uint32_t u = 1; u < mAttachmentArray.Length();) {
+ nCompare = ::CompareAttachmentPartId(mAttachmentArray[u - 1].mUrl.get(),
+ mAttachmentArray[u].mUrl.get());
+ if (nCompare == 0 ||
+ nCompare == -2) // [u-1] is the same as or a parent of [u]
+ {
+ // shuffle the array down (and thus keeping the sorted order)
+ mAttachmentArray.RemoveElementAt(u);
+ } else {
+ ++u;
+ }
+ }
+
+ return NS_OK;
+}
+
+// Static compare callback for sorting.
+int nsAttachmentState::CompareAttachmentsByPartId(const void* aLeft,
+ const void* aRight) {
+ msgAttachment& attachLeft = *((msgAttachment*)aLeft);
+ msgAttachment& attachRight = *((msgAttachment*)aRight);
+ return ::CompareAttachmentPartId(attachLeft.mUrl.get(),
+ attachRight.mUrl.get());
+}
+
+// ------------------------------------
+// Helper class to coordinate deleting attachments from a message.
+//
+// Implementation notes:
+// The basic technique is to use nsIMsgMessageService.streamMessage() to
+// stream the message through a streamconverter which is set up to strip
+// out the attachments. The result is written out to a temporary file,
+// which is then copied over the old message using
+// nsIMsgCopyService.copyFileMessage() and the old message deleted with
+// nsIMsgFolder.deleteMessages(). Phew.
+//
+// The nsIStreamListener, nsIUrlListener and nsIMsgCopyServiceListener
+// inheritances here are just unfortunately-exposed implementation details.
+// And they are a bit of a mess. Some are used multiple times, for different
+// phases of the operation. So we use m_state to keep track.
+class AttachmentDeleter : public nsIStreamListener,
+ public nsIUrlListener,
+ public nsIMsgCopyServiceListener {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSIURLLISTENER
+ NS_DECL_NSIMSGCOPYSERVICELISTENER
+
+ public:
+ AttachmentDeleter();
+ nsresult StartProcessing(nsMessenger* aMessenger, nsIMsgWindow* aMsgWindow,
+ nsAttachmentState* aAttach, bool aSaveFirst);
+
+ public:
+ nsAttachmentState* mAttach; // list of attachments to process
+ bool mSaveFirst; // detach (true) or delete (false)
+ nsCOMPtr<nsIFile> mMsgFile; // temporary file (processed mail)
+ nsCOMPtr<nsIOutputStream> mMsgFileStream; // temporary file (processed mail)
+ nsCOMPtr<nsIMsgMessageService> mMessageService; // original message service
+ nsCOMPtr<nsIMsgDBHdr> mOriginalMessage; // original message header
+ nsCOMPtr<nsIMsgFolder> mMessageFolder; // original message folder
+ nsCOMPtr<nsIMessenger> mMessenger; // our messenger instance
+ nsCOMPtr<nsIMsgWindow> mMsgWindow; // our UI window
+ nsMsgKey mOriginalMessageKey; // old message key
+ nsMsgKey mNewMessageKey; // new message key
+ uint32_t mOrigMsgFlags;
+
+ enum {
+ eStarting,
+ eCopyingNewMsg,
+ eUpdatingFolder, // for IMAP
+ eDeletingOldMessage,
+ eSelectingNewMessage
+ } m_state;
+ // temp
+ nsTArray<nsCString> mDetachedFileUris;
+
+ // The listener to invoke when the full operation is complete.
+ nsCOMPtr<nsIUrlListener> mListener;
+
+ private:
+ nsresult InternalStartProcessing(nsMessenger* aMessenger,
+ nsIMsgWindow* aMsgWindow,
+ nsAttachmentState* aAttach, bool aSaveFirst);
+ nsresult DeleteOriginalMessage();
+ virtual ~AttachmentDeleter();
+};
+
+//
+// nsISupports
+//
+NS_IMPL_ISUPPORTS(AttachmentDeleter, nsIStreamListener, nsIRequestObserver,
+ nsIUrlListener, nsIMsgCopyServiceListener)
+
+//
+// nsIRequestObserver
+//
+NS_IMETHODIMP
+AttachmentDeleter::OnStartRequest(nsIRequest* aRequest) {
+ // called when we start processing the StreamMessage request.
+ // This is called after OnStartRunningUrl().
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+AttachmentDeleter::OnStopRequest(nsIRequest* aRequest, nsresult aStatusCode) {
+ // called when we have completed processing the StreamMessage request.
+ // This is called before OnStopRunningUrl(). This means that we have now
+ // received all data of the message and we have completed processing.
+ // We now start to copy the processed message from the temporary file
+ // back into the message store, replacing the original message.
+
+ mMessageFolder->CopyDataDone();
+ if (NS_FAILED(aStatusCode)) return aStatusCode;
+
+ // copy the file back into the folder. Note: setting msgToReplace only copies
+ // metadata, so we do the delete ourselves
+ nsCOMPtr<nsIMsgCopyServiceListener> listenerCopyService;
+ nsresult rv = this->QueryInterface(NS_GET_IID(nsIMsgCopyServiceListener),
+ getter_AddRefs(listenerCopyService));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mMsgFileStream->Close();
+ mMsgFileStream = nullptr;
+ mNewMessageKey = nsMsgKey_None;
+ nsCOMPtr<nsIMsgCopyService> copyService =
+ do_GetService("@mozilla.org/messenger/messagecopyservice;1");
+ m_state = eCopyingNewMsg;
+ // clone file because nsIFile on Windows caches the wrong file size.
+ nsCOMPtr<nsIFile> clone;
+ mMsgFile->Clone(getter_AddRefs(clone));
+ if (copyService) {
+ nsCString originalKeys;
+ mOriginalMessage->GetStringProperty("keywords", originalKeys);
+ rv = copyService->CopyFileMessage(clone, mMessageFolder, mOriginalMessage,
+ false, mOrigMsgFlags, originalKeys,
+ listenerCopyService, mMsgWindow);
+ }
+ return rv;
+}
+
+//
+// nsIStreamListener
+//
+
+NS_IMETHODIMP
+AttachmentDeleter::OnDataAvailable(nsIRequest* aRequest,
+ nsIInputStream* aInStream,
+ uint64_t aSrcOffset, uint32_t aCount) {
+ if (!mMsgFileStream) return NS_ERROR_NULL_POINTER;
+ return mMessageFolder->CopyDataToOutputStreamForAppend(aInStream, aCount,
+ mMsgFileStream);
+}
+
+//
+// nsIUrlListener
+//
+
+NS_IMETHODIMP
+AttachmentDeleter::OnStartRunningUrl(nsIURI* aUrl) {
+ // called when we start processing the StreamMessage request. This is
+ // called before OnStartRequest().
+ return NS_OK;
+}
+
+nsresult AttachmentDeleter::DeleteOriginalMessage() {
+ nsCOMPtr<nsIMsgCopyServiceListener> listenerCopyService;
+ QueryInterface(NS_GET_IID(nsIMsgCopyServiceListener),
+ getter_AddRefs(listenerCopyService));
+
+ mOriginalMessage->SetUint32Property("attachmentDetached", 1);
+ RefPtr<nsIMsgDBHdr> doomed(mOriginalMessage);
+ mOriginalMessage = nullptr;
+ m_state = eDeletingOldMessage;
+ return mMessageFolder->DeleteMessages({doomed}, // messages
+ mMsgWindow, // msgWindow
+ true, // deleteStorage
+ false, // isMove
+ listenerCopyService, // listener
+ false); // allowUndo
+}
+
+// This is called (potentially) multiple times.
+// Firstly, as a result of StreamMessage() (when the message is being passed
+// through a streamconverter to strip the attachments).
+// Secondly, after the DeleteMessages() call. But maybe not for IMAP?
+// Maybe also after CopyFileMessage()? Gah.
+NS_IMETHODIMP
+AttachmentDeleter::OnStopRunningUrl(nsIURI* aUrl, nsresult aExitCode) {
+ nsresult rv = NS_OK;
+ if (mOriginalMessage && m_state == eUpdatingFolder)
+ rv = DeleteOriginalMessage();
+
+ return rv;
+}
+
+//
+// nsIMsgCopyServiceListener
+//
+
+NS_IMETHODIMP
+AttachmentDeleter::OnStartCopy(void) {
+ // never called?
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+AttachmentDeleter::OnProgress(uint32_t aProgress, uint32_t aProgressMax) {
+ // never called?
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+AttachmentDeleter::SetMessageKey(nsMsgKey aKey) {
+ // called during the copy of the modified message back into the message
+ // store to notify us of the message key of the newly created message.
+ mNewMessageKey = aKey;
+
+ nsCString folderURI;
+ nsresult rv = mMessageFolder->GetURI(folderURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mozilla::JSONStringWriteFunc<nsCString> jsonString;
+ mozilla::JSONWriter data(jsonString);
+ data.Start();
+ data.IntProperty("oldMessageKey", mOriginalMessageKey);
+ data.IntProperty("newMessageKey", aKey);
+ data.StringProperty("folderURI", folderURI);
+ data.End();
+
+ nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+ if (obs) {
+ obs->NotifyObservers(nullptr, "attachment-delete-msgkey-changed",
+ NS_ConvertUTF8toUTF16(jsonString.StringCRef()).get());
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+AttachmentDeleter::GetMessageId(nsACString& aMessageId) {
+ // never called?
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+AttachmentDeleter::OnStopCopy(nsresult aStatus) {
+ // This is called via `CopyFileMessage()` and `DeleteMessages()`.
+ // `m_state` tells us which callback it is.
+ if (m_state == eDeletingOldMessage) {
+ m_state = eSelectingNewMessage;
+
+ // OK... that's it. The entire operation is now done.
+ // (there may still be another call to OnStopRunningUrl(), but that'll be
+ // a no-op in this state).
+ if (mListener) {
+ mListener->OnStopRunningUrl(nullptr, aStatus);
+ }
+ return NS_OK;
+ }
+
+ // For non-IMAP messages, the original is deleted here, for IMAP messages
+ // that happens in `OnStopRunningUrl()` which isn't called for non-IMAP
+ // messages.
+ const nsACString& messageUri = mAttach->mAttachmentArray[0].mMessageUri;
+ if (mOriginalMessage &&
+ !Substring(messageUri, 0, 13).EqualsLiteral("imap-message:")) {
+ return DeleteOriginalMessage();
+ } else {
+ // Arrange for the message to be deleted in the next `OnStopRunningUrl()`
+ // call.
+ m_state = eUpdatingFolder;
+ }
+
+ return NS_OK;
+}
+
+//
+// local methods
+//
+
+AttachmentDeleter::AttachmentDeleter()
+ : mAttach(nullptr),
+ mSaveFirst(false),
+ mOriginalMessageKey(nsMsgKey_None),
+ mNewMessageKey(nsMsgKey_None),
+ mOrigMsgFlags(0),
+ m_state(eStarting) {}
+
+AttachmentDeleter::~AttachmentDeleter() {
+ if (mAttach) {
+ delete mAttach;
+ }
+ if (mMsgFileStream) {
+ mMsgFileStream->Close();
+ mMsgFileStream = nullptr;
+ }
+ if (mMsgFile) {
+ mMsgFile->Remove(false);
+ }
+}
+
+nsresult AttachmentDeleter::StartProcessing(nsMessenger* aMessenger,
+ nsIMsgWindow* aMsgWindow,
+ nsAttachmentState* aAttach,
+ bool detaching) {
+ if (mListener) {
+ mListener->OnStartRunningUrl(nullptr);
+ }
+
+ nsresult rv =
+ InternalStartProcessing(aMessenger, aMsgWindow, aAttach, detaching);
+ if (NS_FAILED(rv)) {
+ if (mListener) {
+ mListener->OnStopRunningUrl(nullptr, rv);
+ }
+ }
+ return rv;
+}
+
+nsresult AttachmentDeleter::InternalStartProcessing(nsMessenger* aMessenger,
+ nsIMsgWindow* aMsgWindow,
+ nsAttachmentState* aAttach,
+ bool detaching) {
+ aMessenger->QueryInterface(NS_GET_IID(nsIMessenger),
+ getter_AddRefs(mMessenger));
+ mMsgWindow = aMsgWindow;
+ mAttach = aAttach;
+
+ nsresult rv;
+
+ // all attachments refer to the same message
+ const nsCString& messageUri = mAttach->mAttachmentArray[0].mMessageUri;
+
+ // get the message service, original message and folder for this message
+ rv = GetMessageServiceFromURI(messageUri, getter_AddRefs(mMessageService));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = mMessageService->MessageURIToMsgHdr(messageUri,
+ getter_AddRefs(mOriginalMessage));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = mOriginalMessage->GetMessageKey(&mOriginalMessageKey);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = mOriginalMessage->GetFolder(getter_AddRefs(mMessageFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+ mOriginalMessage->GetFlags(&mOrigMsgFlags);
+
+ // ensure that we can store and delete messages in this folder, if we
+ // can't then we can't do attachment deleting
+ bool canDelete = false;
+ mMessageFolder->GetCanDeleteMessages(&canDelete);
+ bool canFile = false;
+ mMessageFolder->GetCanFileMessages(&canFile);
+ if (!canDelete || !canFile) return NS_ERROR_FAILURE;
+
+ // create an output stream on a temporary file. This stream will save the
+ // modified message data to a file which we will later use to replace the
+ // existing message. The file is removed in the destructor.
+ rv = GetSpecialDirectoryWithFileName(NS_OS_TEMP_DIR, "nsmail.tmp",
+ getter_AddRefs(mMsgFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // For temp file, we should use restrictive 00600 instead of
+ // ATTACHMENT_PERMISSION
+ rv = mMsgFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 00600);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = MsgNewBufferedFileOutputStream(getter_AddRefs(mMsgFileStream), mMsgFile,
+ -1, ATTACHMENT_PERMISSION);
+
+ // Create the additional header for data conversion. This will tell the stream
+ // converter which MIME emitter we want to use, and it will tell the MIME
+ // emitter which attachments should be deleted.
+ // It also supplies the path of the already-saved attachments, so that
+ // path can be noted in the message, where those attachements are removed.
+ // The X-Mozilla-External-Attachment-URL header will be added, with the
+ // location of the saved attachment.
+ const char* partId;
+ const char* nextField;
+ nsAutoCString sHeader("attach&del=");
+ nsAutoCString detachToHeader("&detachTo=");
+ for (uint32_t u = 0; u < mAttach->mAttachmentArray.Length(); ++u) {
+ if (u > 0) {
+ sHeader.Append(',');
+ if (detaching) detachToHeader.Append(',');
+ }
+ partId = GetAttachmentPartId(mAttach->mAttachmentArray[u].mUrl.get());
+ if (partId) {
+ nextField = PL_strchr(partId, '&');
+ sHeader.Append(partId, nextField ? nextField - partId : -1);
+ }
+ if (detaching) {
+ // The URI can contain commas, so percent-encode those first.
+ nsAutoCString uri(mDetachedFileUris[u]);
+ int ind = uri.FindChar(',');
+ while (ind != kNotFound) {
+ uri.Replace(ind, 1, "%2C");
+ ind = uri.FindChar(',');
+ }
+ detachToHeader.Append(uri);
+ }
+ }
+
+ if (detaching) sHeader.Append(detachToHeader);
+ // stream this message to our listener converting it via the attachment mime
+ // converter. The listener will just write the converted message straight to
+ // disk.
+ nsCOMPtr<nsISupports> listenerSupports;
+ rv = this->QueryInterface(NS_GET_IID(nsISupports),
+ getter_AddRefs(listenerSupports));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIUrlListener> listenerUrlListener =
+ do_QueryInterface(listenerSupports, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIURI> dummyNull;
+ rv = mMessageService->StreamMessage(messageUri, listenerSupports, mMsgWindow,
+ listenerUrlListener, true, sHeader, false,
+ getter_AddRefs(dummyNull));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+// ------------------------------------
+
+NS_IMETHODIMP
+nsMessenger::DetachAttachment(const nsACString& aContentType,
+ const nsACString& aURL,
+ const nsACString& aDisplayName,
+ const nsACString& aMessageUri, bool aSaveFirst,
+ bool withoutWarning = false) {
+ if (aSaveFirst)
+ return SaveOneAttachment(aContentType, aURL, aDisplayName, aMessageUri,
+ true);
+ AutoTArray<nsCString, 1> contentTypeArray = {
+ PromiseFlatCString(aContentType)};
+ AutoTArray<nsCString, 1> urlArray = {PromiseFlatCString(aURL)};
+ AutoTArray<nsCString, 1> displayNameArray = {
+ PromiseFlatCString(aDisplayName)};
+ AutoTArray<nsCString, 1> messageUriArray = {PromiseFlatCString(aMessageUri)};
+ return DetachAttachments(contentTypeArray, urlArray, displayNameArray,
+ messageUriArray, nullptr, nullptr, withoutWarning);
+}
+
+NS_IMETHODIMP
+nsMessenger::DetachAllAttachments(const nsTArray<nsCString>& aContentTypeArray,
+ const nsTArray<nsCString>& aUrlArray,
+ const nsTArray<nsCString>& aDisplayNameArray,
+ const nsTArray<nsCString>& aMessageUriArray,
+ bool aSaveFirst,
+ bool withoutWarning = false) {
+ NS_ENSURE_ARG_MIN(aContentTypeArray.Length(), 1);
+ MOZ_ASSERT(aContentTypeArray.Length() == aUrlArray.Length() &&
+ aUrlArray.Length() == aDisplayNameArray.Length() &&
+ aDisplayNameArray.Length() == aMessageUriArray.Length());
+
+ if (aSaveFirst)
+ return SaveAllAttachments(aContentTypeArray, aUrlArray, aDisplayNameArray,
+ aMessageUriArray, true);
+ else
+ return DetachAttachments(aContentTypeArray, aUrlArray, aDisplayNameArray,
+ aMessageUriArray, nullptr, nullptr,
+ withoutWarning);
+}
+
+nsresult nsMessenger::DetachAttachments(
+ const nsTArray<nsCString>& aContentTypeArray,
+ const nsTArray<nsCString>& aUrlArray,
+ const nsTArray<nsCString>& aDisplayNameArray,
+ const nsTArray<nsCString>& aMessageUriArray,
+ nsTArray<nsCString>* saveFileUris, nsIUrlListener* aListener,
+ bool withoutWarning) {
+ // if withoutWarning no dialog for user
+ if (!withoutWarning && NS_FAILED(PromptIfDeleteAttachments(
+ saveFileUris != nullptr, aDisplayNameArray)))
+ return NS_OK;
+
+ nsresult rv = NS_OK;
+
+ // ensure that our arguments are valid
+ // char * partId;
+ for (uint32_t u = 0; u < aContentTypeArray.Length(); ++u) {
+ // ensure all of the message URI are the same, we cannot process
+ // attachments from different messages
+ if (u > 0 && aMessageUriArray[0] != aMessageUriArray[u]) {
+ rv = NS_ERROR_INVALID_ARG;
+ break;
+ }
+
+ // ensure that we don't have deleted messages in this list
+ if (aContentTypeArray[u].EqualsLiteral(MIMETYPE_DELETED)) {
+ rv = NS_ERROR_INVALID_ARG;
+ break;
+ }
+
+ // for the moment we prevent any attachments other than root level
+ // attachments being deleted (i.e. you can't delete attachments from a
+ // email forwarded as an attachment). We do this by ensuring that the
+ // part id only has a single period in it (e.g. "1.2").
+ // TODO: support non-root level attachment delete
+ // partId = ::GetAttachmentPartId(aUrlArray[u]);
+ // if (!partId || PL_strchr(partId, '.') != PL_strrchr(partId, '.'))
+ // {
+ // rv = NS_ERROR_INVALID_ARG;
+ // break;
+ // }
+ }
+ if (NS_FAILED(rv)) {
+ Alert("deleteAttachmentFailure");
+ return rv;
+ }
+
+ // TODO: ensure that nothing else is processing this message uri at the same
+ // time
+
+ // TODO: if any of the selected attachments are messages that contain other
+ // attachments we need to warn the user that all sub-attachments of those
+ // messages will also be deleted. Best to display a list of them.
+
+ RefPtr<AttachmentDeleter> deleter = new AttachmentDeleter;
+ deleter->mListener = aListener;
+ if (saveFileUris) {
+ deleter->mDetachedFileUris = saveFileUris->Clone();
+ }
+ // create the attachments for use by the deleter
+ nsAttachmentState* attach = new nsAttachmentState;
+ rv = attach->Init(aContentTypeArray, aUrlArray, aDisplayNameArray,
+ aMessageUriArray);
+ if (NS_SUCCEEDED(rv)) rv = attach->PrepareForAttachmentDelete();
+ if (NS_FAILED(rv)) {
+ delete attach;
+ return rv;
+ }
+
+ // initialize our deleter with the attachments and details. The deleter
+ // takes ownership of 'attach' immediately irrespective of the return value
+ // (error or not).
+ return deleter->StartProcessing(this, mMsgWindow, attach,
+ saveFileUris != nullptr);
+}
+
+nsresult nsMessenger::PromptIfDeleteAttachments(
+ bool aSaveFirst, const nsTArray<nsCString>& aDisplayNameArray) {
+ nsresult rv = NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIPrompt> dialog(do_GetInterface(mDocShell));
+ if (!dialog) return rv;
+
+ if (!mStringBundle) {
+ rv = InitStringBundle();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // create the list of attachments we are removing
+ nsString displayString;
+ nsString attachmentList;
+ for (uint32_t u = 0; u < aDisplayNameArray.Length(); ++u) {
+ ConvertAndSanitizeFileName(aDisplayNameArray[u], displayString);
+ attachmentList.Append(displayString);
+ attachmentList.Append(char16_t('\n'));
+ }
+ AutoTArray<nsString, 1> formatStrings = {attachmentList};
+
+ // format the message and display
+ nsString promptMessage;
+ const char* propertyName =
+ aSaveFirst ? "detachAttachments" : "deleteAttachments";
+ rv = mStringBundle->FormatStringFromName(propertyName, formatStrings,
+ promptMessage);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool dialogResult = false;
+ rv = dialog->Confirm(nullptr, promptMessage.get(), &dialogResult);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return dialogResult ? NS_OK : NS_ERROR_FAILURE;
+}
diff --git a/comm/mailnews/base/src/nsMessenger.h b/comm/mailnews/base/src/nsMessenger.h
new file mode 100644
index 0000000000..b6eab3f179
--- /dev/null
+++ b/comm/mailnews/base/src/nsMessenger.h
@@ -0,0 +1,118 @@
+/* -*- 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 __nsMsgAppCore_h
+#define __nsMsgAppCore_h
+
+#include "nscore.h"
+#include "nsIMessenger.h"
+#include "nsCOMPtr.h"
+#include "nsITransactionManager.h"
+#include "nsIFile.h"
+#include "nsIDocShell.h"
+#include "nsString.h"
+#include "nsIStringBundle.h"
+#include "nsIFile.h"
+#include "nsIFilePicker.h"
+#include "nsWeakReference.h"
+#include "mozIDOMWindow.h"
+#include "nsTArray.h"
+#include "nsIMsgStatusFeedback.h"
+
+class nsSaveAllAttachmentsState;
+
+class nsMessenger : public nsIMessenger, public nsSupportsWeakReference {
+ using PathString = mozilla::PathString;
+
+ public:
+ nsMessenger();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMESSENGER
+
+ nsresult Alert(const char* stringName);
+
+ nsresult SaveAttachment(nsIFile* file, const nsACString& unescapedUrl,
+ const nsACString& messageUri,
+ const nsACString& contentType,
+ nsSaveAllAttachmentsState* saveState,
+ nsIUrlListener* aListener);
+ nsresult PromptIfFileExists(nsIFile* file);
+ nsresult DetachAttachments(const nsTArray<nsCString>& aContentTypeArray,
+ const nsTArray<nsCString>& aUrlArray,
+ const nsTArray<nsCString>& aDisplayNameArray,
+ const nsTArray<nsCString>& aMessageUriArray,
+ nsTArray<nsCString>* saveFileUris,
+ nsIUrlListener* aListener,
+ bool withoutWarning = false);
+ nsresult SaveAllAttachments(const nsTArray<nsCString>& contentTypeArray,
+ const nsTArray<nsCString>& urlArray,
+ const nsTArray<nsCString>& displayNameArray,
+ const nsTArray<nsCString>& messageUriArray,
+ bool detaching);
+ nsresult SaveOneAttachment(const nsACString& aContentType,
+ const nsACString& aURL,
+ const nsACString& aDisplayName,
+ const nsACString& aMessageUri, bool detaching);
+
+ protected:
+ virtual ~nsMessenger();
+
+ void GetString(const nsString& aStringName, nsString& stringValue);
+ nsresult InitStringBundle();
+ nsresult PromptIfDeleteAttachments(
+ bool saveFirst, const nsTArray<nsCString>& displayNameArray);
+
+ private:
+ nsresult GetLastSaveDirectory(nsIFile** aLastSaveAsDir);
+ // if aLocalFile is a dir, we use it. otherwise, we use the parent of
+ // aLocalFile.
+ nsresult SetLastSaveDirectory(nsIFile* aLocalFile);
+
+ nsresult AdjustFileIfNameTooLong(nsIFile* aFile);
+
+ nsresult GetSaveAsFile(const nsAString& aMsgFilename,
+ int32_t* aSaveAsFileType, nsIFile** aSaveAsFile);
+
+ nsresult GetSaveToDir(nsIFile** aSaveToDir);
+ nsresult ShowPicker(nsIFilePicker* aPicker,
+ nsIFilePicker::ResultCode* aResult);
+
+ class nsFilePickerShownCallback : public nsIFilePickerShownCallback {
+ virtual ~nsFilePickerShownCallback() {}
+
+ public:
+ nsFilePickerShownCallback();
+ NS_DECL_ISUPPORTS
+
+ NS_IMETHOD Done(nsIFilePicker::ResultCode aResult) override;
+
+ public:
+ bool mPickerDone;
+ nsIFilePicker::ResultCode mResult;
+ };
+
+ nsString mId;
+ nsCOMPtr<nsITransactionManager> mTxnMgr;
+
+ /* rhp - need this to drive message display */
+ nsCOMPtr<mozIDOMWindowProxy> mWindow;
+ nsCOMPtr<nsIMsgWindow> mMsgWindow;
+ nsCOMPtr<nsIDocShell> mDocShell;
+
+ // String bundles...
+ nsCOMPtr<nsIStringBundle> mStringBundle;
+
+ nsCOMPtr<nsISupports> mSearchContext;
+};
+
+#define NS_MESSENGER_CID \
+ { /* f436a174-e2c0-4955-9afe-e3feb68aee56 */ \
+ 0xf436a174, 0xe2c0, 0x4955, { \
+ 0x9a, 0xfe, 0xe3, 0xfe, 0xb6, 0x8a, 0xee, 0x56 \
+ } \
+ }
+
+#endif
diff --git a/comm/mailnews/base/src/nsMessengerBootstrap.cpp b/comm/mailnews/base/src/nsMessengerBootstrap.cpp
new file mode 100644
index 0000000000..9d01b5380d
--- /dev/null
+++ b/comm/mailnews/base/src/nsMessengerBootstrap.cpp
@@ -0,0 +1,84 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsMessengerBootstrap.h"
+#include "nsCOMPtr.h"
+
+#include "nsIMutableArray.h"
+#include "nsIMsgFolder.h"
+#include "nsIWindowWatcher.h"
+#include "nsMsgUtils.h"
+#include "nsISupportsPrimitives.h"
+#include "mozIDOMWindow.h"
+#include "nsComponentManagerUtils.h"
+#include "nsServiceManagerUtils.h"
+
+NS_IMPL_ISUPPORTS(nsMessengerBootstrap, nsIMessengerWindowService)
+
+nsMessengerBootstrap::nsMessengerBootstrap() {}
+
+nsMessengerBootstrap::~nsMessengerBootstrap() {}
+
+NS_IMETHODIMP nsMessengerBootstrap::OpenMessengerWindowWithUri(
+ const char* windowType, const nsACString& aFolderURI,
+ nsMsgKey aMessageKey) {
+ bool standAloneMsgWindow = false;
+ nsAutoCString chromeUrl("chrome://messenger/content/");
+ if (windowType && !strcmp(windowType, "mail:messageWindow")) {
+ chromeUrl.AppendLiteral("messageWindow.xhtml");
+ standAloneMsgWindow = true;
+ } else {
+ chromeUrl.AppendLiteral("messenger.xhtml");
+ }
+ nsresult rv;
+ nsCOMPtr<nsIMutableArray> argsArray(
+ do_CreateInstance(NS_ARRAY_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // create scriptable versions of our strings that we can store in our
+ // nsIMutableArray....
+ if (!aFolderURI.IsEmpty()) {
+ if (standAloneMsgWindow) {
+ nsCOMPtr<nsIMsgFolder> folder;
+ rv = GetExistingFolder(aFolderURI, getter_AddRefs(folder));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsAutoCString msgUri;
+ folder->GetBaseMessageURI(msgUri);
+
+ nsCOMPtr<nsISupportsCString> scriptableMsgURI(
+ do_CreateInstance(NS_SUPPORTS_CSTRING_CONTRACTID));
+ NS_ENSURE_TRUE(scriptableMsgURI, NS_ERROR_FAILURE);
+ msgUri.Append('#');
+ msgUri.AppendInt(aMessageKey, 10);
+ scriptableMsgURI->SetData(msgUri);
+ argsArray->AppendElement(scriptableMsgURI);
+ }
+ nsCOMPtr<nsISupportsCString> scriptableFolderURI(
+ do_CreateInstance(NS_SUPPORTS_CSTRING_CONTRACTID));
+ NS_ENSURE_TRUE(scriptableFolderURI, NS_ERROR_FAILURE);
+
+ scriptableFolderURI->SetData(aFolderURI);
+ argsArray->AppendElement(scriptableFolderURI);
+
+ if (!standAloneMsgWindow) {
+ nsCOMPtr<nsISupportsPRUint32> scriptableMessageKey(
+ do_CreateInstance(NS_SUPPORTS_PRUINT32_CONTRACTID));
+ NS_ENSURE_TRUE(scriptableMessageKey, NS_ERROR_FAILURE);
+ scriptableMessageKey->SetData(aMessageKey);
+ argsArray->AppendElement(scriptableMessageKey);
+ }
+ }
+
+ nsCOMPtr<nsIWindowWatcher> wwatch(
+ do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // we need to use the "mailnews.reuse_thread_window2" pref
+ // to determine if we should open a new window, or use an existing one.
+ nsCOMPtr<mozIDOMWindowProxy> newWindow;
+ return wwatch->OpenWindow(0, chromeUrl, "_blank"_ns,
+ "chrome,all,dialog=no"_ns, argsArray,
+ getter_AddRefs(newWindow));
+}
diff --git a/comm/mailnews/base/src/nsMessengerBootstrap.h b/comm/mailnews/base/src/nsMessengerBootstrap.h
new file mode 100644
index 0000000000..a81e81eeb9
--- /dev/null
+++ b/comm/mailnews/base/src/nsMessengerBootstrap.h
@@ -0,0 +1,30 @@
+/* -*- 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 __nsMessenger_h
+#define __nsMessenger_h
+
+#include "nscore.h"
+#include "nsIMessengerWindowService.h"
+
+#define NS_MESSENGERBOOTSTRAP_CID \
+ { /* 4a85a5d0-cddd-11d2-b7f6-00805f05ffa5 */ \
+ 0x4a85a5d0, 0xcddd, 0x11d2, { \
+ 0xb7, 0xf6, 0x00, 0x80, 0x5f, 0x05, 0xff, 0xa5 \
+ } \
+ }
+
+class nsMessengerBootstrap : public nsIMessengerWindowService {
+ public:
+ nsMessengerBootstrap();
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIMESSENGERWINDOWSERVICE
+
+ private:
+ virtual ~nsMessengerBootstrap();
+};
+
+#endif
diff --git a/comm/mailnews/base/src/nsMessengerOSXIntegration.h b/comm/mailnews/base/src/nsMessengerOSXIntegration.h
new file mode 100644
index 0000000000..e4b503cb12
--- /dev/null
+++ b/comm/mailnews/base/src/nsMessengerOSXIntegration.h
@@ -0,0 +1,24 @@
+/* -*- 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 __nsMessengerOSXIntegration_h
+#define __nsMessengerOSXIntegration_h
+
+#include "nsIMessengerOSIntegration.h"
+
+class nsMessengerOSXIntegration : public nsIMessengerOSIntegration {
+ public:
+ nsMessengerOSXIntegration();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMESSENGEROSINTEGRATION
+
+ private:
+ virtual ~nsMessengerOSXIntegration();
+
+ nsresult RestoreDockIcon();
+};
+
+#endif // __nsMessengerOSXIntegration_h
diff --git a/comm/mailnews/base/src/nsMessengerOSXIntegration.mm b/comm/mailnews/base/src/nsMessengerOSXIntegration.mm
new file mode 100644
index 0000000000..858e1417df
--- /dev/null
+++ b/comm/mailnews/base/src/nsMessengerOSXIntegration.mm
@@ -0,0 +1,63 @@
+/* -*- 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 "nsMessengerOSXIntegration.h"
+#include "nsObjCExceptions.h"
+#include "nsString.h"
+#include "mozilla/ErrorResult.h"
+
+#include <Carbon/Carbon.h>
+#import <Cocoa/Cocoa.h>
+
+nsMessengerOSXIntegration::nsMessengerOSXIntegration() {}
+
+nsMessengerOSXIntegration::~nsMessengerOSXIntegration() {}
+
+NS_IMPL_ADDREF(nsMessengerOSXIntegration)
+NS_IMPL_RELEASE(nsMessengerOSXIntegration)
+
+NS_INTERFACE_MAP_BEGIN(nsMessengerOSXIntegration)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIMessengerOSIntegration)
+ NS_INTERFACE_MAP_ENTRY(nsIMessengerOSIntegration)
+NS_INTERFACE_MAP_END
+
+nsresult nsMessengerOSXIntegration::RestoreDockIcon() {
+ NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
+
+ id tile = [[NSApplication sharedApplication] dockTile];
+ [tile setBadgeLabel:nil];
+
+ return NS_OK;
+
+ NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
+}
+
+NS_IMETHODIMP
+nsMessengerOSXIntegration::UpdateUnreadCount(uint32_t unreadCount, const nsAString& unreadTooltip) {
+ NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
+
+ if (unreadCount == 0) {
+ RestoreDockIcon();
+ return NS_OK;
+ }
+
+ nsAutoString total;
+ if (unreadCount > 99) {
+ total.AppendLiteral("99+");
+ } else {
+ total.AppendInt(unreadCount);
+ }
+ id tile = [[NSApplication sharedApplication] dockTile];
+ [tile setBadgeLabel:[NSString stringWithFormat:@"%S", (const unichar*)total.get()]];
+ return NS_OK;
+
+ NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
+}
+
+NS_IMETHODIMP
+nsMessengerOSXIntegration::OnExit() {
+ RestoreDockIcon();
+ return NS_OK;
+}
diff --git a/comm/mailnews/base/src/nsMessengerUnixIntegration.cpp b/comm/mailnews/base/src/nsMessengerUnixIntegration.cpp
new file mode 100644
index 0000000000..96ddb9c48a
--- /dev/null
+++ b/comm/mailnews/base/src/nsMessengerUnixIntegration.cpp
@@ -0,0 +1,24 @@
+/* -*- 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 "nsMessengerUnixIntegration.h"
+#include "nsString.h"
+
+/**
+ * This is only a placeholder for now, register it in components.conf later if
+ * needed.
+ */
+nsMessengerUnixIntegration::nsMessengerUnixIntegration() {}
+
+NS_IMPL_ISUPPORTS(nsMessengerUnixIntegration, nsIMessengerOSIntegration)
+
+NS_IMETHODIMP
+nsMessengerUnixIntegration::UpdateUnreadCount(uint32_t unreadCount,
+ const nsAString& unreadTooltip) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMessengerUnixIntegration::OnExit() { return NS_OK; }
diff --git a/comm/mailnews/base/src/nsMessengerUnixIntegration.h b/comm/mailnews/base/src/nsMessengerUnixIntegration.h
new file mode 100644
index 0000000000..180f268560
--- /dev/null
+++ b/comm/mailnews/base/src/nsMessengerUnixIntegration.h
@@ -0,0 +1,22 @@
+/* -*- 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 __nsMessengerUnixIntegration_h
+#define __nsMessengerUnixIntegration_h
+
+#include "nsIMessengerOSIntegration.h"
+
+class nsMessengerUnixIntegration : public nsIMessengerOSIntegration {
+ public:
+ nsMessengerUnixIntegration();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMESSENGEROSINTEGRATION
+
+ private:
+ virtual ~nsMessengerUnixIntegration() {}
+};
+
+#endif // __nsMessengerUnixIntegration_h
diff --git a/comm/mailnews/base/src/nsMessengerWinIntegration.cpp b/comm/mailnews/base/src/nsMessengerWinIntegration.cpp
new file mode 100644
index 0000000000..7db74af5e0
--- /dev/null
+++ b/comm/mailnews/base/src/nsMessengerWinIntegration.cpp
@@ -0,0 +1,379 @@
+/* -*- 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 <windows.h>
+#include <shellapi.h>
+#include <strsafe.h>
+
+#include "mozilla/Components.h"
+#include "mozilla/Services.h"
+#include "mozIDOMWindow.h"
+#include "nsIBaseWindow.h"
+#include "nsIDocShell.h"
+#include "nsIMsgWindow.h"
+#include "nsIObserverService.h"
+#include "nsIPrefService.h"
+#include "nsIWidget.h"
+#include "nsIWindowMediator.h"
+#include "nsMessengerWinIntegration.h"
+#include "nsMsgDBFolder.h"
+#include "nsPIDOMWindow.h"
+
+#define IDI_MAILBIFF 32576
+#define SHOW_TRAY_ICON_PREF "mail.biff.show_tray_icon"
+#define SHOW_TRAY_ICON_ALWAYS_PREF "mail.biff.show_tray_icon_always"
+
+// since we are including windows.h in this file, undefine get user name....
+#ifdef GetUserName
+# undef GetUserName
+#endif
+
+#ifndef NIIF_USER
+# define NIIF_USER 0x00000004
+#endif
+
+#ifndef NIIF_NOSOUND
+# define NIIF_NOSOUND 0x00000010
+#endif
+
+using namespace mozilla;
+
+nsMessengerWinIntegration::nsMessengerWinIntegration() {}
+
+nsMessengerWinIntegration::~nsMessengerWinIntegration() {}
+
+NS_IMPL_ADDREF(nsMessengerWinIntegration)
+NS_IMPL_RELEASE(nsMessengerWinIntegration)
+
+NS_INTERFACE_MAP_BEGIN(nsMessengerWinIntegration)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIMessengerOSIntegration)
+ NS_INTERFACE_MAP_ENTRY(nsIMessengerWindowsIntegration)
+ NS_INTERFACE_MAP_ENTRY(nsIMessengerOSIntegration)
+NS_INTERFACE_MAP_END
+
+static HWND hwndForDOMWindow(mozIDOMWindowProxy* window) {
+ if (!window) {
+ return 0;
+ }
+ nsCOMPtr<nsPIDOMWindowOuter> pidomwindow = nsPIDOMWindowOuter::From(window);
+
+ nsCOMPtr<nsIBaseWindow> ppBaseWindow =
+ do_QueryInterface(pidomwindow->GetDocShell());
+ if (!ppBaseWindow) return 0;
+
+ nsCOMPtr<nsIWidget> ppWidget;
+ ppBaseWindow->GetMainWidget(getter_AddRefs(ppWidget));
+
+ return (HWND)(ppWidget->GetNativeData(NS_NATIVE_WIDGET));
+}
+
+static void activateWindow(mozIDOMWindowProxy* win) {
+ // Try to get native window handle.
+ HWND hwnd = hwndForDOMWindow(win);
+ if (hwnd) {
+ // Restore the window if it is minimized.
+ if (::IsIconic(hwnd)) ::ShowWindow(hwnd, SW_RESTORE);
+ // Use the OS call, if possible.
+ ::SetForegroundWindow(hwnd);
+ } else {
+ // Use internal method.
+ nsCOMPtr<nsPIDOMWindowOuter> privateWindow = nsPIDOMWindowOuter::From(win);
+ privateWindow->Focus(mozilla::dom::CallerType::System);
+ }
+}
+
+NOTIFYICONDATAW sMailIconData = {
+ /* cbSize */ (DWORD)NOTIFYICONDATAW_V2_SIZE,
+ /* hWnd */ 0,
+ /* uID */ 2,
+ /* uFlags */ NIF_ICON | NIF_MESSAGE | NIF_TIP | NIF_INFO,
+ /* uCallbackMessage */ WM_USER,
+ /* hIcon */ 0,
+ /* szTip */ L"",
+ /* dwState */ 0,
+ /* dwStateMask */ 0,
+ /* szInfo */ L"",
+ /* uVersion */ {30000},
+ /* szInfoTitle */ L"",
+ /* dwInfoFlags */ NIIF_USER | NIIF_NOSOUND};
+
+static nsCOMArray<nsIBaseWindow> sHiddenWindows;
+static HWND sIconWindow;
+static uint32_t sUnreadCount;
+/* static */
+LRESULT CALLBACK nsMessengerWinIntegration::IconWindowProc(HWND msgWindow,
+ UINT msg, WPARAM wp,
+ LPARAM lp) {
+ nsresult rv;
+ static UINT sTaskbarRecreated;
+
+ switch (msg) {
+ case WM_USER:
+ if (msg == WM_USER && lp == WM_LBUTTONDOWN) {
+ nsCOMPtr<nsIPrefBranch> prefBranch =
+ do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, FALSE);
+ bool showTrayIcon;
+ rv = prefBranch->GetBoolPref(SHOW_TRAY_ICON_PREF, &showTrayIcon);
+ NS_ENSURE_SUCCESS(rv, FALSE);
+ bool showTrayIconAlways;
+ if (NS_FAILED(prefBranch->GetBoolPref(SHOW_TRAY_ICON_ALWAYS_PREF,
+ &showTrayIconAlways))) {
+ showTrayIconAlways = false;
+ }
+ if ((!showTrayIcon || !sUnreadCount) && !showTrayIconAlways) {
+ ::Shell_NotifyIconW(NIM_DELETE, &sMailIconData);
+ if (auto instance = reinterpret_cast<nsMessengerWinIntegration*>(
+ ::GetWindowLongPtrW(msgWindow, GWLP_USERDATA))) {
+ instance->mTrayIconShown = false;
+ }
+ }
+
+ // No minimzed window, bring the most recent 3pane window to the front.
+ if (sHiddenWindows.Length() == 0) {
+ nsCOMPtr<nsIWindowMediator> windowMediator =
+ do_GetService(NS_WINDOWMEDIATOR_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, FALSE);
+
+ nsCOMPtr<mozIDOMWindowProxy> domWindow;
+ rv = windowMediator->GetMostRecentBrowserWindow(
+ getter_AddRefs(domWindow));
+ NS_ENSURE_SUCCESS(rv, FALSE);
+ if (domWindow) {
+ activateWindow(domWindow);
+ return TRUE;
+ }
+ }
+
+ // Bring the minimized windows to the front.
+ for (uint32_t i = 0; i < sHiddenWindows.Length(); i++) {
+ auto window = sHiddenWindows.SafeElementAt(i);
+ if (!window) {
+ continue;
+ }
+ window->SetVisibility(true);
+
+ nsCOMPtr<nsIWidget> widget;
+ window->GetMainWidget(getter_AddRefs(widget));
+ if (!widget) {
+ continue;
+ }
+
+ HWND hwnd = (HWND)(widget->GetNativeData(NS_NATIVE_WIDGET));
+ ::ShowWindow(hwnd, SW_RESTORE);
+ ::SetForegroundWindow(hwnd);
+
+ nsCOMPtr<nsIObserverService> obs =
+ mozilla::services::GetObserverService();
+ obs->NotifyObservers(window, "windows-refresh-badge-tray", 0);
+ }
+
+ sHiddenWindows.Clear();
+ }
+ break;
+ case WM_CREATE:
+ sTaskbarRecreated = ::RegisterWindowMessageW(L"TaskbarCreated");
+ break;
+ default:
+ if (msg == sTaskbarRecreated) {
+ // When taskbar is recreated (e.g. by restarting Windows Explorer), all
+ // tray icons are removed. If there are windows minimized to tray icon,
+ // we have to recreate the tray icon, otherwise the windows can't be
+ // restored.
+ if (auto instance = reinterpret_cast<nsMessengerWinIntegration*>(
+ ::GetWindowLongPtrW(msgWindow, GWLP_USERDATA))) {
+ instance->mTrayIconShown = false;
+ }
+ for (uint32_t i = 0; i < sHiddenWindows.Length(); i++) {
+ auto window = sHiddenWindows.SafeElementAt(i);
+ if (!window) {
+ continue;
+ }
+ nsCOMPtr<nsIObserverService> obs =
+ mozilla::services::GetObserverService();
+ obs->NotifyObservers(window, "windows-refresh-badge-tray", 0);
+ }
+ }
+ break;
+ }
+ return ::DefWindowProc(msgWindow, msg, wp, lp);
+}
+
+nsresult nsMessengerWinIntegration::HideWindow(nsIBaseWindow* aWindow) {
+ NS_ENSURE_ARG(aWindow);
+ aWindow->SetVisibility(false);
+ sHiddenWindows.AppendElement(aWindow);
+
+ nsresult rv;
+ rv = CreateIconWindow();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!mTrayIconShown) {
+ auto idi = IDI_APPLICATION;
+ if (sUnreadCount > 0) {
+ idi = MAKEINTRESOURCE(IDI_MAILBIFF);
+ }
+ sMailIconData.hIcon = ::LoadIcon(::GetModuleHandle(NULL), idi);
+ nsresult rv = SetTooltip();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ ::Shell_NotifyIconW(NIM_ADD, &sMailIconData);
+ ::Shell_NotifyIconW(NIM_SETVERSION, &sMailIconData);
+ mTrayIconShown = true;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMessengerWinIntegration::ShowWindow(mozIDOMWindowProxy* aWindow) {
+ activateWindow(aWindow);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMessengerWinIntegration::UpdateUnreadCount(uint32_t unreadCount,
+ const nsAString& unreadTooltip) {
+ sUnreadCount = unreadCount;
+ mUnreadTooltip = unreadTooltip;
+ nsresult rv = UpdateTrayIcon();
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMessengerWinIntegration::OnExit() {
+ if (mTrayIconShown) {
+ ::Shell_NotifyIconW(NIM_DELETE, &sMailIconData);
+ mTrayIconShown = false;
+ }
+ return NS_OK;
+}
+
+/**
+ * Set a tooltip to the tray icon. Including the brand short name, and unread
+ * message count.
+ */
+nsresult nsMessengerWinIntegration::SetTooltip() {
+ nsresult rv = NS_OK;
+ if (mBrandShortName.IsEmpty()) {
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ mozilla::components::StringBundle::Service();
+ NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED);
+ nsCOMPtr<nsIStringBundle> bundle;
+ rv = bundleService->CreateBundle(
+ "chrome://branding/locale/brand.properties", getter_AddRefs(bundle));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = bundle->GetStringFromName("brandShortName", mBrandShortName);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ nsString tooltip = mBrandShortName;
+ if (!mUnreadTooltip.IsEmpty()) {
+ tooltip.AppendLiteral("\n");
+ tooltip.Append(mUnreadTooltip);
+ }
+ size_t destLength =
+ sizeof sMailIconData.szTip / (sizeof sMailIconData.szTip[0]);
+ ::StringCchCopyNW(sMailIconData.szTip, destLength, tooltip.get(),
+ tooltip.Length());
+ return rv;
+}
+
+/**
+ * Create a custom window for the taskbar icon if it's not created yet.
+ */
+nsresult nsMessengerWinIntegration::CreateIconWindow() {
+ if (sMailIconData.hWnd) {
+ return NS_OK;
+ }
+
+ const wchar_t kClassName[] = L"IconWindowClass";
+ WNDCLASS classStruct = {/* style */ 0,
+ /* lpfnWndProc */ &IconWindowProc,
+ /* cbClsExtra */ 0,
+ /* cbWndExtra */ 0,
+ /* hInstance */ 0,
+ /* hIcon */ 0,
+ /* hCursor */ 0,
+ /* hbrBackground */ 0,
+ /* lpszMenuName */ 0,
+ /* lpszClassName */ kClassName};
+
+ // Register the window class.
+ NS_ENSURE_TRUE(::RegisterClass(&classStruct), NS_ERROR_FAILURE);
+ // Create the window.
+ NS_ENSURE_TRUE(sIconWindow = ::CreateWindow(
+ /* className */ kClassName,
+ /* title */ 0,
+ /* style */ WS_CAPTION,
+ /* x, y, cx, cy */ 0, 0, 0, 0,
+ /* parent */ 0,
+ /* menu */ 0,
+ /* instance */ 0,
+ /* create struct */ 0),
+ NS_ERROR_FAILURE);
+ NS_ENSURE_TRUE(::SetWindowLongPtrW(sIconWindow, GWLP_USERDATA,
+ reinterpret_cast<LONG_PTR>(this)) == 0,
+ NS_ERROR_FAILURE);
+
+ sMailIconData.hWnd = sIconWindow;
+ return NS_OK;
+}
+
+/**
+ * Update the tray icon according to the current unread count and pref value.
+ */
+nsresult nsMessengerWinIntegration::UpdateTrayIcon() {
+ nsresult rv;
+
+ rv = CreateIconWindow();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!mPrefBranch) {
+ mPrefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ rv = SetTooltip();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool showTrayIconAlways;
+ if (NS_FAILED(mPrefBranch->GetBoolPref(SHOW_TRAY_ICON_ALWAYS_PREF,
+ &showTrayIconAlways))) {
+ showTrayIconAlways = false;
+ }
+ if (sUnreadCount > 0 || showTrayIconAlways) {
+ auto idi = IDI_APPLICATION;
+ if (sUnreadCount > 0) {
+ // Only showing the new mail marker when there are actual unread mail
+ idi = MAKEINTRESOURCE(IDI_MAILBIFF);
+ }
+ sMailIconData.hIcon = ::LoadIcon(::GetModuleHandle(NULL), idi);
+ if (mTrayIconShown) {
+ // If the tray icon is already shown, just modify it.
+ ::Shell_NotifyIconW(NIM_MODIFY, &sMailIconData);
+ } else {
+ bool showTrayIcon;
+ rv = mPrefBranch->GetBoolPref(SHOW_TRAY_ICON_PREF, &showTrayIcon);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (showTrayIcon) {
+ // Show a tray icon only if the pref value is true.
+ ::Shell_NotifyIconW(NIM_ADD, &sMailIconData);
+ ::Shell_NotifyIconW(NIM_SETVERSION, &sMailIconData);
+ mTrayIconShown = true;
+ }
+ }
+ } else if (mTrayIconShown) {
+ if (sHiddenWindows.Length() > 0) {
+ // At least one window is minimized, modify the icon only.
+ sMailIconData.hIcon =
+ ::LoadIcon(::GetModuleHandle(NULL), IDI_APPLICATION);
+ ::Shell_NotifyIconW(NIM_MODIFY, &sMailIconData);
+ } else if (!showTrayIconAlways) {
+ // No unread, no need to show the tray icon.
+ ::Shell_NotifyIconW(NIM_DELETE, &sMailIconData);
+ mTrayIconShown = false;
+ }
+ }
+ return rv;
+}
diff --git a/comm/mailnews/base/src/nsMessengerWinIntegration.h b/comm/mailnews/base/src/nsMessengerWinIntegration.h
new file mode 100644
index 0000000000..0fb9f1a718
--- /dev/null
+++ b/comm/mailnews/base/src/nsMessengerWinIntegration.h
@@ -0,0 +1,39 @@
+/* -*- 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 __nsMessengerWinIntegration_h
+#define __nsMessengerWinIntegration_h
+
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "nsIMessengerWindowsIntegration.h"
+#include "nsIStringBundle.h"
+#include "nsIPrefBranch.h"
+
+class nsMessengerWinIntegration : public nsIMessengerWindowsIntegration {
+ public:
+ nsMessengerWinIntegration();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMESSENGERWINDOWSINTEGRATION
+ NS_DECL_NSIMESSENGEROSINTEGRATION
+
+ private:
+ static LRESULT CALLBACK IconWindowProc(HWND msgWindow, UINT msg, WPARAM wp,
+ LPARAM lp);
+
+ virtual ~nsMessengerWinIntegration();
+
+ nsresult CreateIconWindow();
+ nsresult SetTooltip();
+ nsresult UpdateTrayIcon();
+
+ nsCOMPtr<nsIPrefBranch> mPrefBranch;
+ bool mTrayIconShown = false;
+ nsString mBrandShortName;
+ nsString mUnreadTooltip;
+};
+
+#endif // __nsMessengerWinIntegration_h
diff --git a/comm/mailnews/base/src/nsMsgAccount.cpp b/comm/mailnews/base/src/nsMsgAccount.cpp
new file mode 100644
index 0000000000..8751e4cdde
--- /dev/null
+++ b/comm/mailnews/base/src/nsMsgAccount.cpp
@@ -0,0 +1,413 @@
+/* -*- 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 "plstr.h"
+#include "prmem.h"
+#include "nsIComponentManager.h"
+#include "nsIServiceManager.h"
+#include "nsCRTGlue.h"
+#include "nsCOMPtr.h"
+#include "nsIMsgFolderNotificationService.h"
+#include "nsPrintfCString.h"
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+#include "nsMsgAccount.h"
+#include "nsIMsgAccount.h"
+#include "nsIMsgAccountManager.h"
+#include "nsIObserverService.h"
+#include "mozilla/Services.h"
+#include "nsServiceManagerUtils.h"
+#include "nsMemory.h"
+#include "nsComponentManagerUtils.h"
+#include "nsMsgUtils.h"
+
+NS_IMPL_ISUPPORTS(nsMsgAccount, nsIMsgAccount)
+
+nsMsgAccount::nsMsgAccount()
+ : m_identitiesValid(false), mTriedToGetServer(false) {}
+
+nsMsgAccount::~nsMsgAccount() {}
+
+nsresult nsMsgAccount::getPrefService() {
+ if (m_prefs) return NS_OK;
+
+ nsresult rv;
+ NS_ENSURE_FALSE(m_accountKey.IsEmpty(), NS_ERROR_NOT_INITIALIZED);
+ nsCOMPtr<nsIPrefService> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString accountRoot("mail.account.");
+ accountRoot.Append(m_accountKey);
+ accountRoot.Append('.');
+ return prefs->GetBranch(accountRoot.get(), getter_AddRefs(m_prefs));
+}
+
+NS_IMETHODIMP
+nsMsgAccount::GetIncomingServer(nsIMsgIncomingServer** aIncomingServer) {
+ NS_ENSURE_ARG_POINTER(aIncomingServer);
+
+ // create the incoming server lazily
+ if (!mTriedToGetServer && !m_incomingServer) {
+ mTriedToGetServer = true;
+ // ignore the error (and return null), but it's still bad so warn
+ mozilla::DebugOnly<nsresult> rv = createIncomingServer();
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "couldn't lazily create the server\n");
+ }
+
+ NS_IF_ADDREF(*aIncomingServer = m_incomingServer);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgAccount::CreateServer() {
+ if (m_incomingServer) return NS_ERROR_ALREADY_INITIALIZED;
+ return createIncomingServer();
+}
+
+nsresult nsMsgAccount::createIncomingServer() {
+ // from here, load mail.account.myaccount.server
+ // Load the incoming server
+ //
+ // ex) mail.account.myaccount.server = "myserver"
+
+ nsresult rv = getPrefService();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // get the "server" pref
+ nsCString serverKey;
+ rv = m_prefs->GetCharPref("server", serverKey);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // get the server from the account manager
+ nsCOMPtr<nsIMsgAccountManager> accountManager =
+ do_GetService("@mozilla.org/messenger/account-manager;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = accountManager->GetIncomingServer(serverKey, getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCString hostname;
+ rv = server->GetHostName(hostname);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (hostname.IsEmpty()) {
+ NS_WARNING(
+ nsPrintfCString("Server had no hostname; key=%s", serverKey.get())
+ .get());
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ // store the server in this structure
+ m_incomingServer = server;
+ accountManager->NotifyServerLoaded(server);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgAccount::SetIncomingServer(nsIMsgIncomingServer* aIncomingServer) {
+ NS_ENSURE_ARG_POINTER(aIncomingServer);
+
+ nsCString key;
+ nsresult rv = aIncomingServer->GetKey(key);
+
+ if (NS_SUCCEEDED(rv)) {
+ rv = getPrefService();
+ NS_ENSURE_SUCCESS(rv, rv);
+ m_prefs->SetCharPref("server", key);
+ }
+
+ m_incomingServer = aIncomingServer;
+
+ bool serverValid;
+ (void)aIncomingServer->GetValid(&serverValid);
+ // only notify server loaded if server is valid so
+ // account manager only gets told about finished accounts.
+ if (serverValid) {
+ // this is the point at which we can notify listeners about the
+ // creation of the root folder, which implies creation of the new server.
+ nsCOMPtr<nsIMsgFolder> rootFolder;
+ rv = aIncomingServer->GetRootFolder(getter_AddRefs(rootFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIFolderListener> mailSession =
+ do_GetService("@mozilla.org/messenger/services/session;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ mailSession->OnFolderAdded(nullptr, rootFolder);
+ nsCOMPtr<nsIMsgFolderNotificationService> notifier(
+ do_GetService("@mozilla.org/messenger/msgnotificationservice;1", &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ notifier->NotifyFolderAdded(rootFolder);
+
+ nsCOMPtr<nsIMsgAccountManager> accountManager =
+ do_GetService("@mozilla.org/messenger/account-manager;1", &rv);
+ if (NS_SUCCEEDED(rv)) accountManager->NotifyServerLoaded(aIncomingServer);
+
+ // Force built-in folders to be created and discovered. Then, notify
+ // listeners about them.
+ nsTArray<RefPtr<nsIMsgFolder>> subFolders;
+ rv = rootFolder->GetSubFolders(subFolders);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (nsIMsgFolder* msgFolder : subFolders) {
+ mailSession->OnFolderAdded(rootFolder, msgFolder);
+ notifier->NotifyFolderAdded(msgFolder);
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgAccount::GetIdentities(nsTArray<RefPtr<nsIMsgIdentity>>& identities) {
+ NS_ENSURE_TRUE(m_identitiesValid, NS_ERROR_FAILURE);
+ identities.Clear();
+ identities.AppendElements(m_identities);
+ return NS_OK;
+}
+
+/*
+ * set up the m_identities array
+ * do not call this more than once or we'll leak.
+ */
+nsresult nsMsgAccount::createIdentities() {
+ NS_ENSURE_FALSE(m_identitiesValid, NS_ERROR_FAILURE);
+
+ nsresult rv;
+ m_identities.Clear();
+
+ nsCString identityKey;
+ rv = getPrefService();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ m_prefs->GetCharPref("identities", identityKey);
+ if (identityKey.IsEmpty()) {
+ // not an error if no identities, but strtok will be unhappy.
+ m_identitiesValid = true;
+ return NS_OK;
+ }
+ // get the server from the account manager
+ nsCOMPtr<nsIMsgAccountManager> accountManager =
+ do_GetService("@mozilla.org/messenger/account-manager;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ char* newStr = identityKey.BeginWriting();
+ char* token = NS_strtok(",", &newStr);
+
+ // temporaries used inside the loop
+ nsCOMPtr<nsIMsgIdentity> identity;
+ nsAutoCString key;
+
+ // iterate through id1,id2, etc
+ while (token) {
+ key = token;
+ key.StripWhitespace();
+
+ // create the account
+ rv = accountManager->GetIdentity(key, getter_AddRefs(identity));
+ if (NS_SUCCEEDED(rv)) {
+ m_identities.AppendElement(identity);
+ }
+
+ // advance to next key, if any
+ token = NS_strtok(",", &newStr);
+ }
+
+ m_identitiesValid = true;
+ return rv;
+}
+
+/* attribute nsIMsgIdentity defaultIdentity; */
+NS_IMETHODIMP
+nsMsgAccount::GetDefaultIdentity(nsIMsgIdentity** aDefaultIdentity) {
+ NS_ENSURE_ARG_POINTER(aDefaultIdentity);
+ NS_ENSURE_TRUE(m_identitiesValid, NS_ERROR_NOT_INITIALIZED);
+
+ // Default identity is the first in the list.
+ if (m_identities.IsEmpty()) {
+ *aDefaultIdentity = nullptr;
+ } else {
+ NS_IF_ADDREF(*aDefaultIdentity = m_identities[0]);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgAccount::SetDefaultIdentity(nsIMsgIdentity* aDefaultIdentity) {
+ NS_ENSURE_TRUE(m_identitiesValid, NS_ERROR_FAILURE);
+
+ auto position = m_identities.IndexOf(aDefaultIdentity);
+ if (position == m_identities.NoIndex) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Move it to the front of the list.
+ m_identities.RemoveElementAt(position);
+ m_identities.InsertElementAt(0, aDefaultIdentity);
+
+ nsresult rv = saveIdentitiesPref();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (obs) {
+ obs->NotifyObservers(aDefaultIdentity, "account-default-identity-changed",
+ NS_ConvertUTF8toUTF16(m_accountKey).get());
+ }
+
+ return NS_OK;
+}
+
+/* void addIdentity (in nsIMsgIdentity identity); */
+NS_IMETHODIMP
+nsMsgAccount::AddIdentity(nsIMsgIdentity* identity) {
+ NS_ENSURE_ARG_POINTER(identity);
+ NS_ENSURE_TRUE(m_identitiesValid, NS_ERROR_FAILURE);
+
+ // hack hack - need to add this to the list of identities.
+ // for now just treat this as a Setxxx accessor
+ // when this is actually implemented, don't refcount the default identity
+ nsCString key;
+ nsresult rv = identity->GetKey(key);
+
+ if (NS_SUCCEEDED(rv)) {
+ nsCString identityList;
+ m_prefs->GetCharPref("identities", identityList);
+
+ nsAutoCString newIdentityList(identityList);
+
+ nsAutoCString testKey; // temporary to strip whitespace
+ bool foundIdentity = false; // if the input identity is found
+
+ if (!identityList.IsEmpty()) {
+ char* newStr = identityList.BeginWriting();
+ char* token = NS_strtok(",", &newStr);
+
+ // look for the identity key that we're adding
+ while (token) {
+ testKey = token;
+ testKey.StripWhitespace();
+
+ if (testKey.Equals(key)) foundIdentity = true;
+
+ token = NS_strtok(",", &newStr);
+ }
+ }
+
+ // if it didn't already exist, append it
+ if (!foundIdentity) {
+ if (newIdentityList.IsEmpty())
+ newIdentityList = key;
+ else {
+ newIdentityList.Append(',');
+ newIdentityList.Append(key);
+ }
+ }
+
+ m_prefs->SetCharPref("identities", newIdentityList);
+
+ // now add it to the in-memory list
+ m_identities.AppendElement(identity);
+
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (obs) {
+ obs->NotifyObservers(identity, "account-identity-added",
+ NS_ConvertUTF8toUTF16(key).get());
+ }
+ }
+
+ return NS_OK;
+}
+
+/* void removeIdentity (in nsIMsgIdentity identity); */
+NS_IMETHODIMP
+nsMsgAccount::RemoveIdentity(nsIMsgIdentity* aIdentity) {
+ NS_ENSURE_ARG_POINTER(aIdentity);
+ NS_ENSURE_TRUE(m_identitiesValid, NS_ERROR_FAILURE);
+
+ // At least one identity must stay after the delete.
+ NS_ENSURE_TRUE(m_identities.Length() > 1, NS_ERROR_FAILURE);
+
+ nsCString key;
+ nsresult rv = aIdentity->GetKey(key);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!m_identities.RemoveElement(aIdentity)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Notify before clearing the pref values, so we do not get the superfluous
+ // update notifications.
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (obs) {
+ obs->NotifyObservers(aIdentity, "account-identity-removed",
+ NS_ConvertUTF8toUTF16(key).get());
+ }
+
+ // Clear out the actual pref values associated with the identity.
+ aIdentity->ClearAllValues();
+ return saveIdentitiesPref();
+}
+
+nsresult nsMsgAccount::saveIdentitiesPref() {
+ nsAutoCString newIdentityList;
+
+ // Iterate over the existing identities and build the pref value,
+ // a string of identity keys: id1, id2, idX...
+ nsCString key;
+ bool first = true;
+ for (auto identity : m_identities) {
+ identity->GetKey(key);
+
+ if (first) {
+ newIdentityList = key;
+ first = false;
+ } else {
+ newIdentityList.Append(',');
+ newIdentityList.Append(key);
+ }
+ }
+
+ // Save the pref.
+ m_prefs->SetCharPref("identities", newIdentityList);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAccount::GetKey(nsACString& accountKey) {
+ accountKey = m_accountKey;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgAccount::SetKey(const nsACString& accountKey) {
+ m_accountKey = accountKey;
+ m_prefs = nullptr;
+ m_identitiesValid = false;
+ m_identities.Clear();
+ return createIdentities();
+}
+
+NS_IMETHODIMP
+nsMsgAccount::ToString(nsAString& aResult) {
+ nsAutoString val;
+ aResult.AssignLiteral("[nsIMsgAccount: ");
+ aResult.Append(NS_ConvertASCIItoUTF16(m_accountKey));
+ aResult.Append(']');
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgAccount::ClearAllValues() {
+ nsTArray<nsCString> prefNames;
+ nsresult rv = m_prefs->GetChildList("", prefNames);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (auto& prefName : prefNames) {
+ m_prefs->ClearUserPref(prefName.get());
+ }
+
+ return NS_OK;
+}
diff --git a/comm/mailnews/base/src/nsMsgAccount.h b/comm/mailnews/base/src/nsMsgAccount.h
new file mode 100644
index 0000000000..60b8005390
--- /dev/null
+++ b/comm/mailnews/base/src/nsMsgAccount.h
@@ -0,0 +1,34 @@
+/* -*- 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 "nsIMsgAccount.h"
+#include "nsIPrefBranch.h"
+#include "nsString.h"
+
+class nsMsgAccount : public nsIMsgAccount {
+ public:
+ nsMsgAccount();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMSGACCOUNT
+
+ private:
+ virtual ~nsMsgAccount();
+ nsCString m_accountKey;
+ nsCOMPtr<nsIPrefBranch> m_prefs;
+ nsCOMPtr<nsIMsgIncomingServer> m_incomingServer;
+
+ bool m_identitiesValid;
+ nsTArray<nsCOMPtr<nsIMsgIdentity>> m_identities;
+
+ nsresult getPrefService();
+ nsresult createIncomingServer();
+ nsresult createIdentities();
+ nsresult saveIdentitiesPref();
+
+ // Have we tried to get the server yet?
+ bool mTriedToGetServer;
+};
diff --git a/comm/mailnews/base/src/nsMsgAccountManager.cpp b/comm/mailnews/base/src/nsMsgAccountManager.cpp
new file mode 100644
index 0000000000..5352486cb4
--- /dev/null
+++ b/comm/mailnews/base/src/nsMsgAccountManager.cpp
@@ -0,0 +1,3546 @@
+/* -*- 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/. */
+
+/**
+ * The account manager service - manages all accounts, servers, and identities
+ */
+
+#include "nsCOMPtr.h"
+#include "nsISupports.h"
+#include "nsIThread.h"
+#include "nscore.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/Likely.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/RefCountType.h"
+#include "mozilla/RefPtr.h"
+#include "nsIComponentManager.h"
+#include "nsIServiceManager.h"
+#include "nsMsgAccountManager.h"
+#include "prmem.h"
+#include "prcmon.h"
+#include "prthread.h"
+#include "plstr.h"
+#include "nsString.h"
+#include "nsMemory.h"
+#include "nsUnicharUtils.h"
+#include "nscore.h"
+#include "prprf.h"
+#include "nsIMsgFolderCache.h"
+#include "nsMsgFolderCache.h"
+#include "nsMsgUtils.h"
+#include "nsMsgDBFolder.h"
+#include "nsIFile.h"
+#include "nsIURL.h"
+#include "nsNetCID.h"
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+#include "nsISmtpService.h"
+#include "nsIMsgBiffManager.h"
+#include "nsIMsgPurgeService.h"
+#include "nsIObserverService.h"
+#include "nsINoIncomingServer.h"
+#include "nsIMsgMailSession.h"
+#include "nsIDirectoryService.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsMailDirServiceDefs.h"
+#include "nsMsgFolderFlags.h"
+#include "nsIMsgFolderNotificationService.h"
+#include "nsIImapIncomingServer.h"
+#include "nsIImapUrl.h"
+#include "nsICategoryManager.h"
+#include "nsISupportsPrimitives.h"
+#include "nsIMsgFilterService.h"
+#include "nsIMsgFilter.h"
+#include "nsIMsgSearchSession.h"
+#include "nsIMsgSearchTerm.h"
+#include "nsIDBFolderInfo.h"
+#include "nsIMsgHdr.h"
+#include "nsILineInputStream.h"
+#include "nsThreadUtils.h"
+#include "nsNetUtil.h"
+#include "nsIStringBundle.h"
+#include "nsMsgMessageFlags.h"
+#include "nsIMsgFilterList.h"
+#include "nsDirectoryServiceUtils.h"
+#include "mozilla/Components.h"
+#include "mozilla/Services.h"
+#include "nsIFileStreams.h"
+#include "nsIOutputStream.h"
+#include "nsISafeOutputStream.h"
+#include "nsXULAppAPI.h"
+#include "nsICacheStorageService.h"
+#include "UrlListener.h"
+#include "nsIIDNService.h"
+
+#define PREF_MAIL_ACCOUNTMANAGER_ACCOUNTS "mail.accountmanager.accounts"
+#define PREF_MAIL_ACCOUNTMANAGER_DEFAULTACCOUNT \
+ "mail.accountmanager.defaultaccount"
+#define PREF_MAIL_ACCOUNTMANAGER_LOCALFOLDERSSERVER \
+ "mail.accountmanager.localfoldersserver"
+#define PREF_MAIL_SERVER_PREFIX "mail.server."
+#define ACCOUNT_PREFIX "account"
+#define SERVER_PREFIX "server"
+#define ID_PREFIX "id"
+#define ABOUT_TO_GO_OFFLINE_TOPIC "network:offline-about-to-go-offline"
+#define ACCOUNT_DELIMITER ','
+#define APPEND_ACCOUNTS_VERSION_PREF_NAME "append_preconfig_accounts.version"
+#define MAILNEWS_ROOT_PREF "mailnews."
+#define PREF_MAIL_ACCOUNTMANAGER_APPEND_ACCOUNTS \
+ "mail.accountmanager.appendaccounts"
+
+#define NS_MSGACCOUNT_CID \
+ { \
+ 0x68b25510, 0xe641, 0x11d2, { \
+ 0xb7, 0xfc, 0x0, 0x80, 0x5f, 0x5, 0xff, 0xa5 \
+ } \
+ }
+static NS_DEFINE_CID(kMsgAccountCID, NS_MSGACCOUNT_CID);
+
+#define SEARCH_FOLDER_FLAG "searchFolderFlag"
+#define SEARCH_FOLDER_FLAG_LEN (sizeof(SEARCH_FOLDER_FLAG) - 1)
+
+const char* kSearchFolderUriProp = "searchFolderUri";
+
+bool nsMsgAccountManager::m_haveShutdown = false;
+bool nsMsgAccountManager::m_shutdownInProgress = false;
+
+NS_IMPL_ISUPPORTS(nsMsgAccountManager, nsIMsgAccountManager, nsIObserver,
+ nsISupportsWeakReference, nsIFolderListener)
+
+nsMsgAccountManager::nsMsgAccountManager()
+ : m_accountsLoaded(false),
+ m_emptyTrashInProgress(false),
+ m_cleanupInboxInProgress(false),
+ m_userAuthenticated(false),
+ m_loadingVirtualFolders(false),
+ m_virtualFoldersLoaded(false),
+ m_lastFindServerPort(0) {}
+
+nsMsgAccountManager::~nsMsgAccountManager() {
+ if (!m_haveShutdown) {
+ Shutdown();
+ // Don't remove from Observer service in Shutdown because Shutdown also gets
+ // called from xpcom shutdown observer. And we don't want to remove from
+ // the service in that case.
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (observerService) {
+ observerService->RemoveObserver(this, "search-folders-changed");
+ observerService->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
+ observerService->RemoveObserver(this, "quit-application-granted");
+ observerService->RemoveObserver(this, ABOUT_TO_GO_OFFLINE_TOPIC);
+ observerService->RemoveObserver(this, "sleep_notification");
+ }
+ }
+}
+
+nsresult nsMsgAccountManager::Init() {
+ if (!XRE_IsParentProcess()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsresult rv;
+
+ m_prefs = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (observerService) {
+ observerService->AddObserver(this, "search-folders-changed", true);
+ observerService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, true);
+ observerService->AddObserver(this, "quit-application-granted", true);
+ observerService->AddObserver(this, ABOUT_TO_GO_OFFLINE_TOPIC, true);
+ observerService->AddObserver(this, "profile-before-change", true);
+ observerService->AddObserver(this, "sleep_notification", true);
+ }
+
+ // Make sure PSM gets initialized before any accounts use certificates.
+ net_EnsurePSMInit();
+
+ return NS_OK;
+}
+
+nsresult nsMsgAccountManager::Shutdown() {
+ if (m_haveShutdown) // do not shutdown twice
+ return NS_OK;
+
+ nsresult rv;
+
+ SaveVirtualFolders();
+
+ if (m_dbService) {
+ nsTObserverArray<RefPtr<VirtualFolderChangeListener>>::ForwardIterator iter(
+ m_virtualFolderListeners);
+ RefPtr<VirtualFolderChangeListener> listener;
+
+ while (iter.HasMore()) {
+ listener = iter.GetNext();
+ m_dbService->UnregisterPendingListener(listener);
+ }
+
+ m_dbService = nullptr;
+ }
+ m_virtualFolders.Clear();
+ if (m_msgFolderCache) WriteToFolderCache(m_msgFolderCache);
+ (void)ShutdownServers();
+ (void)UnloadAccounts();
+
+ // shutdown removes nsIIncomingServer listener from biff manager, so do it
+ // after accounts have been unloaded
+ nsCOMPtr<nsIMsgBiffManager> biffService =
+ do_GetService("@mozilla.org/messenger/biffManager;1", &rv);
+ if (NS_SUCCEEDED(rv) && biffService) biffService->Shutdown();
+
+ // shutdown removes nsIIncomingServer listener from purge service, so do it
+ // after accounts have been unloaded
+ nsCOMPtr<nsIMsgPurgeService> purgeService =
+ do_GetService("@mozilla.org/messenger/purgeService;1", &rv);
+ if (NS_SUCCEEDED(rv) && purgeService) purgeService->Shutdown();
+
+ if (m_msgFolderCache) {
+ // The DTOR is meant to do the flushing, but observed behaviour is
+ // that it doesn't always get called. So flush explicitly.
+ m_msgFolderCache->Flush();
+ m_msgFolderCache = nullptr;
+ }
+
+ m_haveShutdown = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgAccountManager::GetShutdownInProgress(bool* _retval) {
+ NS_ENSURE_ARG_POINTER(_retval);
+ *_retval = m_shutdownInProgress;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgAccountManager::GetUserNeedsToAuthenticate(bool* aRetval) {
+ NS_ENSURE_ARG_POINTER(aRetval);
+ if (!m_userAuthenticated)
+ return m_prefs->GetBoolPref("mail.password_protect_local_cache", aRetval);
+ *aRetval = !m_userAuthenticated;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgAccountManager::SetUserNeedsToAuthenticate(bool aUserNeedsToAuthenticate) {
+ m_userAuthenticated = !aUserNeedsToAuthenticate;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAccountManager::Observe(nsISupports* aSubject,
+ const char* aTopic,
+ const char16_t* someData) {
+ if (!strcmp(aTopic, "search-folders-changed")) {
+ nsCOMPtr<nsIMsgFolder> virtualFolder = do_QueryInterface(aSubject);
+ nsCOMPtr<nsIMsgDatabase> db;
+ nsCOMPtr<nsIDBFolderInfo> dbFolderInfo;
+ virtualFolder->GetDBFolderInfoAndDB(getter_AddRefs(dbFolderInfo),
+ getter_AddRefs(db));
+ nsCString srchFolderUris;
+ dbFolderInfo->GetCharProperty(kSearchFolderUriProp, srchFolderUris);
+ AddVFListenersForVF(virtualFolder, srchFolderUris);
+ return NS_OK;
+ }
+ if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
+ Shutdown();
+ return NS_OK;
+ }
+ if (!strcmp(aTopic, "quit-application-granted")) {
+ // CleanupOnExit will set m_shutdownInProgress to true.
+ CleanupOnExit();
+ return NS_OK;
+ }
+ if (!strcmp(aTopic, ABOUT_TO_GO_OFFLINE_TOPIC)) {
+ nsAutoString dataString(u"offline"_ns);
+ if (someData) {
+ nsAutoString someDataString(someData);
+ if (dataString.Equals(someDataString)) CloseCachedConnections();
+ }
+ return NS_OK;
+ }
+ if (!strcmp(aTopic, "sleep_notification")) return CloseCachedConnections();
+
+ if (!strcmp(aTopic, "profile-before-change")) {
+ Shutdown();
+ return NS_OK;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgAccountManager::GetUniqueAccountKey(nsACString& aResult) {
+ int32_t lastKey = 0;
+ nsresult rv;
+ nsCOMPtr<nsIPrefService> prefservice(
+ do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIPrefBranch> prefBranch;
+ prefservice->GetBranch("", getter_AddRefs(prefBranch));
+
+ rv = prefBranch->GetIntPref("mail.account.lastKey", &lastKey);
+ if (NS_FAILED(rv) || lastKey == 0) {
+ // If lastKey pref does not contain a valid value, loop over existing
+ // pref names mail.account.* .
+ nsCOMPtr<nsIPrefBranch> prefBranchAccount;
+ rv = prefservice->GetBranch("mail.account.",
+ getter_AddRefs(prefBranchAccount));
+ if (NS_SUCCEEDED(rv)) {
+ nsTArray<nsCString> prefList;
+ rv = prefBranchAccount->GetChildList("", prefList);
+ if (NS_SUCCEEDED(rv)) {
+ // Pref names are of the format accountX.
+ // Find the maximum value of 'X' used so far.
+ for (auto& prefName : prefList) {
+ if (StringBeginsWith(prefName, nsLiteralCString(ACCOUNT_PREFIX))) {
+ int32_t dotPos = prefName.FindChar('.');
+ if (dotPos != kNotFound) {
+ nsCString keyString(Substring(prefName, strlen(ACCOUNT_PREFIX),
+ dotPos - strlen(ACCOUNT_PREFIX)));
+ int32_t thisKey = keyString.ToInteger(&rv);
+ if (NS_SUCCEEDED(rv)) lastKey = std::max(lastKey, thisKey);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // Use next available key and store the value in the pref.
+ aResult.Assign(ACCOUNT_PREFIX);
+ aResult.AppendInt(++lastKey);
+ rv = prefBranch->SetIntPref("mail.account.lastKey", lastKey);
+ } else {
+ // If pref service is not working, try to find a free accountX key
+ // by checking which keys exist.
+ int32_t i = 1;
+ nsCOMPtr<nsIMsgAccount> account;
+
+ do {
+ aResult = ACCOUNT_PREFIX;
+ aResult.AppendInt(i++);
+ GetAccount(aResult, getter_AddRefs(account));
+ } while (account);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgAccountManager::GetUniqueServerKey(nsACString& aResult) {
+ nsAutoCString prefResult;
+ bool usePrefsScan = true;
+ nsresult rv;
+ nsCOMPtr<nsIPrefService> prefService(
+ do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ if (NS_FAILED(rv)) usePrefsScan = false;
+
+ // Loop over existing pref names mail.server.server(lastKey).type
+ nsCOMPtr<nsIPrefBranch> prefBranchServer;
+ if (prefService) {
+ rv = prefService->GetBranch(PREF_MAIL_SERVER_PREFIX,
+ getter_AddRefs(prefBranchServer));
+ if (NS_FAILED(rv)) usePrefsScan = false;
+ }
+
+ if (usePrefsScan) {
+ nsAutoCString type;
+ nsAutoCString typeKey;
+ for (uint32_t lastKey = 1;; lastKey++) {
+ aResult.AssignLiteral(SERVER_PREFIX);
+ aResult.AppendInt(lastKey);
+ typeKey.Assign(aResult);
+ typeKey.AppendLiteral(".type");
+ prefBranchServer->GetCharPref(typeKey.get(), type);
+ if (type.IsEmpty()) // a server slot with no type is considered empty
+ return NS_OK;
+ }
+ } else {
+ // If pref service fails, try to find a free serverX key
+ // by checking which keys exist.
+ nsAutoCString internalResult;
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ uint32_t i = 1;
+ do {
+ aResult.AssignLiteral(SERVER_PREFIX);
+ aResult.AppendInt(i++);
+ m_incomingServers.Get(aResult, getter_AddRefs(server));
+ } while (server);
+ return NS_OK;
+ }
+}
+
+nsresult nsMsgAccountManager::CreateIdentity(nsIMsgIdentity** _retval) {
+ NS_ENSURE_ARG_POINTER(_retval);
+ nsresult rv;
+ nsAutoCString key;
+ nsCOMPtr<nsIMsgIdentity> identity;
+ int32_t i = 1;
+ do {
+ key.AssignLiteral(ID_PREFIX);
+ key.AppendInt(i++);
+ m_identities.Get(key, getter_AddRefs(identity));
+ } while (identity);
+
+ rv = createKeyedIdentity(key, _retval);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgAccountManager::GetIdentity(const nsACString& key,
+ nsIMsgIdentity** _retval) {
+ NS_ENSURE_ARG_POINTER(_retval);
+ nsresult rv = NS_OK;
+ *_retval = nullptr;
+
+ if (!key.IsEmpty()) {
+ nsCOMPtr<nsIMsgIdentity> identity;
+ m_identities.Get(key, getter_AddRefs(identity));
+ if (identity)
+ identity.forget(_retval);
+ else // identity doesn't exist. create it.
+ rv = createKeyedIdentity(key, _retval);
+ }
+
+ return rv;
+}
+
+/*
+ * the shared identity-creation code
+ * create an identity and add it to the accountmanager's list.
+ */
+nsresult nsMsgAccountManager::createKeyedIdentity(const nsACString& key,
+ nsIMsgIdentity** aIdentity) {
+ nsresult rv;
+ nsCOMPtr<nsIMsgIdentity> identity =
+ do_CreateInstance("@mozilla.org/messenger/identity;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ identity->SetKey(key);
+ m_identities.InsertOrUpdate(key, identity);
+ identity.forget(aIdentity);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgAccountManager::CreateIncomingServer(const nsACString& username,
+ const nsACString& hostname,
+ const nsACString& type,
+ nsIMsgIncomingServer** _retval) {
+ NS_ENSURE_ARG_POINTER(_retval);
+
+ nsresult rv = LoadAccounts();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString key;
+ GetUniqueServerKey(key);
+ rv = createKeyedServer(key, username, hostname, type, _retval);
+ if (*_retval) {
+ nsCString defaultStore;
+ m_prefs->GetCharPref("mail.serverDefaultStoreContractID", defaultStore);
+ (*_retval)->SetCharValue("storeContractID", defaultStore);
+
+ // From when we first create the account until we have created some folders,
+ // we can change the store type.
+ (*_retval)->SetBoolValue("canChangeStoreType", true);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgAccountManager::GetIncomingServer(const nsACString& key,
+ nsIMsgIncomingServer** _retval) {
+ NS_ENSURE_ARG_POINTER(_retval);
+ nsresult rv;
+
+ if (m_incomingServers.Get(key, _retval)) return NS_OK;
+
+ // server doesn't exist, so create it
+ // this is really horrible because we are doing our own prefname munging
+ // instead of leaving it up to the incoming server.
+ // this should be fixed somehow so that we can create the incoming server
+ // and then read from the incoming server's attributes
+
+ // in order to create the right kind of server, we have to look
+ // at the pref for this server to get the username, hostname, and type
+ nsAutoCString serverPrefPrefix(PREF_MAIL_SERVER_PREFIX);
+ serverPrefPrefix.Append(key);
+
+ nsCString serverType;
+ nsAutoCString serverPref(serverPrefPrefix);
+ serverPref.AppendLiteral(".type");
+ rv = m_prefs->GetCharPref(serverPref.get(), serverType);
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_NOT_INITIALIZED);
+
+ //
+ // .userName
+ serverPref = serverPrefPrefix;
+ serverPref.AppendLiteral(".userName");
+ nsCString username;
+ rv = m_prefs->GetCharPref(serverPref.get(), username);
+
+ // .hostname
+ serverPref = serverPrefPrefix;
+ serverPref.AppendLiteral(".hostname");
+ nsCString hostname;
+ rv = m_prefs->GetCharPref(serverPref.get(), hostname);
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_NOT_INITIALIZED);
+
+ return createKeyedServer(key, username, hostname, serverType, _retval);
+}
+
+NS_IMETHODIMP
+nsMsgAccountManager::RemoveIncomingServer(nsIMsgIncomingServer* aServer,
+ bool aRemoveFiles) {
+ NS_ENSURE_ARG_POINTER(aServer);
+
+ nsCString serverKey;
+ nsresult rv = aServer->GetKey(serverKey);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // close cached connections and forget session password
+ LogoutOfServer(aServer);
+
+ // invalidate the FindServer() cache if we are removing the cached server
+ if (m_lastFindServerResult == aServer)
+ SetLastServerFound(nullptr, EmptyCString(), EmptyCString(), 0,
+ EmptyCString());
+
+ m_incomingServers.Remove(serverKey);
+
+ nsCOMPtr<nsIMsgFolder> rootFolder;
+ rv = aServer->GetRootFolder(getter_AddRefs(rootFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsTArray<RefPtr<nsIMsgFolder>> allDescendants;
+ rv = rootFolder->GetDescendants(allDescendants);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgFolderNotificationService> notifier =
+ do_GetService("@mozilla.org/messenger/msgnotificationservice;1");
+ nsCOMPtr<nsIFolderListener> mailSession =
+ do_GetService("@mozilla.org/messenger/services/session;1");
+
+ for (auto folder : allDescendants) {
+ folder->ForceDBClosed();
+ if (notifier) notifier->NotifyFolderDeleted(folder);
+ if (mailSession) {
+ nsCOMPtr<nsIMsgFolder> parentFolder;
+ folder->GetParent(getter_AddRefs(parentFolder));
+ mailSession->OnFolderRemoved(parentFolder, folder);
+ }
+ }
+ if (notifier) notifier->NotifyFolderDeleted(rootFolder);
+ if (mailSession) mailSession->OnFolderRemoved(nullptr, rootFolder);
+
+ removeListenersFromFolder(rootFolder);
+ NotifyServerUnloaded(aServer);
+ if (aRemoveFiles) {
+ rv = aServer->RemoveFiles();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (obs) {
+ obs->NotifyObservers(aServer, "message-server-removed",
+ NS_ConvertUTF8toUTF16(serverKey).get());
+ }
+
+ // now clear out the server once and for all.
+ // watch out! could be scary
+ aServer->ClearAllValues();
+ rootFolder->Shutdown(true);
+ return rv;
+}
+
+/*
+ * create a server when you know the key and the type
+ */
+nsresult nsMsgAccountManager::createKeyedServer(
+ const nsACString& key, const nsACString& username,
+ const nsACString& hostname, const nsACString& type,
+ nsIMsgIncomingServer** aServer) {
+ nsresult rv;
+ *aServer = nullptr;
+
+ // construct the contractid
+ nsAutoCString serverContractID("@mozilla.org/messenger/server;1?type=");
+ serverContractID += type;
+
+ // finally, create the server
+ // (This will fail if type is from an extension that has been removed)
+ nsCOMPtr<nsIMsgIncomingServer> server =
+ do_CreateInstance(serverContractID.get(), &rv);
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_NOT_AVAILABLE);
+
+ int32_t port;
+ nsCOMPtr<nsIMsgIncomingServer> existingServer;
+ server->SetKey(key);
+ server->SetType(type);
+ server->SetUsername(username);
+ server->SetHostName(hostname);
+ server->GetPort(&port);
+ FindServer(username, hostname, type, port, getter_AddRefs(existingServer));
+ // don't allow duplicate servers.
+ if (existingServer) return NS_ERROR_FAILURE;
+
+ m_incomingServers.InsertOrUpdate(key, server);
+
+ // now add all listeners that are supposed to be
+ // waiting on root folders
+ nsCOMPtr<nsIMsgFolder> rootFolder;
+ rv = server->GetRootFolder(getter_AddRefs(rootFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsTObserverArray<nsCOMPtr<nsIFolderListener>>::ForwardIterator iter(
+ mFolderListeners);
+ while (iter.HasMore()) {
+ rootFolder->AddFolderListener(iter.GetNext());
+ }
+
+ server.forget(aServer);
+ return NS_OK;
+}
+
+void nsMsgAccountManager::removeListenersFromFolder(nsIMsgFolder* aFolder) {
+ nsTObserverArray<nsCOMPtr<nsIFolderListener>>::ForwardIterator iter(
+ mFolderListeners);
+ while (iter.HasMore()) {
+ aFolder->RemoveFolderListener(iter.GetNext());
+ }
+}
+
+NS_IMETHODIMP
+nsMsgAccountManager::RemoveAccount(nsIMsgAccount* aAccount,
+ bool aRemoveFiles = false) {
+ NS_ENSURE_ARG_POINTER(aAccount);
+ // Hold account in scope while we tidy up potentially-shared identities.
+ nsresult rv = LoadAccounts();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!m_accounts.RemoveElement(aAccount)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ rv = OutputAccountsPref();
+ // If we couldn't write out the pref, restore the account.
+ if (NS_FAILED(rv)) {
+ m_accounts.AppendElement(aAccount);
+ return rv;
+ }
+
+ // If it's the default, choose a new default account.
+ if (m_defaultAccount == aAccount) AutosetDefaultAccount();
+
+ // XXX - need to figure out if this is the last time this server is
+ // being used, and only send notification then.
+ // (and only remove from hashtable then too!)
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = aAccount->GetIncomingServer(getter_AddRefs(server));
+ if (NS_SUCCEEDED(rv) && server) RemoveIncomingServer(server, aRemoveFiles);
+
+ nsTArray<RefPtr<nsIMsgIdentity>> identities;
+ rv = aAccount->GetIdentities(identities);
+ if (NS_SUCCEEDED(rv)) {
+ for (auto identity : identities) {
+ bool identityStillUsed = false;
+ // for each identity, see if any remaining account still uses it,
+ // and if not, clear it.
+ // Note that we are also searching here accounts with missing servers from
+ // unloaded extension types.
+ for (auto account : m_accounts) {
+ nsTArray<RefPtr<nsIMsgIdentity>> existingIdentities;
+ account->GetIdentities(existingIdentities);
+ auto pos = existingIdentities.IndexOf(identity);
+ if (pos != existingIdentities.NoIndex) {
+ identityStillUsed = true;
+ break;
+ }
+ }
+ // clear out all identity information if no other account uses it.
+ if (!identityStillUsed) identity->ClearAllValues();
+ }
+ }
+
+ nsCString accountKey;
+ aAccount->GetKey(accountKey);
+
+ // It is not a critical problem if this fails as the account was already
+ // removed from the list of accounts so should not ever be referenced.
+ // Just print it out for debugging.
+ rv = aAccount->ClearAllValues();
+ NS_ASSERTION(NS_SUCCEEDED(rv), "removing of account prefs failed");
+
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ if (obs) {
+ obs->NotifyObservers(nullptr, "message-account-removed",
+ NS_ConvertUTF8toUTF16(accountKey).get());
+ }
+ return NS_OK;
+}
+
+nsresult nsMsgAccountManager::OutputAccountsPref() {
+ nsCString accountKey;
+ mAccountKeyList.Truncate();
+
+ for (uint32_t index = 0; index < m_accounts.Length(); index++) {
+ m_accounts[index]->GetKey(accountKey);
+ if (index) mAccountKeyList.Append(ACCOUNT_DELIMITER);
+ mAccountKeyList.Append(accountKey);
+ }
+ return m_prefs->SetCharPref(PREF_MAIL_ACCOUNTMANAGER_ACCOUNTS,
+ mAccountKeyList);
+}
+
+/**
+ * Get the default account. If no default account, return null.
+ */
+NS_IMETHODIMP
+nsMsgAccountManager::GetDefaultAccount(nsIMsgAccount** aDefaultAccount) {
+ NS_ENSURE_ARG_POINTER(aDefaultAccount);
+
+ nsresult rv = LoadAccounts();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!m_defaultAccount) {
+ nsCString defaultKey;
+ rv = m_prefs->GetCharPref(PREF_MAIL_ACCOUNTMANAGER_DEFAULTACCOUNT,
+ defaultKey);
+ if (NS_SUCCEEDED(rv)) {
+ rv = GetAccount(defaultKey, getter_AddRefs(m_defaultAccount));
+ if (NS_SUCCEEDED(rv) && m_defaultAccount) {
+ bool canBeDefault = false;
+ rv = CheckDefaultAccount(m_defaultAccount, canBeDefault);
+ if (NS_FAILED(rv) || !canBeDefault) m_defaultAccount = nullptr;
+ }
+ }
+ }
+
+ NS_IF_ADDREF(*aDefaultAccount = m_defaultAccount);
+ return NS_OK;
+}
+
+/**
+ * Check if the given account can be default.
+ */
+nsresult nsMsgAccountManager::CheckDefaultAccount(nsIMsgAccount* aAccount,
+ bool& aCanBeDefault) {
+ aCanBeDefault = false;
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ // Server could be null if created by an unloaded extension.
+ nsresult rv = aAccount->GetIncomingServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (server) {
+ // Check if server can be default.
+ rv = server->GetCanBeDefaultServer(&aCanBeDefault);
+ }
+ return rv;
+}
+
+/**
+ * Pick the first account that can be default and make it the default.
+ */
+nsresult nsMsgAccountManager::AutosetDefaultAccount() {
+ for (nsIMsgAccount* account : m_accounts) {
+ bool canBeDefault = false;
+ nsresult rv = CheckDefaultAccount(account, canBeDefault);
+ if (NS_SUCCEEDED(rv) && canBeDefault) {
+ return SetDefaultAccount(account);
+ }
+ }
+
+ // No accounts can be the default. Clear it.
+ if (m_defaultAccount) {
+ nsCOMPtr<nsIMsgAccount> oldAccount = m_defaultAccount;
+ m_defaultAccount = nullptr;
+ (void)setDefaultAccountPref(nullptr);
+ (void)notifyDefaultServerChange(oldAccount, nullptr);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgAccountManager::SetDefaultAccount(nsIMsgAccount* aDefaultAccount) {
+ if (!aDefaultAccount) return NS_ERROR_INVALID_ARG;
+
+ if (m_defaultAccount != aDefaultAccount) {
+ bool canBeDefault = false;
+ nsresult rv = CheckDefaultAccount(aDefaultAccount, canBeDefault);
+ if (NS_FAILED(rv) || !canBeDefault) {
+ // Report failure if we were explicitly asked to use an unusable server.
+ return NS_ERROR_INVALID_ARG;
+ }
+ nsCOMPtr<nsIMsgAccount> oldAccount = m_defaultAccount;
+ m_defaultAccount = aDefaultAccount;
+ (void)setDefaultAccountPref(aDefaultAccount);
+ (void)notifyDefaultServerChange(oldAccount, aDefaultAccount);
+ }
+ return NS_OK;
+}
+
+// fire notifications
+nsresult nsMsgAccountManager::notifyDefaultServerChange(
+ nsIMsgAccount* aOldAccount, nsIMsgAccount* aNewAccount) {
+ nsresult rv;
+
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ nsCOMPtr<nsIMsgFolder> rootFolder;
+
+ // first tell old server it's no longer the default
+ if (aOldAccount) {
+ rv = aOldAccount->GetIncomingServer(getter_AddRefs(server));
+ if (NS_SUCCEEDED(rv) && server) {
+ rv = server->GetRootFolder(getter_AddRefs(rootFolder));
+ if (NS_SUCCEEDED(rv) && rootFolder)
+ rootFolder->NotifyBoolPropertyChanged(kDefaultServer, true, false);
+ }
+ }
+
+ // now tell new server it is.
+ if (aNewAccount) {
+ rv = aNewAccount->GetIncomingServer(getter_AddRefs(server));
+ if (NS_SUCCEEDED(rv) && server) {
+ rv = server->GetRootFolder(getter_AddRefs(rootFolder));
+ if (NS_SUCCEEDED(rv) && rootFolder)
+ rootFolder->NotifyBoolPropertyChanged(kDefaultServer, false, true);
+ }
+ }
+
+ // only notify if the user goes and changes default account
+ if (aOldAccount && aNewAccount) {
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+
+ if (observerService)
+ observerService->NotifyObservers(nullptr, "mailDefaultAccountChanged",
+ nullptr);
+ }
+
+ return NS_OK;
+}
+
+nsresult nsMsgAccountManager::setDefaultAccountPref(
+ nsIMsgAccount* aDefaultAccount) {
+ nsresult rv;
+
+ if (aDefaultAccount) {
+ nsCString key;
+ rv = aDefaultAccount->GetKey(key);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = m_prefs->SetCharPref(PREF_MAIL_ACCOUNTMANAGER_DEFAULTACCOUNT, key);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else
+ m_prefs->ClearUserPref(PREF_MAIL_ACCOUNTMANAGER_DEFAULTACCOUNT);
+
+ return NS_OK;
+}
+
+void nsMsgAccountManager::LogoutOfServer(nsIMsgIncomingServer* aServer) {
+ if (!aServer) return;
+ mozilla::DebugOnly<nsresult> rv = aServer->Shutdown();
+ NS_ASSERTION(NS_SUCCEEDED(rv), "Shutdown of server failed");
+ rv = aServer->ForgetSessionPassword(false);
+ NS_ASSERTION(NS_SUCCEEDED(rv),
+ "failed to remove the password associated with server");
+}
+
+NS_IMETHODIMP nsMsgAccountManager::GetFolderCache(
+ nsIMsgFolderCache** aFolderCache) {
+ NS_ENSURE_ARG_POINTER(aFolderCache);
+
+ if (m_msgFolderCache) {
+ NS_IF_ADDREF(*aFolderCache = m_msgFolderCache);
+ return NS_OK;
+ }
+
+ // Create the foldercache.
+ nsCOMPtr<nsIFile> cacheFile;
+ nsresult rv = NS_GetSpecialDirectory(NS_APP_MESSENGER_FOLDER_CACHE_50_FILE,
+ getter_AddRefs(cacheFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIFile> legacyFile;
+ rv = NS_GetSpecialDirectory(NS_APP_MESSENGER_LEGACY_FOLDER_CACHE_50_FILE,
+ getter_AddRefs(legacyFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+ m_msgFolderCache = new nsMsgFolderCache();
+ m_msgFolderCache->Init(cacheFile, legacyFile);
+ NS_IF_ADDREF(*aFolderCache = m_msgFolderCache);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgAccountManager::GetAccounts(nsTArray<RefPtr<nsIMsgAccount>>& accounts) {
+ nsresult rv = LoadAccounts();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ accounts.Clear();
+ accounts.SetCapacity(m_accounts.Length());
+ for (auto existingAccount : m_accounts) {
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ existingAccount->GetIncomingServer(getter_AddRefs(server));
+ if (!server) continue;
+ if (server) {
+ bool hidden = false;
+ server->GetHidden(&hidden);
+ if (hidden) continue;
+ }
+ accounts.AppendElement(existingAccount);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgAccountManager::GetAllIdentities(
+ nsTArray<RefPtr<nsIMsgIdentity>>& result) {
+ nsresult rv = LoadAccounts();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ result.Clear();
+
+ for (auto account : m_accounts) {
+ nsTArray<RefPtr<nsIMsgIdentity>> identities;
+ rv = account->GetIdentities(identities);
+ if (NS_FAILED(rv)) continue;
+
+ for (auto identity : identities) {
+ // Have we already got this identity?
+ nsAutoCString key;
+ rv = identity->GetKey(key);
+ if (NS_FAILED(rv)) continue;
+
+ bool found = false;
+ for (auto thisIdentity : result) {
+ nsAutoCString thisKey;
+ rv = thisIdentity->GetKey(thisKey);
+ if (NS_FAILED(rv)) continue;
+
+ if (key == thisKey) {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) result.AppendElement(identity);
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgAccountManager::GetAllServers(
+ nsTArray<RefPtr<nsIMsgIncomingServer>>& servers) {
+ servers.Clear();
+ nsresult rv = LoadAccounts();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (auto iter = m_incomingServers.Iter(); !iter.Done(); iter.Next()) {
+ nsCOMPtr<nsIMsgIncomingServer>& server = iter.Data();
+ if (!server) continue;
+
+ bool hidden = false;
+ server->GetHidden(&hidden);
+ if (hidden) continue;
+
+ nsCString type;
+ if (NS_FAILED(server->GetType(type))) {
+ NS_WARNING("server->GetType() failed");
+ continue;
+ }
+
+ if (!type.EqualsLiteral("im")) {
+ servers.AppendElement(server);
+ }
+ }
+ return NS_OK;
+}
+
+nsresult nsMsgAccountManager::LoadAccounts() {
+ nsresult rv;
+
+ // for now safeguard multiple calls to this function
+ if (m_accountsLoaded) return NS_OK;
+
+ // If we have code trying to do things after we've unloaded accounts,
+ // ignore it.
+ if (m_shutdownInProgress || m_haveShutdown) return NS_ERROR_FAILURE;
+
+ // Make sure correct modules are loaded before creating any server.
+ nsCOMPtr<nsIObserver> moduleLoader;
+ moduleLoader =
+ do_GetService("@mozilla.org/messenger/imap-module-loader;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgMailSession> mailSession =
+ do_GetService("@mozilla.org/messenger/services/session;1", &rv);
+
+ if (NS_SUCCEEDED(rv))
+ mailSession->AddFolderListener(
+ this, nsIFolderListener::added | nsIFolderListener::removed |
+ nsIFolderListener::intPropertyChanged);
+
+ // Ensure biff service has started
+ nsCOMPtr<nsIMsgBiffManager> biffService =
+ do_GetService("@mozilla.org/messenger/biffManager;1", &rv);
+
+ if (NS_SUCCEEDED(rv)) biffService->Init();
+
+ // Ensure purge service has started
+ nsCOMPtr<nsIMsgPurgeService> purgeService =
+ do_GetService("@mozilla.org/messenger/purgeService;1", &rv);
+
+ if (NS_SUCCEEDED(rv)) purgeService->Init();
+
+ nsCOMPtr<nsIPrefService> prefservice(
+ do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // mail.accountmanager.accounts is the main entry point for all accounts
+ nsCString accountList;
+ rv = m_prefs->GetCharPref(PREF_MAIL_ACCOUNTMANAGER_ACCOUNTS, accountList);
+
+ /**
+ * Check to see if we need to add pre-configured accounts.
+ * Following prefs are important to note in understanding the procedure here.
+ *
+ * 1. pref("mailnews.append_preconfig_accounts.version", version number);
+ * This pref registers the current version in the user prefs file. A default
+ * value is stored in mailnews.js file. If a given vendor needs to add more
+ * preconfigured accounts, the default version number can be increased.
+ * Comparing version number from user's prefs file and the default one from
+ * mailnews.js, we can add new accounts and any other version level changes
+ * that need to be done.
+ *
+ * 2. pref("mail.accountmanager.appendaccounts", <comma sep. account list>);
+ * This pref contains the list of pre-configured accounts that ISP/Vendor
+ * wants to add to the existing accounts list.
+ */
+ nsCOMPtr<nsIPrefBranch> defaultsPrefBranch;
+ rv = prefservice->GetDefaultBranch(MAILNEWS_ROOT_PREF,
+ getter_AddRefs(defaultsPrefBranch));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIPrefBranch> prefBranch;
+ rv = prefservice->GetBranch(MAILNEWS_ROOT_PREF, getter_AddRefs(prefBranch));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t appendAccountsCurrentVersion = 0;
+ int32_t appendAccountsDefaultVersion = 0;
+ rv = prefBranch->GetIntPref(APPEND_ACCOUNTS_VERSION_PREF_NAME,
+ &appendAccountsCurrentVersion);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = defaultsPrefBranch->GetIntPref(APPEND_ACCOUNTS_VERSION_PREF_NAME,
+ &appendAccountsDefaultVersion);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Update the account list if needed
+ if ((appendAccountsCurrentVersion <= appendAccountsDefaultVersion)) {
+ // Get a list of pre-configured accounts
+ nsCString appendAccountList;
+ rv = m_prefs->GetCharPref(PREF_MAIL_ACCOUNTMANAGER_APPEND_ACCOUNTS,
+ appendAccountList);
+ appendAccountList.StripWhitespace();
+
+ // If there are pre-configured accounts, we need to add them to the
+ // existing list.
+ if (!appendAccountList.IsEmpty()) {
+ if (!accountList.IsEmpty()) {
+ // Tokenize the data and add each account
+ // in the user's current mailnews account list
+ nsTArray<nsCString> accountsArray;
+ ParseString(accountList, ACCOUNT_DELIMITER, accountsArray);
+ uint32_t i = accountsArray.Length();
+
+ // Append each account in the pre-configured account list
+ ParseString(appendAccountList, ACCOUNT_DELIMITER, accountsArray);
+
+ // Now add each account that does not already appear in the list
+ for (; i < accountsArray.Length(); i++) {
+ if (accountsArray.IndexOf(accountsArray[i]) == i) {
+ accountList.Append(ACCOUNT_DELIMITER);
+ accountList.Append(accountsArray[i]);
+ }
+ }
+ } else {
+ accountList = appendAccountList;
+ }
+ // Increase the version number so that updates will happen as and when
+ // needed
+ rv = prefBranch->SetIntPref(APPEND_ACCOUNTS_VERSION_PREF_NAME,
+ appendAccountsCurrentVersion + 1);
+ }
+ }
+
+ // It is ok to return null accounts like when we create new profile.
+ m_accountsLoaded = true;
+ m_haveShutdown = false;
+
+ if (accountList.IsEmpty()) return NS_OK;
+
+ /* parse accountList and run loadAccount on each string, comma-separated */
+ nsCOMPtr<nsIMsgAccount> account;
+ // Tokenize the data and add each account
+ // in the user's current mailnews account list
+ nsTArray<nsCString> accountsArray;
+ ParseString(accountList, ACCOUNT_DELIMITER, accountsArray);
+
+ // These are the duplicate accounts we found. We keep track of these
+ // because if any other server defers to one of these accounts, we need
+ // to defer to the correct account.
+ nsCOMArray<nsIMsgAccount> dupAccounts;
+
+ // Now add each account that does not already appear in the list
+ for (uint32_t i = 0; i < accountsArray.Length(); i++) {
+ // if we've already seen this exact account, advance to the next account.
+ // After the loop, we'll notice that we don't have as many actual accounts
+ // as there were accounts in the pref, and rewrite the pref.
+ if (accountsArray.IndexOf(accountsArray[i]) != i) continue;
+
+ // get the "server" pref to see if we already have an account with this
+ // server. If we do, we ignore this account.
+ nsAutoCString serverKeyPref("mail.account.");
+ serverKeyPref += accountsArray[i];
+
+ nsCOMPtr<nsIPrefBranch> accountPrefBranch;
+ rv = prefservice->GetBranch(serverKeyPref.get(),
+ getter_AddRefs(accountPrefBranch));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ serverKeyPref += ".server";
+ nsCString serverKey;
+ rv = m_prefs->GetCharPref(serverKeyPref.get(), serverKey);
+ if (NS_FAILED(rv)) continue;
+
+ nsCOMPtr<nsIMsgAccount> serverAccount;
+ findAccountByServerKey(serverKey, getter_AddRefs(serverAccount));
+ // If we have an existing account with the same server, ignore this account
+ if (serverAccount) continue;
+
+ if (NS_FAILED(createKeyedAccount(accountsArray[i], true,
+ getter_AddRefs(account))) ||
+ !account) {
+ NS_WARNING("unexpected entry in account list; prefs corrupt?");
+ continue;
+ }
+
+ // See nsIMsgAccount.idl for a description of the secondsToLeaveUnavailable
+ // and timeFoundUnavailable preferences
+ nsAutoCString toLeavePref(PREF_MAIL_SERVER_PREFIX);
+ toLeavePref.Append(serverKey);
+ nsAutoCString unavailablePref(
+ toLeavePref); // this is the server-specific prefix
+ unavailablePref.AppendLiteral(".timeFoundUnavailable");
+ toLeavePref.AppendLiteral(".secondsToLeaveUnavailable");
+ int32_t secondsToLeave = 0;
+ int32_t timeUnavailable = 0;
+
+ m_prefs->GetIntPref(toLeavePref.get(), &secondsToLeave);
+
+ // force load of accounts (need to find a better way to do this)
+ nsTArray<RefPtr<nsIMsgIdentity>> unused;
+ account->GetIdentities(unused);
+
+ rv = account->CreateServer();
+ bool deleteAccount = NS_FAILED(rv);
+
+ if (secondsToLeave) { // we need to process timeUnavailable
+ if (NS_SUCCEEDED(rv)) // clear the time if server is available
+ {
+ m_prefs->ClearUserPref(unavailablePref.get());
+ }
+ // NS_ERROR_NOT_AVAILABLE signifies a server that could not be
+ // instantiated, presumably because of an invalid type.
+ else if (rv == NS_ERROR_NOT_AVAILABLE) {
+ m_prefs->GetIntPref(unavailablePref.get(), &timeUnavailable);
+ if (!timeUnavailable) { // we need to set it, this must be the first
+ // time unavailable
+ uint32_t nowSeconds;
+ PRTime2Seconds(PR_Now(), &nowSeconds);
+ m_prefs->SetIntPref(unavailablePref.get(), nowSeconds);
+ deleteAccount = false;
+ }
+ }
+ }
+
+ // Our server is still unavailable. Have we timed out yet?
+ if (rv == NS_ERROR_NOT_AVAILABLE && timeUnavailable != 0) {
+ uint32_t nowSeconds;
+ PRTime2Seconds(PR_Now(), &nowSeconds);
+ if ((int32_t)nowSeconds < timeUnavailable + secondsToLeave)
+ deleteAccount = false;
+ }
+
+ if (deleteAccount) {
+ dupAccounts.AppendObject(account);
+ m_accounts.RemoveElement(account);
+ }
+ }
+
+ // Check if we removed one or more of the accounts in the pref string.
+ // If so, rewrite the pref string.
+ if (accountsArray.Length() != m_accounts.Length()) OutputAccountsPref();
+
+ int32_t cnt = dupAccounts.Count();
+ nsCOMPtr<nsIMsgAccount> dupAccount;
+
+ // Go through the accounts seeing if any existing server is deferred to
+ // an account we removed. If so, fix the deferral. Then clean up the prefs
+ // for the removed account.
+ for (int32_t i = 0; i < cnt; i++) {
+ dupAccount = dupAccounts[i];
+ for (auto iter = m_incomingServers.Iter(); !iter.Done(); iter.Next()) {
+ /*
+ * This loop gets run for every incoming server, and is passed a
+ * duplicate account. It checks that the server is not deferred to the
+ * duplicate account. If it is, then it looks up the information for the
+ * duplicate account's server (username, hostName, type), and finds an
+ * account with a server with the same username, hostname, and type, and
+ * if it finds one, defers to that account instead. Generally, this will
+ * be a Local Folders account, since 2.0 has a bug where duplicate Local
+ * Folders accounts are created.
+ */
+ nsCOMPtr<nsIMsgIncomingServer>& server = iter.Data();
+ nsCString type;
+ server->GetType(type);
+ if (type.EqualsLiteral("pop3")) {
+ nsCString deferredToAccount;
+ // Get the pref directly, because the GetDeferredToAccount accessor
+ // attempts to fix broken deferrals, but we know more about what the
+ // deferred to account was.
+ server->GetCharValue("deferred_to_account", deferredToAccount);
+ if (!deferredToAccount.IsEmpty()) {
+ nsCString dupAccountKey;
+ dupAccount->GetKey(dupAccountKey);
+ if (deferredToAccount.Equals(dupAccountKey)) {
+ nsresult rv;
+ nsCString accountPref("mail.account.");
+ nsCString dupAccountServerKey;
+ accountPref.Append(dupAccountKey);
+ accountPref.AppendLiteral(".server");
+ nsCOMPtr<nsIPrefService> prefservice(
+ do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ if (NS_FAILED(rv)) {
+ continue;
+ }
+ nsCOMPtr<nsIPrefBranch> prefBranch(
+ do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ if (NS_FAILED(rv)) {
+ continue;
+ }
+ rv =
+ prefBranch->GetCharPref(accountPref.get(), dupAccountServerKey);
+ if (NS_FAILED(rv)) {
+ continue;
+ }
+ nsCOMPtr<nsIPrefBranch> serverPrefBranch;
+ nsCString serverKeyPref(PREF_MAIL_SERVER_PREFIX);
+ serverKeyPref.Append(dupAccountServerKey);
+ serverKeyPref.Append('.');
+ rv = prefservice->GetBranch(serverKeyPref.get(),
+ getter_AddRefs(serverPrefBranch));
+ if (NS_FAILED(rv)) {
+ continue;
+ }
+ nsCString userName;
+ nsCString hostName;
+ nsCString type;
+ serverPrefBranch->GetCharPref("userName", userName);
+ serverPrefBranch->GetCharPref("hostname", hostName);
+ serverPrefBranch->GetCharPref("type", type);
+ // Find a server with the same info.
+ nsCOMPtr<nsIMsgAccountManager> accountManager =
+ do_GetService("@mozilla.org/messenger/account-manager;1", &rv);
+ if (NS_FAILED(rv)) {
+ continue;
+ }
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ accountManager->FindServer(userName, hostName, type, 0,
+ getter_AddRefs(server));
+ if (server) {
+ nsCOMPtr<nsIMsgAccount> replacement;
+ accountManager->FindAccountForServer(server,
+ getter_AddRefs(replacement));
+ if (replacement) {
+ nsCString accountKey;
+ replacement->GetKey(accountKey);
+ if (!accountKey.IsEmpty())
+ server->SetCharValue("deferred_to_account", accountKey);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ nsAutoCString accountKeyPref("mail.account.");
+ nsCString dupAccountKey;
+ dupAccount->GetKey(dupAccountKey);
+ if (dupAccountKey.IsEmpty()) continue;
+ accountKeyPref.Append(dupAccountKey);
+ accountKeyPref.Append('.');
+
+ nsCOMPtr<nsIPrefBranch> accountPrefBranch;
+ rv = prefservice->GetBranch(accountKeyPref.get(),
+ getter_AddRefs(accountPrefBranch));
+ if (accountPrefBranch) {
+ nsTArray<nsCString> prefNames;
+ nsresult rv = accountPrefBranch->GetChildList("", prefNames);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (auto& prefName : prefNames) {
+ accountPrefBranch->ClearUserPref(prefName.get());
+ }
+ }
+ }
+
+ // Make sure we have an account that points at the local folders server
+ nsCString localFoldersServerKey;
+ rv = m_prefs->GetCharPref(PREF_MAIL_ACCOUNTMANAGER_LOCALFOLDERSSERVER,
+ localFoldersServerKey);
+
+ if (!localFoldersServerKey.IsEmpty()) {
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = GetIncomingServer(localFoldersServerKey, getter_AddRefs(server));
+ if (server) {
+ nsCOMPtr<nsIMsgAccount> localFoldersAccount;
+ findAccountByServerKey(localFoldersServerKey,
+ getter_AddRefs(localFoldersAccount));
+ // If we don't have an existing account pointing at the local folders
+ // server, we're going to add one.
+ if (!localFoldersAccount) {
+ nsCOMPtr<nsIMsgAccount> account;
+ (void)CreateAccount(getter_AddRefs(account));
+ if (account) account->SetIncomingServer(server);
+ }
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgAccountManager::ReactivateAccounts() {
+ for (nsIMsgAccount* account : m_accounts) {
+ // This will error out if the account already has its server, or
+ // if this isn't the account that the extension is trying to reactivate.
+ if (NS_SUCCEEDED(account->CreateServer())) {
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ account->GetIncomingServer(getter_AddRefs(server));
+ // This triggers all of the notifications required by the UI.
+ account->SetIncomingServer(server);
+ }
+ }
+ return NS_OK;
+}
+
+// this routine goes through all the identities and makes sure
+// that the special folders for each identity have the
+// correct special folder flags set, e.g, the Sent folder has
+// the sent flag set.
+//
+// it also goes through all the spam settings for each account
+// and makes sure the folder flags are set there, too
+NS_IMETHODIMP
+nsMsgAccountManager::SetSpecialFolders() {
+ nsTArray<RefPtr<nsIMsgIdentity>> identities;
+ GetAllIdentities(identities);
+
+ for (auto identity : identities) {
+ nsresult rv;
+ nsCString folderUri;
+ nsCOMPtr<nsIMsgFolder> folder;
+
+ identity->GetFccFolder(folderUri);
+ if (!folderUri.IsEmpty() &&
+ NS_SUCCEEDED(GetOrCreateFolder(folderUri, getter_AddRefs(folder)))) {
+ nsCOMPtr<nsIMsgFolder> parent;
+ rv = folder->GetParent(getter_AddRefs(parent));
+ if (NS_SUCCEEDED(rv) && parent) {
+ rv = folder->SetFlag(nsMsgFolderFlags::SentMail);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ identity->GetDraftFolder(folderUri);
+ if (!folderUri.IsEmpty() &&
+ NS_SUCCEEDED(GetOrCreateFolder(folderUri, getter_AddRefs(folder)))) {
+ nsCOMPtr<nsIMsgFolder> parent;
+ rv = folder->GetParent(getter_AddRefs(parent));
+ if (NS_SUCCEEDED(rv) && parent) {
+ rv = folder->SetFlag(nsMsgFolderFlags::Drafts);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ identity->GetArchiveFolder(folderUri);
+ if (!folderUri.IsEmpty() &&
+ NS_SUCCEEDED(GetOrCreateFolder(folderUri, getter_AddRefs(folder)))) {
+ nsCOMPtr<nsIMsgFolder> parent;
+ rv = folder->GetParent(getter_AddRefs(parent));
+ if (NS_SUCCEEDED(rv) && parent) {
+ bool archiveEnabled;
+ identity->GetArchiveEnabled(&archiveEnabled);
+ if (archiveEnabled)
+ rv = folder->SetFlag(nsMsgFolderFlags::Archive);
+ else
+ rv = folder->ClearFlag(nsMsgFolderFlags::Archive);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ identity->GetStationeryFolder(folderUri);
+ if (!folderUri.IsEmpty() &&
+ NS_SUCCEEDED(GetOrCreateFolder(folderUri, getter_AddRefs(folder)))) {
+ nsCOMPtr<nsIMsgFolder> parent;
+ rv = folder->GetParent(getter_AddRefs(parent));
+ if (NS_SUCCEEDED(rv) && parent) {
+ folder->SetFlag(nsMsgFolderFlags::Templates);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+ }
+
+ // XXX todo
+ // get all servers
+ // get all spam settings for each server
+ // set the JUNK folder flag on the spam folders, right?
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgAccountManager::UnloadAccounts() {
+ // release the default account
+ m_defaultAccount = nullptr;
+ for (auto iter = m_incomingServers.Iter(); !iter.Done(); iter.Next()) {
+ nsCOMPtr<nsIMsgIncomingServer>& server = iter.Data();
+ if (!server) continue;
+ nsresult rv;
+ NotifyServerUnloaded(server);
+
+ nsCOMPtr<nsIMsgFolder> rootFolder;
+ rv = server->GetRootFolder(getter_AddRefs(rootFolder));
+ if (NS_SUCCEEDED(rv)) {
+ removeListenersFromFolder(rootFolder);
+
+ rootFolder->Shutdown(true);
+ }
+ }
+
+ m_accounts.Clear(); // will release all elements
+ m_identities.Clear();
+ m_incomingServers.Clear();
+ mAccountKeyList.Truncate();
+ SetLastServerFound(nullptr, EmptyCString(), EmptyCString(), 0,
+ EmptyCString());
+
+ if (m_accountsLoaded) {
+ nsCOMPtr<nsIMsgMailSession> mailSession =
+ do_GetService("@mozilla.org/messenger/services/session;1");
+ if (mailSession) mailSession->RemoveFolderListener(this);
+ m_accountsLoaded = false;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgAccountManager::ShutdownServers() {
+ for (auto iter = m_incomingServers.Iter(); !iter.Done(); iter.Next()) {
+ nsCOMPtr<nsIMsgIncomingServer>& server = iter.Data();
+ if (server) server->Shutdown();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgAccountManager::CloseCachedConnections() {
+ for (auto iter = m_incomingServers.Iter(); !iter.Done(); iter.Next()) {
+ nsCOMPtr<nsIMsgIncomingServer>& server = iter.Data();
+ if (server) server->CloseCachedConnections();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgAccountManager::CleanupOnExit() {
+ // This can get called multiple times, and potentially re-entrantly.
+ // So add some protection against that.
+ if (m_shutdownInProgress) return NS_OK;
+
+ m_shutdownInProgress = true;
+
+ nsresult rv;
+ // If enabled, clear cache on shutdown. This is common to all accounts.
+ bool clearCache = false;
+ m_prefs->GetBoolPref("privacy.clearOnShutdown.cache", &clearCache);
+ if (clearCache) {
+ nsCOMPtr<nsICacheStorageService> cacheStorageService =
+ do_GetService("@mozilla.org/netwerk/cache-storage-service;1", &rv);
+ if (NS_SUCCEEDED(rv)) cacheStorageService->Clear();
+ }
+
+ for (auto iter = m_incomingServers.Iter(); !iter.Done(); iter.Next()) {
+ nsCOMPtr<nsIMsgIncomingServer>& server = iter.Data();
+
+ bool emptyTrashOnExit = false;
+ bool cleanupInboxOnExit = false;
+
+ if (WeAreOffline()) break;
+
+ if (!server) continue;
+
+ server->GetEmptyTrashOnExit(&emptyTrashOnExit);
+ nsCOMPtr<nsIImapIncomingServer> imapserver = do_QueryInterface(server);
+ if (imapserver) {
+ imapserver->GetCleanupInboxOnExit(&cleanupInboxOnExit);
+ imapserver->SetShuttingDown(true);
+ }
+ if (emptyTrashOnExit || cleanupInboxOnExit) {
+ nsCOMPtr<nsIMsgFolder> root;
+ server->GetRootFolder(getter_AddRefs(root));
+ nsCString type;
+ server->GetType(type);
+ if (root) {
+ nsString passwd;
+ int32_t authMethod = 0;
+ bool serverRequiresPasswordForAuthentication = true;
+ bool isImap = type.EqualsLiteral("imap");
+ if (isImap) {
+ server->GetServerRequiresPasswordForBiff(
+ &serverRequiresPasswordForAuthentication);
+ server->GetPassword(passwd);
+ server->GetAuthMethod(&authMethod);
+ }
+ if (!isImap || (isImap && (!serverRequiresPasswordForAuthentication ||
+ !passwd.IsEmpty() ||
+ authMethod == nsMsgAuthMethod::OAuth2))) {
+ nsCOMPtr<nsIMsgAccountManager> accountManager =
+ do_GetService("@mozilla.org/messenger/account-manager;1", &rv);
+ if (NS_FAILED(rv)) continue;
+
+ if (isImap && cleanupInboxOnExit) {
+ // Find the inbox.
+ nsTArray<RefPtr<nsIMsgFolder>> subFolders;
+ rv = root->GetSubFolders(subFolders);
+ if (NS_SUCCEEDED(rv)) {
+ for (nsIMsgFolder* folder : subFolders) {
+ uint32_t flags;
+ folder->GetFlags(&flags);
+ if (flags & nsMsgFolderFlags::Inbox) {
+ // This is inbox, so Compact() it. There's an implied
+ // Expunge too, because this is IMAP.
+ RefPtr<UrlListener> cleanupListener = new UrlListener();
+ RefPtr<nsMsgAccountManager> self = this;
+ // This runs when the compaction (+expunge) is complete.
+ cleanupListener->mStopFn =
+ [self](nsIURI* url, nsresult status) -> nsresult {
+ if (self->m_folderDoingCleanupInbox) {
+ PR_CEnterMonitor(self->m_folderDoingCleanupInbox);
+ PR_CNotifyAll(self->m_folderDoingCleanupInbox);
+ self->m_cleanupInboxInProgress = false;
+ PR_CExitMonitor(self->m_folderDoingCleanupInbox);
+ self->m_folderDoingCleanupInbox = nullptr;
+ }
+ return NS_OK;
+ };
+
+ rv = folder->Compact(cleanupListener, nullptr);
+ if (NS_SUCCEEDED(rv))
+ accountManager->SetFolderDoingCleanupInbox(folder);
+ break;
+ }
+ }
+ }
+ }
+
+ if (emptyTrashOnExit) {
+ RefPtr<UrlListener> emptyTrashListener = new UrlListener();
+ RefPtr<nsMsgAccountManager> self = this;
+ // This runs when the trash-emptying is complete.
+ // (It'll be a nsIImapUrl::nsImapDeleteAllMsgs url).
+ emptyTrashListener->mStopFn = [self](nsIURI* url,
+ nsresult status) -> nsresult {
+ if (self->m_folderDoingEmptyTrash) {
+ PR_CEnterMonitor(self->m_folderDoingEmptyTrash);
+ PR_CNotifyAll(self->m_folderDoingEmptyTrash);
+ self->m_emptyTrashInProgress = false;
+ PR_CExitMonitor(self->m_folderDoingEmptyTrash);
+ self->m_folderDoingEmptyTrash = nullptr;
+ }
+ return NS_OK;
+ };
+
+ rv = root->EmptyTrash(emptyTrashListener);
+ if (isImap && NS_SUCCEEDED(rv))
+ accountManager->SetFolderDoingEmptyTrash(root);
+ }
+
+ if (isImap) {
+ nsCOMPtr<nsIThread> thread(do_GetCurrentThread());
+
+ // Pause until any possible inbox-compaction and trash-emptying
+ // are complete (or time out).
+ bool inProgress = false;
+ if (cleanupInboxOnExit) {
+ int32_t loopCount = 0; // used to break out after 5 seconds
+ accountManager->GetCleanupInboxInProgress(&inProgress);
+ while (inProgress && loopCount++ < 5000) {
+ accountManager->GetCleanupInboxInProgress(&inProgress);
+ PR_CEnterMonitor(root);
+ PR_CWait(root, PR_MicrosecondsToInterval(1000UL));
+ PR_CExitMonitor(root);
+ NS_ProcessPendingEvents(thread,
+ PR_MicrosecondsToInterval(1000UL));
+ }
+ }
+ if (emptyTrashOnExit) {
+ accountManager->GetEmptyTrashInProgress(&inProgress);
+ int32_t loopCount = 0;
+ while (inProgress && loopCount++ < 5000) {
+ accountManager->GetEmptyTrashInProgress(&inProgress);
+ PR_CEnterMonitor(root);
+ PR_CWait(root, PR_MicrosecondsToInterval(1000UL));
+ PR_CExitMonitor(root);
+ NS_ProcessPendingEvents(thread,
+ PR_MicrosecondsToInterval(1000UL));
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // Try to do this early on in the shutdown process before
+ // necko shuts itself down.
+ CloseCachedConnections();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgAccountManager::WriteToFolderCache(nsIMsgFolderCache* folderCache) {
+ for (auto iter = m_incomingServers.Iter(); !iter.Done(); iter.Next()) {
+ iter.Data()->WriteToFolderCache(folderCache);
+ }
+ return NS_OK;
+}
+
+nsresult nsMsgAccountManager::createKeyedAccount(const nsCString& key,
+ bool forcePositionToEnd,
+ nsIMsgAccount** aAccount) {
+ nsresult rv;
+ nsCOMPtr<nsIMsgAccount> account = do_CreateInstance(kMsgAccountCID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ account->SetKey(key);
+
+ nsCString localFoldersAccountKey;
+ nsCString lastFolderAccountKey;
+ if (!forcePositionToEnd) {
+ nsCOMPtr<nsIMsgIncomingServer> localFoldersServer;
+ rv = GetLocalFoldersServer(getter_AddRefs(localFoldersServer));
+ if (NS_SUCCEEDED(rv)) {
+ for (auto account : m_accounts) {
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = account->GetIncomingServer(getter_AddRefs(server));
+ if (NS_SUCCEEDED(rv) && server == localFoldersServer) {
+ account->GetKey(localFoldersAccountKey);
+ break;
+ }
+ }
+ }
+
+ // Extracting the account key of the last mail acoount.
+ for (int32_t index = m_accounts.Length() - 1; index >= 0; index--) {
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = m_accounts[index]->GetIncomingServer(getter_AddRefs(server));
+ if (NS_SUCCEEDED(rv) && server) {
+ nsCString accountType;
+ rv = server->GetType(accountType);
+ if (NS_SUCCEEDED(rv) && !accountType.EqualsLiteral("im")) {
+ m_accounts[index]->GetKey(lastFolderAccountKey);
+ break;
+ }
+ }
+ }
+ }
+
+ if (!forcePositionToEnd && !localFoldersAccountKey.IsEmpty() &&
+ !lastFolderAccountKey.IsEmpty() &&
+ lastFolderAccountKey == localFoldersAccountKey) {
+ // Insert account before Local Folders if that is the last account.
+ m_accounts.InsertElementAt(m_accounts.Length() - 1, account);
+ } else {
+ m_accounts.AppendElement(account);
+ }
+
+ nsCString newAccountKeyList;
+ nsCString accountKey;
+ for (uint32_t index = 0; index < m_accounts.Length(); index++) {
+ m_accounts[index]->GetKey(accountKey);
+ if (index) newAccountKeyList.Append(ACCOUNT_DELIMITER);
+ newAccountKeyList.Append(accountKey);
+ }
+ mAccountKeyList = newAccountKeyList;
+
+ m_prefs->SetCharPref(PREF_MAIL_ACCOUNTMANAGER_ACCOUNTS, mAccountKeyList);
+ account.forget(aAccount);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgAccountManager::CreateAccount(nsIMsgAccount** _retval) {
+ NS_ENSURE_ARG_POINTER(_retval);
+
+ nsAutoCString key;
+ GetUniqueAccountKey(key);
+
+ return createKeyedAccount(key, false, _retval);
+}
+
+NS_IMETHODIMP
+nsMsgAccountManager::GetAccount(const nsACString& aKey,
+ nsIMsgAccount** aAccount) {
+ NS_ENSURE_ARG_POINTER(aAccount);
+ *aAccount = nullptr;
+
+ for (uint32_t i = 0; i < m_accounts.Length(); ++i) {
+ nsCOMPtr<nsIMsgAccount> account(m_accounts[i]);
+ nsCString key;
+ account->GetKey(key);
+ if (key.Equals(aKey)) {
+ account.forget(aAccount);
+ break;
+ }
+ }
+
+ // If not found, create on demand.
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgAccountManager::FindServerIndex(nsIMsgIncomingServer* server,
+ int32_t* result) {
+ NS_ENSURE_ARG_POINTER(server);
+ NS_ENSURE_ARG_POINTER(result);
+
+ nsCString key;
+ nsresult rv = server->GetKey(key);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // do this by account because the account list is in order
+ uint32_t i;
+ for (i = 0; i < m_accounts.Length(); ++i) {
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = m_accounts[i]->GetIncomingServer(getter_AddRefs(server));
+ if (!server || NS_FAILED(rv)) continue;
+
+ nsCString serverKey;
+ rv = server->GetKey(serverKey);
+ if (NS_FAILED(rv)) continue;
+
+ // stop when found,
+ // index will be set to the current index
+ if (serverKey.Equals(key)) break;
+ }
+
+ // Even if the search failed, we can return index.
+ // This means that all servers not in the array return an index higher
+ // than all "registered" servers.
+ *result = i;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAccountManager::AddIncomingServerListener(
+ nsIIncomingServerListener* serverListener) {
+ m_incomingServerListeners.AppendObject(serverListener);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAccountManager::RemoveIncomingServerListener(
+ nsIIncomingServerListener* serverListener) {
+ m_incomingServerListeners.RemoveObject(serverListener);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAccountManager::NotifyServerLoaded(
+ nsIMsgIncomingServer* server) {
+ int32_t count = m_incomingServerListeners.Count();
+ for (int32_t i = 0; i < count; i++) {
+ nsIIncomingServerListener* listener = m_incomingServerListeners[i];
+ listener->OnServerLoaded(server);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAccountManager::NotifyServerUnloaded(
+ nsIMsgIncomingServer* server) {
+ NS_ENSURE_ARG_POINTER(server);
+
+ int32_t count = m_incomingServerListeners.Count();
+ // Clear this to cut shutdown leaks. We are always passing valid non-null
+ // server here.
+ server->SetFilterList(nullptr);
+
+ for (int32_t i = 0; i < count; i++) {
+ nsIIncomingServerListener* listener = m_incomingServerListeners[i];
+ listener->OnServerUnloaded(server);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAccountManager::NotifyServerChanged(
+ nsIMsgIncomingServer* server) {
+ int32_t count = m_incomingServerListeners.Count();
+ for (int32_t i = 0; i < count; i++) {
+ nsIIncomingServerListener* listener = m_incomingServerListeners[i];
+ listener->OnServerChanged(server);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgAccountManager::FindServerByURI(nsIURI* aURI,
+ nsIMsgIncomingServer** aResult) {
+ NS_ENSURE_ARG_POINTER(aURI);
+
+ nsresult rv = LoadAccounts();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Get username and hostname and port so we can get the server
+ nsAutoCString username;
+ nsAutoCString escapedUsername;
+ rv = aURI->GetUserPass(escapedUsername);
+ if (NS_SUCCEEDED(rv) && !escapedUsername.IsEmpty())
+ MsgUnescapeString(escapedUsername, 0, username);
+
+ nsAutoCString hostname;
+ nsAutoCString escapedHostname;
+ rv = aURI->GetHost(escapedHostname);
+ if (NS_SUCCEEDED(rv) && !escapedHostname.IsEmpty()) {
+ MsgUnescapeString(escapedHostname, 0, hostname);
+ }
+
+ nsAutoCString type;
+ rv = aURI->GetScheme(type);
+ if (NS_SUCCEEDED(rv) && !type.IsEmpty()) {
+ // Remove "-message" from the scheme in case we get called with
+ // "imap-message", "mailbox-message", or friends.
+ if (StringEndsWith(type, "-message"_ns)) type.SetLength(type.Length() - 8);
+ // now modify type if pop or news
+ if (type.EqualsLiteral("pop")) type.AssignLiteral("pop3");
+ // we use "nntp" in the server list so translate it here.
+ else if (type.EqualsLiteral("news"))
+ type.AssignLiteral("nntp");
+ // we use "any" as the wildcard type.
+ else if (type.EqualsLiteral("any"))
+ type.Truncate();
+ }
+
+ int32_t port = 0;
+ // check the port of the scheme is not 'none' or blank
+ if (!(type.EqualsLiteral("none") || type.IsEmpty())) {
+ rv = aURI->GetPort(&port);
+ // Set the port to zero if we got a -1 (use default)
+ if (NS_SUCCEEDED(rv) && (port == -1)) port = 0;
+ }
+
+ return findServerInternal(username, hostname, type, port, aResult);
+}
+
+nsresult nsMsgAccountManager::findServerInternal(
+ const nsACString& username, const nsACString& serverHostname,
+ const nsACString& type, int32_t port, nsIMsgIncomingServer** aResult) {
+ if ((m_lastFindServerUserName.Equals(username)) &&
+ (m_lastFindServerHostName.Equals(serverHostname)) &&
+ (m_lastFindServerType.Equals(type)) && (m_lastFindServerPort == port) &&
+ m_lastFindServerResult) {
+ NS_ADDREF(*aResult = m_lastFindServerResult);
+ return NS_OK;
+ }
+
+ nsresult rv;
+ nsCString hostname;
+ nsCOMPtr<nsIIDNService> idnService =
+ do_GetService("@mozilla.org/network/idn-service;1");
+ rv = idnService->Normalize(serverHostname, hostname);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (auto iter = m_incomingServers.Iter(); !iter.Done(); iter.Next()) {
+ // Find matching server by user+host+type+port.
+ nsCOMPtr<nsIMsgIncomingServer>& server = iter.Data();
+
+ if (!server) continue;
+
+ nsCString thisHostname;
+ rv = server->GetHostName(thisHostname);
+ if (NS_FAILED(rv)) continue;
+
+ rv = idnService->Normalize(thisHostname, thisHostname);
+ if (NS_FAILED(rv)) continue;
+
+ // If the hostname was a IP with trailing dot, that dot gets removed
+ // during URI mutation. We may well be here in findServerInternal to
+ // find a server from a folder URI. Remove the trailing dot so we can
+ // find the server.
+ nsCString thisHostnameNoDot(thisHostname);
+ if (!thisHostname.IsEmpty() &&
+ thisHostname.CharAt(thisHostname.Length() - 1) == '.') {
+ thisHostnameNoDot.Cut(thisHostname.Length() - 1, 1);
+ }
+
+ nsCString thisUsername;
+ rv = server->GetUsername(thisUsername);
+ if (NS_FAILED(rv)) continue;
+
+ nsCString thisType;
+ rv = server->GetType(thisType);
+ if (NS_FAILED(rv)) continue;
+
+ int32_t thisPort = -1; // use the default port identifier
+ // Don't try and get a port for the 'none' scheme
+ if (!thisType.EqualsLiteral("none")) {
+ rv = server->GetPort(&thisPort);
+ if (NS_FAILED(rv)) {
+ continue;
+ }
+ }
+
+ // treat "" as a wild card, so if the caller passed in "" for the desired
+ // attribute treat it as a match
+ if ((type.IsEmpty() || thisType.Equals(type)) &&
+ (hostname.IsEmpty() ||
+ thisHostname.Equals(hostname, nsCaseInsensitiveCStringComparator) ||
+ thisHostnameNoDot.Equals(hostname,
+ nsCaseInsensitiveCStringComparator)) &&
+ (!(port != 0) || (port == thisPort)) &&
+ (username.IsEmpty() || thisUsername.Equals(username))) {
+ // stop on first find; cache for next time
+ SetLastServerFound(server, hostname, username, port, type);
+
+ NS_ADDREF(*aResult = server); // Was populated from member variable.
+ return NS_OK;
+ }
+ }
+
+ return NS_ERROR_UNEXPECTED;
+}
+
+// Always return NS_OK;
+NS_IMETHODIMP
+nsMsgAccountManager::FindServer(const nsACString& username,
+ const nsACString& hostname,
+ const nsACString& type, int32_t port,
+ nsIMsgIncomingServer** aResult) {
+ *aResult = nullptr;
+ findServerInternal(username, hostname, type, port, aResult);
+ return NS_OK;
+}
+
+void nsMsgAccountManager::findAccountByServerKey(const nsCString& aKey,
+ nsIMsgAccount** aResult) {
+ *aResult = nullptr;
+
+ for (uint32_t i = 0; i < m_accounts.Length(); ++i) {
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ nsresult rv = m_accounts[i]->GetIncomingServer(getter_AddRefs(server));
+ if (!server || NS_FAILED(rv)) continue;
+
+ nsCString key;
+ rv = server->GetKey(key);
+ if (NS_FAILED(rv)) continue;
+
+ // if the keys are equal, the servers are equal
+ if (key.Equals(aKey)) {
+ NS_ADDREF(*aResult = m_accounts[i]);
+ break; // stop on first found account
+ }
+ }
+}
+
+NS_IMETHODIMP
+nsMsgAccountManager::FindAccountForServer(nsIMsgIncomingServer* server,
+ nsIMsgAccount** aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ if (!server) {
+ (*aResult) = nullptr;
+ return NS_OK;
+ }
+
+ nsresult rv;
+
+ nsCString key;
+ rv = server->GetKey(key);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ findAccountByServerKey(key, aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgAccountManager::GetFirstIdentityForServer(nsIMsgIncomingServer* aServer,
+ nsIMsgIdentity** aIdentity) {
+ NS_ENSURE_ARG_POINTER(aServer);
+ NS_ENSURE_ARG_POINTER(aIdentity);
+
+ nsTArray<RefPtr<nsIMsgIdentity>> identities;
+ nsresult rv = GetIdentitiesForServer(aServer, identities);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // not all servers have identities
+ // for example, Local Folders
+ if (identities.IsEmpty()) {
+ *aIdentity = nullptr;
+ } else {
+ NS_IF_ADDREF(*aIdentity = identities[0]);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgAccountManager::GetIdentitiesForServer(
+ nsIMsgIncomingServer* server,
+ nsTArray<RefPtr<nsIMsgIdentity>>& identities) {
+ NS_ENSURE_ARG_POINTER(server);
+ nsresult rv = LoadAccounts();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ identities.Clear();
+
+ nsAutoCString serverKey;
+ rv = server->GetKey(serverKey);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (auto account : m_accounts) {
+ nsCOMPtr<nsIMsgIncomingServer> thisServer;
+ rv = account->GetIncomingServer(getter_AddRefs(thisServer));
+ if (NS_FAILED(rv) || !thisServer) continue;
+
+ nsAutoCString thisServerKey;
+ rv = thisServer->GetKey(thisServerKey);
+ if (serverKey.Equals(thisServerKey)) {
+ nsTArray<RefPtr<nsIMsgIdentity>> theseIdentities;
+ rv = account->GetIdentities(theseIdentities);
+ NS_ENSURE_SUCCESS(rv, rv);
+ identities.AppendElements(theseIdentities);
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgAccountManager::GetServersForIdentity(
+ nsIMsgIdentity* aIdentity,
+ nsTArray<RefPtr<nsIMsgIncomingServer>>& servers) {
+ NS_ENSURE_ARG_POINTER(aIdentity);
+ servers.Clear();
+
+ nsresult rv;
+ rv = LoadAccounts();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (auto account : m_accounts) {
+ nsTArray<RefPtr<nsIMsgIdentity>> identities;
+ if (NS_FAILED(account->GetIdentities(identities))) continue;
+
+ nsCString identityKey;
+ aIdentity->GetKey(identityKey);
+ for (auto thisIdentity : identities) {
+ nsCString thisIdentityKey;
+ rv = thisIdentity->GetKey(thisIdentityKey);
+
+ if (NS_SUCCEEDED(rv) && identityKey.Equals(thisIdentityKey)) {
+ nsCOMPtr<nsIMsgIncomingServer> thisServer;
+ rv = account->GetIncomingServer(getter_AddRefs(thisServer));
+ if (thisServer && NS_SUCCEEDED(rv)) {
+ servers.AppendElement(thisServer);
+ break;
+ }
+ }
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgAccountManager::AddRootFolderListener(nsIFolderListener* aListener) {
+ NS_ENSURE_TRUE(aListener, NS_OK);
+ mFolderListeners.AppendElement(aListener);
+ for (auto iter = m_incomingServers.Iter(); !iter.Done(); iter.Next()) {
+ nsCOMPtr<nsIMsgFolder> rootFolder;
+ nsresult rv = iter.Data()->GetRootFolder(getter_AddRefs(rootFolder));
+ if (NS_FAILED(rv)) {
+ continue;
+ }
+ rv = rootFolder->AddFolderListener(aListener);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgAccountManager::RemoveRootFolderListener(nsIFolderListener* aListener) {
+ NS_ENSURE_TRUE(aListener, NS_OK);
+ mFolderListeners.RemoveElement(aListener);
+ for (auto iter = m_incomingServers.Iter(); !iter.Done(); iter.Next()) {
+ nsCOMPtr<nsIMsgFolder> rootFolder;
+ nsresult rv = iter.Data()->GetRootFolder(getter_AddRefs(rootFolder));
+ if (NS_FAILED(rv)) {
+ continue;
+ }
+ rv = rootFolder->RemoveFolderListener(aListener);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAccountManager::SetLocalFoldersServer(
+ nsIMsgIncomingServer* aServer) {
+ NS_ENSURE_ARG_POINTER(aServer);
+ nsCString key;
+ nsresult rv = aServer->GetKey(key);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return m_prefs->SetCharPref(PREF_MAIL_ACCOUNTMANAGER_LOCALFOLDERSSERVER, key);
+}
+
+NS_IMETHODIMP nsMsgAccountManager::GetLocalFoldersServer(
+ nsIMsgIncomingServer** aServer) {
+ NS_ENSURE_ARG_POINTER(aServer);
+
+ nsCString serverKey;
+
+ nsresult rv = m_prefs->GetCharPref(
+ PREF_MAIL_ACCOUNTMANAGER_LOCALFOLDERSSERVER, serverKey);
+
+ if (NS_SUCCEEDED(rv) && !serverKey.IsEmpty()) {
+ rv = GetIncomingServer(serverKey, aServer);
+ if (NS_SUCCEEDED(rv)) return rv;
+ // otherwise, we're going to fall through to looking for an existing local
+ // folders account, because now we fail creating one if one already exists.
+ }
+
+ // try ("nobody","Local Folders","none"), and work down to any "none" server.
+ rv = findServerInternal("nobody"_ns, "Local Folders"_ns, "none"_ns, 0,
+ aServer);
+ if (NS_FAILED(rv) || !*aServer) {
+ rv = findServerInternal("nobody"_ns, EmptyCString(), "none"_ns, 0, aServer);
+ if (NS_FAILED(rv) || !*aServer) {
+ rv = findServerInternal(EmptyCString(), "Local Folders"_ns, "none"_ns, 0,
+ aServer);
+ if (NS_FAILED(rv) || !*aServer)
+ rv = findServerInternal(EmptyCString(), EmptyCString(), "none"_ns, 0,
+ aServer);
+ }
+ }
+
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!*aServer) return NS_ERROR_FAILURE;
+
+ // we don't want the Smart Mailboxes server to be the local server.
+ bool hidden;
+ (*aServer)->GetHidden(&hidden);
+ if (hidden) return NS_ERROR_FAILURE;
+
+ rv = SetLocalFoldersServer(*aServer);
+ return rv;
+}
+
+nsresult nsMsgAccountManager::GetLocalFoldersPrettyName(
+ nsString& localFoldersName) {
+ // we don't want "nobody at Local Folders" to show up in the
+ // folder pane, so we set the pretty name to a localized "Local Folders"
+ nsCOMPtr<nsIStringBundle> bundle;
+ nsresult rv;
+ nsCOMPtr<nsIStringBundleService> sBundleService =
+ mozilla::components::StringBundle::Service();
+ NS_ENSURE_TRUE(sBundleService, NS_ERROR_UNEXPECTED);
+
+ rv = sBundleService->CreateBundle(
+ "chrome://messenger/locale/messenger.properties", getter_AddRefs(bundle));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return bundle->GetStringFromName("localFolders", localFoldersName);
+}
+
+NS_IMETHODIMP
+nsMsgAccountManager::CreateLocalMailAccount() {
+ // create the server
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ nsresult rv = CreateIncomingServer("nobody"_ns, "Local Folders"_ns, "none"_ns,
+ getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsString localFoldersName;
+ rv = GetLocalFoldersPrettyName(localFoldersName);
+ NS_ENSURE_SUCCESS(rv, rv);
+ server->SetPrettyName(localFoldersName);
+
+ nsCOMPtr<nsINoIncomingServer> noServer;
+ noServer = do_QueryInterface(server, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ // create the directory structure for old 4.x "Local Mail"
+ // under <profile dir>/Mail/Local Folders or
+ // <"mail.directory" pref>/Local Folders
+ nsCOMPtr<nsIFile> mailDir;
+ bool dirExists;
+
+ // we want <profile>/Mail
+ rv = NS_GetSpecialDirectory(NS_APP_MAIL_50_DIR, getter_AddRefs(mailDir));
+ if (NS_FAILED(rv)) return rv;
+
+ rv = mailDir->Exists(&dirExists);
+ if (NS_SUCCEEDED(rv) && !dirExists)
+ rv = mailDir->Create(nsIFile::DIRECTORY_TYPE, 0775);
+ if (NS_FAILED(rv)) return rv;
+
+ // set the default local path for "none"
+ rv = server->SetDefaultLocalPath(mailDir);
+ if (NS_FAILED(rv)) return rv;
+
+ // Create an account when valid server values are established.
+ // This will keep the status of accounts sane by avoiding the addition of
+ // incomplete accounts.
+ nsCOMPtr<nsIMsgAccount> account;
+ rv = CreateAccount(getter_AddRefs(account));
+ if (NS_FAILED(rv)) return rv;
+
+ // notice, no identity for local mail
+ // hook the server to the account
+ // after we set the server's local path
+ // (see bug #66018)
+ account->SetIncomingServer(server);
+
+ // remember this as the local folders server
+ return SetLocalFoldersServer(server);
+}
+
+NS_IMETHODIMP
+nsMsgAccountManager::SetFolderDoingEmptyTrash(nsIMsgFolder* folder) {
+ m_folderDoingEmptyTrash = folder;
+ m_emptyTrashInProgress = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgAccountManager::GetEmptyTrashInProgress(bool* bVal) {
+ NS_ENSURE_ARG_POINTER(bVal);
+ *bVal = m_emptyTrashInProgress;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgAccountManager::SetFolderDoingCleanupInbox(nsIMsgFolder* folder) {
+ m_folderDoingCleanupInbox = folder;
+ m_cleanupInboxInProgress = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgAccountManager::GetCleanupInboxInProgress(bool* bVal) {
+ NS_ENSURE_ARG_POINTER(bVal);
+ *bVal = m_cleanupInboxInProgress;
+ return NS_OK;
+}
+
+void nsMsgAccountManager::SetLastServerFound(nsIMsgIncomingServer* server,
+ const nsACString& hostname,
+ const nsACString& username,
+ const int32_t port,
+ const nsACString& type) {
+ m_lastFindServerResult = server;
+ m_lastFindServerHostName = hostname;
+ m_lastFindServerUserName = username;
+ m_lastFindServerPort = port;
+ m_lastFindServerType = type;
+}
+
+NS_IMETHODIMP
+nsMsgAccountManager::SaveAccountInfo() {
+ nsresult rv;
+ nsCOMPtr<nsIPrefService> pref(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return pref->SavePrefFile(nullptr);
+}
+
+NS_IMETHODIMP
+nsMsgAccountManager::GetChromePackageName(const nsACString& aExtensionName,
+ nsACString& aChromePackageName) {
+ nsresult rv;
+ nsCOMPtr<nsICategoryManager> catman =
+ do_GetService(NS_CATEGORYMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsISimpleEnumerator> e;
+ rv = catman->EnumerateCategory(MAILNEWS_ACCOUNTMANAGER_EXTENSIONS,
+ getter_AddRefs(e));
+ if (NS_SUCCEEDED(rv) && e) {
+ while (true) {
+ nsCOMPtr<nsISupports> supports;
+ rv = e->GetNext(getter_AddRefs(supports));
+ nsCOMPtr<nsISupportsCString> catEntry = do_QueryInterface(supports);
+ if (NS_FAILED(rv) || !catEntry) break;
+
+ nsAutoCString entryString;
+ rv = catEntry->GetData(entryString);
+ if (NS_FAILED(rv)) break;
+
+ nsCString contractidString;
+ rv = catman->GetCategoryEntry(MAILNEWS_ACCOUNTMANAGER_EXTENSIONS,
+ entryString, contractidString);
+ if (NS_FAILED(rv)) break;
+
+ nsCOMPtr<nsIMsgAccountManagerExtension> extension =
+ do_GetService(contractidString.get(), &rv);
+ if (NS_FAILED(rv) || !extension) break;
+
+ nsCString name;
+ rv = extension->GetName(name);
+ if (NS_FAILED(rv)) break;
+
+ if (name.Equals(aExtensionName))
+ return extension->GetChromePackageName(aChromePackageName);
+ }
+ }
+ return NS_ERROR_UNEXPECTED;
+}
+
+class VFChangeListenerEvent : public mozilla::Runnable {
+ public:
+ VFChangeListenerEvent(VirtualFolderChangeListener* vfChangeListener,
+ nsIMsgFolder* virtFolder, nsIMsgDatabase* virtDB)
+ : mozilla::Runnable("VFChangeListenerEvent"),
+ mVFChangeListener(vfChangeListener),
+ mFolder(virtFolder),
+ mDB(virtDB) {}
+
+ NS_IMETHOD Run() {
+ if (mVFChangeListener) mVFChangeListener->ProcessUpdateEvent(mFolder, mDB);
+ return NS_OK;
+ }
+
+ private:
+ RefPtr<VirtualFolderChangeListener> mVFChangeListener;
+ nsCOMPtr<nsIMsgFolder> mFolder;
+ nsCOMPtr<nsIMsgDatabase> mDB;
+};
+
+NS_IMPL_ISUPPORTS(VirtualFolderChangeListener, nsIDBChangeListener)
+
+VirtualFolderChangeListener::VirtualFolderChangeListener()
+ : m_searchOnMsgStatus(false), m_batchingEvents(false) {}
+
+nsresult VirtualFolderChangeListener::Init() {
+ nsCOMPtr<nsIMsgDatabase> msgDB;
+ nsCOMPtr<nsIDBFolderInfo> dbFolderInfo;
+
+ nsresult rv = m_virtualFolder->GetDBFolderInfoAndDB(
+ getter_AddRefs(dbFolderInfo), getter_AddRefs(msgDB));
+ if (NS_SUCCEEDED(rv) && msgDB) {
+ nsCString searchTermString;
+ dbFolderInfo->GetCharProperty("searchStr", searchTermString);
+ nsCOMPtr<nsIMsgFilterService> filterService =
+ do_GetService("@mozilla.org/messenger/services/filters;1", &rv);
+ nsCOMPtr<nsIMsgFilterList> filterList;
+ rv = filterService->GetTempFilterList(m_virtualFolder,
+ getter_AddRefs(filterList));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIMsgFilter> tempFilter;
+ filterList->CreateFilter(u"temp"_ns, getter_AddRefs(tempFilter));
+ NS_ENSURE_SUCCESS(rv, rv);
+ filterList->ParseCondition(tempFilter, searchTermString.get());
+ NS_ENSURE_SUCCESS(rv, rv);
+ m_searchSession =
+ do_CreateInstance("@mozilla.org/messenger/searchSession;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsTArray<RefPtr<nsIMsgSearchTerm>> searchTerms;
+ rv = tempFilter->GetSearchTerms(searchTerms);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // we add the search scope right before we match the header,
+ // because we don't want the search scope caching the body input
+ // stream, because that holds onto the mailbox file, breaking
+ // compaction.
+
+ // add each search term to the search session
+ for (nsIMsgSearchTerm* searchTerm : searchTerms) {
+ nsMsgSearchAttribValue attrib;
+ searchTerm->GetAttrib(&attrib);
+ if (attrib == nsMsgSearchAttrib::MsgStatus) m_searchOnMsgStatus = true;
+ m_searchSession->AppendTerm(searchTerm);
+ }
+ }
+ return rv;
+}
+
+/**
+ * nsIDBChangeListener
+ */
+
+NS_IMETHODIMP
+VirtualFolderChangeListener::OnHdrPropertyChanged(
+ nsIMsgDBHdr* aHdrChanged, const nsACString& property, bool aPreChange,
+ uint32_t* aStatus, nsIDBChangeListener* aInstigator) {
+ const uint32_t kMatch = 0x1;
+ const uint32_t kRead = 0x2;
+ const uint32_t kNew = 0x4;
+ NS_ENSURE_ARG_POINTER(aHdrChanged);
+ NS_ENSURE_ARG_POINTER(aStatus);
+
+ uint32_t flags;
+ bool match;
+ nsCOMPtr<nsIMsgDatabase> msgDB;
+ nsresult rv = m_folderWatching->GetMsgDatabase(getter_AddRefs(msgDB));
+ NS_ENSURE_SUCCESS(rv, rv);
+ // we don't want any early returns from this function, until we've
+ // called ClearScopes on the search session.
+ m_searchSession->AddScopeTerm(nsMsgSearchScope::offlineMail,
+ m_folderWatching);
+ rv = m_searchSession->MatchHdr(aHdrChanged, msgDB, &match);
+ m_searchSession->ClearScopes();
+ NS_ENSURE_SUCCESS(rv, rv);
+ aHdrChanged->GetFlags(&flags);
+
+ if (aPreChange) // We're looking at the old header, save status
+ {
+ *aStatus = 0;
+ if (match) *aStatus |= kMatch;
+ if (flags & nsMsgMessageFlags::Read) *aStatus |= kRead;
+ if (flags & nsMsgMessageFlags::New) *aStatus |= kNew;
+ return NS_OK;
+ }
+
+ // This is the post change section where changes are detected
+
+ bool wasMatch = *aStatus & kMatch;
+ if (!match && !wasMatch) // header not in virtual folder
+ return NS_OK;
+
+ int32_t totalDelta = 0, unreadDelta = 0, newDelta = 0;
+
+ if (match) {
+ totalDelta++;
+ if (!(flags & nsMsgMessageFlags::Read)) unreadDelta++;
+ if (flags & nsMsgMessageFlags::New) newDelta++;
+ }
+
+ if (wasMatch) {
+ totalDelta--;
+ if (!(*aStatus & kRead)) unreadDelta--;
+ if (*aStatus & kNew) newDelta--;
+ }
+
+ if (!(unreadDelta || totalDelta || newDelta)) return NS_OK;
+
+ nsCOMPtr<nsIMsgDatabase> virtDatabase;
+ nsCOMPtr<nsIDBFolderInfo> dbFolderInfo;
+ rv = m_virtualFolder->GetDBFolderInfoAndDB(getter_AddRefs(dbFolderInfo),
+ getter_AddRefs(virtDatabase));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (unreadDelta) dbFolderInfo->ChangeNumUnreadMessages(unreadDelta);
+
+ if (newDelta) {
+ int32_t numNewMessages;
+ m_virtualFolder->GetNumNewMessages(false, &numNewMessages);
+ m_virtualFolder->SetNumNewMessages(numNewMessages + newDelta);
+ m_virtualFolder->SetHasNewMessages(numNewMessages + newDelta > 0);
+ }
+
+ if (totalDelta) {
+ dbFolderInfo->ChangeNumMessages(totalDelta);
+ nsCString searchUri;
+ m_virtualFolder->GetURI(searchUri);
+ msgDB->UpdateHdrInCache(searchUri, aHdrChanged, totalDelta == 1);
+ }
+
+ PostUpdateEvent(m_virtualFolder, virtDatabase);
+
+ return NS_OK;
+}
+
+void VirtualFolderChangeListener::DecrementNewMsgCount() {
+ int32_t numNewMessages;
+ m_virtualFolder->GetNumNewMessages(false, &numNewMessages);
+ if (numNewMessages > 0) numNewMessages--;
+ m_virtualFolder->SetNumNewMessages(numNewMessages);
+ if (!numNewMessages) m_virtualFolder->SetHasNewMessages(false);
+}
+
+NS_IMETHODIMP VirtualFolderChangeListener::OnHdrFlagsChanged(
+ nsIMsgDBHdr* aHdrChanged, uint32_t aOldFlags, uint32_t aNewFlags,
+ nsIDBChangeListener* aInstigator) {
+ nsCOMPtr<nsIMsgDatabase> msgDB;
+
+ nsresult rv = m_folderWatching->GetMsgDatabase(getter_AddRefs(msgDB));
+ bool oldMatch = false, newMatch = false;
+ // we don't want any early returns from this function, until we've
+ // called ClearScopes 0n the search session.
+ m_searchSession->AddScopeTerm(nsMsgSearchScope::offlineMail,
+ m_folderWatching);
+ rv = m_searchSession->MatchHdr(aHdrChanged, msgDB, &newMatch);
+ if (NS_SUCCEEDED(rv) && m_searchOnMsgStatus) {
+ // if status is a search criteria, check if the header matched before
+ // it changed, in order to determine if we need to bump the counts.
+ aHdrChanged->SetFlags(aOldFlags);
+ rv = m_searchSession->MatchHdr(aHdrChanged, msgDB, &oldMatch);
+ // restore new flags even on match failure.
+ aHdrChanged->SetFlags(aNewFlags);
+ } else
+ oldMatch = newMatch;
+ m_searchSession->ClearScopes();
+ NS_ENSURE_SUCCESS(rv, rv);
+ // we don't want to change the total counts if this virtual folder is open in
+ // a view, because we won't remove the header from view while it's open. On
+ // the other hand, it's hard to fix the count when the user clicks away to
+ // another folder, w/o re-running the search, or setting some sort of pending
+ // count change. Maybe this needs to be handled in the view code...the view
+ // could do the same calculation and also keep track of the counts changed.
+ // Then, when the view was closed, if it's a virtual folder, it could update
+ // the counts for the db.
+ if (oldMatch != newMatch ||
+ (oldMatch && (aOldFlags & nsMsgMessageFlags::Read) !=
+ (aNewFlags & nsMsgMessageFlags::Read))) {
+ nsCOMPtr<nsIMsgDatabase> virtDatabase;
+ nsCOMPtr<nsIDBFolderInfo> dbFolderInfo;
+
+ rv = m_virtualFolder->GetDBFolderInfoAndDB(getter_AddRefs(dbFolderInfo),
+ getter_AddRefs(virtDatabase));
+ NS_ENSURE_SUCCESS(rv, rv);
+ int32_t totalDelta = 0, unreadDelta = 0;
+ if (oldMatch != newMatch) {
+ // bool isOpen = false;
+ // nsCOMPtr<nsIMsgMailSession> mailSession =
+ // do_GetService("@mozilla.org/messenger/services/session;1");
+ // if (mailSession && aFolder)
+ // mailSession->IsFolderOpenInWindow(m_virtualFolder, &isOpen);
+ // we can't remove headers that no longer match - but we might add headers
+ // that newly match, someday.
+ // if (!isOpen)
+ totalDelta = (oldMatch) ? -1 : 1;
+ }
+ bool msgHdrIsRead;
+ aHdrChanged->GetIsRead(&msgHdrIsRead);
+ if (oldMatch == newMatch) // read flag changed state
+ unreadDelta = (msgHdrIsRead) ? -1 : 1;
+ else if (oldMatch) // else header should removed
+ unreadDelta = (aOldFlags & nsMsgMessageFlags::Read) ? 0 : -1;
+ else // header should be added
+ unreadDelta = (aNewFlags & nsMsgMessageFlags::Read) ? 0 : 1;
+ if (unreadDelta) dbFolderInfo->ChangeNumUnreadMessages(unreadDelta);
+ if (totalDelta) dbFolderInfo->ChangeNumMessages(totalDelta);
+ if (unreadDelta == -1 && aOldFlags & nsMsgMessageFlags::New)
+ DecrementNewMsgCount();
+
+ if (totalDelta) {
+ nsCString searchUri;
+ m_virtualFolder->GetURI(searchUri);
+ msgDB->UpdateHdrInCache(searchUri, aHdrChanged, totalDelta == 1);
+ }
+
+ PostUpdateEvent(m_virtualFolder, virtDatabase);
+ } else if (oldMatch && (aOldFlags & nsMsgMessageFlags::New) &&
+ !(aNewFlags & nsMsgMessageFlags::New))
+ DecrementNewMsgCount();
+
+ return rv;
+}
+
+NS_IMETHODIMP VirtualFolderChangeListener::OnHdrDeleted(
+ nsIMsgDBHdr* aHdrDeleted, nsMsgKey aParentKey, int32_t aFlags,
+ nsIDBChangeListener* aInstigator) {
+ nsCOMPtr<nsIMsgDatabase> msgDB;
+
+ nsresult rv = m_folderWatching->GetMsgDatabase(getter_AddRefs(msgDB));
+ NS_ENSURE_SUCCESS(rv, rv);
+ bool match = false;
+ m_searchSession->AddScopeTerm(nsMsgSearchScope::offlineMail,
+ m_folderWatching);
+ // Since the notifier went to the trouble of passing in the msg flags,
+ // we should use them when doing the match.
+ uint32_t msgFlags;
+ aHdrDeleted->GetFlags(&msgFlags);
+ aHdrDeleted->SetFlags(aFlags);
+ rv = m_searchSession->MatchHdr(aHdrDeleted, msgDB, &match);
+ aHdrDeleted->SetFlags(msgFlags);
+ m_searchSession->ClearScopes();
+ if (match) {
+ nsCOMPtr<nsIMsgDatabase> virtDatabase;
+ nsCOMPtr<nsIDBFolderInfo> dbFolderInfo;
+
+ rv = m_virtualFolder->GetDBFolderInfoAndDB(getter_AddRefs(dbFolderInfo),
+ getter_AddRefs(virtDatabase));
+ NS_ENSURE_SUCCESS(rv, rv);
+ bool msgHdrIsRead;
+ aHdrDeleted->GetIsRead(&msgHdrIsRead);
+ if (!msgHdrIsRead) dbFolderInfo->ChangeNumUnreadMessages(-1);
+ dbFolderInfo->ChangeNumMessages(-1);
+ if (aFlags & nsMsgMessageFlags::New) {
+ int32_t numNewMessages;
+ m_virtualFolder->GetNumNewMessages(false, &numNewMessages);
+ m_virtualFolder->SetNumNewMessages(numNewMessages - 1);
+ if (numNewMessages == 1) m_virtualFolder->SetHasNewMessages(false);
+ }
+
+ nsCString searchUri;
+ m_virtualFolder->GetURI(searchUri);
+ msgDB->UpdateHdrInCache(searchUri, aHdrDeleted, false);
+
+ PostUpdateEvent(m_virtualFolder, virtDatabase);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP VirtualFolderChangeListener::OnHdrAdded(
+ nsIMsgDBHdr* aNewHdr, nsMsgKey aParentKey, int32_t aFlags,
+ nsIDBChangeListener* aInstigator) {
+ nsCOMPtr<nsIMsgDatabase> msgDB;
+
+ nsresult rv = m_folderWatching->GetMsgDatabase(getter_AddRefs(msgDB));
+ NS_ENSURE_SUCCESS(rv, rv);
+ bool match = false;
+ if (!m_searchSession) return NS_ERROR_NULL_POINTER;
+
+ m_searchSession->AddScopeTerm(nsMsgSearchScope::offlineMail,
+ m_folderWatching);
+ rv = m_searchSession->MatchHdr(aNewHdr, msgDB, &match);
+ m_searchSession->ClearScopes();
+ if (match) {
+ nsCOMPtr<nsIMsgDatabase> virtDatabase;
+ nsCOMPtr<nsIDBFolderInfo> dbFolderInfo;
+
+ rv = m_virtualFolder->GetDBFolderInfoAndDB(getter_AddRefs(dbFolderInfo),
+ getter_AddRefs(virtDatabase));
+ NS_ENSURE_SUCCESS(rv, rv);
+ bool msgHdrIsRead;
+ uint32_t msgFlags;
+ aNewHdr->GetIsRead(&msgHdrIsRead);
+ aNewHdr->GetFlags(&msgFlags);
+ if (!msgHdrIsRead) dbFolderInfo->ChangeNumUnreadMessages(1);
+ if (msgFlags & nsMsgMessageFlags::New) {
+ int32_t numNewMessages;
+ m_virtualFolder->GetNumNewMessages(false, &numNewMessages);
+ m_virtualFolder->SetHasNewMessages(true);
+ m_virtualFolder->SetNumNewMessages(numNewMessages + 1);
+ }
+ nsCString searchUri;
+ m_virtualFolder->GetURI(searchUri);
+ msgDB->UpdateHdrInCache(searchUri, aNewHdr, true);
+ dbFolderInfo->ChangeNumMessages(1);
+ PostUpdateEvent(m_virtualFolder, virtDatabase);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP VirtualFolderChangeListener::OnParentChanged(
+ nsMsgKey aKeyChanged, nsMsgKey oldParent, nsMsgKey newParent,
+ nsIDBChangeListener* aInstigator) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP VirtualFolderChangeListener::OnAnnouncerGoingAway(
+ nsIDBChangeAnnouncer* instigator) {
+ nsCOMPtr<nsIMsgDatabase> msgDB = do_QueryInterface(instigator);
+ if (msgDB) msgDB->RemoveListener(this);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+VirtualFolderChangeListener::OnEvent(nsIMsgDatabase* aDB, const char* aEvent) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP VirtualFolderChangeListener::OnReadChanged(
+ nsIDBChangeListener* aInstigator) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP VirtualFolderChangeListener::OnJunkScoreChanged(
+ nsIDBChangeListener* aInstigator) {
+ return NS_OK;
+}
+
+nsresult VirtualFolderChangeListener::PostUpdateEvent(
+ nsIMsgFolder* virtualFolder, nsIMsgDatabase* virtDatabase) {
+ if (m_batchingEvents) return NS_OK;
+ m_batchingEvents = true;
+ nsCOMPtr<nsIRunnable> event =
+ new VFChangeListenerEvent(this, virtualFolder, virtDatabase);
+ return NS_DispatchToCurrentThread(event);
+}
+
+void VirtualFolderChangeListener::ProcessUpdateEvent(nsIMsgFolder* virtFolder,
+ nsIMsgDatabase* virtDB) {
+ m_batchingEvents = false;
+ virtFolder->UpdateSummaryTotals(true); // force update from db.
+ virtDB->Commit(nsMsgDBCommitType::kLargeCommit);
+}
+
+nsresult nsMsgAccountManager::GetVirtualFoldersFile(nsCOMPtr<nsIFile>& aFile) {
+ nsCOMPtr<nsIFile> profileDir;
+ nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
+ getter_AddRefs(profileDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = profileDir->AppendNative("virtualFolders.dat"_ns);
+ if (NS_SUCCEEDED(rv)) aFile = profileDir;
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgAccountManager::LoadVirtualFolders() {
+ nsCOMPtr<nsIFile> file;
+ GetVirtualFoldersFile(file);
+ if (!file) return NS_ERROR_FAILURE;
+
+ if (m_virtualFoldersLoaded) return NS_OK;
+
+ m_loadingVirtualFolders = true;
+
+ // Before loading virtual folders, ensure that all real folders exist.
+ // Some may not have been created yet, which would break virtual folders
+ // that depend on them.
+ nsTArray<RefPtr<nsIMsgIncomingServer>> allServers;
+ nsresult rv = GetAllServers(allServers);
+ NS_ENSURE_SUCCESS(rv, rv);
+ for (auto server : allServers) {
+ if (server) {
+ nsCOMPtr<nsIMsgFolder> rootFolder;
+ server->GetRootFolder(getter_AddRefs(rootFolder));
+ if (rootFolder) {
+ nsTArray<RefPtr<nsIMsgFolder>> dummy;
+ rootFolder->GetSubFolders(dummy);
+ }
+ }
+ }
+
+ if (!m_dbService) {
+ m_dbService = do_GetService("@mozilla.org/msgDatabase/msgDBService;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ nsCOMPtr<nsIFileInputStream> fileStream =
+ do_CreateInstance(NS_LOCALFILEINPUTSTREAM_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = fileStream->Init(file, PR_RDONLY, 0664, false);
+ nsCOMPtr<nsILineInputStream> lineInputStream(do_QueryInterface(fileStream));
+
+ bool isMore = true;
+ nsAutoCString buffer;
+ int32_t version = -1;
+ nsCOMPtr<nsIMsgFolder> virtualFolder;
+ nsCOMPtr<nsIDBFolderInfo> dbFolderInfo;
+
+ while (isMore && NS_SUCCEEDED(lineInputStream->ReadLine(buffer, &isMore))) {
+ if (!buffer.IsEmpty()) {
+ if (version == -1) {
+ buffer.Cut(0, 8);
+ nsresult irv;
+ version = buffer.ToInteger(&irv);
+ continue;
+ }
+ if (StringBeginsWith(buffer, "uri="_ns)) {
+ buffer.Cut(0, 4);
+ dbFolderInfo = nullptr;
+
+ rv = GetOrCreateFolder(buffer, getter_AddRefs(virtualFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ virtualFolder->SetFlag(nsMsgFolderFlags::Virtual);
+
+ nsCOMPtr<nsIMsgFolder> grandParent;
+ nsCOMPtr<nsIMsgFolder> oldParent;
+ nsCOMPtr<nsIMsgFolder> parentFolder;
+ bool isServer;
+ // This loop handles creating virtual folders without an existing
+ // parent.
+ do {
+ // need to add the folder as a sub-folder of its parent.
+ int32_t lastSlash = buffer.RFindChar('/');
+ if (lastSlash == kNotFound) break;
+ nsDependentCSubstring parentUri(buffer, 0, lastSlash);
+ // hold a reference so it won't get deleted before it's parented.
+ oldParent = parentFolder;
+
+ rv = GetOrCreateFolder(parentUri, getter_AddRefs(parentFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString currentFolderNameStr;
+ nsAutoCString currentFolderNameCStr;
+ MsgUnescapeString(
+ nsCString(Substring(buffer, lastSlash + 1, buffer.Length())), 0,
+ currentFolderNameCStr);
+ CopyUTF8toUTF16(currentFolderNameCStr, currentFolderNameStr);
+ nsCOMPtr<nsIMsgFolder> childFolder;
+ nsCOMPtr<nsIMsgDatabase> db;
+ // force db to get created.
+ // XXX TODO: is this SetParent() right? Won't it screw up if virtual
+ // folder is nested >2 deep? Leave for now, but revisit when getting
+ // rid of dangling folders (BenC).
+ virtualFolder->SetParent(parentFolder);
+ rv = virtualFolder->GetMsgDatabase(getter_AddRefs(db));
+ if (rv == NS_MSG_ERROR_FOLDER_SUMMARY_MISSING)
+ m_dbService->CreateNewDB(virtualFolder, getter_AddRefs(db));
+ if (db)
+ rv = db->GetDBFolderInfo(getter_AddRefs(dbFolderInfo));
+ else
+ break;
+
+ parentFolder->AddSubfolder(currentFolderNameStr,
+ getter_AddRefs(childFolder));
+ if (childFolder) parentFolder->NotifyFolderAdded(childFolder);
+ // here we make sure if our parent is rooted - if not, we're
+ // going to loop and add our parent as a child of its grandparent
+ // and repeat until we get to the server, or a folder that
+ // has its parent set.
+ parentFolder->GetParent(getter_AddRefs(grandParent));
+ parentFolder->GetIsServer(&isServer);
+ buffer.SetLength(lastSlash);
+ } while (!grandParent && !isServer);
+ } else if (dbFolderInfo && StringBeginsWith(buffer, "scope="_ns)) {
+ buffer.Cut(0, 6);
+ // if this is a cross folder virtual folder, we have a list of folders
+ // uris, and we have to add a pending listener for each of them.
+ if (!buffer.IsEmpty()) {
+ ParseAndVerifyVirtualFolderScope(buffer);
+ dbFolderInfo->SetCharProperty(kSearchFolderUriProp, buffer);
+ AddVFListenersForVF(virtualFolder, buffer);
+ }
+ } else if (dbFolderInfo && StringBeginsWith(buffer, "terms="_ns)) {
+ buffer.Cut(0, 6);
+ dbFolderInfo->SetCharProperty("searchStr", buffer);
+ } else if (dbFolderInfo && StringBeginsWith(buffer, "searchOnline="_ns)) {
+ buffer.Cut(0, 13);
+ dbFolderInfo->SetBooleanProperty("searchOnline",
+ buffer.EqualsLiteral("true"));
+ } else if (dbFolderInfo &&
+ Substring(buffer, 0, SEARCH_FOLDER_FLAG_LEN + 1)
+ .Equals(SEARCH_FOLDER_FLAG "=")) {
+ buffer.Cut(0, SEARCH_FOLDER_FLAG_LEN + 1);
+ dbFolderInfo->SetCharProperty(SEARCH_FOLDER_FLAG, buffer);
+ }
+ }
+ }
+
+ m_loadingVirtualFolders = false;
+ m_virtualFoldersLoaded = true;
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgAccountManager::SaveVirtualFolders() {
+ if (!m_virtualFoldersLoaded) return NS_OK;
+
+ nsCOMPtr<nsIFile> file;
+ GetVirtualFoldersFile(file);
+
+ // Open a buffered, safe output stream
+ nsCOMPtr<nsIOutputStream> outStream;
+ nsresult rv = MsgNewSafeBufferedFileOutputStream(
+ getter_AddRefs(outStream), file, PR_CREATE_FILE | PR_WRONLY | PR_TRUNCATE,
+ 0664);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ WriteLineToOutputStream("version=", "1", outStream);
+ for (auto iter = m_incomingServers.Iter(); !iter.Done(); iter.Next()) {
+ nsCOMPtr<nsIMsgIncomingServer>& server = iter.Data();
+ if (server) {
+ nsCOMPtr<nsIMsgFolder> rootFolder;
+ server->GetRootFolder(getter_AddRefs(rootFolder));
+ if (rootFolder) {
+ nsTArray<RefPtr<nsIMsgFolder>> virtualFolders;
+ nsresult rv = rootFolder->GetFoldersWithFlags(nsMsgFolderFlags::Virtual,
+ virtualFolders);
+ if (NS_FAILED(rv)) {
+ continue;
+ }
+ for (auto msgFolder : virtualFolders) {
+ nsCOMPtr<nsIMsgDatabase> db;
+ nsCOMPtr<nsIDBFolderInfo> dbFolderInfo;
+ rv = msgFolder->GetDBFolderInfoAndDB(
+ getter_AddRefs(dbFolderInfo),
+ getter_AddRefs(db)); // force db to get created.
+ if (dbFolderInfo) {
+ nsCString srchFolderUri;
+ nsCString searchTerms;
+ nsCString regexScope;
+ nsCString vfFolderFlag;
+ bool searchOnline = false;
+ dbFolderInfo->GetBooleanProperty("searchOnline", false,
+ &searchOnline);
+ dbFolderInfo->GetCharProperty(kSearchFolderUriProp, srchFolderUri);
+ dbFolderInfo->GetCharProperty("searchStr", searchTerms);
+ // logically searchFolderFlag is an int, but since we want to
+ // write out a string, get it as a string.
+ dbFolderInfo->GetCharProperty(SEARCH_FOLDER_FLAG, vfFolderFlag);
+ nsCString uri;
+ msgFolder->GetURI(uri);
+ if (!srchFolderUri.IsEmpty() && !searchTerms.IsEmpty()) {
+ WriteLineToOutputStream("uri=", uri.get(), outStream);
+ if (!vfFolderFlag.IsEmpty())
+ WriteLineToOutputStream(SEARCH_FOLDER_FLAG "=",
+ vfFolderFlag.get(), outStream);
+ WriteLineToOutputStream("scope=", srchFolderUri.get(), outStream);
+ WriteLineToOutputStream("terms=", searchTerms.get(), outStream);
+ WriteLineToOutputStream(
+ "searchOnline=", searchOnline ? "true" : "false", outStream);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ nsCOMPtr<nsISafeOutputStream> safeStream = do_QueryInterface(outStream, &rv);
+ NS_ASSERTION(safeStream, "expected a safe output stream!");
+ if (safeStream) {
+ rv = safeStream->Finish();
+ if (NS_FAILED(rv)) {
+ NS_WARNING("failed to save personal dictionary file! possible data loss");
+ }
+ }
+ return rv;
+}
+
+nsresult nsMsgAccountManager::WriteLineToOutputStream(
+ const char* prefix, const char* line, nsIOutputStream* outputStream) {
+ uint32_t writeCount;
+ outputStream->Write(prefix, strlen(prefix), &writeCount);
+ outputStream->Write(line, strlen(line), &writeCount);
+ outputStream->Write("\n", 1, &writeCount);
+ return NS_OK;
+}
+
+/**
+ * Parse the '|' separated folder uri string into individual folders, verify
+ * that the folders are real. If we were to add things like wildcards, we
+ * could implement the expansion into real folders here.
+ *
+ * @param buffer On input, list of folder uri's, on output, verified list.
+ */
+void nsMsgAccountManager::ParseAndVerifyVirtualFolderScope(nsCString& buffer) {
+ if (buffer.Equals("*")) {
+ // This is a special virtual folder that searches all folders in all
+ // accounts. Folders are chosen by the front end at search time.
+ return;
+ }
+ nsCString verifiedFolders;
+ nsTArray<nsCString> folderUris;
+ ParseString(buffer, '|', folderUris);
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ nsCOMPtr<nsIMsgFolder> parent;
+
+ for (uint32_t i = 0; i < folderUris.Length(); i++) {
+ nsCOMPtr<nsIMsgFolder> realFolder;
+ nsresult rv = GetOrCreateFolder(folderUris[i], getter_AddRefs(realFolder));
+ if (!NS_SUCCEEDED(rv)) {
+ continue;
+ }
+ realFolder->GetParent(getter_AddRefs(parent));
+ if (!parent) continue;
+ realFolder->GetServer(getter_AddRefs(server));
+ if (!server) continue;
+ if (!verifiedFolders.IsEmpty()) verifiedFolders.Append('|');
+ verifiedFolders.Append(folderUris[i]);
+ }
+ buffer.Assign(verifiedFolders);
+}
+
+// This conveniently works to add a single folder as well.
+nsresult nsMsgAccountManager::AddVFListenersForVF(
+ nsIMsgFolder* virtualFolder, const nsCString& srchFolderUris) {
+ if (srchFolderUris.Equals("*")) {
+ return NS_OK;
+ }
+
+ nsresult rv;
+ if (!m_dbService) {
+ m_dbService = do_GetService("@mozilla.org/msgDatabase/msgDBService;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Avoid any possible duplicate listeners for this virtual folder.
+ RemoveVFListenerForVF(virtualFolder, nullptr);
+
+ nsTArray<nsCString> folderUris;
+ ParseString(srchFolderUris, '|', folderUris);
+
+ for (uint32_t i = 0; i < folderUris.Length(); i++) {
+ nsCOMPtr<nsIMsgFolder> realFolder;
+ rv = GetOrCreateFolder(folderUris[i], getter_AddRefs(realFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+ RefPtr<VirtualFolderChangeListener> dbListener =
+ new VirtualFolderChangeListener();
+ NS_ENSURE_TRUE(dbListener, NS_ERROR_OUT_OF_MEMORY);
+ dbListener->m_virtualFolder = virtualFolder;
+ dbListener->m_folderWatching = realFolder;
+ if (NS_FAILED(dbListener->Init())) {
+ dbListener = nullptr;
+ continue;
+ }
+ m_virtualFolderListeners.AppendElement(dbListener);
+ m_dbService->RegisterPendingListener(realFolder, dbListener);
+ }
+ if (!m_virtualFolders.Contains(virtualFolder)) {
+ m_virtualFolders.AppendElement(virtualFolder);
+ }
+ return NS_OK;
+}
+
+// This is called if a folder that's part of the scope of a saved search
+// has gone away.
+nsresult nsMsgAccountManager::RemoveVFListenerForVF(nsIMsgFolder* virtualFolder,
+ nsIMsgFolder* folder) {
+ nsresult rv;
+ if (!m_dbService) {
+ m_dbService = do_GetService("@mozilla.org/msgDatabase/msgDBService;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ nsTObserverArray<RefPtr<VirtualFolderChangeListener>>::ForwardIterator iter(
+ m_virtualFolderListeners);
+ RefPtr<VirtualFolderChangeListener> listener;
+
+ while (iter.HasMore()) {
+ listener = iter.GetNext();
+ if (listener->m_virtualFolder == virtualFolder &&
+ (!folder || listener->m_folderWatching == folder)) {
+ m_dbService->UnregisterPendingListener(listener);
+ m_virtualFolderListeners.RemoveElement(listener);
+ if (folder) {
+ break;
+ }
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAccountManager::GetAllFolders(
+ nsTArray<RefPtr<nsIMsgFolder>>& aAllFolders) {
+ aAllFolders.Clear();
+ nsTArray<RefPtr<nsIMsgIncomingServer>> allServers;
+ nsresult rv = GetAllServers(allServers);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (auto server : allServers) {
+ if (server) {
+ nsCOMPtr<nsIMsgFolder> rootFolder;
+ server->GetRootFolder(getter_AddRefs(rootFolder));
+ if (rootFolder) {
+ nsTArray<RefPtr<nsIMsgFolder>> descendents;
+ rootFolder->GetDescendants(descendents);
+ aAllFolders.AppendElements(descendents);
+ }
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAccountManager::OnFolderAdded(nsIMsgFolder* parent,
+ nsIMsgFolder* folder) {
+ if (!parent) {
+ // This method gets called for folders that aren't connected to anything,
+ // such as a junk folder that appears when an IMAP account is created. We
+ // don't want these added to the virtual folder.
+ return NS_OK;
+ }
+
+ uint32_t folderFlags;
+ folder->GetFlags(&folderFlags);
+
+ bool addToSmartFolders = false;
+ folder->IsSpecialFolder(nsMsgFolderFlags::Inbox |
+ nsMsgFolderFlags::Templates |
+ nsMsgFolderFlags::Trash |
+ nsMsgFolderFlags::Drafts | nsMsgFolderFlags::Junk,
+ false, &addToSmartFolders);
+ // For Sent/Archives/Trash, we treat sub-folders of those folders as
+ // "special", and want to add them the smart folders search scope.
+ // So we check if this is a sub-folder of one of those special folders
+ // and set the corresponding folderFlag if so.
+ if (!addToSmartFolders) {
+ bool isSpecial = false;
+ folder->IsSpecialFolder(nsMsgFolderFlags::SentMail, true, &isSpecial);
+ if (isSpecial) {
+ addToSmartFolders = true;
+ folderFlags |= nsMsgFolderFlags::SentMail;
+ }
+ folder->IsSpecialFolder(nsMsgFolderFlags::Archive, true, &isSpecial);
+ if (isSpecial) {
+ addToSmartFolders = true;
+ folderFlags |= nsMsgFolderFlags::Archive;
+ }
+ folder->IsSpecialFolder(nsMsgFolderFlags::Trash, true, &isSpecial);
+ if (isSpecial) {
+ addToSmartFolders = true;
+ folderFlags |= nsMsgFolderFlags::Trash;
+ }
+ }
+ nsresult rv = NS_OK;
+ // if this is a special folder, check if we have a saved search over
+ // folders with this flag, and if so, add this folder to the scope.
+ if (addToSmartFolders) {
+ // quick way to enumerate the saved searches.
+ for (nsCOMPtr<nsIMsgFolder> virtualFolder : m_virtualFolders) {
+ nsCOMPtr<nsIMsgDatabase> db;
+ nsCOMPtr<nsIDBFolderInfo> dbFolderInfo;
+ virtualFolder->GetDBFolderInfoAndDB(getter_AddRefs(dbFolderInfo),
+ getter_AddRefs(db));
+ if (dbFolderInfo) {
+ uint32_t vfFolderFlag;
+ dbFolderInfo->GetUint32Property("searchFolderFlag", 0, &vfFolderFlag);
+ // found a saved search over folders w/ the same flag as the new folder.
+ if (vfFolderFlag & folderFlags) {
+ nsCString searchURI;
+ dbFolderInfo->GetCharProperty(kSearchFolderUriProp, searchURI);
+
+ // "normalize" searchURI so we can search for |folderURI|.
+ if (!searchURI.IsEmpty()) {
+ searchURI.Insert('|', 0);
+ searchURI.Append('|');
+ }
+ nsCString folderURI;
+ folder->GetURI(folderURI);
+ folderURI.Insert('|', 0);
+ folderURI.Append('|');
+
+ int32_t index = searchURI.Find(folderURI);
+ if (index == kNotFound) {
+ searchURI.Cut(0, 1);
+ folderURI.Cut(0, 1);
+ folderURI.SetLength(folderURI.Length() - 1);
+ searchURI.Append(folderURI);
+ dbFolderInfo->SetCharProperty(kSearchFolderUriProp, searchURI);
+ nsCOMPtr<nsIObserverService> obs =
+ mozilla::services::GetObserverService();
+ obs->NotifyObservers(virtualFolder, "search-folders-changed",
+ nullptr);
+ }
+
+ // Add sub-folders to smart folder.
+ nsTArray<RefPtr<nsIMsgFolder>> allDescendants;
+ rv = folder->GetDescendants(allDescendants);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgFolder> parentFolder;
+ for (auto subFolder : allDescendants) {
+ subFolder->GetParent(getter_AddRefs(parentFolder));
+ OnFolderAdded(parentFolder, subFolder);
+ }
+ }
+ }
+ }
+ }
+
+ // Find any virtual folders that search `parent`, and add `folder` to them.
+ if (!(folderFlags & nsMsgFolderFlags::Virtual)) {
+ nsTObserverArray<RefPtr<VirtualFolderChangeListener>>::ForwardIterator iter(
+ m_virtualFolderListeners);
+ RefPtr<VirtualFolderChangeListener> listener;
+
+ while (iter.HasMore()) {
+ listener = iter.GetNext();
+ if (listener->m_folderWatching == parent) {
+ nsCOMPtr<nsIMsgDatabase> db;
+ nsCOMPtr<nsIDBFolderInfo> dbFolderInfo;
+ listener->m_virtualFolder->GetDBFolderInfoAndDB(
+ getter_AddRefs(dbFolderInfo), getter_AddRefs(db));
+
+ uint32_t vfFolderFlag;
+ dbFolderInfo->GetUint32Property("searchFolderFlag", 0, &vfFolderFlag);
+ if (addToSmartFolders && vfFolderFlag &&
+ !(vfFolderFlag & nsMsgFolderFlags::Trash)) {
+ // Don't add folders of one type to the unified folder of another
+ // type, unless it's the Trash unified folder.
+ continue;
+ }
+ nsCString searchURI;
+ dbFolderInfo->GetCharProperty(kSearchFolderUriProp, searchURI);
+
+ // "normalize" searchURI so we can search for |folderURI|.
+ if (!searchURI.IsEmpty()) {
+ searchURI.Insert('|', 0);
+ searchURI.Append('|');
+ }
+ nsCString folderURI;
+ folder->GetURI(folderURI);
+
+ int32_t index = searchURI.Find(folderURI);
+ if (index == kNotFound) {
+ searchURI.Cut(0, 1);
+ searchURI.Append(folderURI);
+ dbFolderInfo->SetCharProperty(kSearchFolderUriProp, searchURI);
+ nsCOMPtr<nsIObserverService> obs =
+ mozilla::services::GetObserverService();
+ obs->NotifyObservers(listener->m_virtualFolder,
+ "search-folders-changed", nullptr);
+ }
+ }
+ }
+ }
+
+ // need to make sure this isn't happening during loading of virtualfolders.dat
+ if (folderFlags & nsMsgFolderFlags::Virtual && !m_loadingVirtualFolders) {
+ // When a new virtual folder is added, need to create a db Listener for it.
+ nsCOMPtr<nsIMsgDatabase> virtDatabase;
+ nsCOMPtr<nsIDBFolderInfo> dbFolderInfo;
+ rv = folder->GetDBFolderInfoAndDB(getter_AddRefs(dbFolderInfo),
+ getter_AddRefs(virtDatabase));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCString srchFolderUri;
+ dbFolderInfo->GetCharProperty(kSearchFolderUriProp, srchFolderUri);
+ AddVFListenersForVF(folder, srchFolderUri);
+ rv = SaveVirtualFolders();
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgAccountManager::OnMessageAdded(nsIMsgFolder* parent,
+ nsIMsgDBHdr* msg) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAccountManager::OnFolderRemoved(nsIMsgFolder* parentFolder,
+ nsIMsgFolder* folder) {
+ nsresult rv = NS_OK;
+ uint32_t folderFlags;
+ folder->GetFlags(&folderFlags);
+ // if we removed a VF, flush VF list to disk.
+ if (folderFlags & nsMsgFolderFlags::Virtual) {
+ RemoveVFListenerForVF(folder, nullptr);
+ m_virtualFolders.RemoveElement(folder);
+ rv = SaveVirtualFolders();
+ // clear flags on deleted folder if it's a virtual folder, so that creating
+ // a new folder with the same name doesn't cause confusion.
+ folder->SetFlags(0);
+ return rv;
+ }
+ // need to update the saved searches to check for a few things:
+ // 1. Folder removed was in the scope of a saved search - if so, remove the
+ // uri from the scope of the saved search.
+ // 2. If the scope is now empty, remove the saved search.
+
+ // build a "normalized" uri that we can do a find on.
+ nsCString removedFolderURI;
+ folder->GetURI(removedFolderURI);
+ removedFolderURI.Insert('|', 0);
+ removedFolderURI.Append('|');
+
+ // Enumerate the saved searches.
+ nsTObserverArray<RefPtr<VirtualFolderChangeListener>>::ForwardIterator iter(
+ m_virtualFolderListeners);
+ RefPtr<VirtualFolderChangeListener> listener;
+
+ while (iter.HasMore()) {
+ listener = iter.GetNext();
+ nsCOMPtr<nsIMsgDatabase> db;
+ nsCOMPtr<nsIDBFolderInfo> dbFolderInfo;
+ nsCOMPtr<nsIMsgFolder> savedSearch = listener->m_virtualFolder;
+ savedSearch->GetDBFolderInfoAndDB(getter_AddRefs(dbFolderInfo),
+ getter_AddRefs(db));
+ if (dbFolderInfo) {
+ nsCString searchURI;
+ dbFolderInfo->GetCharProperty(kSearchFolderUriProp, searchURI);
+ // "normalize" searchURI so we can search for |folderURI|.
+ searchURI.Insert('|', 0);
+ searchURI.Append('|');
+ int32_t index = searchURI.Find(removedFolderURI);
+ if (index != kNotFound) {
+ RemoveVFListenerForVF(savedSearch, folder);
+
+ // remove |folderURI
+ searchURI.Cut(index, removedFolderURI.Length() - 1);
+ // remove last '|' we added
+ searchURI.SetLength(searchURI.Length() - 1);
+
+ uint32_t vfFolderFlag;
+ dbFolderInfo->GetUint32Property("searchFolderFlag", 0, &vfFolderFlag);
+
+ // If saved search is empty now, delete it. But not if it's a smart
+ // folder.
+ if (searchURI.IsEmpty() && !vfFolderFlag) {
+ db = nullptr;
+ dbFolderInfo = nullptr;
+
+ nsCOMPtr<nsIMsgFolder> parent;
+ rv = savedSearch->GetParent(getter_AddRefs(parent));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!parent) continue;
+ parent->PropagateDelete(savedSearch, true);
+ } else {
+ if (!searchURI.IsEmpty()) {
+ // Remove leading '|' we added (or one after |folderURI, if first
+ // URI).
+ searchURI.Cut(0, 1);
+ }
+ dbFolderInfo->SetCharProperty(kSearchFolderUriProp, searchURI);
+ nsCOMPtr<nsIObserverService> obs =
+ mozilla::services::GetObserverService();
+ obs->NotifyObservers(savedSearch, "search-folders-changed", nullptr);
+ }
+ }
+ }
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgAccountManager::OnMessageRemoved(nsIMsgFolder* parent,
+ nsIMsgDBHdr* msg) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAccountManager::OnFolderPropertyChanged(
+ nsIMsgFolder* folder, const nsACString& property,
+ const nsACString& oldValue, const nsACString& newValue) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsMsgAccountManager::OnFolderIntPropertyChanged(nsIMsgFolder* aFolder,
+ const nsACString& aProperty,
+ int64_t oldValue,
+ int64_t newValue) {
+ if (aProperty.Equals(kFolderFlag)) {
+ if (newValue & nsMsgFolderFlags::Virtual) {
+ // This is a virtual folder, let's get out of here.
+ return NS_OK;
+ }
+ uint32_t smartFlagsChanged =
+ (oldValue ^ newValue) &
+ (nsMsgFolderFlags::SpecialUse & ~nsMsgFolderFlags::Queue);
+ if (smartFlagsChanged) {
+ if (smartFlagsChanged & newValue) {
+ // if the smart folder flag was set, calling OnFolderAdded will
+ // do the right thing.
+ nsCOMPtr<nsIMsgFolder> parent;
+ aFolder->GetParent(getter_AddRefs(parent));
+ nsresult rv = OnFolderAdded(parent, aFolder);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // This folder has one of the smart folder flags.
+ // Remove it from any other smart folders it might've been included in
+ // because of the flags of its ancestors.
+ RemoveFolderFromSmartFolder(
+ aFolder, (nsMsgFolderFlags::SpecialUse & ~nsMsgFolderFlags::Queue) &
+ ~newValue);
+ return NS_OK;
+ }
+ RemoveFolderFromSmartFolder(aFolder, smartFlagsChanged);
+
+ nsTArray<RefPtr<nsIMsgFolder>> allDescendants;
+ nsresult rv = aFolder->GetDescendants(allDescendants);
+ NS_ENSURE_SUCCESS(rv, rv);
+ for (auto subFolder : allDescendants) {
+ RemoveFolderFromSmartFolder(subFolder, smartFlagsChanged);
+ }
+ }
+ }
+ return NS_OK;
+}
+
+nsresult nsMsgAccountManager::RemoveFolderFromSmartFolder(
+ nsIMsgFolder* aFolder, uint32_t flagsChanged) {
+ nsCString removedFolderURI;
+ aFolder->GetURI(removedFolderURI);
+ removedFolderURI.Insert('|', 0);
+ removedFolderURI.Append('|');
+ uint32_t flags;
+ aFolder->GetFlags(&flags);
+ NS_ASSERTION(!(flags & flagsChanged), "smart folder flag should not be set");
+ // Flag was removed. Look for smart folder based on that flag,
+ // and remove this folder from its scope.
+ for (nsCOMPtr<nsIMsgFolder> virtualFolder : m_virtualFolders) {
+ nsCOMPtr<nsIMsgDatabase> db;
+ nsCOMPtr<nsIDBFolderInfo> dbFolderInfo;
+ virtualFolder->GetDBFolderInfoAndDB(getter_AddRefs(dbFolderInfo),
+ getter_AddRefs(db));
+ if (dbFolderInfo) {
+ uint32_t vfFolderFlag;
+ dbFolderInfo->GetUint32Property("searchFolderFlag", 0, &vfFolderFlag);
+ // found a smart folder over the removed flag
+ if (vfFolderFlag & flagsChanged) {
+ nsCString searchURI;
+ dbFolderInfo->GetCharProperty(kSearchFolderUriProp, searchURI);
+ // "normalize" searchURI so we can search for |folderURI|.
+ searchURI.Insert('|', 0);
+ searchURI.Append('|');
+ int32_t index = searchURI.Find(removedFolderURI);
+ if (index != kNotFound) {
+ RemoveVFListenerForVF(virtualFolder, aFolder);
+
+ // remove |folderURI
+ searchURI.Cut(index, removedFolderURI.Length() - 1);
+ // remove last '|' we added
+ searchURI.SetLength(searchURI.Length() - 1);
+
+ // remove leading '|' we added (or one after |folderURI, if first URI)
+ searchURI.Cut(0, 1);
+ dbFolderInfo->SetCharProperty(kSearchFolderUriProp, searchURI);
+ nsCOMPtr<nsIObserverService> obs =
+ mozilla::services::GetObserverService();
+ obs->NotifyObservers(virtualFolder, "search-folders-changed",
+ nullptr);
+ }
+ }
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgAccountManager::OnFolderBoolPropertyChanged(
+ nsIMsgFolder* folder, const nsACString& property, bool oldValue,
+ bool newValue) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsMsgAccountManager::OnFolderUnicharPropertyChanged(
+ nsIMsgFolder* folder, const nsACString& property, const nsAString& oldValue,
+ const nsAString& newValue) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsMsgAccountManager::OnFolderPropertyFlagChanged(
+ nsIMsgDBHdr* msg, const nsACString& property, uint32_t oldFlag,
+ uint32_t newFlag) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsMsgAccountManager::OnFolderEvent(nsIMsgFolder* aFolder,
+ const nsACString& aEvent) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsMsgAccountManager::FolderUriForPath(nsIFile* aLocalPath,
+ nsACString& aMailboxUri) {
+ NS_ENSURE_ARG_POINTER(aLocalPath);
+ bool equals;
+ if (m_lastPathLookedUp &&
+ NS_SUCCEEDED(aLocalPath->Equals(m_lastPathLookedUp, &equals)) && equals) {
+ aMailboxUri = m_lastFolderURIForPath;
+ return NS_OK;
+ }
+ nsTArray<RefPtr<nsIMsgFolder>> folders;
+ nsresult rv = GetAllFolders(folders);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (auto folder : folders) {
+ nsCOMPtr<nsIFile> folderPath;
+ rv = folder->GetFilePath(getter_AddRefs(folderPath));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Check if we're equal
+ rv = folderPath->Equals(aLocalPath, &equals);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (equals) {
+ rv = folder->GetURI(aMailboxUri);
+ m_lastFolderURIForPath = aMailboxUri;
+ aLocalPath->Clone(getter_AddRefs(m_lastPathLookedUp));
+ return rv;
+ }
+ }
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsMsgAccountManager::GetSortOrder(nsIMsgIncomingServer* aServer,
+ int32_t* aSortOrder) {
+ NS_ENSURE_ARG_POINTER(aServer);
+ NS_ENSURE_ARG_POINTER(aSortOrder);
+
+ // If the passed in server is the default, return its sort order as 0
+ // regardless of its server sort order.
+
+ nsCOMPtr<nsIMsgAccount> defaultAccount;
+ nsresult rv = GetDefaultAccount(getter_AddRefs(defaultAccount));
+ if (NS_SUCCEEDED(rv) && defaultAccount) {
+ nsCOMPtr<nsIMsgIncomingServer> defaultServer;
+ rv = m_defaultAccount->GetIncomingServer(getter_AddRefs(defaultServer));
+ if (NS_SUCCEEDED(rv) && (aServer == defaultServer)) {
+ *aSortOrder = 0;
+ return NS_OK;
+ }
+ // It is OK if there is no default account.
+ }
+
+ // This function returns the sort order by querying the server object for its
+ // sort order value and then incrementing it by the position of the server in
+ // the accounts list. This ensures that even when several accounts have the
+ // same sort order value, the returned value is not the same and keeps
+ // their relative order in the account list when and unstable sort is run
+ // on the returned sort order values.
+ int32_t sortOrder;
+ int32_t serverIndex;
+
+ rv = aServer->GetSortOrder(&sortOrder);
+ if (NS_SUCCEEDED(rv)) rv = FindServerIndex(aServer, &serverIndex);
+
+ if (NS_FAILED(rv)) {
+ *aSortOrder = 999999999;
+ } else {
+ *aSortOrder = sortOrder + serverIndex;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgAccountManager::ReorderAccounts(const nsTArray<nsCString>& newAccounts) {
+ nsTArray<nsCString> allNewAccounts = newAccounts.Clone();
+
+ // Add all hidden accounts to the list of new accounts.
+ nsresult rv;
+ for (auto account : m_accounts) {
+ nsCString key;
+ account->GetKey(key);
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = account->GetIncomingServer(getter_AddRefs(server));
+ if (NS_SUCCEEDED(rv) && server) {
+ bool hidden = false;
+ rv = server->GetHidden(&hidden);
+ if (NS_SUCCEEDED(rv) && hidden && !allNewAccounts.Contains(key)) {
+ allNewAccounts.AppendElement(key);
+ }
+ }
+ }
+
+ // Check that the new account list contains all the existing accounts,
+ // just in a different order.
+ if (allNewAccounts.Length() != m_accounts.Length())
+ return NS_ERROR_INVALID_ARG;
+
+ for (uint32_t i = 0; i < m_accounts.Length(); i++) {
+ nsCString accountKey;
+ m_accounts[i]->GetKey(accountKey);
+ if (!allNewAccounts.Contains(accountKey)) return NS_ERROR_INVALID_ARG;
+ }
+
+ // In-place swap the elements in m_accounts to the order defined in
+ // newAccounts.
+ for (uint32_t i = 0; i < allNewAccounts.Length(); i++) {
+ nsCString newKey = allNewAccounts[i];
+ for (uint32_t j = i; j < m_accounts.Length(); j++) {
+ nsCString oldKey;
+ m_accounts[j]->GetKey(oldKey);
+ if (newKey.Equals(oldKey)) {
+ if (i != j) std::swap(m_accounts[i], m_accounts[j]);
+ break;
+ }
+ }
+ }
+
+ return OutputAccountsPref();
+}
diff --git a/comm/mailnews/base/src/nsMsgAccountManager.h b/comm/mailnews/base/src/nsMsgAccountManager.h
new file mode 100644
index 0000000000..9dc1e45da3
--- /dev/null
+++ b/comm/mailnews/base/src/nsMsgAccountManager.h
@@ -0,0 +1,211 @@
+/* -*- 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 Original Code has been modified by IBM Corporation. Modifications made
+ * by IBM described herein are Copyright (c) International Business Machines
+ * Corporation, 2000. Modifications to Mozilla code or documentation identified
+ * per MPL Section 3.3
+ *
+ * Date Modified by Description of modification
+ * 04/20/2000 IBM Corp. OS/2 VisualAge build.
+ */
+
+#include "nscore.h"
+#include "nsIMsgAccountManager.h"
+#include "nsCOMPtr.h"
+#include "nsISmtpServer.h"
+#include "nsIPrefBranch.h"
+#include "nsIMsgFolderCache.h"
+#include "nsIMsgFolder.h"
+#include "nsIObserver.h"
+#include "nsWeakReference.h"
+#include "nsIUrlListener.h"
+#include "nsCOMArray.h"
+#include "nsIMsgSearchSession.h"
+#include "nsInterfaceHashtable.h"
+#include "nsIMsgDatabase.h"
+#include "nsIDBChangeListener.h"
+#include "nsTObserverArray.h"
+
+class VirtualFolderChangeListener final : public nsIDBChangeListener {
+ public:
+ VirtualFolderChangeListener();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIDBCHANGELISTENER
+
+ nsresult Init();
+ /**
+ * Posts an event to update the summary totals and commit the db.
+ * We post the event to avoid committing each time we're called
+ * in a synchronous loop.
+ */
+ nsresult PostUpdateEvent(nsIMsgFolder* folder, nsIMsgDatabase* db);
+ /// Handles event posted to event queue to batch notifications.
+ void ProcessUpdateEvent(nsIMsgFolder* folder, nsIMsgDatabase* db);
+
+ void DecrementNewMsgCount();
+
+ // folder we're listening to db changes on behalf of.
+ nsCOMPtr<nsIMsgFolder> m_virtualFolder;
+ // folder whose db we're listening to.
+ nsCOMPtr<nsIMsgFolder> m_folderWatching;
+ nsCOMPtr<nsIMsgSearchSession> m_searchSession;
+ bool m_searchOnMsgStatus;
+ bool m_batchingEvents;
+
+ private:
+ ~VirtualFolderChangeListener() {}
+};
+
+class nsMsgAccountManager : public nsIMsgAccountManager,
+ public nsIObserver,
+ public nsSupportsWeakReference,
+ public nsIFolderListener {
+ public:
+ nsMsgAccountManager();
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ /* nsIMsgAccountManager methods */
+
+ NS_DECL_NSIMSGACCOUNTMANAGER
+ NS_DECL_NSIOBSERVER
+ NS_DECL_NSIFOLDERLISTENER
+
+ nsresult Init();
+ nsresult Shutdown();
+ void LogoutOfServer(nsIMsgIncomingServer* aServer);
+
+ private:
+ virtual ~nsMsgAccountManager();
+
+ bool m_accountsLoaded;
+ nsCOMPtr<nsIMsgFolderCache> m_msgFolderCache;
+ nsTArray<nsCOMPtr<nsIMsgAccount>> m_accounts;
+ nsInterfaceHashtable<nsCStringHashKey, nsIMsgIdentity> m_identities;
+ nsInterfaceHashtable<nsCStringHashKey, nsIMsgIncomingServer>
+ m_incomingServers;
+ nsCOMPtr<nsIMsgAccount> m_defaultAccount;
+ nsCOMArray<nsIIncomingServerListener> m_incomingServerListeners;
+ nsTObserverArray<RefPtr<VirtualFolderChangeListener>>
+ m_virtualFolderListeners;
+ nsTArray<nsCOMPtr<nsIMsgFolder>> m_virtualFolders;
+ nsCOMPtr<nsIMsgFolder> m_folderDoingEmptyTrash;
+ nsCOMPtr<nsIMsgFolder> m_folderDoingCleanupInbox;
+ bool m_emptyTrashInProgress;
+ bool m_cleanupInboxInProgress;
+
+ nsCString mAccountKeyList;
+
+ // These are static because the account manager may go away during
+ // shutdown, and get recreated.
+ static bool m_haveShutdown;
+ static bool m_shutdownInProgress;
+
+ bool m_userAuthenticated;
+ bool m_loadingVirtualFolders;
+ bool m_virtualFoldersLoaded;
+
+ /* we call FindServer() a lot. so cache the last server found */
+ nsCOMPtr<nsIMsgIncomingServer> m_lastFindServerResult;
+ nsCString m_lastFindServerHostName;
+ nsCString m_lastFindServerUserName;
+ int32_t m_lastFindServerPort;
+ nsCString m_lastFindServerType;
+
+ void SetLastServerFound(nsIMsgIncomingServer* server,
+ const nsACString& hostname,
+ const nsACString& username, const int32_t port,
+ const nsACString& type);
+
+ // Cache the results of the last call to FolderUriFromDirInProfile
+ nsCOMPtr<nsIFile> m_lastPathLookedUp;
+ nsCString m_lastFolderURIForPath;
+
+ /* internal creation routines - updates m_identities and m_incomingServers */
+ nsresult createKeyedAccount(const nsCString& key, bool forcePositionToEnd,
+ nsIMsgAccount** _retval);
+ nsresult createKeyedServer(const nsACString& key, const nsACString& username,
+ const nsACString& password, const nsACString& type,
+ nsIMsgIncomingServer** _retval);
+
+ nsresult createKeyedIdentity(const nsACString& key, nsIMsgIdentity** _retval);
+
+ nsresult GetLocalFoldersPrettyName(nsString& localFoldersName);
+
+ /**
+ * Check if the given account can be the set as the default account.
+ */
+ nsresult CheckDefaultAccount(nsIMsgAccount* aAccount, bool& aCanBeDefault);
+
+ /**
+ * Find a new account that can serve as default.
+ */
+ nsresult AutosetDefaultAccount();
+
+ // sets the pref for the default server
+ nsresult setDefaultAccountPref(nsIMsgAccount* aDefaultAccount);
+
+ // Write out the accounts pref from the m_accounts list of accounts.
+ nsresult OutputAccountsPref();
+
+ // fires notifications to the appropriate root folders
+ nsresult notifyDefaultServerChange(nsIMsgAccount* aOldAccount,
+ nsIMsgAccount* aNewAccount);
+
+ //
+ // account enumerators
+ // ("element" is always an account)
+ //
+
+ // find the servers that correspond to the given identity
+ static bool findServersForIdentity(nsISupports* element, void* aData);
+
+ void findAccountByServerKey(const nsCString& aKey, nsIMsgAccount** aResult);
+
+ //
+ // server enumerators
+ // ("element" is always a server)
+ //
+
+ nsresult findServerInternal(const nsACString& username,
+ const nsACString& hostname,
+ const nsACString& type, int32_t port,
+ nsIMsgIncomingServer** aResult);
+
+ // handle virtual folders
+ static nsresult GetVirtualFoldersFile(nsCOMPtr<nsIFile>& file);
+ static nsresult WriteLineToOutputStream(const char* prefix, const char* line,
+ nsIOutputStream* outputStream);
+ void ParseAndVerifyVirtualFolderScope(nsCString& buffer);
+ nsresult AddVFListenersForVF(nsIMsgFolder* virtualFolder,
+ const nsCString& srchFolderUris);
+
+ nsresult RemoveVFListenerForVF(nsIMsgFolder* virtualFolder,
+ nsIMsgFolder* folder);
+
+ nsresult RemoveFolderFromSmartFolder(nsIMsgFolder* aFolder,
+ uint32_t flagsChanged);
+
+ nsresult SetSendLaterUriPref(nsIMsgIncomingServer* server);
+
+ nsCOMPtr<nsIPrefBranch> m_prefs;
+ nsCOMPtr<nsIMsgDBService> m_dbService;
+
+ //
+ // root folder listener stuff
+ //
+
+ // this array is for folder listeners that are supposed to be listening
+ // on the root folders.
+ // When a new server is created, all of the the folder listeners
+ // should be added to the new server
+ // When a new listener is added, it should be added to all root folders.
+ // similar for when servers are deleted or listeners removed
+ nsTObserverArray<nsCOMPtr<nsIFolderListener>> mFolderListeners;
+
+ void removeListenersFromFolder(nsIMsgFolder* aFolder);
+};
diff --git a/comm/mailnews/base/src/nsMsgBiffManager.cpp b/comm/mailnews/base/src/nsMsgBiffManager.cpp
new file mode 100644
index 0000000000..9d990a30bd
--- /dev/null
+++ b/comm/mailnews/base/src/nsMsgBiffManager.cpp
@@ -0,0 +1,341 @@
+/* -*- 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 "nsMsgBiffManager.h"
+#include "nsIMsgAccountManager.h"
+#include "nsStatusBarBiffManager.h"
+#include "nsCOMArray.h"
+#include "mozilla/Logging.h"
+#include "nspr.h"
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+#include "nsIObserverService.h"
+#include "nsComponentManagerUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "nsMsgUtils.h"
+#include "nsITimer.h"
+#include "mozilla/Services.h"
+
+#define PREF_BIFF_JITTER "mail.biff.add_interval_jitter"
+
+#define NS_STATUSBARBIFFMANAGER_CID \
+ { \
+ 0x7f9a9fb0, 0x4161, 0x11d4, { \
+ 0x98, 0x76, 0x00, 0xc0, 0x4f, 0xa0, 0xd2, 0xa6 \
+ } \
+ }
+static NS_DEFINE_CID(kStatusBarBiffManagerCID, NS_STATUSBARBIFFMANAGER_CID);
+
+static mozilla::LazyLogModule MsgBiffLogModule("MsgBiff");
+
+NS_IMPL_ISUPPORTS(nsMsgBiffManager, nsIMsgBiffManager,
+ nsIIncomingServerListener, nsIObserver,
+ nsISupportsWeakReference)
+
+void OnBiffTimer(nsITimer* timer, void* aBiffManager) {
+ nsMsgBiffManager* biffManager = (nsMsgBiffManager*)aBiffManager;
+ biffManager->PerformBiff();
+}
+
+nsMsgBiffManager::nsMsgBiffManager() {
+ mHaveShutdown = false;
+ mInited = false;
+}
+
+nsMsgBiffManager::~nsMsgBiffManager() {
+ if (mBiffTimer) mBiffTimer->Cancel();
+
+ if (!mHaveShutdown) Shutdown();
+
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (observerService) {
+ observerService->RemoveObserver(this, "wake_notification");
+ observerService->RemoveObserver(this, "sleep_notification");
+ }
+}
+
+NS_IMETHODIMP nsMsgBiffManager::Init() {
+ if (mInited) return NS_OK;
+
+ mInited = true;
+ nsresult rv;
+
+ nsCOMPtr<nsIMsgAccountManager> accountManager =
+ do_GetService("@mozilla.org/messenger/account-manager;1", &rv);
+ if (NS_SUCCEEDED(rv)) accountManager->AddIncomingServerListener(this);
+
+ // in turbo mode on profile change we don't need to do anything below this
+ if (mHaveShutdown) {
+ mHaveShutdown = false;
+ return NS_OK;
+ }
+
+ // Ensure status bar biff service has started
+ nsCOMPtr<nsIFolderListener> statusBarBiffService =
+ do_GetService(kStatusBarBiffManagerCID, &rv);
+
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (observerService) {
+ observerService->AddObserver(this, "sleep_notification", true);
+ observerService->AddObserver(this, "wake_notification", true);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgBiffManager::Shutdown() {
+ if (mBiffTimer) {
+ mBiffTimer->Cancel();
+ mBiffTimer = nullptr;
+ }
+
+ nsresult rv;
+ nsCOMPtr<nsIMsgAccountManager> accountManager =
+ do_GetService("@mozilla.org/messenger/account-manager;1", &rv);
+ if (NS_SUCCEEDED(rv)) accountManager->RemoveIncomingServerListener(this);
+
+ mHaveShutdown = true;
+ mInited = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgBiffManager::Observe(nsISupports* aSubject,
+ const char* aTopic,
+ const char16_t* someData) {
+ if (!strcmp(aTopic, "sleep_notification") && mBiffTimer) {
+ mBiffTimer->Cancel();
+ mBiffTimer = nullptr;
+ } else if (!strcmp(aTopic, "wake_notification")) {
+ // wait 10 seconds after waking up to start biffing again.
+ nsresult rv = NS_NewTimerWithFuncCallback(
+ getter_AddRefs(mBiffTimer), OnBiffTimer, (void*)this, 10000,
+ nsITimer::TYPE_ONE_SHOT, "nsMsgBiffManager::OnBiffTimer", nullptr);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Could not start mBiffTimer timer");
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgBiffManager::AddServerBiff(nsIMsgIncomingServer* server) {
+ NS_ENSURE_ARG_POINTER(server);
+
+ int32_t biffMinutes;
+
+ nsresult rv = server->GetBiffMinutes(&biffMinutes);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Don't add if biffMinutes isn't > 0
+ if (biffMinutes > 0) {
+ int32_t serverIndex = FindServer(server);
+ // Only add it if it hasn't been added already.
+ if (serverIndex == -1) {
+ nsBiffEntry biffEntry;
+ biffEntry.server = server;
+ rv = SetNextBiffTime(biffEntry, PR_Now());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ AddBiffEntry(biffEntry);
+ SetupNextBiff();
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgBiffManager::RemoveServerBiff(nsIMsgIncomingServer* server) {
+ int32_t pos = FindServer(server);
+ if (pos != -1) mBiffArray.RemoveElementAt(pos);
+
+ // Should probably reset biff time if this was the server that gets biffed
+ // next.
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgBiffManager::ForceBiff(nsIMsgIncomingServer* server) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgBiffManager::ForceBiffAll() { return NS_OK; }
+
+NS_IMETHODIMP nsMsgBiffManager::OnServerLoaded(nsIMsgIncomingServer* server) {
+ NS_ENSURE_ARG_POINTER(server);
+
+ bool doBiff = false;
+ nsresult rv = server->GetDoBiff(&doBiff);
+
+ if (NS_SUCCEEDED(rv) && doBiff) rv = AddServerBiff(server);
+
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgBiffManager::OnServerUnloaded(nsIMsgIncomingServer* server) {
+ return RemoveServerBiff(server);
+}
+
+NS_IMETHODIMP nsMsgBiffManager::OnServerChanged(nsIMsgIncomingServer* server) {
+ // nothing required. If the hostname or username changed
+ // the next time biff fires, we'll ping the right server
+ return NS_OK;
+}
+
+int32_t nsMsgBiffManager::FindServer(nsIMsgIncomingServer* server) {
+ uint32_t count = mBiffArray.Length();
+ for (uint32_t i = 0; i < count; i++) {
+ if (server == mBiffArray[i].server.get()) return i;
+ }
+ return -1;
+}
+
+nsresult nsMsgBiffManager::AddBiffEntry(nsBiffEntry& biffEntry) {
+ uint32_t i;
+ uint32_t count = mBiffArray.Length();
+ for (i = 0; i < count; i++) {
+ if (biffEntry.nextBiffTime < mBiffArray[i].nextBiffTime) break;
+ }
+ MOZ_LOG(MsgBiffLogModule, mozilla::LogLevel::Info,
+ ("inserting biff entry at %d", i));
+ mBiffArray.InsertElementAt(i, biffEntry);
+ return NS_OK;
+}
+
+nsresult nsMsgBiffManager::SetNextBiffTime(nsBiffEntry& biffEntry,
+ PRTime currentTime) {
+ nsIMsgIncomingServer* server = biffEntry.server;
+ NS_ENSURE_TRUE(server, NS_ERROR_FAILURE);
+
+ int32_t biffInterval;
+ nsresult rv = server->GetBiffMinutes(&biffInterval);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Add biffInterval, converted in microseconds, to current time.
+ // Force 64-bit multiplication.
+ PRTime chosenTimeInterval = biffInterval * 60000000LL;
+ biffEntry.nextBiffTime = currentTime + chosenTimeInterval;
+
+ // Check if we should jitter.
+ nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
+ if (prefs) {
+ bool shouldUseBiffJitter = false;
+ prefs->GetBoolPref(PREF_BIFF_JITTER, &shouldUseBiffJitter);
+ if (shouldUseBiffJitter) {
+ // Calculate a jitter of +/-5% on chosenTimeInterval
+ // - minimum 1 second (to avoid a modulo with 0)
+ // - maximum 30 seconds (to avoid problems when biffInterval is very
+ // large)
+ int64_t jitter = (int64_t)(0.05 * (int64_t)chosenTimeInterval);
+ jitter =
+ std::max<int64_t>(1000000LL, std::min<int64_t>(jitter, 30000000LL));
+ jitter = ((rand() % 2) ? 1 : -1) * (rand() % jitter);
+
+ biffEntry.nextBiffTime += jitter;
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult nsMsgBiffManager::SetupNextBiff() {
+ if (mBiffArray.Length() > 0) {
+ // Get the next biff entry
+ const nsBiffEntry& biffEntry = mBiffArray[0];
+ PRTime currentTime = PR_Now();
+ int64_t biffDelay;
+ int64_t ms(1000);
+
+ if (currentTime > biffEntry.nextBiffTime) {
+ // Let's wait 30 seconds before firing biff again
+ biffDelay = 30 * PR_USEC_PER_SEC;
+ } else
+ biffDelay = biffEntry.nextBiffTime - currentTime;
+
+ // Convert biffDelay into milliseconds
+ int64_t timeInMS = biffDelay / ms;
+ uint32_t timeInMSUint32 = (uint32_t)timeInMS;
+
+ // Can't currently reset a timer when it's in the process of
+ // calling Notify. So, just release the timer here and create a new one.
+ if (mBiffTimer) mBiffTimer->Cancel();
+
+ MOZ_LOG(MsgBiffLogModule, mozilla::LogLevel::Info,
+ ("setting %d timer", timeInMSUint32));
+
+ nsresult rv = NS_NewTimerWithFuncCallback(
+ getter_AddRefs(mBiffTimer), OnBiffTimer, (void*)this, timeInMSUint32,
+ nsITimer::TYPE_ONE_SHOT, "nsMsgBiffManager::OnBiffTimer", nullptr);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Could not start mBiffTimer timer");
+ }
+ }
+ return NS_OK;
+}
+
+// This is the function that does a biff on all of the servers whose time it is
+// to biff.
+nsresult nsMsgBiffManager::PerformBiff() {
+ PRTime currentTime = PR_Now();
+ nsCOMArray<nsIMsgFolder> targetFolders;
+ MOZ_LOG(MsgBiffLogModule, mozilla::LogLevel::Info, ("performing biffs"));
+
+ uint32_t count = mBiffArray.Length();
+ for (uint32_t i = 0; i < count; i++) {
+ // Take a copy of the entry rather than the a reference so that we can
+ // remove and add if necessary, but keep the references and memory alive.
+ nsBiffEntry current = mBiffArray[i];
+ if (current.nextBiffTime < currentTime) {
+ bool serverBusy = false;
+ bool serverRequiresPassword = true;
+ bool passwordPromptRequired;
+
+ current.server->GetPasswordPromptRequired(&passwordPromptRequired);
+ current.server->GetServerBusy(&serverBusy);
+ current.server->GetServerRequiresPasswordForBiff(&serverRequiresPassword);
+ // find the dest folder we're actually downloading to...
+ nsCOMPtr<nsIMsgFolder> rootMsgFolder;
+ current.server->GetRootMsgFolder(getter_AddRefs(rootMsgFolder));
+ int32_t targetFolderIndex = targetFolders.IndexOfObject(rootMsgFolder);
+ if (targetFolderIndex == kNotFound)
+ targetFolders.AppendObject(rootMsgFolder);
+
+ // so if we need to be authenticated to biff, check that we are
+ // (since we don't want to prompt the user for password UI)
+ // and make sure the server isn't already in the middle of downloading
+ // new messages
+ if (!serverBusy && (!serverRequiresPassword || !passwordPromptRequired) &&
+ targetFolderIndex == kNotFound) {
+ nsCString serverKey;
+ current.server->GetKey(serverKey);
+ nsresult rv = current.server->PerformBiff(nullptr);
+ MOZ_LOG(MsgBiffLogModule, mozilla::LogLevel::Info,
+ ("biffing server %s rv = %" PRIx32, serverKey.get(),
+ static_cast<uint32_t>(rv)));
+ } else {
+ MOZ_LOG(MsgBiffLogModule, mozilla::LogLevel::Info,
+ ("not biffing server serverBusy = %d requirespassword = %d "
+ "password prompt required = %d targetFolderIndex = %d",
+ serverBusy, serverRequiresPassword, passwordPromptRequired,
+ targetFolderIndex));
+ }
+ // if we didn't do this server because the destination server was already
+ // being biffed into, leave this server in the biff array so it will fire
+ // next.
+ if (targetFolderIndex == kNotFound) {
+ mBiffArray.RemoveElementAt(i);
+ i--; // Because we removed it we need to look at the one that just
+ // moved up.
+ SetNextBiffTime(current, currentTime);
+ AddBiffEntry(current);
+ }
+#ifdef DEBUG_David_Bienvenu
+ else
+ printf("dest account performing biff\n");
+#endif
+ } else
+ // since we're in biff order, there's no reason to keep checking
+ break;
+ }
+ SetupNextBiff();
+ return NS_OK;
+}
diff --git a/comm/mailnews/base/src/nsMsgBiffManager.h b/comm/mailnews/base/src/nsMsgBiffManager.h
new file mode 100644
index 0000000000..e334429d71
--- /dev/null
+++ b/comm/mailnews/base/src/nsMsgBiffManager.h
@@ -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/. */
+
+#ifndef NSMSGBIFFMANAGER_H
+#define NSMSGBIFFMANAGER_H
+
+#include "msgCore.h"
+#include "nsIMsgBiffManager.h"
+#include "nsITimer.h"
+#include "nsTArray.h"
+#include "nsCOMPtr.h"
+#include "nsIIncomingServerListener.h"
+#include "nsWeakReference.h"
+#include "nsIObserver.h"
+
+typedef struct {
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ PRTime nextBiffTime;
+} nsBiffEntry;
+
+class nsMsgBiffManager : public nsIMsgBiffManager,
+ public nsIIncomingServerListener,
+ public nsIObserver,
+ public nsSupportsWeakReference {
+ public:
+ nsMsgBiffManager();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMSGBIFFMANAGER
+ NS_DECL_NSIINCOMINGSERVERLISTENER
+ NS_DECL_NSIOBSERVER
+
+ nsresult PerformBiff();
+
+ protected:
+ virtual ~nsMsgBiffManager();
+
+ int32_t FindServer(nsIMsgIncomingServer* server);
+ nsresult SetNextBiffTime(nsBiffEntry& biffEntry, PRTime currentTime);
+ nsresult SetupNextBiff();
+ nsresult AddBiffEntry(nsBiffEntry& biffEntry);
+
+ protected:
+ nsCOMPtr<nsITimer> mBiffTimer;
+ nsTArray<nsBiffEntry> mBiffArray;
+ bool mHaveShutdown;
+ bool mInited;
+};
+
+#endif // NSMSGBIFFMANAGER_H
diff --git a/comm/mailnews/base/src/nsMsgCompressIStream.cpp b/comm/mailnews/base/src/nsMsgCompressIStream.cpp
new file mode 100644
index 0000000000..a0dd6b7776
--- /dev/null
+++ b/comm/mailnews/base/src/nsMsgCompressIStream.cpp
@@ -0,0 +1,203 @@
+/* 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 "nsMsgCompressIStream.h"
+#include "prio.h"
+#include "prmem.h"
+#include <algorithm>
+
+#define BUFFER_SIZE 16384
+
+nsMsgCompressIStream::nsMsgCompressIStream()
+ : m_dataptr(nullptr), m_dataleft(0), m_inflateAgain(false) {}
+
+nsMsgCompressIStream::~nsMsgCompressIStream() { Close(); }
+
+NS_IMPL_ISUPPORTS(nsMsgCompressIStream, nsIInputStream, nsIAsyncInputStream)
+
+nsresult nsMsgCompressIStream::InitInputStream(nsIInputStream* rawStream) {
+ // protect against repeat calls
+ if (m_iStream) return NS_ERROR_UNEXPECTED;
+
+ // allocate some memory for buffering
+ m_zbuf = mozilla::MakeUnique<char[]>(BUFFER_SIZE);
+ if (!m_zbuf) return NS_ERROR_OUT_OF_MEMORY;
+
+ // allocate some memory for buffering
+ m_databuf = mozilla::MakeUnique<char[]>(BUFFER_SIZE);
+ if (!m_databuf) return NS_ERROR_OUT_OF_MEMORY;
+
+ // set up zlib object
+ m_zstream.zalloc = Z_NULL;
+ m_zstream.zfree = Z_NULL;
+ m_zstream.opaque = Z_NULL;
+
+ // http://zlib.net/manual.html is rather silent on the topic, but
+ // perl's Compress::Raw::Zlib manual says:
+ // -WindowBits
+ // To compress an RFC 1951 data stream, set WindowBits to -MAX_WBITS.
+ if (inflateInit2(&m_zstream, -MAX_WBITS) != Z_OK) return NS_ERROR_FAILURE;
+
+ m_iStream = rawStream;
+
+ return NS_OK;
+}
+
+nsresult nsMsgCompressIStream::DoInflation() {
+ // if there's something in the input buffer of the zstream, process it.
+ m_zstream.next_out = (Bytef*)m_databuf.get();
+ m_zstream.avail_out = BUFFER_SIZE;
+ int zr = inflate(&m_zstream, Z_SYNC_FLUSH);
+
+ // inflate() should normally be called until it returns
+ // Z_STREAM_END or an error, and Z_BUF_ERROR just means
+ // unable to progress any further (possible if we filled
+ // an output buffer exactly)
+ if (zr == Z_BUF_ERROR || zr == Z_STREAM_END) zr = Z_OK;
+
+ // otherwise it's an error
+ if (zr != Z_OK) return NS_ERROR_FAILURE;
+
+ // http://www.zlib.net/manual.html says:
+ // If inflate returns Z_OK and with zero avail_out, it must be called
+ // again after making room in the output buffer because there might be
+ // more output pending.
+ m_inflateAgain = m_zstream.avail_out ? false : true;
+
+ // set the pointer to the start of the buffer, and the count to how
+ // based on how many bytes are left unconsumed.
+ m_dataptr = m_databuf.get();
+ m_dataleft = BUFFER_SIZE - m_zstream.avail_out;
+
+ return NS_OK;
+}
+
+/* void close (); */
+NS_IMETHODIMP nsMsgCompressIStream::Close() { return CloseWithStatus(NS_OK); }
+
+NS_IMETHODIMP nsMsgCompressIStream::CloseWithStatus(nsresult reason) {
+ nsresult rv = NS_OK;
+
+ if (m_iStream) {
+ // pass the status through to our wrapped stream
+ nsCOMPtr<nsIAsyncInputStream> asyncInputStream =
+ do_QueryInterface(m_iStream);
+ if (asyncInputStream) rv = asyncInputStream->CloseWithStatus(reason);
+
+ // tidy up
+ m_iStream = nullptr;
+ inflateEnd(&m_zstream);
+ }
+
+ // clean up all the buffers
+ m_zbuf = nullptr;
+ m_databuf = nullptr;
+ m_dataptr = nullptr;
+ m_dataleft = 0;
+
+ return rv;
+}
+
+/* unsigned long long available (); */
+NS_IMETHODIMP nsMsgCompressIStream::Available(uint64_t* aResult) {
+ if (!m_iStream) return NS_BASE_STREAM_CLOSED;
+
+ // check if there's anything still in flight
+ if (!m_dataleft && m_inflateAgain) {
+ nsresult rv = DoInflation();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // we'll be returning this many to the next read, guaranteed
+ if (m_dataleft) {
+ *aResult = m_dataleft;
+ return NS_OK;
+ }
+
+ // this value isn't accurate, but will give a good true/false
+ // indication for idle purposes, and next read will fill
+ // m_dataleft, so we'll have an accurate count for the next call.
+ return m_iStream->Available(aResult);
+}
+
+/* [noscript] unsigned long read (in charPtr aBuf, in unsigned long aCount); */
+NS_IMETHODIMP nsMsgCompressIStream::Read(char* aBuf, uint32_t aCount,
+ uint32_t* aResult) {
+ if (!m_iStream) {
+ *aResult = 0;
+ return NS_OK;
+ }
+
+ // There are two stages of buffering:
+ // * m_zbuf contains the compressed data from the remote server
+ // * m_databuf contains the uncompressed raw bytes for consumption
+ // by the local client.
+ //
+ // Each buffer will only be filled when the following buffers
+ // have been entirely consumed.
+ //
+ // m_dataptr and m_dataleft are respectively a pointer to the
+ // unconsumed portion of m_databuf and the number of bytes
+ // of uncompressed data remaining in m_databuf.
+ //
+ // both buffers have a maximum size of BUFFER_SIZE, so it is
+ // possible that multiple inflate passes will be required to
+ // consume all of m_zbuf.
+ while (!m_dataleft) {
+ // get some more data if we don't already have any
+ if (!m_inflateAgain) {
+ uint32_t bytesRead;
+ nsresult rv =
+ m_iStream->Read(m_zbuf.get(), (uint32_t)BUFFER_SIZE, &bytesRead);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!bytesRead) return NS_BASE_STREAM_CLOSED;
+ m_zstream.next_in = (Bytef*)m_zbuf.get();
+ m_zstream.avail_in = bytesRead;
+ }
+
+ nsresult rv = DoInflation();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ *aResult = std::min(m_dataleft, aCount);
+
+ if (*aResult) {
+ memcpy(aBuf, m_dataptr, *aResult);
+ m_dataptr += *aResult;
+ m_dataleft -= *aResult;
+ }
+
+ return NS_OK;
+}
+
+/* [noscript] unsigned long readSegments (in nsWriteSegmentFun aWriter, in
+ * voidPtr aClosure, in unsigned long aCount); */
+NS_IMETHODIMP nsMsgCompressIStream::ReadSegments(nsWriteSegmentFun aWriter,
+ void* aClosure,
+ uint32_t aCount,
+ uint32_t* _retval) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsMsgCompressIStream::AsyncWait(nsIInputStreamCallback* callback,
+ uint32_t flags, uint32_t amount,
+ nsIEventTarget* target) {
+ if (!m_iStream) return NS_BASE_STREAM_CLOSED;
+
+ nsCOMPtr<nsIAsyncInputStream> asyncInputStream = do_QueryInterface(m_iStream);
+ if (asyncInputStream)
+ return asyncInputStream->AsyncWait(callback, flags, amount, target);
+
+ return NS_OK;
+}
+
+/* boolean isNonBlocking (); */
+NS_IMETHODIMP nsMsgCompressIStream::IsNonBlocking(bool* aNonBlocking) {
+ *aNonBlocking = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgCompressIStream::StreamStatus() {
+ return m_iStream->StreamStatus();
+}
diff --git a/comm/mailnews/base/src/nsMsgCompressIStream.h b/comm/mailnews/base/src/nsMsgCompressIStream.h
new file mode 100644
index 0000000000..642d35f9fa
--- /dev/null
+++ b/comm/mailnews/base/src/nsMsgCompressIStream.h
@@ -0,0 +1,33 @@
+/* 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 "msgCore.h"
+#include "nsIAsyncInputStream.h"
+#include "nsIInputStream.h"
+#include "nsCOMPtr.h"
+#include "mozilla/UniquePtr.h"
+#include "zlib.h"
+
+class nsMsgCompressIStream final : public nsIAsyncInputStream {
+ public:
+ nsMsgCompressIStream();
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ NS_DECL_NSIINPUTSTREAM
+ NS_DECL_NSIASYNCINPUTSTREAM
+
+ nsresult InitInputStream(nsIInputStream* rawStream);
+
+ protected:
+ ~nsMsgCompressIStream();
+ nsresult DoInflation();
+ nsCOMPtr<nsIInputStream> m_iStream;
+ mozilla::UniquePtr<char[]> m_zbuf;
+ mozilla::UniquePtr<char[]> m_databuf;
+ char* m_dataptr;
+ uint32_t m_dataleft;
+ bool m_inflateAgain;
+ z_stream m_zstream;
+};
diff --git a/comm/mailnews/base/src/nsMsgCompressOStream.cpp b/comm/mailnews/base/src/nsMsgCompressOStream.cpp
new file mode 100644
index 0000000000..fd490274ca
--- /dev/null
+++ b/comm/mailnews/base/src/nsMsgCompressOStream.cpp
@@ -0,0 +1,128 @@
+/* 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 "nsMsgCompressOStream.h"
+#include "prio.h"
+#include "prmem.h"
+
+#define BUFFER_SIZE 16384
+
+nsMsgCompressOStream::nsMsgCompressOStream() : m_zbuf(nullptr) {}
+
+nsMsgCompressOStream::~nsMsgCompressOStream() { Close(); }
+
+NS_IMPL_ISUPPORTS(nsMsgCompressOStream, nsIOutputStream)
+
+nsresult nsMsgCompressOStream::InitOutputStream(nsIOutputStream* rawStream) {
+ // protect against repeat calls
+ if (m_oStream) return NS_ERROR_UNEXPECTED;
+
+ // allocate some memory for a buffer
+ m_zbuf = mozilla::MakeUnique<char[]>(BUFFER_SIZE);
+ if (!m_zbuf) return NS_ERROR_OUT_OF_MEMORY;
+
+ // set up the zlib object
+ m_zstream.zalloc = Z_NULL;
+ m_zstream.zfree = Z_NULL;
+ m_zstream.opaque = Z_NULL;
+
+ // http://zlib.net/manual.html is rather silent on the topic, but
+ // perl's Compress::Raw::Zlib manual says:
+ // -WindowBits [...]
+ // To compress an RFC 1951 data stream, set WindowBits to -MAX_WBITS.
+ if (deflateInit2(&m_zstream, Z_DEFAULT_COMPRESSION, Z_DEFLATED, -MAX_WBITS,
+ MAX_MEM_LEVEL, Z_DEFAULT_STRATEGY) != Z_OK)
+ return NS_ERROR_FAILURE;
+
+ m_oStream = rawStream;
+
+ return NS_OK;
+}
+
+/* void close (); */
+NS_IMETHODIMP nsMsgCompressOStream::Close() {
+ if (m_oStream) {
+ m_oStream = nullptr;
+ deflateEnd(&m_zstream);
+ }
+ m_zbuf = nullptr;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgCompressOStream::Write(const char* buf, uint32_t count, uint32_t* result) {
+ if (!m_oStream) return NS_BASE_STREAM_CLOSED;
+
+ m_zstream.next_in = (Bytef*)buf;
+ m_zstream.avail_in = count;
+
+ // keep looping until the buffer doesn't get filled
+ do {
+ m_zstream.next_out = (Bytef*)m_zbuf.get();
+ m_zstream.avail_out = BUFFER_SIZE;
+ // Using "Z_SYNC_FLUSH" may cause excess flushes if the calling
+ // code does a lot of small writes. An option with the IMAP
+ // protocol is to check the buffer for "\n" at the end, but
+ // in the interests of keeping this generic, don't optimise
+ // yet. An alternative is to require ->Flush always, but that
+ // is likely to break callers.
+ int zr = deflate(&m_zstream, Z_SYNC_FLUSH);
+ if (zr == Z_STREAM_END || zr == Z_BUF_ERROR)
+ zr = Z_OK; // not an error for our purposes
+ if (zr != Z_OK) return NS_ERROR_FAILURE;
+
+ uint32_t out_size = BUFFER_SIZE - m_zstream.avail_out;
+ const char* out_buf = m_zbuf.get();
+
+ // push everything in the buffer before repeating
+ while (out_size) {
+ uint32_t out_result;
+ nsresult rv = m_oStream->Write(out_buf, out_size, &out_result);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!out_result) return NS_BASE_STREAM_CLOSED;
+ out_size -= out_result;
+ out_buf += out_result;
+ }
+
+ // http://www.zlib.net/manual.html says:
+ // If deflate returns with avail_out == 0, this function must be
+ // called again with the same value of the flush parameter and
+ // more output space (updated avail_out), until the flush is
+ // complete (deflate returns with non-zero avail_out).
+ } while (!m_zstream.avail_out);
+
+ *result = count;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgCompressOStream::Flush(void) {
+ if (!m_oStream) return NS_BASE_STREAM_CLOSED;
+
+ return m_oStream->Flush();
+}
+
+NS_IMETHODIMP
+nsMsgCompressOStream::WriteFrom(nsIInputStream* inStr, uint32_t count,
+ uint32_t* _retval) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsMsgCompressOStream::WriteSegments(nsReadSegmentFun reader, void* closure,
+ uint32_t count, uint32_t* _retval) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/* boolean isNonBlocking (); */
+NS_IMETHODIMP nsMsgCompressOStream::IsNonBlocking(bool* aNonBlocking) {
+ *aNonBlocking = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgCompressOStream::StreamStatus() {
+ return m_oStream->StreamStatus();
+}
diff --git a/comm/mailnews/base/src/nsMsgCompressOStream.h b/comm/mailnews/base/src/nsMsgCompressOStream.h
new file mode 100644
index 0000000000..96d38ad9c0
--- /dev/null
+++ b/comm/mailnews/base/src/nsMsgCompressOStream.h
@@ -0,0 +1,26 @@
+/* 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 "msgCore.h"
+#include "nsIOutputStream.h"
+#include "nsCOMPtr.h"
+#include "mozilla/UniquePtr.h"
+#include "zlib.h"
+
+class nsMsgCompressOStream final : public nsIOutputStream {
+ public:
+ nsMsgCompressOStream();
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ NS_DECL_NSIOUTPUTSTREAM
+
+ nsresult InitOutputStream(nsIOutputStream* rawStream);
+
+ protected:
+ ~nsMsgCompressOStream();
+ nsCOMPtr<nsIOutputStream> m_oStream;
+ mozilla::UniquePtr<char[]> m_zbuf;
+ z_stream m_zstream;
+};
diff --git a/comm/mailnews/base/src/nsMsgContentPolicy.cpp b/comm/mailnews/base/src/nsMsgContentPolicy.cpp
new file mode 100644
index 0000000000..6494732cad
--- /dev/null
+++ b/comm/mailnews/base/src/nsMsgContentPolicy.cpp
@@ -0,0 +1,928 @@
+/* -*- 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 "nsMsgContentPolicy.h"
+#include "nsIMsgMailSession.h"
+#include "nsIPermissionManager.h"
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+#include "nsIAbManager.h"
+#include "nsIAbDirectory.h"
+#include "nsIAbCard.h"
+#include "nsIMsgWindow.h"
+#include "nsIMsgHdr.h"
+#include "nsIEncryptedSMIMEURIsSrvc.h"
+#include "nsNetUtil.h"
+#include "nsIMsgComposeService.h"
+#include "nsIDocShellTreeItem.h"
+#include "nsIWebNavigation.h"
+#include "nsContentPolicyUtils.h"
+#include "nsFrameLoaderOwner.h"
+#include "nsFrameLoader.h"
+#include "nsMsgUtils.h"
+#include "nsThreadUtils.h"
+#include "mozilla/mailnews/MimeHeaderParser.h"
+#include "mozilla/dom/HTMLImageElement.h"
+#include "nsINntpUrl.h"
+#include "nsILoadInfo.h"
+#include "nsSandboxFlags.h"
+#include "nsQueryObject.h"
+#include "mozilla/dom/WindowGlobalParent.h"
+#include "mozilla/SyncRunnable.h"
+#include "nsIObserverService.h"
+
+static const char kBlockRemoteImages[] =
+ "mailnews.message_display.disable_remote_image";
+static const char kTrustedDomains[] = "mail.trusteddomains";
+
+using namespace mozilla;
+using namespace mozilla::mailnews;
+
+// Per message headder flags to keep track of whether the user is allowing
+// remote content for a particular message. if you change or add more values to
+// these constants, be sure to modify the corresponding definitions in
+// mailWindowOverlay.js
+#define kNoRemoteContentPolicy 0
+#define kBlockRemoteContent 1
+#define kAllowRemoteContent 2
+
+NS_IMPL_ISUPPORTS(nsMsgContentPolicy, nsIContentPolicy, nsIMsgContentPolicy,
+ nsIObserver, nsISupportsWeakReference)
+
+nsMsgContentPolicy::nsMsgContentPolicy() { mBlockRemoteImages = true; }
+
+nsMsgContentPolicy::~nsMsgContentPolicy() {
+ // hey, we are going away...clean up after ourself....unregister our observer
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> prefInternal =
+ do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ prefInternal->RemoveObserver(kBlockRemoteImages, this);
+ }
+}
+
+nsresult nsMsgContentPolicy::Init() {
+ nsresult rv;
+
+ // register ourself as an observer on the mail preference to block remote
+ // images
+ nsCOMPtr<nsIPrefBranch> prefInternal =
+ do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ prefInternal->AddObserver(kBlockRemoteImages, this, true);
+
+ prefInternal->GetCharPref(kTrustedDomains, mTrustedMailDomains);
+ prefInternal->GetBoolPref(kBlockRemoteImages, &mBlockRemoteImages);
+
+ // Grab a handle on the PermissionManager service for managing allowed remote
+ // content senders.
+ mPermissionManager = do_GetService(NS_PERMISSIONMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+/**
+ * @returns true if the sender referenced by aMsgHdr is explicitly allowed to
+ * load remote images according to the PermissionManager
+ */
+bool nsMsgContentPolicy::ShouldAcceptRemoteContentForSender(
+ nsIMsgDBHdr* aMsgHdr) {
+ if (!aMsgHdr) return false;
+
+ // extract the e-mail address from the msg hdr
+ nsCString author;
+ nsresult rv = aMsgHdr->GetAuthor(getter_Copies(author));
+ NS_ENSURE_SUCCESS(rv, false);
+
+ nsCString emailAddress;
+ ExtractEmail(EncodedHeader(author), emailAddress);
+ if (emailAddress.IsEmpty()) return false;
+
+ nsCOMPtr<nsIIOService> ios =
+ do_GetService("@mozilla.org/network/io-service;1", &rv);
+ NS_ENSURE_SUCCESS(rv, false);
+ nsCOMPtr<nsIURI> mailURI;
+ emailAddress.InsertLiteral("chrome://messenger/content/email=", 0);
+ rv = ios->NewURI(emailAddress, nullptr, nullptr, getter_AddRefs(mailURI));
+ NS_ENSURE_SUCCESS(rv, false);
+
+ // check with permission manager
+ uint32_t permission = 0;
+ mozilla::OriginAttributes attrs;
+ RefPtr<mozilla::BasePrincipal> principal =
+ mozilla::BasePrincipal::CreateContentPrincipal(mailURI, attrs);
+ rv = mPermissionManager->TestPermissionFromPrincipal(principal, "image"_ns,
+ &permission);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ // Only return true if the permission manager has an explicit allow
+ return (permission == nsIPermissionManager::ALLOW_ACTION);
+}
+
+/**
+ * Extract the host name from aContentLocation, and look it up in our list
+ * of trusted domains.
+ */
+bool nsMsgContentPolicy::IsTrustedDomain(nsIURI* aContentLocation) {
+ bool trustedDomain = false;
+ // get the host name of the server hosting the remote image
+ nsAutoCString host;
+ nsresult rv = aContentLocation->GetHost(host);
+
+ if (NS_SUCCEEDED(rv) && !mTrustedMailDomains.IsEmpty())
+ trustedDomain = MsgHostDomainIsTrusted(host, mTrustedMailDomains);
+
+ return trustedDomain;
+}
+
+NS_IMETHODIMP
+nsMsgContentPolicy::ShouldLoad(nsIURI* aContentLocation, nsILoadInfo* aLoadInfo,
+ const nsACString& aMimeGuess,
+ int16_t* aDecision) {
+ nsresult rv = NS_OK;
+ ExtContentPolicyType aContentType = aLoadInfo->GetExternalContentPolicyType();
+ nsCOMPtr<nsISupports> aRequestingContext;
+ if (aContentType == ExtContentPolicy::TYPE_DOCUMENT)
+ aRequestingContext = aLoadInfo->ContextForTopLevelLoad();
+ else
+ aRequestingContext = aLoadInfo->LoadingNode();
+ nsCOMPtr<nsIPrincipal> loadingPrincipal = aLoadInfo->GetLoadingPrincipal();
+ nsCOMPtr<nsIURI> aRequestingLocation;
+ if (loadingPrincipal) {
+ BasePrincipal::Cast(loadingPrincipal)
+ ->GetURI(getter_AddRefs(aRequestingLocation));
+ }
+
+ // The default decision at the start of the function is to accept the load.
+ // Once we have checked the content type and the requesting location, then
+ // we switch it to reject.
+ //
+ // Be very careful about returning error codes - if this method returns an
+ // NS_ERROR_*, any decision made here will be ignored, and the document could
+ // be accepted when we don't want it to be.
+ //
+ // In most cases if an error occurs, its something we didn't expect so we
+ // should be rejecting the document anyway.
+ *aDecision = nsIContentPolicy::ACCEPT;
+
+ NS_ENSURE_ARG_POINTER(aContentLocation);
+
+#ifdef DEBUG_MsgContentPolicy
+ fprintf(stderr, "aContentType: %d\naContentLocation = %s\n", aContentType,
+ aContentLocation->GetSpecOrDefault().get());
+ fprintf(stderr, "aRequestingContext is %s\n",
+ aRequestingContext ? "not null" : "null");
+#endif
+
+#ifndef MOZ_THUNDERBIRD
+ // Go find out if we are dealing with mailnews. Anything else
+ // isn't our concern and we accept content.
+ nsCOMPtr<nsIDocShell> rootDocShell;
+ rv = GetRootDocShellForContext(aRequestingContext,
+ getter_AddRefs(rootDocShell));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // We only want to deal with mailnews
+ if (rootDocShell->GetAppType() != nsIDocShell::APP_TYPE_MAIL) return NS_OK;
+#endif
+
+ switch (aContentType) {
+ // Plugins (nsIContentPolicy::TYPE_OBJECT) are blocked on document load.
+ case ExtContentPolicy::TYPE_DOCUMENT:
+ // At this point, we have no intention of supporting a different JS
+ // setting on a subdocument, so we don't worry about TYPE_SUBDOCUMENT
+ // here.
+
+ if (NS_IsMainThread()) {
+ rv = SetDisableItemsOnMailNewsUrlDocshells(aContentLocation, aLoadInfo);
+ } else {
+ auto SetDisabling = [&, location = nsCOMPtr(aContentLocation),
+ loadInfo = nsCOMPtr(aLoadInfo)]() -> auto {
+ rv = SetDisableItemsOnMailNewsUrlDocshells(location, loadInfo);
+ };
+ nsCOMPtr<nsIRunnable> task =
+ NS_NewRunnableFunction("SetDisabling", SetDisabling);
+ mozilla::SyncRunnable::DispatchToThread(
+ mozilla::GetMainThreadSerialEventTarget(), task);
+ }
+ // if something went wrong during the tweaking, reject this content
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to set disable items on docShells");
+ *aDecision = nsIContentPolicy::REJECT_TYPE;
+ return NS_OK;
+ }
+ break;
+
+ case ExtContentPolicy::TYPE_CSP_REPORT:
+ // We cannot block CSP reports.
+ *aDecision = nsIContentPolicy::ACCEPT;
+ return NS_OK;
+ break;
+
+ default:
+ break;
+ }
+
+ // NOTE: Not using NS_ENSURE_ARG_POINTER because this is a legitimate case
+ // that can happen. Also keep in mind that the default policy used for a
+ // failure code is ACCEPT.
+ if (!aRequestingLocation) return NS_ERROR_INVALID_POINTER;
+
+#ifdef DEBUG_MsgContentPolicy
+ fprintf(stderr, "aRequestingLocation = %s\n",
+ aRequestingLocation->GetSpecOrDefault().get());
+#endif
+
+ // If the requesting location is safe, accept the content location request.
+ if (IsSafeRequestingLocation(aRequestingLocation)) return rv;
+
+ // Now default to reject so early returns via NS_ENSURE_SUCCESS
+ // cause content to be rejected.
+ *aDecision = nsIContentPolicy::REJECT_REQUEST;
+
+ // We want to establish the following:
+ // \--------\ requester | | |
+ // content \------------\ | | |
+ // requested \| mail message | news message | http(s)/data etc.
+ // -------------------------+---------------+--------------+------------------
+ // mail message content | load if same | don't load | don't load
+ // mailbox, imap, JsAccount | message (1) | (2) | (3)
+ // -------------------------+---------------+--------------+------------------
+ // news message | don't load (4)| load (5) | load (6)
+ // -------------------------+---------------+--------------+------------------
+ // http(s)/data, etc. | (default) | (default) | (default)
+ // -------------------------+---------------+--------------+------------------
+ nsCOMPtr<nsIMsgMessageUrl> contentURL(do_QueryInterface(aContentLocation));
+ if (contentURL) {
+ nsCOMPtr<nsINntpUrl> contentNntpURL(do_QueryInterface(aContentLocation));
+ if (!contentNntpURL) {
+ // Mail message (mailbox, imap or JsAccount) content requested, for
+ // example a message part, like an image: To load mail message content the
+ // requester must have the same "normalized" principal. This is basically
+ // a "same origin" test, it protects against cross-loading of mail message
+ // content from other mail or news messages.
+ nsCOMPtr<nsIMsgMessageUrl> requestURL(
+ do_QueryInterface(aRequestingLocation));
+ // If the request URL is not also a message URL, then we don't accept.
+ if (requestURL) {
+ nsCString contentPrincipalSpec, requestPrincipalSpec;
+ nsresult rv1 = contentURL->GetNormalizedSpec(contentPrincipalSpec);
+ nsresult rv2 = requestURL->GetNormalizedSpec(requestPrincipalSpec);
+ if (NS_SUCCEEDED(rv1) && NS_SUCCEEDED(rv2) &&
+ contentPrincipalSpec.Equals(requestPrincipalSpec))
+ *aDecision = nsIContentPolicy::ACCEPT; // (1)
+ }
+ return NS_OK; // (2) and (3)
+ }
+
+ // News message content requested. Don't accept request coming
+ // from a mail message since it would access the news server.
+ nsCOMPtr<nsIMsgMessageUrl> requestURL(
+ do_QueryInterface(aRequestingLocation));
+ if (requestURL) {
+ nsCOMPtr<nsINntpUrl> requestNntpURL(
+ do_QueryInterface(aRequestingLocation));
+ if (!requestNntpURL) return NS_OK; // (4)
+ }
+ *aDecision = nsIContentPolicy::ACCEPT; // (5) and (6)
+ return NS_OK;
+ }
+
+ // If exposed protocol not covered by the test above or protocol that has been
+ // specifically exposed by an add-on, or is a chrome url, then allow the load.
+ if (IsExposedProtocol(aContentLocation)) {
+ *aDecision = nsIContentPolicy::ACCEPT;
+ return NS_OK;
+ }
+
+ // Never load unexposed protocols except for web protocols and file.
+ // Protocols like ftp are always blocked.
+ if (ShouldBlockUnexposedProtocol(aContentLocation)) return NS_OK;
+
+ // Mailnews URIs are not loaded in child processes, so I think that beyond
+ // here, if we're in a child process, the decision will always be accept.
+ //
+ // targetContext->Canonical does not work in a child process, so we can't
+ // really move on anyway.
+ if (!XRE_IsParentProcess()) {
+ *aDecision = nsIContentPolicy::ACCEPT;
+ return NS_OK;
+ }
+
+ // Find out the URI that originally initiated the set of requests for this
+ // context.
+ RefPtr<mozilla::dom::BrowsingContext> targetContext;
+ rv = aLoadInfo->GetTargetBrowsingContext(getter_AddRefs(targetContext));
+ NS_ENSURE_SUCCESS(rv, NS_OK);
+
+ if (!targetContext) {
+ *aDecision = nsIContentPolicy::ACCEPT;
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIURI> originatorLocation;
+ dom::CanonicalBrowsingContext* cbc = targetContext->Canonical();
+ if (cbc) {
+ dom::WindowGlobalParent* wgp = cbc->GetCurrentWindowGlobal();
+ if (wgp) {
+ originatorLocation = wgp->GetDocumentURI();
+ }
+ }
+ if (!originatorLocation) {
+ return NS_OK;
+ }
+
+#ifdef DEBUG_MsgContentPolicy
+ fprintf(stderr, "originatorLocation = %s\n",
+ originatorLocation->GetSpecOrDefault().get());
+#endif
+
+ // Don't load remote content for encrypted messages.
+ nsCOMPtr<nsIEncryptedSMIMEURIsService> encryptedURIService = do_GetService(
+ "@mozilla.org/messenger-smime/smime-encrypted-uris-service;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ bool isEncrypted;
+ rv = encryptedURIService->IsEncrypted(aRequestingLocation->GetSpecOrDefault(),
+ &isEncrypted);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (isEncrypted) {
+ *aDecision = nsIContentPolicy::REJECT_REQUEST;
+ NotifyContentWasBlocked(targetContext->Id(), aContentLocation);
+ return NS_OK;
+ }
+
+ // If we are allowing all remote content...
+ if (!mBlockRemoteImages) {
+ *aDecision = nsIContentPolicy::ACCEPT;
+ return NS_OK;
+ }
+
+ uint32_t permission;
+ mozilla::OriginAttributes attrs;
+ RefPtr<mozilla::BasePrincipal> principal =
+ mozilla::BasePrincipal::CreateContentPrincipal(aContentLocation, attrs);
+ mPermissionManager->TestPermissionFromPrincipal(principal, "image"_ns,
+ &permission);
+ switch (permission) {
+ case nsIPermissionManager::UNKNOWN_ACTION: {
+ // No exception was found for this location.
+ break;
+ }
+ case nsIPermissionManager::ALLOW_ACTION: {
+ *aDecision = nsIContentPolicy::ACCEPT;
+ return NS_OK;
+ }
+ case nsIPermissionManager::DENY_ACTION: {
+ *aDecision = nsIContentPolicy::REJECT_REQUEST;
+ return NS_OK;
+ }
+ }
+
+ // Handle compose windows separately from mail. Work out if we're in a compose
+ // window or not.
+ nsCOMPtr<nsIMsgCompose> msgCompose =
+ GetMsgComposeForBrowsingContext(targetContext);
+ if (msgCompose) {
+ ComposeShouldLoad(msgCompose, aRequestingContext, originatorLocation,
+ aContentLocation, aDecision);
+ return NS_OK;
+ }
+
+ // Allow content when using a remote page.
+ bool isHttp;
+ bool isHttps;
+ rv = originatorLocation->SchemeIs("http", &isHttp);
+ nsresult rv2 = originatorLocation->SchemeIs("https", &isHttps);
+ if (NS_SUCCEEDED(rv) && NS_SUCCEEDED(rv2) && (isHttp || isHttps)) {
+ *aDecision = nsIContentPolicy::ACCEPT;
+ return NS_OK;
+ }
+
+ // The default decision is still to reject.
+ ShouldAcceptContentForPotentialMsg(targetContext->Id(), aRequestingLocation,
+ aContentLocation, aDecision);
+ return NS_OK;
+}
+
+/**
+ * Determines if the requesting location is a safe one, i.e. its under the
+ * app/user's control - so file, about, chrome etc.
+ */
+bool nsMsgContentPolicy::IsSafeRequestingLocation(nsIURI* aRequestingLocation) {
+ if (!aRequestingLocation) return false;
+
+ // If aRequestingLocation is one of chrome, resource, file or view-source,
+ // allow aContentLocation to load.
+ bool isChrome;
+ bool isRes;
+ bool isFile;
+ bool isViewSource;
+
+ nsresult rv = aRequestingLocation->SchemeIs("chrome", &isChrome);
+ NS_ENSURE_SUCCESS(rv, false);
+ rv = aRequestingLocation->SchemeIs("resource", &isRes);
+ NS_ENSURE_SUCCESS(rv, false);
+ rv = aRequestingLocation->SchemeIs("file", &isFile);
+ NS_ENSURE_SUCCESS(rv, false);
+ rv = aRequestingLocation->SchemeIs("view-source", &isViewSource);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ if (isChrome || isRes || isFile || isViewSource) return true;
+
+ // Only allow about: to load anything if the requesting location is not the
+ // special about:blank one.
+ bool isAbout;
+ rv = aRequestingLocation->SchemeIs("about", &isAbout);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ if (!isAbout) return false;
+
+ nsCString fullSpec;
+ rv = aRequestingLocation->GetSpec(fullSpec);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ return !fullSpec.EqualsLiteral("about:blank");
+}
+
+/**
+ * Determines if the content location is a scheme that we're willing to expose
+ * for unlimited loading of content.
+ */
+bool nsMsgContentPolicy::IsExposedProtocol(nsIURI* aContentLocation) {
+ nsAutoCString contentScheme;
+ nsresult rv = aContentLocation->GetScheme(contentScheme);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ // Check some exposed protocols. Not all protocols in the list of
+ // network.protocol-handler.expose.* prefs in all-thunderbird.js are
+ // admitted purely based on their scheme.
+ // news, snews, nntp, imap and mailbox are checked before the call
+ // to this function by matching content location and requesting location.
+ if (contentScheme.LowerCaseEqualsLiteral("mailto")) return true;
+
+ if (contentScheme.LowerCaseEqualsLiteral("about")) {
+ // We want to allow about pages to load content freely. But not about:blank.
+ nsAutoCString fullSpec;
+ rv = aContentLocation->GetSpec(fullSpec);
+ NS_ENSURE_SUCCESS(rv, false);
+ if (fullSpec.EqualsLiteral("about:blank")) {
+ return false;
+ }
+ return true;
+ }
+
+ // check if customized exposed scheme
+ if (mCustomExposedProtocols.Contains(contentScheme)) return true;
+
+ bool isChrome;
+ rv = aContentLocation->SchemeIs("chrome", &isChrome);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ bool isRes;
+ rv = aContentLocation->SchemeIs("resource", &isRes);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ bool isData;
+ rv = aContentLocation->SchemeIs("data", &isData);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ bool isMozExtension;
+ rv = aContentLocation->SchemeIs("moz-extension", &isMozExtension);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ return isChrome || isRes || isData || isMozExtension;
+}
+
+/**
+ * We block most unexposed protocols that access remote data
+ * - apart from web protocols, and file.
+ */
+bool nsMsgContentPolicy::ShouldBlockUnexposedProtocol(
+ nsIURI* aContentLocation) {
+ // Error condition - we must return true so that we block.
+
+ // about:blank is "web", it should not be blocked.
+ nsAutoCString fullSpec;
+ nsresult rv = aContentLocation->GetSpec(fullSpec);
+ NS_ENSURE_SUCCESS(rv, true);
+ if (fullSpec.EqualsLiteral("about:blank")) {
+ return false;
+ }
+
+ bool isHttp;
+ rv = aContentLocation->SchemeIs("http", &isHttp);
+ NS_ENSURE_SUCCESS(rv, true);
+
+ bool isHttps;
+ rv = aContentLocation->SchemeIs("https", &isHttps);
+ NS_ENSURE_SUCCESS(rv, true);
+
+ bool isWs; // websocket
+ rv = aContentLocation->SchemeIs("ws", &isWs);
+ NS_ENSURE_SUCCESS(rv, true);
+
+ bool isWss; // secure websocket
+ rv = aContentLocation->SchemeIs("wss", &isWss);
+ NS_ENSURE_SUCCESS(rv, true);
+
+ bool isBlob;
+ rv = aContentLocation->SchemeIs("blob", &isBlob);
+ NS_ENSURE_SUCCESS(rv, true);
+
+ bool isFile;
+ rv = aContentLocation->SchemeIs("file", &isFile);
+ NS_ENSURE_SUCCESS(rv, true);
+
+ return !isHttp && !isHttps && !isWs && !isWss && !isBlob && !isFile;
+}
+
+/**
+ * The default for this function will be to reject the content request.
+ * When determining if to allow the request for a given msg hdr, the function
+ * will go through the list of remote content blocking criteria:
+ *
+ * #1 Allow if there is a db header for a manual override.
+ * #2 Allow if the message is in an RSS folder.
+ * #3 Allow if the domain for the remote image in our white list.
+ * #4 Allow if the author has been specifically white listed.
+ */
+int16_t nsMsgContentPolicy::ShouldAcceptRemoteContentForMsgHdr(
+ nsIMsgDBHdr* aMsgHdr, nsIURI* aRequestingLocation,
+ nsIURI* aContentLocation) {
+ if (!aMsgHdr) return static_cast<int16_t>(nsIContentPolicy::REJECT_REQUEST);
+
+ // Case #1, check the db hdr for the remote content policy on this particular
+ // message.
+ uint32_t remoteContentPolicy = kNoRemoteContentPolicy;
+ aMsgHdr->GetUint32Property("remoteContentPolicy", &remoteContentPolicy);
+
+ // Case #2, check if the message is in an RSS folder
+ bool isRSS = false;
+ IsRSSArticle(aRequestingLocation, &isRSS);
+
+ // Case #3, the domain for the remote image is in our white list
+ bool trustedDomain = IsTrustedDomain(aContentLocation);
+
+ // Case 4 means looking up items in the permissions database. So if
+ // either of the two previous items means we load the data, just do it.
+ if (isRSS || remoteContentPolicy == kAllowRemoteContent || trustedDomain)
+ return nsIContentPolicy::ACCEPT;
+
+ // Case #4, author is in our white list..
+ bool allowForSender = ShouldAcceptRemoteContentForSender(aMsgHdr);
+
+ int16_t result = allowForSender
+ ? static_cast<int16_t>(nsIContentPolicy::ACCEPT)
+ : static_cast<int16_t>(nsIContentPolicy::REJECT_REQUEST);
+
+ // kNoRemoteContentPolicy means we have never set a value on the message
+ if (result == nsIContentPolicy::REJECT_REQUEST && !remoteContentPolicy)
+ aMsgHdr->SetUint32Property("remoteContentPolicy", kBlockRemoteContent);
+
+ return result;
+}
+
+class RemoteContentNotifierEvent : public mozilla::Runnable {
+ public:
+ RemoteContentNotifierEvent(uint64_t aBrowsingContextId, nsIURI* aContentURI)
+ : mozilla::Runnable("RemoteContentNotifierEvent"),
+ mBrowsingContextId(aBrowsingContextId),
+ mContentURI(aContentURI) {}
+
+ NS_IMETHOD Run() {
+ nsAutoString data;
+ data.AppendInt(mBrowsingContextId);
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ observerService->NotifyObservers(mContentURI, "remote-content-blocked",
+ data.get());
+ return NS_OK;
+ }
+
+ private:
+ uint64_t mBrowsingContextId;
+ nsCOMPtr<nsIURI> mContentURI;
+};
+
+/**
+ * This function is used to show a blocked remote content notification.
+ */
+void nsMsgContentPolicy::NotifyContentWasBlocked(uint64_t aBrowsingContextId,
+ nsIURI* aContentLocation) {
+ // Post this as an event because it can cause dom mutations, and we
+ // get called at a bad time to be causing dom mutations.
+ NS_DispatchToCurrentThread(
+ new RemoteContentNotifierEvent(aBrowsingContextId, aContentLocation));
+}
+
+/**
+ * This function is used to determine if we allow content for a remote message.
+ * If we reject loading remote content, then we'll inform the message window
+ * that this message has remote content (and hence we are not loading it).
+ *
+ * See ShouldAcceptRemoteContentForMsgHdr for the actual decisions that
+ * determine if we are going to allow remote content.
+ */
+void nsMsgContentPolicy::ShouldAcceptContentForPotentialMsg(
+ uint64_t aBrowsingContextId, nsIURI* aRequestingLocation,
+ nsIURI* aContentLocation, int16_t* aDecision) {
+ NS_ASSERTION(
+ *aDecision == nsIContentPolicy::REJECT_REQUEST,
+ "AllowContentForPotentialMessage expects default decision to be reject!");
+
+ // Is it a mailnews url?
+ nsresult rv;
+ nsCOMPtr<nsIMsgMessageUrl> msgUrl(
+ do_QueryInterface(aRequestingLocation, &rv));
+ if (NS_FAILED(rv)) {
+ // It isn't a mailnews url - so we accept the load here, and let other
+ // content policies make the decision if we should be loading it or not.
+ *aDecision = nsIContentPolicy::ACCEPT;
+ return;
+ }
+
+ nsCString resourceURI;
+ rv = msgUrl->GetUri(resourceURI);
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ rv = GetMsgDBHdrFromURI(resourceURI, getter_AddRefs(msgHdr));
+
+ // Get a decision on whether or not to allow remote content for this message
+ // header.
+ *aDecision = ShouldAcceptRemoteContentForMsgHdr(msgHdr, aRequestingLocation,
+ aContentLocation);
+
+ // If we're not allowing the remote content, tell the nsIMsgWindow loading
+ // this url that this is the case, so that the UI knows to show the remote
+ // content header bar, so the user can override if they wish.
+ if (*aDecision == nsIContentPolicy::REJECT_REQUEST) {
+ NotifyContentWasBlocked(aBrowsingContextId, aContentLocation);
+ }
+}
+
+/**
+ * Content policy logic for compose windows
+ */
+void nsMsgContentPolicy::ComposeShouldLoad(nsIMsgCompose* aMsgCompose,
+ nsISupports* aRequestingContext,
+ nsIURI* aOriginatorLocation,
+ nsIURI* aContentLocation,
+ int16_t* aDecision) {
+ NS_ASSERTION(*aDecision == nsIContentPolicy::REJECT_REQUEST,
+ "ComposeShouldLoad expects default decision to be reject!");
+
+ nsCString originalMsgURI;
+ nsresult rv = aMsgCompose->GetOriginalMsgURI(originalMsgURI);
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ if (!originalMsgURI.IsEmpty()) {
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ rv = GetMsgDBHdrFromURI(originalMsgURI, getter_AddRefs(msgHdr));
+ NS_ENSURE_SUCCESS_VOID(rv);
+ *aDecision =
+ ShouldAcceptRemoteContentForMsgHdr(msgHdr, nullptr, aContentLocation);
+
+ if (!aOriginatorLocation->GetSpecOrDefault().EqualsLiteral(
+ "about:blank?compose")) {
+ return;
+ }
+ }
+
+ // We want to allow the user to add remote content, but do that only when
+ // the allowRemoteContent was set. This way quoted remoted content won't
+ // automatically load, but e.g. pasted content will load because the UI
+ // code toggles the flag.
+ nsCOMPtr<mozilla::dom::Element> element =
+ do_QueryInterface(aRequestingContext);
+ RefPtr<mozilla::dom::HTMLImageElement> image =
+ mozilla::dom::HTMLImageElement::FromNodeOrNull(element);
+ if (image) {
+ // Special case image elements.
+ bool allowRemoteContent = false;
+ aMsgCompose->GetAllowRemoteContent(&allowRemoteContent);
+ if (allowRemoteContent) {
+ *aDecision = nsIContentPolicy::ACCEPT;
+ return;
+ }
+ }
+}
+
+already_AddRefed<nsIMsgCompose>
+nsMsgContentPolicy::GetMsgComposeForBrowsingContext(
+ mozilla::dom::BrowsingContext* aBrowsingContext) {
+ nsresult rv;
+
+ nsIDocShell* shell = aBrowsingContext->GetDocShell();
+ if (!shell) return nullptr;
+ nsCOMPtr<nsIDocShellTreeItem> docShellTreeItem(shell);
+
+ nsCOMPtr<nsIDocShellTreeItem> rootItem;
+ rv = docShellTreeItem->GetInProcessSameTypeRootTreeItem(
+ getter_AddRefs(rootItem));
+ NS_ENSURE_SUCCESS(rv, nullptr);
+
+ nsCOMPtr<nsIDocShell> docShell(do_QueryInterface(rootItem, &rv));
+ NS_ENSURE_SUCCESS(rv, nullptr);
+
+ nsCOMPtr<nsIMsgComposeService> composeService(
+ do_GetService("@mozilla.org/messengercompose;1", &rv));
+ NS_ENSURE_SUCCESS(rv, nullptr);
+
+ nsCOMPtr<nsIMsgCompose> msgCompose;
+ // Don't bother checking rv, as GetMsgComposeForDocShell returns
+ // NS_ERROR_FAILURE for not found.
+ composeService->GetMsgComposeForDocShell(docShell,
+ getter_AddRefs(msgCompose));
+ return msgCompose.forget();
+}
+
+nsresult nsMsgContentPolicy::SetDisableItemsOnMailNewsUrlDocshells(
+ nsIURI* aContentLocation, nsILoadInfo* aLoadInfo) {
+ // XXX if this class changes so that this method can be called from
+ // ShouldProcess, and if it's possible for this to be null when called from
+ // ShouldLoad, but not in the corresponding ShouldProcess call,
+ // we need to re-think the assumptions underlying this code.
+
+ NS_ENSURE_ARG_POINTER(aContentLocation);
+ NS_ENSURE_ARG_POINTER(aLoadInfo);
+
+ RefPtr<mozilla::dom::BrowsingContext> browsingContext =
+ aLoadInfo->GetTargetBrowsingContext();
+ if (!browsingContext) {
+ return NS_OK;
+ }
+
+ // We're only worried about policy settings in content docshells.
+ if (!browsingContext->IsContent()) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIDocShell> docShell = browsingContext->GetDocShell();
+ if (!docShell) {
+ // If there's no docshell to get to, there's nowhere for the JavaScript to
+ // run, so we're already safe and don't need to disable anything.
+ return NS_OK;
+ }
+
+ // Ensure starting off unsandboxed. We sandbox later if needed.
+ MOZ_ALWAYS_SUCCEEDS(browsingContext->SetSandboxFlags(SANDBOXED_NONE));
+
+ nsresult rv;
+ bool isAllowedContent = !ShouldBlockUnexposedProtocol(aContentLocation);
+ nsCOMPtr<nsIMsgMessageUrl> msgUrl = do_QueryInterface(aContentLocation);
+ if (!msgUrl && !isAllowedContent) {
+ // If it's not a mailnews url or allowed content url (http[s]|file) then
+ // bail; otherwise set whether JavaScript is allowed.
+ return NS_OK;
+ }
+
+ if (!isAllowedContent) {
+ // Disable JavaScript on message URLs.
+ rv = browsingContext->SetAllowJavascript(false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = browsingContext->SetAllowContentRetargetingOnChildren(false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // NOTE! Do not set single sandboxing flags only. Sandboxing - when used -
+ // starts off with all things sandboxed, and individual sandbox keywords
+ // will *allow* the specific feature.
+ // Disabling by setting single flags without starting off with all things
+ // sandboxed would the normal assumptions about sandboxing.
+ // The flags - contrary to the keywords - *prevent* a given feature.
+ uint32_t sandboxFlags = SANDBOX_ALL_FLAGS;
+
+ // Do not block links with target attribute from opening (at all).
+ // xref bug 421310 - we would like to prevent using target, but *handle*
+ // links like the target wasn't there.
+ sandboxFlags &= ~SANDBOXED_AUXILIARY_NAVIGATION;
+
+ // For some unexplicable reason, when SANDBOXED_ORIGIN is in affect, then
+ // images will not work with test --verify. So unset it.
+ sandboxFlags &= ~SANDBOXED_ORIGIN;
+
+ // Having both SANDBOXED_TOPLEVEL_NAVIGATION and
+ // SANDBOXED_TOPLEVEL_NAVIGATION_USER_ACTIVATION will generate a warning,
+ // see BothAllowTopNavigationAndUserActivationPresent. So unset it.
+ sandboxFlags &= ~SANDBOXED_TOPLEVEL_NAVIGATION_USER_ACTIVATION;
+
+ rv = browsingContext->SetSandboxFlags(sandboxFlags);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ // JavaScript is allowed on non-message URLs.
+ rv = browsingContext->SetAllowJavascript(true);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = browsingContext->SetAllowContentRetargetingOnChildren(true);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ rv = docShell->SetAllowPlugins(false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+/**
+ * Gets the root docshell from a requesting context.
+ */
+nsresult nsMsgContentPolicy::GetRootDocShellForContext(
+ nsISupports* aRequestingContext, nsIDocShell** aDocShell) {
+ NS_ENSURE_ARG_POINTER(aRequestingContext);
+ nsresult rv;
+
+ nsIDocShell* shell = NS_CP_GetDocShellFromContext(aRequestingContext);
+ NS_ENSURE_TRUE(shell, NS_ERROR_NULL_POINTER);
+ nsCOMPtr<nsIDocShellTreeItem> docshellTreeItem(shell);
+
+ nsCOMPtr<nsIDocShellTreeItem> rootItem;
+ rv = docshellTreeItem->GetInProcessRootTreeItem(getter_AddRefs(rootItem));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return CallQueryInterface(rootItem, aDocShell);
+}
+
+/**
+ * Gets the originating URI that started off a set of requests, accounting
+ * for multiple iframes.
+ *
+ * Navigates up the docshell tree from aRequestingContext and finds the
+ * highest parent with the same type docshell as aRequestingContext, then
+ * returns the URI associated with that docshell.
+ */
+nsresult nsMsgContentPolicy::GetOriginatingURIForContext(
+ nsISupports* aRequestingContext, nsIURI** aURI) {
+ NS_ENSURE_ARG_POINTER(aRequestingContext);
+ nsresult rv;
+
+ nsIDocShell* shell = NS_CP_GetDocShellFromContext(aRequestingContext);
+ if (!shell) {
+ *aURI = nullptr;
+ return NS_OK;
+ }
+ nsCOMPtr<nsIDocShellTreeItem> docshellTreeItem(shell);
+
+ nsCOMPtr<nsIDocShellTreeItem> rootItem;
+ rv = docshellTreeItem->GetInProcessSameTypeRootTreeItem(
+ getter_AddRefs(rootItem));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIWebNavigation> webNavigation(do_QueryInterface(rootItem, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return webNavigation->GetCurrentURI(aURI);
+}
+
+NS_IMETHODIMP
+nsMsgContentPolicy::ShouldProcess(nsIURI* aContentLocation,
+ nsILoadInfo* aLoadInfo,
+ const nsACString& aMimeGuess,
+ int16_t* aDecision) {
+ // XXX Returning ACCEPT is presumably only a reasonable thing to do if we
+ // think that ShouldLoad is going to catch all possible cases (i.e. that
+ // everything we use to make decisions is going to be available at
+ // ShouldLoad time, and not only become available in time for ShouldProcess).
+ // Do we think that's actually the case?
+ *aDecision = nsIContentPolicy::ACCEPT;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgContentPolicy::Observe(nsISupports* aSubject,
+ const char* aTopic,
+ const char16_t* aData) {
+ if (!strcmp(NS_PREFBRANCH_PREFCHANGE_TOPIC_ID, aTopic)) {
+ NS_LossyConvertUTF16toASCII pref(aData);
+
+ nsresult rv;
+
+ nsCOMPtr<nsIPrefBranch> prefBranchInt = do_QueryInterface(aSubject, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (pref.Equals(kBlockRemoteImages))
+ prefBranchInt->GetBoolPref(kBlockRemoteImages, &mBlockRemoteImages);
+ }
+
+ return NS_OK;
+}
+
+/**
+ * Implementation of nsIMsgContentPolicy
+ *
+ */
+NS_IMETHODIMP
+nsMsgContentPolicy::AddExposedProtocol(const nsACString& aScheme) {
+ if (mCustomExposedProtocols.Contains(nsCString(aScheme))) return NS_OK;
+
+ mCustomExposedProtocols.AppendElement(aScheme);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgContentPolicy::RemoveExposedProtocol(const nsACString& aScheme) {
+ mCustomExposedProtocols.RemoveElement(nsCString(aScheme));
+
+ return NS_OK;
+}
diff --git a/comm/mailnews/base/src/nsMsgContentPolicy.h b/comm/mailnews/base/src/nsMsgContentPolicy.h
new file mode 100644
index 0000000000..e6d3d10e79
--- /dev/null
+++ b/comm/mailnews/base/src/nsMsgContentPolicy.h
@@ -0,0 +1,95 @@
+/* -*- 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/. */
+
+/******************************************************************************
+ * nsMsgContentPolicy enforces the specified content policy on images, js,
+ * plugins, etc. This is the class used to determine what elements in a message
+ * should be loaded.
+ *
+ * nsMsgCookiePolicy enforces our cookie policy for mail and RSS messages.
+ ******************************************************************************/
+
+#ifndef _nsMsgContentPolicy_H_
+#define _nsMsgContentPolicy_H_
+
+#include "nsIContentPolicy.h"
+#include "nsIObserver.h"
+#include "nsWeakReference.h"
+#include "nsString.h"
+#include "nsIMsgMailNewsUrl.h"
+#include "nsIMsgCompose.h"
+#include "nsIDocShell.h"
+#include "nsIPermissionManager.h"
+#include "nsIMsgContentPolicy.h"
+#include "nsTArray.h"
+
+/* DBFCFDF0-4489-4faa-8122-190FD1EFA16C */
+#define NS_MSGCONTENTPOLICY_CID \
+ { \
+ 0xdbfcfdf0, 0x4489, 0x4faa, { \
+ 0x81, 0x22, 0x19, 0xf, 0xd1, 0xef, 0xa1, 0x6c \
+ } \
+ }
+
+#define NS_MSGCONTENTPOLICY_CONTRACTID "@mozilla.org/messenger/content-policy;1"
+
+class nsIMsgDBHdr;
+class nsIDocShell;
+
+class nsMsgContentPolicy : public nsIContentPolicy,
+ public nsIObserver,
+ public nsIMsgContentPolicy,
+ public nsSupportsWeakReference {
+ public:
+ nsMsgContentPolicy();
+
+ nsresult Init();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICONTENTPOLICY
+ NS_DECL_NSIOBSERVER
+ NS_DECL_NSIMSGCONTENTPOLICY
+
+ protected:
+ virtual ~nsMsgContentPolicy();
+
+ bool mBlockRemoteImages;
+ nsCString mTrustedMailDomains;
+ nsCOMPtr<nsIPermissionManager> mPermissionManager;
+
+ bool IsTrustedDomain(nsIURI* aContentLocation);
+ bool IsSafeRequestingLocation(nsIURI* aRequestingLocation);
+ bool IsExposedProtocol(nsIURI* aContentLocation);
+ bool IsExposedChromeProtocol(nsIURI* aContentLocation);
+ bool ShouldBlockUnexposedProtocol(nsIURI* aContentLocation);
+
+ bool ShouldAcceptRemoteContentForSender(nsIMsgDBHdr* aMsgHdr);
+ int16_t ShouldAcceptRemoteContentForMsgHdr(nsIMsgDBHdr* aMsgHdr,
+ nsIURI* aRequestingLocation,
+ nsIURI* aContentLocation);
+ void NotifyContentWasBlocked(uint64_t aBrowsingContextId,
+ nsIURI* aContentLocation);
+ void ShouldAcceptContentForPotentialMsg(uint64_t aBrowsingContextId,
+ nsIURI* aOriginatorLocation,
+ nsIURI* aContentLocation,
+ int16_t* aDecision);
+ void ComposeShouldLoad(nsIMsgCompose* aMsgCompose,
+ nsISupports* aRequestingContext,
+ nsIURI* aOriginatorLocation, nsIURI* aContentLocation,
+ int16_t* aDecision);
+ already_AddRefed<nsIMsgCompose> GetMsgComposeForBrowsingContext(
+ mozilla::dom::BrowsingContext* aRequestingContext);
+
+ nsresult GetRootDocShellForContext(nsISupports* aRequestingContext,
+ nsIDocShell** aDocShell);
+ nsresult GetOriginatingURIForContext(nsISupports* aRequestingContext,
+ nsIURI** aURI);
+ nsresult SetDisableItemsOnMailNewsUrlDocshells(nsIURI* aContentLocation,
+ nsILoadInfo* aLoadInfo);
+
+ nsTArray<nsCString> mCustomExposedProtocols;
+};
+
+#endif // _nsMsgContentPolicy_H_
diff --git a/comm/mailnews/base/src/nsMsgCopyService.cpp b/comm/mailnews/base/src/nsMsgCopyService.cpp
new file mode 100644
index 0000000000..22df6bb6e9
--- /dev/null
+++ b/comm/mailnews/base/src/nsMsgCopyService.cpp
@@ -0,0 +1,587 @@
+/* -*- 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 "nsMsgCopyService.h"
+#include "nsCOMArray.h"
+#include "nspr.h"
+#include "nsIFile.h"
+#include "nsIMsgFolderNotificationService.h"
+#include "nsComponentManagerUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "nsMsgUtils.h"
+#include "mozilla/Logging.h"
+
+static mozilla::LazyLogModule gCopyServiceLog("MsgCopyService");
+
+// ******************** nsCopySource ******************
+
+nsCopySource::nsCopySource() : m_processed(false) {
+ MOZ_COUNT_CTOR(nsCopySource);
+}
+
+nsCopySource::nsCopySource(nsIMsgFolder* srcFolder) : m_processed(false) {
+ MOZ_COUNT_CTOR(nsCopySource);
+ m_msgFolder = srcFolder;
+}
+
+nsCopySource::~nsCopySource() { MOZ_COUNT_DTOR(nsCopySource); }
+
+void nsCopySource::AddMessage(nsIMsgDBHdr* aMsg) {
+ m_messageArray.AppendElement(aMsg);
+}
+
+// ************ nsCopyRequest *****************
+//
+
+nsCopyRequest::nsCopyRequest()
+ : m_requestType(nsCopyMessagesType),
+ m_isMoveOrDraftOrTemplate(false),
+ m_processed(false),
+ m_newMsgFlags(0) {
+ MOZ_COUNT_CTOR(nsCopyRequest);
+}
+
+nsCopyRequest::~nsCopyRequest() {
+ MOZ_COUNT_DTOR(nsCopyRequest);
+
+ int32_t j = m_copySourceArray.Length();
+ while (j-- > 0) delete m_copySourceArray.ElementAt(j);
+}
+
+nsresult nsCopyRequest::Init(nsCopyRequestType type, nsISupports* aSupport,
+ nsIMsgFolder* dstFolder, bool bVal,
+ uint32_t newMsgFlags,
+ const nsACString& newMsgKeywords,
+ nsIMsgCopyServiceListener* listener,
+ nsIMsgWindow* msgWindow, bool allowUndo) {
+ nsresult rv = NS_OK;
+ m_requestType = type;
+ m_srcSupport = aSupport;
+ m_dstFolder = dstFolder;
+ m_isMoveOrDraftOrTemplate = bVal;
+ m_allowUndo = allowUndo;
+ m_newMsgFlags = newMsgFlags;
+ m_newMsgKeywords = newMsgKeywords;
+
+ if (listener) m_listener = listener;
+ if (msgWindow) {
+ m_msgWindow = msgWindow;
+ if (m_allowUndo) msgWindow->GetTransactionManager(getter_AddRefs(m_txnMgr));
+ }
+ if (type == nsCopyFoldersType) {
+ // To support multiple copy folder operations to the same destination, we
+ // need to save the leaf name of the src file spec so that FindRequest() is
+ // able to find the right request when copy finishes.
+ nsCOMPtr<nsIMsgFolder> srcFolder = do_QueryInterface(aSupport, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsString folderName;
+ rv = srcFolder->GetName(folderName);
+ NS_ENSURE_SUCCESS(rv, rv);
+ m_dstFolderName = folderName;
+ }
+
+ return rv;
+}
+
+nsCopySource* nsCopyRequest::AddNewCopySource(nsIMsgFolder* srcFolder) {
+ nsCopySource* newSrc = new nsCopySource(srcFolder);
+ if (newSrc) {
+ m_copySourceArray.AppendElement(newSrc);
+ if (srcFolder == m_dstFolder) newSrc->m_processed = true;
+ }
+ return newSrc;
+}
+
+// ************* nsMsgCopyService ****************
+//
+
+nsMsgCopyService::nsMsgCopyService() {}
+
+nsMsgCopyService::~nsMsgCopyService() {
+ int32_t i = m_copyRequests.Length();
+
+ while (i-- > 0) ClearRequest(m_copyRequests.ElementAt(i), NS_ERROR_FAILURE);
+}
+
+void nsMsgCopyService::LogCopyCompletion(nsISupports* aSrc,
+ nsIMsgFolder* aDest) {
+ nsCString srcFolderUri, destFolderUri;
+ nsCOMPtr<nsIMsgFolder> srcFolder(do_QueryInterface(aSrc));
+ if (srcFolder) srcFolder->GetURI(srcFolderUri);
+ aDest->GetURI(destFolderUri);
+ MOZ_LOG(gCopyServiceLog, mozilla::LogLevel::Info,
+ ("NotifyCompletion - src %s dest %s\n", srcFolderUri.get(),
+ destFolderUri.get()));
+}
+
+void nsMsgCopyService::LogCopyRequest(const char* logMsg,
+ nsCopyRequest* aRequest) {
+ nsCString srcFolderUri, destFolderUri;
+ nsCOMPtr<nsIMsgFolder> srcFolder(do_QueryInterface(aRequest->m_srcSupport));
+ if (srcFolder) srcFolder->GetURI(srcFolderUri);
+ aRequest->m_dstFolder->GetURI(destFolderUri);
+ uint32_t numMsgs = 0;
+ if (aRequest->m_requestType == nsCopyMessagesType &&
+ aRequest->m_copySourceArray.Length() > 0) {
+ numMsgs = aRequest->m_copySourceArray[0]->m_messageArray.Length();
+ }
+ MOZ_LOG(gCopyServiceLog, mozilla::LogLevel::Info,
+ ("request %p %s - src %s dest %s numItems %d type=%d", aRequest,
+ logMsg, srcFolderUri.get(), destFolderUri.get(), numMsgs,
+ aRequest->m_requestType));
+}
+
+nsresult nsMsgCopyService::ClearRequest(nsCopyRequest* aRequest, nsresult rv) {
+ if (aRequest) {
+ if (MOZ_LOG_TEST(gCopyServiceLog, mozilla::LogLevel::Info))
+ LogCopyRequest(
+ NS_SUCCEEDED(rv) ? "Clearing OK request" : "Clearing failed request",
+ aRequest);
+
+ if (NS_SUCCEEDED(rv) && aRequest->m_requestType == nsCopyFoldersType) {
+ // Send folder copy/move notifications to nsIMsgFolderListeners.
+ // BAD SMELL ALERT: Seems odd that this is the only place the folder
+ // notification is invoked from the copyService.
+ // For message copy/move operations, the folder code handles the
+ // notification (to take one example).
+ // This suggests lack of clarity of responsibility.
+ nsCOMPtr<nsIMsgFolderNotificationService> notifier(
+ do_GetService("@mozilla.org/messenger/msgnotificationservice;1"));
+ if (notifier) {
+ for (nsCopySource* copySource : aRequest->m_copySourceArray) {
+ notifier->NotifyFolderMoveCopyCompleted(
+ aRequest->m_isMoveOrDraftOrTemplate, copySource->m_msgFolder,
+ aRequest->m_dstFolder);
+ }
+ }
+ }
+
+ // undo stuff
+ if (aRequest->m_allowUndo && aRequest->m_copySourceArray.Length() > 1 &&
+ aRequest->m_txnMgr)
+ aRequest->m_txnMgr->EndBatch(false);
+
+ m_copyRequests.RemoveElement(aRequest);
+ if (aRequest->m_listener) aRequest->m_listener->OnStopCopy(rv);
+ delete aRequest;
+ }
+
+ return rv;
+}
+
+nsresult nsMsgCopyService::QueueRequest(nsCopyRequest* aRequest,
+ bool* aCopyImmediately) {
+ NS_ENSURE_ARG_POINTER(aRequest);
+ NS_ENSURE_ARG_POINTER(aCopyImmediately);
+ *aCopyImmediately = true;
+ nsCopyRequest* copyRequest;
+
+ // Check through previous requests to see if the copy can start immediately.
+ uint32_t cnt = m_copyRequests.Length();
+
+ for (uint32_t i = 0; i < cnt; i++) {
+ copyRequest = m_copyRequests.ElementAt(i);
+ if (aRequest->m_requestType == nsCopyFoldersType) {
+ // For copy folder, see if both destination folder (root)
+ // (ie, Local Folder) and folder name (ie, abc) are the same.
+ if (copyRequest->m_dstFolderName == aRequest->m_dstFolderName &&
+ SameCOMIdentity(copyRequest->m_dstFolder, aRequest->m_dstFolder)) {
+ *aCopyImmediately = false;
+ break;
+ }
+ } else if (SameCOMIdentity(copyRequest->m_dstFolder,
+ aRequest->m_dstFolder)) {
+ // If dst are same and we already have a request, we cannot copy
+ // immediately.
+ *aCopyImmediately = false;
+ break;
+ }
+ }
+
+ // Queue it.
+ m_copyRequests.AppendElement(aRequest);
+ return NS_OK;
+}
+
+nsresult nsMsgCopyService::DoCopy(nsCopyRequest* aRequest) {
+ NS_ENSURE_ARG(aRequest);
+ bool copyImmediately;
+ QueueRequest(aRequest, &copyImmediately);
+ if (MOZ_LOG_TEST(gCopyServiceLog, mozilla::LogLevel::Info))
+ LogCopyRequest(copyImmediately ? "DoCopy" : "QueueRequest", aRequest);
+
+ // if no active request for this dest folder then we can copy immediately
+ if (copyImmediately) return DoNextCopy();
+
+ return NS_OK;
+}
+
+nsresult nsMsgCopyService::DoNextCopy() {
+ nsresult rv = NS_OK;
+ nsCopyRequest* copyRequest = nullptr;
+ nsCopySource* copySource = nullptr;
+ uint32_t i, j, scnt;
+
+ uint32_t cnt = m_copyRequests.Length();
+ if (cnt > 0) {
+ nsCOMArray<nsIMsgFolder> activeTargets;
+
+ // ** jt -- always FIFO
+ for (i = 0; i < cnt; i++) {
+ copyRequest = m_copyRequests.ElementAt(i);
+ copySource = nullptr;
+ scnt = copyRequest->m_copySourceArray.Length();
+ if (!copyRequest->m_processed) {
+ // if the target folder of this request already has an active
+ // copy request, skip this request for now.
+ if (activeTargets.ContainsObject(copyRequest->m_dstFolder)) {
+ copyRequest = nullptr;
+ continue;
+ }
+ if (scnt <= 0) goto found; // must be CopyFileMessage
+ for (j = 0; j < scnt; j++) {
+ copySource = copyRequest->m_copySourceArray.ElementAt(j);
+ if (!copySource->m_processed) goto found;
+ }
+ if (j >= scnt) // all processed set the value
+ copyRequest->m_processed = true;
+ }
+ if (copyRequest->m_processed) {
+ // Keep track of folders actively getting copied to.
+ activeTargets.AppendObject(copyRequest->m_dstFolder);
+ }
+ }
+ found:
+ if (copyRequest && !copyRequest->m_processed) {
+ if (copyRequest->m_listener) copyRequest->m_listener->OnStartCopy();
+ if (copyRequest->m_requestType == nsCopyMessagesType && copySource) {
+ copySource->m_processed = true;
+ rv = copyRequest->m_dstFolder->CopyMessages(
+ copySource->m_msgFolder, copySource->m_messageArray,
+ copyRequest->m_isMoveOrDraftOrTemplate, copyRequest->m_msgWindow,
+ copyRequest->m_listener, false,
+ copyRequest->m_allowUndo); // isFolder operation false
+
+ } else if (copyRequest->m_requestType == nsCopyFoldersType) {
+ NS_ENSURE_STATE(copySource);
+ copySource->m_processed = true;
+
+ nsCOMPtr<nsIMsgFolder> dstFolder = copyRequest->m_dstFolder;
+ nsCOMPtr<nsIMsgFolder> srcFolder = copySource->m_msgFolder;
+
+ // If folder transfer is not within the same server and if a folder
+ // move was requested, set the request move flag false to avoid
+ // removing the list of marked deleted messages in the source folder.
+ bool isMove = copyRequest->m_isMoveOrDraftOrTemplate;
+ if (copyRequest->m_isMoveOrDraftOrTemplate) {
+ bool sameServer;
+ IsOnSameServer(dstFolder, srcFolder, &sameServer);
+ if (!sameServer) copyRequest->m_isMoveOrDraftOrTemplate = false;
+ }
+
+ // NOTE: The folder invokes NotifyCompletion() when the operation is
+ // complete. Some folders (localfolder!) invoke it before CopyFolder()
+ // even returns. This will likely delete the request object, so
+ // you have to assume that copyRequest is invalid when CopyFolder()
+ // returns.
+ rv = dstFolder->CopyFolder(srcFolder, isMove, copyRequest->m_msgWindow,
+ copyRequest->m_listener);
+ // If CopyFolder() fails (e.g. destination folder already exists),
+ // it won't send a completion notification (NotifyCompletion()).
+ // So copyRequest will still exist, and we need to ditch it.
+ if (NS_FAILED(rv)) {
+ ClearRequest(copyRequest, rv);
+ }
+ } else if (copyRequest->m_requestType == nsCopyFileMessageType) {
+ nsCOMPtr<nsIFile> aFile(
+ do_QueryInterface(copyRequest->m_srcSupport, &rv));
+ if (NS_SUCCEEDED(rv)) {
+ // ** in case of saving draft/template; the very first
+ // time we may not have the original message to replace
+ // with; if we do we shall have an instance of copySource
+ nsCOMPtr<nsIMsgDBHdr> aMessage;
+ if (copySource) {
+ aMessage = copySource->m_messageArray[0];
+ copySource->m_processed = true;
+ }
+ copyRequest->m_processed = true;
+ rv = copyRequest->m_dstFolder->CopyFileMessage(
+ aFile, aMessage, copyRequest->m_isMoveOrDraftOrTemplate,
+ copyRequest->m_newMsgFlags, copyRequest->m_newMsgKeywords,
+ copyRequest->m_msgWindow, copyRequest->m_listener);
+ }
+ }
+ }
+ }
+ return rv;
+}
+
+/**
+ * Find a request in m_copyRequests which matches the passed in source
+ * and destination folders.
+ *
+ * @param aSupport the iSupports of the source folder.
+ * @param dstFolder the destination folder of the copy request.
+ */
+nsCopyRequest* nsMsgCopyService::FindRequest(nsISupports* aSupport,
+ nsIMsgFolder* dstFolder) {
+ nsCopyRequest* copyRequest = nullptr;
+ uint32_t cnt = m_copyRequests.Length();
+ for (uint32_t i = 0; i < cnt; i++) {
+ copyRequest = m_copyRequests.ElementAt(i);
+ if (SameCOMIdentity(copyRequest->m_srcSupport, aSupport) &&
+ SameCOMIdentity(copyRequest->m_dstFolder.get(), dstFolder))
+ break;
+
+ // When copying folders the notification of the message copy serves as a
+ // proxy for the folder copy. Check for that here.
+ if (copyRequest->m_requestType == nsCopyFoldersType) {
+ // If the src is different then check next request.
+ if (!SameCOMIdentity(copyRequest->m_srcSupport, aSupport)) {
+ copyRequest = nullptr;
+ continue;
+ }
+
+ // See if the parent of the copied folder is the same as the one when the
+ // request was made. Note if the destination folder is already a server
+ // folder then no need to get parent.
+ nsCOMPtr<nsIMsgFolder> parentMsgFolder;
+ nsresult rv = NS_OK;
+ bool isServer = false;
+ dstFolder->GetIsServer(&isServer);
+ if (!isServer) rv = dstFolder->GetParent(getter_AddRefs(parentMsgFolder));
+ if ((NS_FAILED(rv)) || (!parentMsgFolder && !isServer) ||
+ (copyRequest->m_dstFolder.get() != parentMsgFolder)) {
+ copyRequest = nullptr;
+ continue;
+ }
+
+ // Now checks if the folder name is the same.
+ nsString folderName;
+ rv = dstFolder->GetName(folderName);
+ if (NS_FAILED(rv)) {
+ copyRequest = nullptr;
+ continue;
+ }
+
+ if (copyRequest->m_dstFolderName == folderName) break;
+ } else
+ copyRequest = nullptr;
+ }
+
+ return copyRequest;
+}
+
+NS_IMPL_ISUPPORTS(nsMsgCopyService, nsIMsgCopyService)
+
+MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHODIMP nsMsgCopyService::CopyMessages(
+ nsIMsgFolder* srcFolder, /* UI src folder */
+ nsTArray<RefPtr<nsIMsgDBHdr>> const& messages, nsIMsgFolder* dstFolder,
+ bool isMove, nsIMsgCopyServiceListener* listener, nsIMsgWindow* window,
+ bool allowUndo) {
+ NS_ENSURE_ARG_POINTER(srcFolder);
+ NS_ENSURE_ARG_POINTER(dstFolder);
+
+ MOZ_LOG(gCopyServiceLog, mozilla::LogLevel::Debug, ("CopyMessages"));
+
+ if (srcFolder == dstFolder) {
+ NS_ERROR("src and dest folders for msg copy can't be the same");
+ return NS_ERROR_FAILURE;
+ }
+ nsCopyRequest* copyRequest;
+ nsCopySource* copySource = nullptr;
+ nsIMsgDBHdr* msg;
+ nsCOMPtr<nsIMsgFolder> curFolder;
+ nsCOMPtr<nsISupports> aSupport;
+ int cnt;
+ nsresult rv;
+
+ // XXX TODO
+ // JUNK MAIL RELATED
+ // make sure dest folder exists
+ // and has proper flags, before we start copying?
+
+ // bail early if nothing to do
+ if (messages.IsEmpty()) {
+ if (listener) {
+ listener->OnStartCopy();
+ listener->OnStopCopy(NS_OK);
+ }
+ return NS_OK;
+ }
+
+ copyRequest = new nsCopyRequest();
+ if (!copyRequest) return NS_ERROR_OUT_OF_MEMORY;
+
+ nsTArray<RefPtr<nsIMsgDBHdr>> unprocessed = messages.Clone();
+ aSupport = srcFolder;
+
+ rv = copyRequest->Init(nsCopyMessagesType, aSupport, dstFolder, isMove,
+ 0 /* new msg flags, not used */, EmptyCString(),
+ listener, window, allowUndo);
+ if (NS_FAILED(rv)) goto done;
+
+ if (MOZ_LOG_TEST(gCopyServiceLog, mozilla::LogLevel::Info))
+ LogCopyRequest("CopyMessages request", copyRequest);
+
+ // Build up multiple nsCopySource objects. Each holds a single source folder
+ // and all the messages in the folder that are to be copied.
+ cnt = unprocessed.Length();
+ while (cnt-- > 0) {
+ msg = unprocessed[cnt];
+ rv = msg->GetFolder(getter_AddRefs(curFolder));
+
+ if (NS_FAILED(rv)) goto done;
+ if (!copySource) {
+ // Begin a folder grouping.
+ copySource = copyRequest->AddNewCopySource(curFolder);
+ if (!copySource) {
+ rv = NS_ERROR_OUT_OF_MEMORY;
+ goto done;
+ }
+ }
+
+ // Stash message if in the current folder grouping.
+ if (curFolder == copySource->m_msgFolder) {
+ copySource->AddMessage(msg);
+ unprocessed.RemoveElementAt((size_t)cnt);
+ }
+
+ if (cnt == 0) {
+ // Finished a folder. Start a new pass to handle any remaining messages
+ // in other folders.
+ cnt = unprocessed.Length();
+ if (cnt > 0) {
+ // Force to create a new one and continue grouping the messages.
+ copySource = nullptr;
+ }
+ }
+ }
+
+ // undo stuff
+ if (NS_SUCCEEDED(rv) && copyRequest->m_allowUndo &&
+ copyRequest->m_copySourceArray.Length() > 1 && copyRequest->m_txnMgr) {
+ nsCOMPtr<nsITransactionManager> txnMgr = copyRequest->m_txnMgr;
+ txnMgr->BeginBatch(nullptr);
+ }
+
+done:
+
+ if (NS_FAILED(rv))
+ delete copyRequest;
+ else
+ rv = DoCopy(copyRequest);
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgCopyService::CopyFolder(nsIMsgFolder* srcFolder, nsIMsgFolder* dstFolder,
+ bool isMove, nsIMsgCopyServiceListener* listener,
+ nsIMsgWindow* window) {
+ NS_ENSURE_ARG_POINTER(srcFolder);
+ NS_ENSURE_ARG_POINTER(dstFolder);
+ nsCopyRequest* copyRequest;
+ nsresult rv;
+ nsCOMPtr<nsIMsgFolder> curFolder;
+
+ copyRequest = new nsCopyRequest();
+ rv = copyRequest->Init(nsCopyFoldersType, srcFolder, dstFolder, isMove,
+ 0 /* new msg flags, not used */, EmptyCString(),
+ listener, window, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ copyRequest->AddNewCopySource(srcFolder);
+ return DoCopy(copyRequest);
+}
+
+NS_IMETHODIMP
+nsMsgCopyService::CopyFileMessage(nsIFile* file, nsIMsgFolder* dstFolder,
+ nsIMsgDBHdr* msgToReplace, bool isDraft,
+ uint32_t aMsgFlags,
+ const nsACString& aNewMsgKeywords,
+ nsIMsgCopyServiceListener* listener,
+ nsIMsgWindow* window) {
+ nsresult rv = NS_ERROR_NULL_POINTER;
+ nsCopyRequest* copyRequest;
+ nsCopySource* copySource = nullptr;
+
+ NS_ENSURE_ARG_POINTER(file);
+ NS_ENSURE_ARG_POINTER(dstFolder);
+
+ copyRequest = new nsCopyRequest();
+ if (!copyRequest) return rv;
+
+ rv = copyRequest->Init(nsCopyFileMessageType, file, dstFolder, isDraft,
+ aMsgFlags, aNewMsgKeywords, listener, window, false);
+ if (NS_FAILED(rv)) goto done;
+
+ if (msgToReplace) {
+ // The actual source of the message is a file not a folder, but
+ // we still need an nsCopySource to reference the old message header
+ // which will be used to recover message metadata.
+ copySource = copyRequest->AddNewCopySource(nullptr);
+ if (!copySource) {
+ rv = NS_ERROR_OUT_OF_MEMORY;
+ goto done;
+ }
+ copySource->AddMessage(msgToReplace);
+ }
+
+done:
+ if (NS_FAILED(rv)) {
+ delete copyRequest;
+ } else {
+ rv = DoCopy(copyRequest);
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgCopyService::NotifyCompletion(nsISupports* aSupport,
+ nsIMsgFolder* dstFolder, nsresult result) {
+ if (MOZ_LOG_TEST(gCopyServiceLog, mozilla::LogLevel::Info))
+ LogCopyCompletion(aSupport, dstFolder);
+ nsCopyRequest* copyRequest = nullptr;
+ uint32_t numOrigRequests = m_copyRequests.Length();
+ do {
+ // loop for copy requests, because if we do a cross server folder copy,
+ // we'll have a copy request for the folder copy, which will in turn
+ // generate a copy request for the messages in the folder, which
+ // will have the same src support.
+ copyRequest = FindRequest(aSupport, dstFolder);
+
+ if (copyRequest) {
+ // ClearRequest can cause a new request to get added to m_copyRequests
+ // with matching source and dest folders if the copy listener starts
+ // a new copy. We want to ignore any such request here, because it wasn't
+ // the one that was completed. So we keep track of how many original
+ // requests there were.
+ if (m_copyRequests.IndexOf(copyRequest) >= numOrigRequests) break;
+ // check if this copy request is done by making sure all the
+ // sources have been processed.
+ int32_t sourceIndex, sourceCount;
+ sourceCount = copyRequest->m_copySourceArray.Length();
+ for (sourceIndex = 0; sourceIndex < sourceCount;) {
+ if (!(copyRequest->m_copySourceArray.ElementAt(sourceIndex))
+ ->m_processed)
+ break;
+ sourceIndex++;
+ }
+ // if all sources processed, mark the request as processed
+ if (sourceIndex >= sourceCount) copyRequest->m_processed = true;
+ // if this request is done, or failed, clear it.
+ if (copyRequest->m_processed || NS_FAILED(result)) {
+ ClearRequest(copyRequest, result);
+ numOrigRequests--;
+ } else
+ break;
+ } else
+ break;
+ } while (copyRequest);
+
+ return DoNextCopy();
+}
diff --git a/comm/mailnews/base/src/nsMsgCopyService.h b/comm/mailnews/base/src/nsMsgCopyService.h
new file mode 100644
index 0000000000..ab9be438d0
--- /dev/null
+++ b/comm/mailnews/base/src/nsMsgCopyService.h
@@ -0,0 +1,91 @@
+/* -*- 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 nsMsgCopyService_h__
+#define nsMsgCopyService_h__
+
+#include "nscore.h"
+#include "nsIMsgCopyService.h"
+#include "nsCOMPtr.h"
+#include "nsIMsgFolder.h"
+#include "nsIMsgHdr.h"
+#include "nsIMsgWindow.h"
+#include "nsITransactionManager.h"
+#include "nsTArray.h"
+
+typedef enum _nsCopyRequestType {
+ nsCopyMessagesType = 0x0,
+ nsCopyFileMessageType = 0x1,
+ nsCopyFoldersType = 0x2
+} nsCopyRequestType;
+
+class nsCopyRequest;
+
+// nsCopySource represents either:
+// 1. a bundle of messages to be copied, all from the same folder.
+// or
+// 2. a folder to be copied (and no messages).
+class nsCopySource {
+ public:
+ nsCopySource();
+ explicit nsCopySource(nsIMsgFolder* srcFolder);
+ ~nsCopySource();
+ void AddMessage(nsIMsgDBHdr* aMsg);
+
+ nsCOMPtr<nsIMsgFolder> m_msgFolder;
+ nsTArray<RefPtr<nsIMsgDBHdr>> m_messageArray;
+ bool m_processed;
+};
+
+class nsCopyRequest {
+ public:
+ nsCopyRequest();
+ ~nsCopyRequest();
+
+ nsresult Init(nsCopyRequestType type, nsISupports* aSupport,
+ nsIMsgFolder* dstFolder, bool bVal, uint32_t newMsgFlags,
+ const nsACString& newMsgKeywords,
+ nsIMsgCopyServiceListener* listener, nsIMsgWindow* msgWindow,
+ bool allowUndo);
+ nsCopySource* AddNewCopySource(nsIMsgFolder* srcFolder);
+
+ nsCOMPtr<nsISupports> m_srcSupport; // ui source folder or file spec
+ nsCOMPtr<nsIMsgFolder> m_dstFolder;
+ nsCOMPtr<nsIMsgWindow> m_msgWindow;
+ nsCOMPtr<nsIMsgCopyServiceListener> m_listener;
+ nsCOMPtr<nsITransactionManager> m_txnMgr;
+ nsCopyRequestType m_requestType;
+ bool m_isMoveOrDraftOrTemplate;
+ bool m_allowUndo;
+ bool m_processed;
+ uint32_t m_newMsgFlags;
+ nsCString m_newMsgKeywords;
+ nsString m_dstFolderName; // used for copy folder.
+ nsTArray<nsCopySource*> m_copySourceArray; // array of nsCopySource
+};
+
+class nsMsgCopyService : public nsIMsgCopyService {
+ public:
+ nsMsgCopyService();
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ NS_DECL_NSIMSGCOPYSERVICE
+
+ private:
+ virtual ~nsMsgCopyService();
+
+ nsresult ClearRequest(nsCopyRequest* aRequest, nsresult rv);
+ nsresult DoCopy(nsCopyRequest* aRequest);
+ nsresult DoNextCopy();
+ nsCopyRequest* FindRequest(nsISupports* aSupport, nsIMsgFolder* dstFolder);
+ nsresult QueueRequest(nsCopyRequest* aRequest, bool* aCopyImmediately);
+ void LogCopyCompletion(nsISupports* aSrc, nsIMsgFolder* aDest);
+ void LogCopyRequest(const char* logMsg, nsCopyRequest* aRequest);
+
+ nsTArray<nsCopyRequest*> m_copyRequests;
+};
+
+#endif
diff --git a/comm/mailnews/base/src/nsMsgDBFolder.cpp b/comm/mailnews/base/src/nsMsgDBFolder.cpp
new file mode 100644
index 0000000000..8e82ce1a5c
--- /dev/null
+++ b/comm/mailnews/base/src/nsMsgDBFolder.cpp
@@ -0,0 +1,5573 @@
+/* -*- 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 "msgCore.h"
+#include "nsUnicharUtils.h"
+#include "nsMsgDBFolder.h"
+#include "nsMsgFolderFlags.h"
+#include "nsIPrefBranch.h"
+#include "nsIPrefService.h"
+#include "nsNetUtil.h"
+#include "nsIMsgFolderCache.h"
+#include "nsIMsgFolderCacheElement.h"
+#include "nsIMsgMailNewsUrl.h"
+#include "nsMsgDatabase.h"
+#include "nsIMsgAccountManager.h"
+#include "nsISeekableStream.h"
+#include "nsNativeCharsetUtils.h"
+#include "nsIChannel.h"
+#include "nsITransport.h"
+#include "nsIWindowWatcher.h"
+#include "nsIMsgFolderCompactor.h"
+#include "nsIDocShell.h"
+#include "nsIMsgWindow.h"
+#include "nsIPrompt.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIAbCard.h"
+#include "nsIAbDirectory.h"
+#include "nsISpamSettings.h"
+#include "nsIMsgFilterPlugin.h"
+#include "nsIMsgMailSession.h"
+#include "nsTextFormatter.h"
+#include "nsReadLine.h"
+#include "nsLayoutCID.h"
+#include "nsIParserUtils.h"
+#include "nsIDocumentEncoder.h"
+#include "nsMsgI18N.h"
+#include "nsIMIMEHeaderParam.h"
+#include "plbase64.h"
+#include <time.h>
+#include "nsIMsgFolderNotificationService.h"
+#include "nsIMimeHeaders.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsIMsgTraitService.h"
+#include "nsIMessenger.h"
+#include "nsThreadUtils.h"
+#include "nsITransactionManager.h"
+#include "nsMsgReadStateTxn.h"
+#include "prmem.h"
+#include "nsIPK11TokenDB.h"
+#include "nsIPK11Token.h"
+#include "nsMsgLocalFolderHdrs.h"
+#define oneHour 3600000000U
+#include "nsMsgUtils.h"
+#include "nsIMsgFilterService.h"
+#include "nsDirectoryServiceUtils.h"
+#include "nsMimeTypes.h"
+#include "nsIMsgFilter.h"
+#include "nsIScriptError.h"
+#include "nsIURIMutator.h"
+#include "nsIXULAppInfo.h"
+#include "nsPrintfCString.h"
+#include "mozilla/Components.h"
+#include "mozilla/intl/LocaleService.h"
+#include "mozilla/Logging.h"
+#include "mozilla/SlicedInputStream.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/Utf8.h"
+#include "nsIPromptService.h"
+#include "nsEmbedCID.h"
+
+using namespace mozilla;
+
+extern LazyLogModule FILTERLOGMODULE;
+extern LazyLogModule DBLog;
+
+static PRTime gtimeOfLastPurgeCheck; // variable to know when to check for
+ // purge threshold
+
+#define PREF_MAIL_PROMPT_PURGE_THRESHOLD "mail.prompt_purge_threshhold"
+#define PREF_MAIL_PURGE_THRESHOLD "mail.purge_threshhold"
+#define PREF_MAIL_PURGE_THRESHOLD_MB "mail.purge_threshhold_mb"
+#define PREF_MAIL_PURGE_MIGRATED "mail.purge_threshold_migrated"
+#define PREF_MAIL_PURGE_ASK "mail.purge.ask"
+#define PREF_MAIL_WARN_FILTER_CHANGED "mail.warn_filter_changed"
+
+const char* kUseServerRetentionProp = "useServerRetention";
+
+/**
+ * mozilla::intl APIs require sizeable buffers. This class abstracts over
+ * the nsTArray.
+ */
+class nsTArrayU8Buffer {
+ public:
+ using CharType = uint8_t;
+
+ // Do not allow copy or move. Move could be added in the future if needed.
+ nsTArrayU8Buffer(const nsTArrayU8Buffer&) = delete;
+ nsTArrayU8Buffer& operator=(const nsTArrayU8Buffer&) = delete;
+
+ explicit nsTArrayU8Buffer(nsTArray<CharType>& aArray) : mArray(aArray) {}
+
+ /**
+ * Ensures the buffer has enough space to accommodate |size| elements.
+ */
+ [[nodiscard]] bool reserve(size_t size) {
+ mArray.SetCapacity(size);
+ // nsTArray::SetCapacity returns void, return true to keep the API the same
+ // as the other Buffer implementations.
+ return true;
+ }
+
+ /**
+ * Returns the raw data inside the buffer.
+ */
+ CharType* data() { return mArray.Elements(); }
+
+ /**
+ * Returns the count of elements written into the buffer.
+ */
+ size_t length() const { return mArray.Length(); }
+
+ /**
+ * Returns the buffer's overall capacity.
+ */
+ size_t capacity() const { return mArray.Capacity(); }
+
+ /**
+ * Resizes the buffer to the given amount of written elements.
+ */
+ void written(size_t amount) {
+ MOZ_ASSERT(amount <= mArray.Capacity());
+ // This sets |mArray|'s internal size so that it matches how much was
+ // written. This is necessary because the write happens across FFI
+ // boundaries.
+ mArray.SetLengthAndRetainStorage(amount);
+ }
+
+ private:
+ nsTArray<CharType>& mArray;
+};
+
+NS_IMPL_ISUPPORTS(nsMsgFolderService, nsIMsgFolderService)
+
+// This method serves the only purpose to re-initialize the
+// folder name strings when UI initialization is done.
+// XXX TODO: This can be removed when the localization system gets
+// initialized in M-C code before, for example, the permission manager
+// triggers folder creation during imap: URI creation.
+// In fact, the entire class together with nsMsgDBFolder::FolderNamesReady()
+// can be removed.
+NS_IMETHODIMP nsMsgFolderService::InitializeFolderStrings() {
+ nsMsgDBFolder::initializeStrings();
+ nsMsgDBFolder::gInitializeStringsDone = true;
+ nsMsgDBFolder::gIsEnglishApp = -1;
+ return NS_OK;
+}
+
+mozilla::UniquePtr<mozilla::intl::Collator>
+ nsMsgDBFolder::gCollationKeyGenerator = nullptr;
+
+nsString nsMsgDBFolder::kLocalizedInboxName;
+nsString nsMsgDBFolder::kLocalizedTrashName;
+nsString nsMsgDBFolder::kLocalizedSentName;
+nsString nsMsgDBFolder::kLocalizedDraftsName;
+nsString nsMsgDBFolder::kLocalizedTemplatesName;
+nsString nsMsgDBFolder::kLocalizedUnsentName;
+nsString nsMsgDBFolder::kLocalizedJunkName;
+nsString nsMsgDBFolder::kLocalizedArchivesName;
+
+nsString nsMsgDBFolder::kLocalizedBrandShortName;
+
+nsrefcnt nsMsgDBFolder::mInstanceCount = 0;
+bool nsMsgDBFolder::gInitializeStringsDone = false;
+// This is used in `nonEnglishApp()` to determine localised
+// folders strings.
+// -1: not retrieved yet, 1: English, 0: non-English.
+int nsMsgDBFolder::gIsEnglishApp;
+
+// We define strings for folder properties and events.
+// Properties:
+constexpr nsLiteralCString kBiffState = "BiffState"_ns;
+constexpr nsLiteralCString kCanFileMessages = "CanFileMessages"_ns;
+constexpr nsLiteralCString kDefaultServer = "DefaultServer"_ns;
+constexpr nsLiteralCString kFlagged = "Flagged"_ns;
+constexpr nsLiteralCString kFolderFlag = "FolderFlag"_ns;
+constexpr nsLiteralCString kFolderSize = "FolderSize"_ns;
+constexpr nsLiteralCString kIsDeferred = "isDeferred"_ns;
+constexpr nsLiteralCString kIsSecure = "isSecure"_ns;
+constexpr nsLiteralCString kJunkStatusChanged = "JunkStatusChanged"_ns;
+constexpr nsLiteralCString kKeywords = "Keywords"_ns;
+constexpr nsLiteralCString kMRMTimeChanged = "MRMTimeChanged"_ns;
+constexpr nsLiteralCString kMsgLoaded = "msgLoaded"_ns;
+constexpr nsLiteralCString kName = "Name"_ns;
+constexpr nsLiteralCString kNewMailReceived = "NewMailReceived"_ns;
+constexpr nsLiteralCString kNewMessages = "NewMessages"_ns;
+constexpr nsLiteralCString kOpen = "open"_ns;
+constexpr nsLiteralCString kSortOrder = "SortOrder"_ns;
+constexpr nsLiteralCString kStatus = "Status"_ns;
+constexpr nsLiteralCString kSynchronize = "Synchronize"_ns;
+constexpr nsLiteralCString kTotalMessages = "TotalMessages"_ns;
+constexpr nsLiteralCString kTotalUnreadMessages = "TotalUnreadMessages"_ns;
+
+// Events:
+constexpr nsLiteralCString kAboutToCompact = "AboutToCompact"_ns;
+constexpr nsLiteralCString kCompactCompleted = "CompactCompleted"_ns;
+constexpr nsLiteralCString kDeleteOrMoveMsgCompleted =
+ "DeleteOrMoveMsgCompleted"_ns;
+constexpr nsLiteralCString kDeleteOrMoveMsgFailed = "DeleteOrMoveMsgFailed"_ns;
+constexpr nsLiteralCString kFiltersApplied = "FiltersApplied"_ns;
+constexpr nsLiteralCString kFolderCreateCompleted = "FolderCreateCompleted"_ns;
+constexpr nsLiteralCString kFolderCreateFailed = "FolderCreateFailed"_ns;
+constexpr nsLiteralCString kFolderLoaded = "FolderLoaded"_ns;
+constexpr nsLiteralCString kNumNewBiffMessages = "NumNewBiffMessages"_ns;
+constexpr nsLiteralCString kRenameCompleted = "RenameCompleted"_ns;
+
+NS_IMPL_ISUPPORTS(nsMsgDBFolder, nsISupportsWeakReference, nsIMsgFolder,
+ nsIDBChangeListener, nsIUrlListener,
+ nsIJunkMailClassificationListener,
+ nsIMsgTraitClassificationListener)
+
+nsMsgDBFolder::nsMsgDBFolder(void)
+ : mAddListener(true),
+ mNewMessages(false),
+ mGettingNewMessages(false),
+ mLastMessageLoaded(nsMsgKey_None),
+ m_numOfflineMsgLines(0),
+ m_bytesAddedToLocalMsg(0),
+ m_tempMessageStreamBytesWritten(0),
+ mFlags(0),
+ mNumUnreadMessages(-1),
+ mNumTotalMessages(-1),
+ mNotifyCountChanges(true),
+ mExpungedBytes(0),
+ mInitializedFromCache(false),
+ mSemaphoreHolder(nullptr),
+ mNumPendingUnreadMessages(0),
+ mNumPendingTotalMessages(0),
+ mFolderSize(kSizeUnknown),
+ mNumNewBiffMessages(0),
+ mHaveParsedURI(false),
+ mIsServerIsValid(false),
+ mIsServer(false),
+ mBayesJunkClassifying(false),
+ mBayesTraitClassifying(false) {
+ if (mInstanceCount++ <= 0) {
+ initializeStrings();
+
+ do {
+ nsresult rv;
+ // We need to check whether we're running under xpcshell,
+ // in that case, we always assume that the strings are good.
+ // XXX TODO: This hack can be removed when the localization system gets
+ // initialized in M-C code before, for example, the permission manager
+ // triggers folder creation during imap: URI creation.
+ nsCOMPtr<nsIXULAppInfo> appinfo =
+ do_GetService("@mozilla.org/xre/app-info;1", &rv);
+ if (NS_FAILED(rv)) break;
+ nsAutoCString appName;
+ rv = appinfo->GetName(appName);
+ if (NS_FAILED(rv)) break;
+ if (appName.Equals("xpcshell")) gInitializeStringsDone = true;
+ } while (false);
+
+ createCollationKeyGenerator();
+ gtimeOfLastPurgeCheck = 0;
+ }
+
+ mProcessingFlag[0].bit = nsMsgProcessingFlags::ClassifyJunk;
+ mProcessingFlag[1].bit = nsMsgProcessingFlags::ClassifyTraits;
+ mProcessingFlag[2].bit = nsMsgProcessingFlags::TraitsDone;
+ mProcessingFlag[3].bit = nsMsgProcessingFlags::FiltersDone;
+ mProcessingFlag[4].bit = nsMsgProcessingFlags::FilterToMove;
+ mProcessingFlag[5].bit = nsMsgProcessingFlags::NotReportedClassified;
+ for (uint32_t i = 0; i < nsMsgProcessingFlags::NumberOfFlags; i++)
+ mProcessingFlag[i].keys = nsMsgKeySetU::Create();
+}
+
+nsMsgDBFolder::~nsMsgDBFolder(void) {
+ for (uint32_t i = 0; i < nsMsgProcessingFlags::NumberOfFlags; i++)
+ delete mProcessingFlag[i].keys;
+
+ if (--mInstanceCount == 0) {
+ nsMsgDBFolder::gCollationKeyGenerator = nullptr;
+ }
+ // shutdown but don't shutdown children.
+ Shutdown(false);
+}
+
+NS_IMETHODIMP nsMsgDBFolder::FolderNamesReady(bool* aReady) {
+ *aReady = gInitializeStringsDone;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::Shutdown(bool shutdownChildren) {
+ if (mDatabase) {
+ mDatabase->RemoveListener(this);
+ mDatabase->ForceClosed();
+ mDatabase = nullptr;
+ if (mBackupDatabase) {
+ mBackupDatabase->ForceClosed();
+ mBackupDatabase = nullptr;
+ }
+ }
+
+ if (shutdownChildren) {
+ int32_t count = mSubFolders.Count();
+
+ for (int32_t i = 0; i < count; i++) mSubFolders[i]->Shutdown(true);
+
+ // Reset incoming server pointer and pathname.
+ mServer = nullptr;
+ mPath = nullptr;
+ mHaveParsedURI = false;
+ mName.Truncate();
+ mSubFolders.Clear();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::ForceDBClosed() {
+ int32_t count = mSubFolders.Count();
+ for (int32_t i = 0; i < count; i++) mSubFolders[i]->ForceDBClosed();
+
+ if (mDatabase) {
+ mDatabase->ForceClosed();
+ mDatabase = nullptr;
+ } else {
+ nsCOMPtr<nsIMsgDBService> mailDBFactory(
+ do_GetService("@mozilla.org/msgDatabase/msgDBService;1"));
+ if (mailDBFactory) mailDBFactory->ForceFolderDBClosed(this);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::CloseAndBackupFolderDB(const nsACString& newName) {
+ ForceDBClosed();
+
+ // We only support backup for mail at the moment
+ if (!(mFlags & nsMsgFolderFlags::Mail)) return NS_OK;
+
+ nsCOMPtr<nsIFile> folderPath;
+ nsresult rv = GetFilePath(getter_AddRefs(folderPath));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> dbFile;
+ rv = GetSummaryFileLocation(folderPath, getter_AddRefs(dbFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> backupDir;
+ rv = CreateBackupDirectory(getter_AddRefs(backupDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> backupDBFile;
+ rv = GetBackupSummaryFile(getter_AddRefs(backupDBFile), newName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (mBackupDatabase) {
+ mBackupDatabase->ForceClosed();
+ mBackupDatabase = nullptr;
+ }
+
+ backupDBFile->Remove(false);
+ bool backupExists;
+ backupDBFile->Exists(&backupExists);
+ NS_ASSERTION(!backupExists, "Couldn't delete database backup");
+ if (backupExists) return NS_ERROR_FAILURE;
+
+ if (!newName.IsEmpty()) {
+ nsAutoCString backupName;
+ rv = backupDBFile->GetNativeLeafName(backupName);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return dbFile->CopyToNative(backupDir, backupName);
+ } else
+ return dbFile->CopyToNative(backupDir, EmptyCString());
+}
+
+NS_IMETHODIMP nsMsgDBFolder::OpenBackupMsgDatabase() {
+ if (mBackupDatabase) return NS_OK;
+ nsCOMPtr<nsIFile> folderPath;
+ nsresult rv = GetFilePath(getter_AddRefs(folderPath));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString folderName;
+ rv = folderPath->GetLeafName(folderName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> backupDir;
+ rv = CreateBackupDirectory(getter_AddRefs(backupDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // We use a dummy message folder file so we can use
+ // GetSummaryFileLocation to get the db file name
+ nsCOMPtr<nsIFile> backupDBDummyFolder;
+ rv = CreateBackupDirectory(getter_AddRefs(backupDBDummyFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = backupDBDummyFolder->Append(folderName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgDBService> msgDBService =
+ do_GetService("@mozilla.org/msgDatabase/msgDBService;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = msgDBService->OpenMailDBFromFile(backupDBDummyFolder, this, false, true,
+ getter_AddRefs(mBackupDatabase));
+ // we add a listener so that we can close the db during OnAnnouncerGoingAway.
+ // There should not be any other calls to the listener with the backup
+ // database
+ if (NS_SUCCEEDED(rv) && mBackupDatabase) mBackupDatabase->AddListener(this);
+
+ if (rv == NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE)
+ // this is normal in reparsing
+ rv = NS_OK;
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::RemoveBackupMsgDatabase() {
+ nsCOMPtr<nsIFile> folderPath;
+ nsresult rv = GetFilePath(getter_AddRefs(folderPath));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString folderName;
+ rv = folderPath->GetLeafName(folderName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> backupDir;
+ rv = CreateBackupDirectory(getter_AddRefs(backupDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // We use a dummy message folder file so we can use
+ // GetSummaryFileLocation to get the db file name
+ nsCOMPtr<nsIFile> backupDBDummyFolder;
+ rv = CreateBackupDirectory(getter_AddRefs(backupDBDummyFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = backupDBDummyFolder->Append(folderName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> backupDBFile;
+ rv =
+ GetSummaryFileLocation(backupDBDummyFolder, getter_AddRefs(backupDBFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (mBackupDatabase) {
+ mBackupDatabase->ForceClosed();
+ mBackupDatabase = nullptr;
+ }
+
+ return backupDBFile->Remove(false);
+}
+
+NS_IMETHODIMP nsMsgDBFolder::StartFolderLoading(void) {
+ if (mDatabase) mDatabase->RemoveListener(this);
+ mAddListener = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::EndFolderLoading(void) {
+ if (mDatabase) mDatabase->AddListener(this);
+ mAddListener = true;
+ UpdateSummaryTotals(true);
+
+ // GGGG check for new mail here and call SetNewMessages...?? -- ONE OF
+ // THE 2 PLACES
+ if (mDatabase) m_newMsgs.Clear();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::GetExpungedBytes(int64_t* count) {
+ NS_ENSURE_ARG_POINTER(count);
+
+ if (mDatabase) {
+ nsresult rv;
+ nsCOMPtr<nsIDBFolderInfo> folderInfo;
+ rv = mDatabase->GetDBFolderInfo(getter_AddRefs(folderInfo));
+ if (NS_FAILED(rv)) return rv;
+ rv = folderInfo->GetExpungedBytes(count);
+ if (NS_SUCCEEDED(rv)) mExpungedBytes = *count; // sync up with the database
+ return rv;
+ } else {
+ ReadDBFolderInfo(false);
+ *count = mExpungedBytes;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetHasNewMessages(bool* hasNewMessages) {
+ NS_ENSURE_ARG_POINTER(hasNewMessages);
+ *hasNewMessages = mNewMessages;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::SetHasNewMessages(bool curNewMessages) {
+ if (curNewMessages != mNewMessages) {
+ // Only change mru time if we're going from doesn't have new to has new.
+ // technically, we should probably update mru time for every new message
+ // but we would pay a performance penalty for that. If the user
+ // opens the folder, the mrutime will get updated anyway.
+ if (curNewMessages) SetMRUTime();
+ bool oldNewMessages = mNewMessages;
+ mNewMessages = curNewMessages;
+ NotifyBoolPropertyChanged(kNewMessages, oldNewMessages, curNewMessages);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetHasFolderOrSubfolderNewMessages(bool* aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+ bool hasNewMessages = mNewMessages;
+
+ if (!hasNewMessages) {
+ int32_t count = mSubFolders.Count();
+ for (int32_t i = 0; i < count; i++) {
+ bool hasNew = false;
+ mSubFolders[i]->GetHasFolderOrSubfolderNewMessages(&hasNew);
+ if (hasNew) {
+ hasNewMessages = true;
+ break;
+ }
+ }
+ }
+
+ *aResult = hasNewMessages;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetGettingNewMessages(bool* gettingNewMessages) {
+ NS_ENSURE_ARG_POINTER(gettingNewMessages);
+ *gettingNewMessages = mGettingNewMessages;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::SetGettingNewMessages(bool gettingNewMessages) {
+ mGettingNewMessages = gettingNewMessages;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetFirstNewMessage(nsIMsgDBHdr** firstNewMessage) {
+ // If there's not a db then there can't be new messages. Return failure since
+ // you should use HasNewMessages first.
+ if (!mDatabase) return NS_ERROR_FAILURE;
+
+ nsresult rv;
+ nsMsgKey key;
+ rv = mDatabase->GetFirstNew(&key);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIMsgDBHdr> hdr;
+ rv = mDatabase->GetMsgHdrForKey(key, getter_AddRefs(hdr));
+ if (NS_FAILED(rv)) return rv;
+
+ return mDatabase->GetMsgHdrForKey(key, firstNewMessage);
+}
+
+NS_IMETHODIMP nsMsgDBFolder::ClearNewMessages() {
+ nsresult rv = NS_OK;
+ bool dbWasCached = mDatabase != nullptr;
+ if (!dbWasCached) GetDatabase();
+
+ if (mDatabase) {
+ mDatabase->GetNewList(m_saveNewMsgs);
+ mDatabase->ClearNewList(true);
+ }
+ if (!dbWasCached) SetMsgDatabase(nullptr);
+
+ m_newMsgs.Clear();
+ mNumNewBiffMessages = 0;
+ return rv;
+}
+
+void nsMsgDBFolder::UpdateNewMessages() {
+ if (!(mFlags & nsMsgFolderFlags::Virtual)) {
+ bool hasNewMessages = false;
+ for (uint32_t keyIndex = 0; keyIndex < m_newMsgs.Length(); keyIndex++) {
+ bool containsKey = false;
+ mDatabase->ContainsKey(m_newMsgs[keyIndex], &containsKey);
+ if (!containsKey) continue;
+ bool isRead = false;
+ nsresult rv2 = mDatabase->IsRead(m_newMsgs[keyIndex], &isRead);
+ if (NS_SUCCEEDED(rv2) && !isRead) {
+ hasNewMessages = true;
+ mDatabase->AddToNewList(m_newMsgs[keyIndex]);
+ }
+ }
+ SetHasNewMessages(hasNewMessages);
+ }
+}
+
+// helper function that gets the cache element that corresponds to the passed in
+// file spec. This could be static, or could live in another class - it's not
+// specific to the current nsMsgDBFolder. If it lived at a higher level, we
+// could cache the account manager and folder cache.
+nsresult nsMsgDBFolder::GetFolderCacheElemFromFile(
+ nsIFile* file, nsIMsgFolderCacheElement** cacheElement) {
+ nsresult result;
+ NS_ENSURE_ARG_POINTER(file);
+ NS_ENSURE_ARG_POINTER(cacheElement);
+ nsCOMPtr<nsIMsgFolderCache> folderCache;
+#ifdef DEBUG_bienvenu1
+ bool exists;
+ NS_ASSERTION(NS_SUCCEEDED(fileSpec->Exists(&exists)) && exists,
+ "whoops, file doesn't exist, mac will break");
+#endif
+ nsCOMPtr<nsIMsgAccountManager> accountMgr =
+ do_GetService("@mozilla.org/messenger/account-manager;1", &result);
+ if (NS_SUCCEEDED(result)) {
+ result = accountMgr->GetFolderCache(getter_AddRefs(folderCache));
+ if (NS_SUCCEEDED(result) && folderCache) {
+ nsCString persistentPath;
+ result = file->GetPersistentDescriptor(persistentPath);
+ NS_ENSURE_SUCCESS(result, result);
+ result =
+ folderCache->GetCacheElement(persistentPath, false, cacheElement);
+ }
+ }
+ return result;
+}
+
+nsresult nsMsgDBFolder::ReadDBFolderInfo(bool force) {
+ // Since it turns out to be pretty expensive to open and close
+ // the DBs all the time, if we have to open it once, get everything
+ // we might need while we're here
+ nsresult result = NS_OK;
+
+ // If we reload the cache we might get stale info, so don't do it.
+ if (!mInitializedFromCache) {
+ // Path is used as a key into the foldercache.
+ nsCOMPtr<nsIFile> dbPath;
+ result = GetFolderCacheKey(getter_AddRefs(dbPath));
+ if (dbPath) {
+ nsCOMPtr<nsIMsgFolderCacheElement> cacheElement;
+ result = GetFolderCacheElemFromFile(dbPath, getter_AddRefs(cacheElement));
+ if (NS_SUCCEEDED(result) && cacheElement) {
+ if (NS_SUCCEEDED(ReadFromFolderCacheElem(cacheElement))) {
+ mInitializedFromCache = true;
+ }
+ }
+ }
+ }
+
+ if (force || !mInitializedFromCache) {
+ nsCOMPtr<nsIDBFolderInfo> folderInfo;
+ nsCOMPtr<nsIMsgDatabase> db;
+ bool weOpenedDB = !mDatabase;
+ result =
+ GetDBFolderInfoAndDB(getter_AddRefs(folderInfo), getter_AddRefs(db));
+ if (NS_SUCCEEDED(result)) {
+ if (folderInfo) {
+ if (!mInitializedFromCache) {
+ folderInfo->GetFlags((int32_t*)&mFlags);
+#ifdef DEBUG_bienvenu1
+ nsString name;
+ GetName(name);
+ NS_ASSERTION(Compare(name, kLocalizedTrashName) ||
+ (mFlags & nsMsgFolderFlags::Trash),
+ "lost trash flag");
+#endif
+ mInitializedFromCache = true;
+ }
+
+ folderInfo->GetNumMessages(&mNumTotalMessages);
+ folderInfo->GetNumUnreadMessages(&mNumUnreadMessages);
+ folderInfo->GetExpungedBytes(&mExpungedBytes);
+
+ nsCString utf8Name;
+ folderInfo->GetFolderName(utf8Name);
+ if (!utf8Name.IsEmpty()) CopyUTF8toUTF16(utf8Name, mName);
+
+ // These should be put in IMAP folder only.
+ // folderInfo->GetImapTotalPendingMessages(&mNumPendingTotalMessages);
+ // folderInfo->GetImapUnreadPendingMessages(&mNumPendingUnreadMessages);
+
+ if (db) {
+ bool hasnew;
+ nsresult rv;
+ rv = db->HasNew(&hasnew);
+ if (NS_FAILED(rv)) return rv;
+ }
+ if (weOpenedDB) CloseDBIfFolderNotOpen(false);
+ }
+ } else {
+ // we tried to open DB but failed - don't keep trying.
+ // If a DB is created, we will call this method with force == TRUE,
+ // and read from the db that way.
+ mInitializedFromCache = true;
+ }
+ }
+ return result;
+}
+
+nsresult nsMsgDBFolder::SendFlagNotifications(nsIMsgDBHdr* item,
+ uint32_t oldFlags,
+ uint32_t newFlags) {
+ nsresult rv = NS_OK;
+ uint32_t changedFlags = oldFlags ^ newFlags;
+ if ((changedFlags & nsMsgMessageFlags::Read) &&
+ (changedFlags & nsMsgMessageFlags::New)) {
+ //..so..if the msg is read in the folder and the folder has new msgs clear
+ // the account level and status bar biffs.
+ rv = NotifyPropertyFlagChanged(item, kStatus, oldFlags, newFlags);
+ rv = SetBiffState(nsMsgBiffState_NoMail);
+ } else if (changedFlags &
+ (nsMsgMessageFlags::Read | nsMsgMessageFlags::Replied |
+ nsMsgMessageFlags::Forwarded | nsMsgMessageFlags::IMAPDeleted |
+ nsMsgMessageFlags::New | nsMsgMessageFlags::Offline))
+ rv = NotifyPropertyFlagChanged(item, kStatus, oldFlags, newFlags);
+ else if ((changedFlags & nsMsgMessageFlags::Marked))
+ rv = NotifyPropertyFlagChanged(item, kFlagged, oldFlags, newFlags);
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::DownloadMessagesForOffline(
+ nsTArray<RefPtr<nsIMsgDBHdr>> const& messages, nsIMsgWindow*) {
+ NS_ASSERTION(false, "imap and news need to override this");
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::DownloadAllForOffline(nsIUrlListener* listener,
+ nsIMsgWindow* msgWindow) {
+ NS_ASSERTION(false, "imap and news need to override this");
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetMsgStore(nsIMsgPluggableStore** aStore) {
+ NS_ENSURE_ARG_POINTER(aStore);
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ nsresult rv = GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, NS_MSG_INVALID_OR_MISSING_SERVER);
+ return server->GetMsgStore(aStore);
+}
+
+nsresult nsMsgDBFolder::GetOfflineFileStream(nsMsgKey msgKey, uint64_t* offset,
+ uint32_t* size,
+ nsIInputStream** aFileStream) {
+ NS_ENSURE_ARG(aFileStream);
+
+ *offset = 0;
+ *size = 0;
+
+ // Initialise to nullptr since this is checked by some callers for the success
+ // of the function.
+ *aFileStream = nullptr;
+
+ nsresult rv = GetDatabase();
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIMsgDBHdr> hdr;
+ rv = mDatabase->GetMsgHdrForKey(msgKey, getter_AddRefs(hdr));
+ NS_ENSURE_SUCCESS(rv, rv);
+ hdr->GetOfflineMessageSize(size);
+ NS_ENSURE_TRUE(*size != 0, NS_ERROR_UNEXPECTED);
+ rv = GetMsgInputStream(hdr, aFileStream);
+ if (NS_FAILED(rv)) {
+ NS_WARNING(nsPrintfCString(
+ "(debug) nsMsgDBFolder::GetOfflineFileStream: "
+ "GetMsgInputStream(hdr, aFileStream); rv=0x%" PRIx32 "\n",
+ static_cast<uint32_t>(rv))
+ .get());
+ // Return early: If we could not find an offline stream, clear the offline
+ // flag which will fall back to reading the message from the server.
+ if (mDatabase) mDatabase->MarkOffline(msgKey, false, nullptr);
+
+ return rv;
+ }
+
+ // Check if the database has the correct offset into the offline store by
+ // reading up to 300 bytes. If it is incorrect, clear the offline flag on the
+ // message and return false. This will cause a fall back to reading the
+ // message from the server. We will also advance the offset past the envelope
+ // header ("From " or "FCC") and "X-Mozilla-Status*" lines so these line are
+ // not included when the message is read from the file.
+ // Note: This occurs for both mbox and maildir offline store and probably any
+ // future pluggable store that may be supported.
+ nsCOMPtr<nsISeekableStream> seekableStream = do_QueryInterface(*aFileStream);
+ if (seekableStream) {
+ int64_t o;
+ seekableStream->Tell(&o);
+ *offset = uint64_t(o);
+ char startOfMsg[301];
+ uint32_t bytesRead = 0;
+ uint32_t bytesToRead = sizeof(startOfMsg) - 1;
+ rv = (*aFileStream)->Read(startOfMsg, bytesToRead, &bytesRead);
+ startOfMsg[bytesRead] = '\0';
+ uint32_t msgOffset = 0;
+ uint32_t keepMsgOffset = 0;
+ char* headerLine = startOfMsg;
+ int32_t findPos;
+ // Check a few lines in startOfMsg[] to verify message record validity.
+ bool line1 = true;
+ bool foundError = false;
+ // If Read() above fails, don't check any lines and set record bad.
+ // Even if Read() succeeds, don't enter the loop below if bytesRead is 0.
+ bool foundNextLine = NS_SUCCEEDED(rv) && (bytesRead > 0) ? true : false;
+ while (foundNextLine) {
+ headerLine = startOfMsg + msgOffset;
+ // Ignore lines beginning X-Mozilla-Status or X-Mozilla-Status2
+ if (!strncmp(headerLine, X_MOZILLA_STATUS, X_MOZILLA_STATUS_LEN) ||
+ !strncmp(headerLine, X_MOZILLA_STATUS2, X_MOZILLA_STATUS2_LEN)) {
+ // If there is an invalid line ahead of X-Mozilla-Status lines,
+ // immediately flag this a bad record. Only the "From " or "FCC"
+ // delimiter line is expected and OK before this.
+ if (foundError) {
+ break;
+ }
+ foundNextLine =
+ MsgAdvanceToNextLine(startOfMsg, msgOffset, bytesRead - 1);
+ line1 = false;
+ continue;
+ }
+ if (line1) {
+ // Ignore "From " and, for Drafts, "FCC" when on first line.
+ if ((!strncmp(headerLine, "From ", 5) ||
+ ((mFlags & nsMsgFolderFlags::Drafts) &&
+ !strncmp(headerLine, "FCC", 3)))) {
+ foundNextLine =
+ MsgAdvanceToNextLine(startOfMsg, msgOffset, bytesRead - 1);
+ line1 = false;
+ continue;
+ }
+ }
+ bool validOrFrom = false;
+ // Check if line looks like a valid header (just check for a colon). Also
+ // a line beginning with "From " as is sometimes returned by broken IMAP
+ // servers is also acceptable. Also, size of message must be greater than
+ // the offset of the first header line into the message (msgOffset).
+ findPos = MsgFindCharInSet(nsDependentCString(headerLine), ":\n\r", 0);
+ if (((findPos != kNotFound && headerLine[findPos] == ':') ||
+ !strncmp(headerLine, "From ", 5)) &&
+ *size > msgOffset) {
+ validOrFrom = true;
+ }
+ if (!foundError) {
+ if (validOrFrom) {
+ // Record looks OK, accept it.
+ break;
+ } else {
+ foundError = true;
+ keepMsgOffset = msgOffset;
+ foundNextLine =
+ MsgAdvanceToNextLine(startOfMsg, msgOffset, bytesRead - 1);
+ if (MOZ_LOG_TEST(DBLog, LogLevel::Info)) {
+ char save;
+ if (foundNextLine) {
+ // Temporarily null terminate the bad header line for logging.
+ save = startOfMsg[msgOffset - 1]; // should be \r or \n
+ startOfMsg[msgOffset - 1] = 0;
+
+ // DEBUG: the error happened while checking network outage
+ // condition.
+ // XXX TODO We may need to check if dovecot and other imap
+ // servers are returning funny "From " line, etc.
+ NS_WARNING(
+ nsPrintfCString("Strange startOfMsg: |%s|\n", startOfMsg)
+ .get());
+ }
+ MOZ_LOG(DBLog, LogLevel::Info,
+ ("Invalid header line in offline store: %s",
+ startOfMsg + keepMsgOffset));
+ if (foundNextLine) startOfMsg[msgOffset - 1] = save;
+ }
+ line1 = false;
+ continue;
+ }
+ } else {
+ if (validOrFrom) {
+ // Previous was bad, this is good, accept the record at bad line.
+ foundError = false;
+ msgOffset = keepMsgOffset;
+ break;
+ }
+ // If reached, two consecutive lines bad, reject the record
+ break;
+ }
+ } // while (foundNextLine)
+
+ if (!foundNextLine) {
+ // Can't find a valid header line in the buffer or buffer read() failed.
+ foundError = true;
+ }
+
+ if (!foundError) {
+ *offset += msgOffset;
+ *size -= msgOffset;
+ seekableStream->Seek(nsISeekableStream::NS_SEEK_SET, *offset);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ } else {
+ // Offline store message record looks bad. Cause message fetch from
+ // server and store in RAM cache.
+ MOZ_ASSERT(mDatabase); // Would have crashed above if mDatabase null!
+ mDatabase->MarkOffline(msgKey, false, nullptr);
+ MOZ_LOG(DBLog, LogLevel::Error,
+ ("Leading offline store file content appears invalid, will fetch "
+ "message from server."));
+ MOZ_LOG(
+ DBLog, LogLevel::Error,
+ ("First 300 bytes of offline store content are:\n%s", startOfMsg));
+ rv = NS_ERROR_FAILURE;
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetLocalMsgStream(nsIMsgDBHdr* hdr,
+ nsIInputStream** stream) {
+ // Eventually this will be purely a matter of fetching the storeToken
+ // from the header and asking the msgStore for an inputstream.
+ // But for now, the InputStream returned by the mbox msgStore doesn't
+ // EOF at the end of the message, so we need to jump through hoops here.
+ // For now, we implement it in the derived classes, as the message size
+ // is stored in different msgHdr attributes depending on folder type.
+ // See Bug 1764857.
+ // Also, IMAP has a gmail hack to work with (multiple msgHdrs referrring
+ // to the same locally-stored message).
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::GetOfflineStoreOutputStream(nsIMsgDBHdr* aHdr,
+ nsIOutputStream** aOutputStream) {
+ NS_ENSURE_ARG_POINTER(aOutputStream);
+ NS_ENSURE_ARG_POINTER(aHdr);
+
+ nsCOMPtr<nsIMsgPluggableStore> offlineStore;
+ nsresult rv = GetMsgStore(getter_AddRefs(offlineStore));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = offlineStore->GetNewMsgOutputStream(this, &aHdr, aOutputStream);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::GetMsgInputStream(nsIMsgDBHdr* aMsgHdr,
+ nsIInputStream** aInputStream) {
+ NS_ENSURE_ARG_POINTER(aMsgHdr);
+ NS_ENSURE_ARG_POINTER(aInputStream);
+ nsCOMPtr<nsIMsgPluggableStore> msgStore;
+ nsresult rv = GetMsgStore(getter_AddRefs(msgStore));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCString storeToken;
+ rv = aMsgHdr->GetStringProperty("storeToken", storeToken);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Handle legacy DB which has mbox offset but no storeToken.
+ // If this is still needed (open question), it should be done as separate
+ // migration pass, probably at folder creation when store and DB are set
+ // up (but that's tricky at the moment, because the DB is created
+ // on-demand).
+ if (storeToken.IsEmpty()) {
+ nsAutoCString storeType;
+ msgStore->GetStoreType(storeType);
+ if (!storeType.EqualsLiteral("mbox")) {
+ return NS_ERROR_FAILURE; // DB is missing storeToken.
+ }
+ uint64_t offset;
+ aMsgHdr->GetMessageOffset(&offset);
+ storeToken = nsPrintfCString("%" PRIu64, offset);
+ rv = aMsgHdr->SetStringProperty("storeToken", storeToken);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ rv = msgStore->GetMsgInputStream(this, storeToken, aInputStream);
+ if (NS_FAILED(rv)) {
+ NS_WARNING(nsPrintfCString(
+ "(debug) nsMsgDBFolder::GetMsgInputStream: msgStore->"
+ "GetMsgInputStream(this, ...) returned error rv=0x%" PRIx32
+ "\n",
+ static_cast<uint32_t>(rv))
+ .get());
+ }
+
+ NS_ENSURE_SUCCESS(rv, rv);
+ return NS_OK;
+}
+
+// path coming in is the root path without the leaf name,
+// on the way out, it's the whole path.
+nsresult nsMsgDBFolder::CreateFileForDB(const nsAString& userLeafName,
+ nsIFile* path, nsIFile** dbFile) {
+ NS_ENSURE_ARG_POINTER(dbFile);
+
+ nsAutoString proposedDBName(userLeafName);
+ NS_MsgHashIfNecessary(proposedDBName);
+
+ // (note, the caller of this will be using the dbFile to call db->Open()
+ // will turn the path into summary file path, and append the ".msf" extension)
+ //
+ // we want db->Open() to create a new summary file
+ // so we have to jump through some hoops to make sure the .msf it will
+ // create is unique. now that we've got the "safe" proposedDBName,
+ // we append ".msf" to see if the file exists. if so, we make the name
+ // unique and then string off the ".msf" so that we pass the right thing
+ // into Open(). this isn't ideal, since this is not atomic
+ // but it will make do.
+ nsresult rv;
+ nsCOMPtr<nsIFile> dbPath = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ dbPath->InitWithFile(path);
+ proposedDBName.AppendLiteral(SUMMARY_SUFFIX);
+ dbPath->Append(proposedDBName);
+ bool exists;
+ dbPath->Exists(&exists);
+ if (exists) {
+ rv = dbPath->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 00600);
+ NS_ENSURE_SUCCESS(rv, rv);
+ dbPath->GetLeafName(proposedDBName);
+ }
+ // now, take the ".msf" off
+ proposedDBName.SetLength(proposedDBName.Length() - SUMMARY_SUFFIX_LENGTH);
+ dbPath->SetLeafName(proposedDBName);
+
+ dbPath.forget(dbFile);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::GetMsgDatabase(nsIMsgDatabase** aMsgDatabase) {
+ NS_ENSURE_ARG_POINTER(aMsgDatabase);
+ GetDatabase();
+ if (!mDatabase) return NS_ERROR_FAILURE;
+ NS_ADDREF(*aMsgDatabase = mDatabase);
+ mDatabase->SetLastUseTime(PR_Now());
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::SetMsgDatabase(nsIMsgDatabase* aMsgDatabase) {
+ if (mDatabase) {
+ // commit here - db might go away when all these refs are released.
+ mDatabase->Commit(nsMsgDBCommitType::kLargeCommit);
+ mDatabase->RemoveListener(this);
+ mDatabase->ClearCachedHdrs();
+ if (!aMsgDatabase) {
+ mDatabase->GetNewList(m_newMsgs);
+ }
+ }
+ mDatabase = aMsgDatabase;
+
+ if (aMsgDatabase) aMsgDatabase->AddListener(this);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::GetDatabaseOpen(bool* aOpen) {
+ NS_ENSURE_ARG_POINTER(aOpen);
+
+ *aOpen = (mDatabase != nullptr);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::GetBackupMsgDatabase(nsIMsgDatabase** aMsgDatabase) {
+ NS_ENSURE_ARG_POINTER(aMsgDatabase);
+ nsresult rv = OpenBackupMsgDatabase();
+ if (NS_FAILED(rv)) {
+ NS_WARNING(nsPrintfCString(
+ "(debug) OpenBackupMsgDatabase(); returns error=0x%" PRIx32
+ "\n",
+ static_cast<uint32_t>(rv))
+ .get());
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!mBackupDatabase) return NS_ERROR_FAILURE;
+
+ NS_ADDREF(*aMsgDatabase = mBackupDatabase);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::GetDBFolderInfoAndDB(nsIDBFolderInfo** folderInfo,
+ nsIMsgDatabase** database) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::OnReadChanged(nsIDBChangeListener* aInstigator) {
+ /* do nothing. if you care about this, override it. see nsNewsFolder.cpp */
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::OnJunkScoreChanged(nsIDBChangeListener* aInstigator) {
+ NotifyFolderEvent(kJunkStatusChanged);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::OnHdrPropertyChanged(nsIMsgDBHdr* aHdrToChange,
+ const nsACString& property, bool aPreChange,
+ uint32_t* aStatus,
+ nsIDBChangeListener* aInstigator) {
+ /* do nothing. if you care about this, override it.*/
+ return NS_OK;
+}
+
+// 1. When the status of a message changes.
+NS_IMETHODIMP nsMsgDBFolder::OnHdrFlagsChanged(
+ nsIMsgDBHdr* aHdrChanged, uint32_t aOldFlags, uint32_t aNewFlags,
+ nsIDBChangeListener* aInstigator) {
+ if (aHdrChanged) {
+ SendFlagNotifications(aHdrChanged, aOldFlags, aNewFlags);
+ UpdateSummaryTotals(true);
+ }
+
+ // The old state was new message state
+ // We check and see if this state has changed
+ if (aOldFlags & nsMsgMessageFlags::New) {
+ // state changing from new to something else
+ if (!(aNewFlags & nsMsgMessageFlags::New))
+ CheckWithNewMessagesStatus(false);
+ }
+
+ return NS_OK;
+}
+
+nsresult nsMsgDBFolder::CheckWithNewMessagesStatus(bool messageAdded) {
+ bool hasNewMessages;
+ if (messageAdded)
+ SetHasNewMessages(true);
+ else // message modified or deleted
+ {
+ if (mDatabase) {
+ nsresult rv = mDatabase->HasNew(&hasNewMessages);
+ NS_ENSURE_SUCCESS(rv, rv);
+ SetHasNewMessages(hasNewMessages);
+ }
+ }
+
+ return NS_OK;
+}
+
+// 3. When a message gets deleted, we need to see if it was new
+// When we lose a new message we need to check if there are still new
+// messages
+NS_IMETHODIMP nsMsgDBFolder::OnHdrDeleted(nsIMsgDBHdr* aHdrChanged,
+ nsMsgKey aParentKey, int32_t aFlags,
+ nsIDBChangeListener* aInstigator) {
+ // check to see if a new message is being deleted
+ // as in this case, if there is only one new message and it's being deleted
+ // the folder newness has to be cleared.
+ CheckWithNewMessagesStatus(false);
+ // Remove all processing flags. This is generally a good thing although
+ // undo-ing a message back into position will not re-gain the flags.
+ nsMsgKey msgKey;
+ aHdrChanged->GetMessageKey(&msgKey);
+ AndProcessingFlags(msgKey, 0);
+ return OnHdrAddedOrDeleted(aHdrChanged, false);
+}
+
+// 2. When a new messages gets added, we need to see if it's new.
+NS_IMETHODIMP nsMsgDBFolder::OnHdrAdded(nsIMsgDBHdr* aHdrChanged,
+ nsMsgKey aParentKey, int32_t aFlags,
+ nsIDBChangeListener* aInstigator) {
+ if (aFlags & nsMsgMessageFlags::New) CheckWithNewMessagesStatus(true);
+ return OnHdrAddedOrDeleted(aHdrChanged, true);
+}
+
+nsresult nsMsgDBFolder::OnHdrAddedOrDeleted(nsIMsgDBHdr* aHdrChanged,
+ bool added) {
+ if (added)
+ NotifyMessageAdded(aHdrChanged);
+ else
+ NotifyMessageRemoved(aHdrChanged);
+ UpdateSummaryTotals(true);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::OnParentChanged(nsMsgKey aKeyChanged,
+ nsMsgKey oldParent,
+ nsMsgKey newParent,
+ nsIDBChangeListener* aInstigator) {
+ nsresult rv = GetDatabase();
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIMsgDBHdr> hdrChanged;
+ mDatabase->GetMsgHdrForKey(aKeyChanged, getter_AddRefs(hdrChanged));
+ // In reality we probably want to just change the parent because otherwise we
+ // will lose things like selection.
+ if (hdrChanged) {
+ // First delete the child from the old threadParent
+ OnHdrAddedOrDeleted(hdrChanged, false);
+ // Then add it to the new threadParent
+ OnHdrAddedOrDeleted(hdrChanged, true);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::OnAnnouncerGoingAway(
+ nsIDBChangeAnnouncer* instigator) {
+ if (mBackupDatabase && instigator == mBackupDatabase) {
+ mBackupDatabase->RemoveListener(this);
+ mBackupDatabase = nullptr;
+ } else if (mDatabase) {
+ mDatabase->RemoveListener(this);
+ mDatabase = nullptr;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::OnEvent(nsIMsgDatabase* aDB, const char* aEvent) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetManyHeadersToDownload(bool* retval) {
+ NS_ENSURE_ARG_POINTER(retval);
+ int32_t numTotalMessages;
+
+ // is there any reason to return false?
+ if (!mDatabase)
+ *retval = true;
+ else if (NS_SUCCEEDED(GetTotalMessages(false, &numTotalMessages)) &&
+ numTotalMessages <= 0)
+ *retval = true;
+ else
+ *retval = false;
+ return NS_OK;
+}
+
+nsresult nsMsgDBFolder::MsgFitsDownloadCriteria(nsMsgKey msgKey, bool* result) {
+ if (!mDatabase) return NS_ERROR_FAILURE;
+
+ nsresult rv;
+ nsCOMPtr<nsIMsgDBHdr> hdr;
+ rv = mDatabase->GetMsgHdrForKey(msgKey, getter_AddRefs(hdr));
+ if (NS_FAILED(rv)) return rv;
+
+ if (hdr) {
+ uint32_t msgFlags = 0;
+ hdr->GetFlags(&msgFlags);
+ // check if we already have this message body offline
+ if (!(msgFlags & nsMsgMessageFlags::Offline)) {
+ *result = true;
+ // check against the server download size limit .
+ nsCOMPtr<nsIMsgIncomingServer> incomingServer;
+ rv = GetServer(getter_AddRefs(incomingServer));
+ if (NS_SUCCEEDED(rv) && incomingServer) {
+ bool limitDownloadSize = false;
+ rv = incomingServer->GetLimitOfflineMessageSize(&limitDownloadSize);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (limitDownloadSize) {
+ int32_t maxDownloadMsgSize = 0;
+ uint32_t msgSize;
+ hdr->GetMessageSize(&msgSize);
+ rv = incomingServer->GetMaxMessageSize(&maxDownloadMsgSize);
+ NS_ENSURE_SUCCESS(rv, rv);
+ maxDownloadMsgSize *= 1024;
+ if (msgSize > (uint32_t)maxDownloadMsgSize) *result = false;
+ }
+ }
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetSupportsOffline(bool* aSupportsOffline) {
+ NS_ENSURE_ARG_POINTER(aSupportsOffline);
+ if (mFlags & nsMsgFolderFlags::Virtual) {
+ *aSupportsOffline = false;
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ nsresult rv = GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!server) return NS_ERROR_FAILURE;
+
+ int32_t offlineSupportLevel;
+ rv = server->GetOfflineSupportLevel(&offlineSupportLevel);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *aSupportsOffline = (offlineSupportLevel >= OFFLINE_SUPPORT_LEVEL_REGULAR);
+ return NS_OK;
+}
+
+// Note: this probably always returns false for local folders!
+// Looks like it's only ever used for IMAP folders.
+NS_IMETHODIMP nsMsgDBFolder::ShouldStoreMsgOffline(nsMsgKey msgKey,
+ bool* result) {
+ NS_ENSURE_ARG(result);
+ uint32_t flags = 0;
+ *result = false;
+ GetFlags(&flags);
+ return flags & nsMsgFolderFlags::Offline
+ ? MsgFitsDownloadCriteria(msgKey, result)
+ : NS_OK;
+}
+
+// Looks like this implementation is only ever used for IMAP folders.
+NS_IMETHODIMP nsMsgDBFolder::HasMsgOffline(nsMsgKey msgKey, bool* result) {
+ NS_ENSURE_ARG(result);
+ *result = false;
+ GetDatabase();
+ if (!mDatabase) return NS_ERROR_FAILURE;
+
+ nsresult rv;
+ nsCOMPtr<nsIMsgDBHdr> hdr;
+ rv = mDatabase->GetMsgHdrForKey(msgKey, getter_AddRefs(hdr));
+ if (NS_FAILED(rv)) return rv;
+
+ if (hdr) {
+ uint32_t msgFlags = 0;
+ hdr->GetFlags(&msgFlags);
+ // check if we already have this message body offline
+ if ((msgFlags & nsMsgMessageFlags::Offline)) *result = true;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetFlags(uint32_t* _retval) {
+ ReadDBFolderInfo(false);
+ *_retval = mFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::ReadFromFolderCacheElem(
+ nsIMsgFolderCacheElement* element) {
+ nsresult rv = NS_OK;
+
+ element->GetCachedUInt32("flags", &mFlags);
+ element->GetCachedInt32("totalMsgs", &mNumTotalMessages);
+ element->GetCachedInt32("totalUnreadMsgs", &mNumUnreadMessages);
+ element->GetCachedInt32("pendingUnreadMsgs", &mNumPendingUnreadMessages);
+ element->GetCachedInt32("pendingMsgs", &mNumPendingTotalMessages);
+ element->GetCachedInt64("expungedBytes", &mExpungedBytes);
+ element->GetCachedInt64("folderSize", &mFolderSize);
+
+#ifdef DEBUG_bienvenu1
+ nsCString uri;
+ GetURI(uri);
+ printf("read total %ld for %s\n", mNumTotalMessages, uri.get());
+#endif
+ return rv;
+}
+
+nsresult nsMsgDBFolder::GetFolderCacheKey(nsIFile** aFile) {
+ nsresult rv;
+ bool isServer = false;
+ GetIsServer(&isServer);
+
+ // if it's a server, we don't need the .msf appended to the name
+ nsCOMPtr<nsIFile> dbPath;
+ if (isServer) {
+ rv = GetFilePath(getter_AddRefs(dbPath));
+ } else {
+ rv = GetSummaryFile(getter_AddRefs(dbPath));
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+ dbPath.forget(aFile);
+ return NS_OK;
+}
+
+nsresult nsMsgDBFolder::FlushToFolderCache() {
+ nsresult rv;
+ nsCOMPtr<nsIMsgAccountManager> accountManager =
+ do_GetService("@mozilla.org/messenger/account-manager;1", &rv);
+ if (NS_SUCCEEDED(rv) && accountManager) {
+ nsCOMPtr<nsIMsgFolderCache> folderCache;
+ rv = accountManager->GetFolderCache(getter_AddRefs(folderCache));
+ if (NS_SUCCEEDED(rv) && folderCache)
+ rv = WriteToFolderCache(folderCache, false);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::WriteToFolderCache(nsIMsgFolderCache* folderCache,
+ bool deep) {
+ nsresult rv = NS_OK;
+
+ if (folderCache) {
+ nsCOMPtr<nsIMsgFolderCacheElement> cacheElement;
+ nsCOMPtr<nsIFile> dbPath;
+ rv = GetFolderCacheKey(getter_AddRefs(dbPath));
+#ifdef DEBUG_bienvenu1
+ bool exists;
+ NS_ASSERTION(NS_SUCCEEDED(dbPath->Exists(&exists)) && exists,
+ "file spec we're adding to cache should exist");
+#endif
+ if (NS_SUCCEEDED(rv) && dbPath) {
+ nsCString persistentPath;
+ rv = dbPath->GetPersistentDescriptor(persistentPath);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = folderCache->GetCacheElement(persistentPath, true,
+ getter_AddRefs(cacheElement));
+ if (NS_SUCCEEDED(rv) && cacheElement)
+ rv = WriteToFolderCacheElem(cacheElement);
+ }
+
+ if (deep) {
+ for (nsIMsgFolder* msgFolder : mSubFolders) {
+ rv = msgFolder->WriteToFolderCache(folderCache, true);
+ if (NS_FAILED(rv)) break;
+ }
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::WriteToFolderCacheElem(
+ nsIMsgFolderCacheElement* element) {
+ nsresult rv = NS_OK;
+
+ element->SetCachedUInt32("flags", mFlags);
+ element->SetCachedInt32("totalMsgs", mNumTotalMessages);
+ element->SetCachedInt32("totalUnreadMsgs", mNumUnreadMessages);
+ element->SetCachedInt32("pendingUnreadMsgs", mNumPendingUnreadMessages);
+ element->SetCachedInt32("pendingMsgs", mNumPendingTotalMessages);
+ element->SetCachedInt64("expungedBytes", mExpungedBytes);
+ element->SetCachedInt64("folderSize", mFolderSize);
+
+#ifdef DEBUG_bienvenu1
+ nsCString uri;
+ GetURI(uri);
+ printf("writing total %ld for %s\n", mNumTotalMessages, uri.get());
+#endif
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::AddMessageDispositionState(
+ nsIMsgDBHdr* aMessage, nsMsgDispositionState aDispositionFlag) {
+ NS_ENSURE_ARG_POINTER(aMessage);
+
+ nsresult rv = GetDatabase();
+ NS_ENSURE_SUCCESS(rv, NS_OK);
+
+ nsMsgKey msgKey;
+ aMessage->GetMessageKey(&msgKey);
+
+ if (aDispositionFlag == nsIMsgFolder::nsMsgDispositionState_Replied)
+ mDatabase->MarkReplied(msgKey, true, nullptr);
+ else if (aDispositionFlag == nsIMsgFolder::nsMsgDispositionState_Forwarded)
+ mDatabase->MarkForwarded(msgKey, true, nullptr);
+ else if (aDispositionFlag == nsIMsgFolder::nsMsgDispositionState_Redirected)
+ mDatabase->MarkRedirected(msgKey, true, nullptr);
+ return NS_OK;
+}
+
+nsresult nsMsgDBFolder::AddMarkAllReadUndoAction(nsIMsgWindow* msgWindow,
+ nsMsgKey* thoseMarked,
+ uint32_t numMarked) {
+ RefPtr<nsMsgReadStateTxn> readStateTxn = new nsMsgReadStateTxn();
+ if (!readStateTxn) return NS_ERROR_OUT_OF_MEMORY;
+
+ nsresult rv = readStateTxn->Init(this, numMarked, thoseMarked);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = readStateTxn->SetTransactionType(nsIMessenger::eMarkAllMsg);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsITransactionManager> txnMgr;
+ rv = msgWindow->GetTransactionManager(getter_AddRefs(txnMgr));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = txnMgr->DoTransaction(readStateTxn);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::MarkAllMessagesRead(nsIMsgWindow* aMsgWindow) {
+ nsresult rv = GetDatabase();
+ m_newMsgs.Clear();
+
+ if (NS_SUCCEEDED(rv)) {
+ EnableNotifications(allMessageCountNotifications, false);
+ nsTArray<nsMsgKey> thoseMarked;
+ rv = mDatabase->MarkAllRead(thoseMarked);
+ EnableNotifications(allMessageCountNotifications, true);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Setup a undo-state
+ if (aMsgWindow && thoseMarked.Length() > 0)
+ rv = AddMarkAllReadUndoAction(aMsgWindow, thoseMarked.Elements(),
+ thoseMarked.Length());
+ }
+
+ SetHasNewMessages(false);
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::MarkThreadRead(nsIMsgThread* thread) {
+ nsresult rv = GetDatabase();
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsTArray<nsMsgKey> keys;
+ return mDatabase->MarkThreadRead(thread, nullptr, keys);
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::OnStartRunningUrl(nsIURI* aUrl) { return NS_OK; }
+
+NS_IMETHODIMP
+nsMsgDBFolder::OnStopRunningUrl(nsIURI* aUrl, nsresult aExitCode) {
+ NS_ENSURE_ARG_POINTER(aUrl);
+ nsCOMPtr<nsIMsgMailNewsUrl> mailUrl = do_QueryInterface(aUrl);
+ if (mailUrl) {
+ bool updatingFolder = false;
+ if (NS_SUCCEEDED(mailUrl->GetUpdatingFolder(&updatingFolder)) &&
+ updatingFolder)
+ NotifyFolderEvent(kFolderLoaded);
+
+ // be sure to remove ourselves as a url listener
+ mailUrl->UnRegisterListener(this);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::GetRetentionSettings(nsIMsgRetentionSettings** settings) {
+ NS_ENSURE_ARG_POINTER(settings);
+ *settings = nullptr;
+ nsresult rv = NS_OK;
+ bool useServerDefaults = false;
+ if (!m_retentionSettings) {
+ nsCString useServerRetention;
+ GetStringProperty(kUseServerRetentionProp, useServerRetention);
+ if (useServerRetention.EqualsLiteral("1")) {
+ nsCOMPtr<nsIMsgIncomingServer> incomingServer;
+ rv = GetServer(getter_AddRefs(incomingServer));
+ if (NS_SUCCEEDED(rv) && incomingServer) {
+ rv = incomingServer->GetRetentionSettings(settings);
+ useServerDefaults = true;
+ }
+ } else {
+ GetDatabase();
+ if (mDatabase) {
+ // get the settings from the db - if the settings from the db say the
+ // folder is not overriding the incoming server settings, get the
+ // settings from the server.
+ rv = mDatabase->GetMsgRetentionSettings(settings);
+ if (NS_SUCCEEDED(rv) && *settings) {
+ (*settings)->GetUseServerDefaults(&useServerDefaults);
+ if (useServerDefaults) {
+ nsCOMPtr<nsIMsgIncomingServer> incomingServer;
+ rv = GetServer(getter_AddRefs(incomingServer));
+ NS_IF_RELEASE(*settings);
+ if (NS_SUCCEEDED(rv) && incomingServer)
+ incomingServer->GetRetentionSettings(settings);
+ }
+ if (useServerRetention.EqualsLiteral("1") != useServerDefaults) {
+ if (useServerDefaults)
+ useServerRetention.Assign('1');
+ else
+ useServerRetention.Assign('0');
+ SetStringProperty(kUseServerRetentionProp, useServerRetention);
+ }
+ }
+ } else
+ return NS_ERROR_FAILURE;
+ }
+ // Only cache the retention settings if we've overridden the server
+ // settings (otherwise, we won't notice changes to the server settings).
+ if (!useServerDefaults) m_retentionSettings = *settings;
+ } else
+ NS_IF_ADDREF(*settings = m_retentionSettings);
+
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::SetRetentionSettings(
+ nsIMsgRetentionSettings* settings) {
+ bool useServerDefaults;
+ nsCString useServerRetention;
+
+ settings->GetUseServerDefaults(&useServerDefaults);
+ if (useServerDefaults) {
+ useServerRetention.Assign('1');
+ m_retentionSettings = nullptr;
+ } else {
+ useServerRetention.Assign('0');
+ m_retentionSettings = settings;
+ }
+ SetStringProperty(kUseServerRetentionProp, useServerRetention);
+ GetDatabase();
+ if (mDatabase) mDatabase->SetMsgRetentionSettings(settings);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetDownloadSettings(
+ nsIMsgDownloadSettings** settings) {
+ NS_ENSURE_ARG_POINTER(settings);
+ nsresult rv = NS_OK;
+ if (!m_downloadSettings) {
+ GetDatabase();
+ if (mDatabase) {
+ // get the settings from the db - if the settings from the db say the
+ // folder is not overriding the incoming server settings, get the settings
+ // from the server.
+ rv =
+ mDatabase->GetMsgDownloadSettings(getter_AddRefs(m_downloadSettings));
+ if (NS_SUCCEEDED(rv) && m_downloadSettings) {
+ bool useServerDefaults;
+ m_downloadSettings->GetUseServerDefaults(&useServerDefaults);
+ if (useServerDefaults) {
+ nsCOMPtr<nsIMsgIncomingServer> incomingServer;
+ rv = GetServer(getter_AddRefs(incomingServer));
+ if (NS_SUCCEEDED(rv) && incomingServer)
+ incomingServer->GetDownloadSettings(
+ getter_AddRefs(m_downloadSettings));
+ }
+ }
+ }
+ }
+ NS_IF_ADDREF(*settings = m_downloadSettings);
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::SetDownloadSettings(
+ nsIMsgDownloadSettings* settings) {
+ m_downloadSettings = settings;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::IsCommandEnabled(const nsACString& command,
+ bool* result) {
+ NS_ENSURE_ARG_POINTER(result);
+ *result = true;
+ return NS_OK;
+}
+
+nsresult nsMsgDBFolder::WriteStartOfNewLocalMessage() {
+ nsAutoCString result;
+ uint32_t writeCount;
+ time_t now = time((time_t*)0);
+ char* ct = ctime(&now);
+ ct[24] = 0;
+ result = "From - ";
+ result += ct;
+ result += MSG_LINEBREAK;
+ m_bytesAddedToLocalMsg = result.Length();
+
+ MOZ_ASSERT(m_tempMessageStream,
+ "Temporary message stream must not be nullptr");
+
+ nsresult rv =
+ m_tempMessageStream->Write(result.get(), result.Length(), &writeCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+ m_tempMessageStreamBytesWritten += writeCount;
+
+ constexpr auto MozillaStatus = "X-Mozilla-Status: 0001"_ns MSG_LINEBREAK;
+ rv = m_tempMessageStream->Write(MozillaStatus.get(), MozillaStatus.Length(),
+ &writeCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+ m_tempMessageStreamBytesWritten += writeCount;
+ m_bytesAddedToLocalMsg += writeCount;
+ constexpr auto MozillaStatus2 =
+ "X-Mozilla-Status2: 00000000"_ns MSG_LINEBREAK;
+ m_bytesAddedToLocalMsg += MozillaStatus2.Length();
+ rv = m_tempMessageStream->Write(MozillaStatus2.get(), MozillaStatus2.Length(),
+ &writeCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+ m_tempMessageStreamBytesWritten += writeCount;
+ return NS_OK;
+}
+
+nsresult nsMsgDBFolder::StartNewOfflineMessage() {
+ bool isLocked;
+ GetLocked(&isLocked);
+ bool hasSemaphore = false;
+ if (isLocked) {
+ // it's OK if we, the folder, have the semaphore.
+ TestSemaphore(static_cast<nsIMsgFolder*>(this), &hasSemaphore);
+ if (!hasSemaphore) {
+ NS_WARNING("folder locked trying to download offline");
+ return NS_MSG_FOLDER_BUSY;
+ }
+ }
+ m_tempMessageStreamBytesWritten = 0;
+ nsresult rv = GetOfflineStoreOutputStream(
+ m_offlineHeader, getter_AddRefs(m_tempMessageStream));
+ if (NS_SUCCEEDED(rv) && !hasSemaphore)
+ AcquireSemaphore(static_cast<nsIMsgFolder*>(this));
+ if (NS_SUCCEEDED(rv)) WriteStartOfNewLocalMessage();
+ m_numOfflineMsgLines = 0;
+ return rv;
+}
+
+nsresult nsMsgDBFolder::EndNewOfflineMessage(nsresult status) {
+ int64_t curStorePos;
+ uint64_t messageOffset;
+ uint32_t messageSize;
+
+ nsMsgKey messageKey;
+
+ nsresult rv1, rv2;
+ nsresult rv = GetDatabase();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ m_offlineHeader->GetMessageKey(&messageKey);
+
+ nsCOMPtr<nsIMsgPluggableStore> msgStore;
+ rv = GetMsgStore(getter_AddRefs(msgStore));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Are we being asked to abort and clean up?
+ if (NS_FAILED(status)) {
+ mDatabase->MarkOffline(messageKey, false, nullptr);
+ if (m_tempMessageStream) {
+ msgStore->DiscardNewMessage(m_tempMessageStream, m_offlineHeader);
+ }
+ m_tempMessageStream = nullptr;
+ m_offlineHeader = nullptr;
+ return NS_OK;
+ }
+
+ if (m_tempMessageStream) {
+ m_tempMessageStream->Flush();
+ }
+
+ // Some sanity checking.
+ // This will go away once nsIMsgPluggableStore stops serving up seekable
+ // output streams.
+ // If quarantining (mailnews.downloadToTempFile == true) is on we'll already
+ // have a non-seekable stream.
+ nsCOMPtr<nsISeekableStream> seekable;
+ if (m_tempMessageStream) seekable = do_QueryInterface(m_tempMessageStream);
+ if (seekable) {
+ int64_t tellPos;
+ seekable->Tell(&tellPos);
+ curStorePos = tellPos;
+
+ // N.B. This only works if we've set the offline flag for the message,
+ // so be careful about moving the call to MarkOffline above.
+ m_offlineHeader->GetMessageOffset(&messageOffset);
+ curStorePos -= messageOffset;
+ m_offlineHeader->GetMessageSize(&messageSize);
+ messageSize += m_bytesAddedToLocalMsg;
+ // unix/mac has a one byte line ending, but the imap server returns
+ // crlf terminated lines.
+ if (MSG_LINEBREAK_LEN == 1) messageSize -= m_numOfflineMsgLines;
+
+ // We clear the offline flag on the message if the size
+ // looks wrong. Check if we're off by more than one byte per line.
+ if (messageSize > (uint32_t)curStorePos &&
+ (messageSize - (uint32_t)curStorePos) >
+ (uint32_t)m_numOfflineMsgLines) {
+ mDatabase->MarkOffline(messageKey, false, nullptr);
+ // we should truncate the offline store at messageOffset
+ ReleaseSemaphore(static_cast<nsIMsgFolder*>(this));
+ rv1 = rv2 = NS_OK;
+ if (msgStore) {
+ // DiscardNewMessage now closes the stream all the time.
+ rv1 = msgStore->DiscardNewMessage(m_tempMessageStream, m_offlineHeader);
+ m_tempMessageStream = nullptr; // avoid accessing closed stream
+ } else {
+ rv2 = m_tempMessageStream->Close();
+ m_tempMessageStream = nullptr; // ditto
+ }
+ // XXX We should check for errors of rv1 and rv2.
+ if (NS_FAILED(rv1)) NS_WARNING("DiscardNewMessage returned error");
+ if (NS_FAILED(rv2))
+ NS_WARNING("m_tempMessageStream->Close() returned error");
+#ifdef _DEBUG
+ nsAutoCString message("Offline message too small: messageSize=");
+ message.AppendInt(messageSize);
+ message.AppendLiteral(" curStorePos=");
+ message.AppendInt(curStorePos);
+ message.AppendLiteral(" numOfflineMsgLines=");
+ message.AppendInt(m_numOfflineMsgLines);
+ message.AppendLiteral(" bytesAdded=");
+ message.AppendInt(m_bytesAddedToLocalMsg);
+ NS_ERROR(message.get());
+#endif
+ m_offlineHeader = nullptr;
+ return NS_ERROR_FAILURE;
+ }
+ } // seekable
+
+ // Success! Finalise the message.
+ mDatabase->MarkOffline(messageKey, true, nullptr);
+ m_offlineHeader->SetOfflineMessageSize(m_tempMessageStreamBytesWritten);
+ m_offlineHeader->SetLineCount(m_numOfflineMsgLines);
+
+ // (But remember, stream might be buffered and closing/flushing could still
+ // fail!)
+
+ rv1 = rv2 = NS_OK;
+ if (msgStore) {
+ rv1 = msgStore->FinishNewMessage(m_tempMessageStream, m_offlineHeader);
+ m_tempMessageStream = nullptr;
+ }
+
+ // We can not let this happen: I think the code assumes this.
+ // That is the if-expression above is always true.
+ NS_ASSERTION(msgStore, "msgStore is nullptr");
+
+ // Notify users of the errors for now, just use NS_WARNING.
+ if (NS_FAILED(rv1)) NS_WARNING("FinishNewMessage returned error");
+ if (NS_FAILED(rv2)) NS_WARNING("m_tempMessageStream->Close() returned error");
+
+ m_tempMessageStream = nullptr;
+ m_offlineHeader = nullptr;
+
+ if (NS_FAILED(rv1)) return rv1;
+ if (NS_FAILED(rv2)) return rv2;
+
+ return rv;
+}
+
+class AutoCompactEvent : public mozilla::Runnable {
+ public:
+ AutoCompactEvent(nsIMsgWindow* aMsgWindow, nsMsgDBFolder* aFolder)
+ : mozilla::Runnable("AutoCompactEvent"),
+ mMsgWindow(aMsgWindow),
+ mFolder(aFolder) {}
+
+ NS_IMETHOD Run() {
+ if (mFolder) mFolder->HandleAutoCompactEvent(mMsgWindow);
+ return NS_OK;
+ }
+
+ private:
+ nsCOMPtr<nsIMsgWindow> mMsgWindow;
+ RefPtr<nsMsgDBFolder> mFolder;
+};
+
+nsresult nsMsgDBFolder::HandleAutoCompactEvent(nsIMsgWindow* aWindow) {
+ nsresult rv;
+ nsCOMPtr<nsIMsgAccountManager> accountMgr =
+ do_GetService("@mozilla.org/messenger/account-manager;1", &rv);
+ if (NS_SUCCEEDED(rv)) {
+ nsTArray<RefPtr<nsIMsgIncomingServer>> allServers;
+ rv = accountMgr->GetAllServers(allServers);
+ NS_ENSURE_SUCCESS(rv, rv);
+ uint32_t numServers = allServers.Length();
+ if (numServers > 0) {
+ nsTArray<RefPtr<nsIMsgFolder>> folderArray;
+ nsTArray<RefPtr<nsIMsgFolder>> offlineFolderArray;
+ int64_t totalExpungedBytes = 0;
+ int64_t offlineExpungedBytes = 0;
+ int64_t localExpungedBytes = 0;
+ uint32_t serverIndex = 0;
+ do {
+ nsCOMPtr<nsIMsgIncomingServer> server(allServers[serverIndex]);
+ nsCOMPtr<nsIMsgPluggableStore> msgStore;
+ rv = server->GetMsgStore(getter_AddRefs(msgStore));
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!msgStore) continue;
+ bool supportsCompaction;
+ msgStore->GetSupportsCompaction(&supportsCompaction);
+ if (!supportsCompaction) continue;
+ nsCOMPtr<nsIMsgFolder> rootFolder;
+ rv = server->GetRootFolder(getter_AddRefs(rootFolder));
+ if (NS_SUCCEEDED(rv) && rootFolder) {
+ int32_t offlineSupportLevel;
+ rv = server->GetOfflineSupportLevel(&offlineSupportLevel);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsTArray<RefPtr<nsIMsgFolder>> allDescendants;
+ rootFolder->GetDescendants(allDescendants);
+ int64_t expungedBytes = 0;
+ if (offlineSupportLevel > 0) {
+ uint32_t flags;
+ for (auto folder : allDescendants) {
+ expungedBytes = 0;
+ folder->GetFlags(&flags);
+ if (flags & nsMsgFolderFlags::Offline)
+ folder->GetExpungedBytes(&expungedBytes);
+ if (expungedBytes > 0) {
+ offlineFolderArray.AppendElement(folder);
+ offlineExpungedBytes += expungedBytes;
+ }
+ }
+ } else // pop or local
+ {
+ for (auto folder : allDescendants) {
+ expungedBytes = 0;
+ folder->GetExpungedBytes(&expungedBytes);
+ if (expungedBytes > 0) {
+ folderArray.AppendElement(folder);
+ localExpungedBytes += expungedBytes;
+ }
+ }
+ }
+ }
+ } while (++serverIndex < numServers);
+ totalExpungedBytes = localExpungedBytes + offlineExpungedBytes;
+ int32_t purgeThreshold;
+ rv = GetPurgeThreshold(&purgeThreshold);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (totalExpungedBytes > ((int64_t)purgeThreshold * 1024)) {
+ bool okToCompact = false;
+ nsCOMPtr<nsIPrefService> pref =
+ do_GetService(NS_PREFSERVICE_CONTRACTID);
+ nsCOMPtr<nsIPrefBranch> branch;
+ pref->GetBranch("", getter_AddRefs(branch));
+
+ bool askBeforePurge;
+ branch->GetBoolPref(PREF_MAIL_PURGE_ASK, &askBeforePurge);
+ if (askBeforePurge && aWindow) {
+ nsCOMPtr<nsIStringBundle> bundle;
+ rv = GetBaseStringBundle(getter_AddRefs(bundle));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString compactSize;
+ FormatFileSize(totalExpungedBytes, true, compactSize);
+
+ bool neverAsk = false; // "Do not ask..." - unchecked by default.
+ int32_t buttonPressed = 0;
+
+ nsCOMPtr<nsIWindowWatcher> ww(
+ do_GetService(NS_WINDOWWATCHER_CONTRACTID));
+ nsCOMPtr<nsIWritablePropertyBag2> props(
+ do_CreateInstance("@mozilla.org/hash-property-bag;1"));
+ props->SetPropertyAsAString(u"compactSize"_ns, compactSize);
+ nsCOMPtr<mozIDOMWindowProxy> migrateWizard;
+ rv = ww->OpenWindow(
+ nullptr,
+ "chrome://messenger/content/compactFoldersDialog.xhtml"_ns,
+ "_blank"_ns, "chrome,dialog,modal,centerscreen"_ns, props,
+ getter_AddRefs(migrateWizard));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = props->GetPropertyAsBool(u"checked"_ns, &neverAsk);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv =
+ props->GetPropertyAsInt32(u"buttonNumClicked"_ns, &buttonPressed);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (buttonPressed == 0) {
+ okToCompact = true;
+ if (neverAsk) // [X] Remove deletions automatically and do not ask
+ branch->SetBoolPref(PREF_MAIL_PURGE_ASK, false);
+ }
+ } else
+ okToCompact = aWindow || !askBeforePurge;
+
+ if (okToCompact) {
+ NotifyFolderEvent(kAboutToCompact);
+
+ if (localExpungedBytes > 0 || offlineExpungedBytes > 0) {
+ nsCOMPtr<nsIMsgFolderCompactor> folderCompactor = do_CreateInstance(
+ "@mozilla.org/messenger/foldercompactor;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ for (nsIMsgFolder* f : offlineFolderArray) {
+ folderArray.AppendElement(f);
+ }
+ rv = folderCompactor->CompactFolders(folderArray, nullptr, aWindow);
+ }
+ }
+ }
+ }
+ }
+ return rv;
+}
+
+nsresult nsMsgDBFolder::AutoCompact(nsIMsgWindow* aWindow) {
+ // we don't check for null aWindow, because this routine can get called
+ // in unit tests where we have no window. Just assume not OK if no window.
+ bool prompt;
+ nsresult rv = GetPromptPurgeThreshold(&prompt);
+ NS_ENSURE_SUCCESS(rv, rv);
+ PRTime timeNow = PR_Now(); // time in microseconds
+ PRTime timeAfterOneHourOfLastPurgeCheck = gtimeOfLastPurgeCheck + oneHour;
+ if (timeAfterOneHourOfLastPurgeCheck < timeNow && prompt) {
+ gtimeOfLastPurgeCheck = timeNow;
+ nsCOMPtr<nsIRunnable> event = new AutoCompactEvent(aWindow, this);
+ // Post this as an event because it can put up an alert, which
+ // might cause issues depending on the stack when we are called.
+ if (event) NS_DispatchToCurrentThread(event);
+ }
+ return rv;
+}
+
+nsresult nsMsgDBFolder::GetPromptPurgeThreshold(bool* aPrompt) {
+ NS_ENSURE_ARG(aPrompt);
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> prefBranch =
+ do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv) && prefBranch) {
+ rv = prefBranch->GetBoolPref(PREF_MAIL_PROMPT_PURGE_THRESHOLD, aPrompt);
+ if (NS_FAILED(rv)) {
+ *aPrompt = false;
+ rv = NS_OK;
+ }
+ }
+ return rv;
+}
+
+nsresult nsMsgDBFolder::GetPurgeThreshold(int32_t* aThreshold) {
+ NS_ENSURE_ARG(aThreshold);
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> prefBranch =
+ do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv) && prefBranch) {
+ int32_t thresholdMB = 200;
+ bool thresholdMigrated = false;
+ prefBranch->GetIntPref(PREF_MAIL_PURGE_THRESHOLD_MB, &thresholdMB);
+ prefBranch->GetBoolPref(PREF_MAIL_PURGE_MIGRATED, &thresholdMigrated);
+ if (!thresholdMigrated) {
+ *aThreshold = 20480;
+ (void)prefBranch->GetIntPref(PREF_MAIL_PURGE_THRESHOLD, aThreshold);
+ if (*aThreshold / 1024 != thresholdMB) {
+ thresholdMB = std::max(1, *aThreshold / 1024);
+ prefBranch->SetIntPref(PREF_MAIL_PURGE_THRESHOLD_MB, thresholdMB);
+ }
+ prefBranch->SetBoolPref(PREF_MAIL_PURGE_MIGRATED, true);
+ }
+ *aThreshold = thresholdMB * 1024;
+ }
+ return rv;
+}
+
+NS_IMETHODIMP // called on the folder that is renamed or about to be deleted
+nsMsgDBFolder::MatchOrChangeFilterDestination(nsIMsgFolder* newFolder,
+ bool caseInsensitive,
+ bool* found) {
+ NS_ENSURE_ARG_POINTER(found);
+ *found = false;
+ nsCString oldUri;
+ nsresult rv = GetURI(oldUri);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCString newUri;
+ if (newFolder) // for matching uri's this will be null
+ {
+ rv = newFolder->GetURI(newUri);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ nsCOMPtr<nsIMsgFilterList> filterList;
+ nsCOMPtr<nsIMsgAccountManager> accountMgr =
+ do_GetService("@mozilla.org/messenger/account-manager;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsTArray<RefPtr<nsIMsgIncomingServer>> allServers;
+ rv = accountMgr->GetAllServers(allServers);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (auto server : allServers) {
+ if (server) {
+ bool canHaveFilters;
+ rv = server->GetCanHaveFilters(&canHaveFilters);
+ if (NS_SUCCEEDED(rv) && canHaveFilters) {
+ // update the filterlist to match the new folder name
+ rv = server->GetFilterList(nullptr, getter_AddRefs(filterList));
+ if (NS_SUCCEEDED(rv) && filterList) {
+ bool match;
+ rv = filterList->MatchOrChangeFilterTarget(oldUri, newUri,
+ caseInsensitive, &match);
+ if (NS_SUCCEEDED(rv) && match) {
+ *found = true;
+ if (newFolder && !newUri.IsEmpty())
+ rv = filterList->SaveToDefaultFile();
+ }
+ }
+ // update the editable filterlist to match the new folder name
+ rv = server->GetEditableFilterList(nullptr, getter_AddRefs(filterList));
+ if (NS_SUCCEEDED(rv) && filterList) {
+ bool match;
+ rv = filterList->MatchOrChangeFilterTarget(oldUri, newUri,
+ caseInsensitive, &match);
+ if (NS_SUCCEEDED(rv) && match) {
+ *found = true;
+ if (newFolder && !newUri.IsEmpty())
+ rv = filterList->SaveToDefaultFile();
+ }
+ }
+ }
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::GetDBTransferInfo(nsIDBFolderInfo** aTransferInfo) {
+ nsCOMPtr<nsIDBFolderInfo> dbFolderInfo;
+ nsCOMPtr<nsIMsgDatabase> db;
+ GetDBFolderInfoAndDB(getter_AddRefs(dbFolderInfo), getter_AddRefs(db));
+ NS_ENSURE_STATE(dbFolderInfo);
+ return dbFolderInfo->GetTransferInfo(aTransferInfo);
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::SetDBTransferInfo(nsIDBFolderInfo* aTransferInfo) {
+ NS_ENSURE_ARG(aTransferInfo);
+ nsCOMPtr<nsIDBFolderInfo> dbFolderInfo;
+ nsCOMPtr<nsIMsgDatabase> db;
+ GetMsgDatabase(getter_AddRefs(db));
+ if (db) {
+ db->GetDBFolderInfo(getter_AddRefs(dbFolderInfo));
+ if (dbFolderInfo) {
+ dbFolderInfo->InitFromTransferInfo(aTransferInfo);
+ dbFolderInfo->SetBooleanProperty("forceReparse", false);
+ }
+ db->SetSummaryValid(true);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::GetStringProperty(const char* propertyName,
+ nsACString& propertyValue) {
+ NS_ENSURE_ARG_POINTER(propertyName);
+ nsCOMPtr<nsIFile> dbPath;
+ nsresult rv = GetFolderCacheKey(getter_AddRefs(dbPath));
+ if (dbPath) {
+ nsCOMPtr<nsIMsgFolderCacheElement> cacheElement;
+ rv = GetFolderCacheElemFromFile(dbPath, getter_AddRefs(cacheElement));
+ if (cacheElement) // try to get from cache
+ rv = cacheElement->GetCachedString(propertyName, propertyValue);
+ if (NS_FAILED(rv)) // if failed, then try to get from db, usually.
+ {
+ if (strcmp(propertyName, MRU_TIME_PROPERTY) == 0 ||
+ strcmp(propertyName, MRM_TIME_PROPERTY) == 0 ||
+ strcmp(propertyName, "LastPurgeTime") == 0) {
+ // Don't open DB for missing time properties.
+ // Missing time properties can happen if the folder was never
+ // accessed, for exaple after an import. They happen if
+ // folderCache.json is removed or becomes invalid after moving
+ // a profile (see bug 1726660).
+ propertyValue.Truncate();
+ return NS_OK;
+ }
+ nsCOMPtr<nsIDBFolderInfo> folderInfo;
+ nsCOMPtr<nsIMsgDatabase> db;
+ bool exists;
+ rv = dbPath->Exists(&exists);
+ if (NS_FAILED(rv) || !exists) return NS_MSG_ERROR_FOLDER_MISSING;
+ bool weOpenedDB = !mDatabase;
+ rv = GetDBFolderInfoAndDB(getter_AddRefs(folderInfo), getter_AddRefs(db));
+ if (NS_SUCCEEDED(rv))
+ rv = folderInfo->GetCharProperty(propertyName, propertyValue);
+ if (weOpenedDB) CloseDBIfFolderNotOpen(false);
+ if (NS_SUCCEEDED(rv)) {
+ // Now that we have the value, store it in our cache.
+ if (cacheElement) {
+ cacheElement->SetCachedString(propertyName, propertyValue);
+ }
+ }
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::SetStringProperty(const char* propertyName,
+ const nsACString& propertyValue) {
+ NS_ENSURE_ARG_POINTER(propertyName);
+ nsCOMPtr<nsIFile> dbPath;
+ GetFolderCacheKey(getter_AddRefs(dbPath));
+ if (dbPath) {
+ nsCOMPtr<nsIMsgFolderCacheElement> cacheElement;
+ GetFolderCacheElemFromFile(dbPath, getter_AddRefs(cacheElement));
+ if (cacheElement) // try to set in the cache
+ cacheElement->SetCachedString(propertyName, propertyValue);
+ }
+ nsCOMPtr<nsIDBFolderInfo> folderInfo;
+ nsCOMPtr<nsIMsgDatabase> db;
+ nsresult rv =
+ GetDBFolderInfoAndDB(getter_AddRefs(folderInfo), getter_AddRefs(db));
+ if (NS_SUCCEEDED(rv)) {
+ folderInfo->SetCharProperty(propertyName, propertyValue);
+ db->Commit(nsMsgDBCommitType::kLargeCommit); // committing the db also
+ // commits the cache
+ }
+ return NS_OK;
+}
+
+// Get/Set ForcePropertyEmpty is only used with inherited properties
+NS_IMETHODIMP
+nsMsgDBFolder::GetForcePropertyEmpty(const char* aPropertyName, bool* _retval) {
+ NS_ENSURE_ARG_POINTER(_retval);
+ nsAutoCString nameEmpty(aPropertyName);
+ nameEmpty.AppendLiteral(".empty");
+ nsCString value;
+ GetStringProperty(nameEmpty.get(), value);
+ *_retval = value.EqualsLiteral("true");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::SetForcePropertyEmpty(const char* aPropertyName, bool aValue) {
+ nsAutoCString nameEmpty(aPropertyName);
+ nameEmpty.AppendLiteral(".empty");
+ return SetStringProperty(nameEmpty.get(), aValue ? "true"_ns : ""_ns);
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::GetInheritedStringProperty(const char* aPropertyName,
+ nsACString& aPropertyValue) {
+ NS_ENSURE_ARG_POINTER(aPropertyName);
+ nsCString value;
+ nsCOMPtr<nsIMsgIncomingServer> server;
+
+ bool forceEmpty = false;
+
+ if (!mIsServer) {
+ GetForcePropertyEmpty(aPropertyName, &forceEmpty);
+ } else {
+ // root folders must get their values from the server
+ GetServer(getter_AddRefs(server));
+ if (server) server->GetForcePropertyEmpty(aPropertyName, &forceEmpty);
+ }
+
+ if (forceEmpty) {
+ aPropertyValue.Truncate();
+ return NS_OK;
+ }
+
+ // servers will automatically inherit from the preference
+ // mail.server.default.(propertyName)
+ if (server) return server->GetCharValue(aPropertyName, aPropertyValue);
+
+ GetStringProperty(aPropertyName, value);
+ if (value.IsEmpty()) {
+ // inherit from the parent
+ nsCOMPtr<nsIMsgFolder> parent;
+ GetParent(getter_AddRefs(parent));
+ if (parent)
+ return parent->GetInheritedStringProperty(aPropertyName, aPropertyValue);
+ }
+
+ aPropertyValue.Assign(value);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::OnMessageClassified(const nsACString& aMsgURI,
+ nsMsgJunkStatus aClassification,
+ uint32_t aJunkPercent) {
+ nsresult rv = GetDatabase();
+ NS_ENSURE_SUCCESS(rv, NS_OK);
+
+ if (aMsgURI.IsEmpty()) // This signifies end of batch.
+ {
+ // Apply filters if needed.
+ if (!mPostBayesMessagesToFilter.IsEmpty()) {
+ // Apply post-bayes filtering.
+ nsCOMPtr<nsIMsgFilterService> filterService(
+ do_GetService("@mozilla.org/messenger/services/filters;1", &rv));
+ if (NS_SUCCEEDED(rv))
+ // We use a null nsIMsgWindow because we don't want some sort of ui
+ // appearing in the middle of automatic filtering (plus I really don't
+ // want to propagate that value.)
+ rv = filterService->ApplyFilters(nsMsgFilterType::PostPlugin,
+ mPostBayesMessagesToFilter, this,
+ nullptr, nullptr);
+ mPostBayesMessagesToFilter.Clear();
+ }
+
+ // If we classified any messages, send out a notification.
+ nsTArray<RefPtr<nsIMsgDBHdr>> hdrs;
+ rv = MsgGetHeadersFromKeys(mDatabase, mClassifiedMsgKeys, hdrs);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!hdrs.IsEmpty()) {
+ nsCOMPtr<nsIMsgFolderNotificationService> notifier(do_GetService(
+ "@mozilla.org/messenger/msgnotificationservice;1", &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ notifier->NotifyMsgsClassified(hdrs, mBayesJunkClassifying,
+ mBayesTraitClassifying);
+ }
+ return rv;
+ }
+
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsISpamSettings> spamSettings;
+ rv = server->GetSpamSettings(getter_AddRefs(spamSettings));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ rv = GetMsgDBHdrFromURI(aMsgURI, getter_AddRefs(msgHdr));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsMsgKey msgKey;
+ rv = msgHdr->GetMessageKey(&msgKey);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // check if this message needs junk classification
+ uint32_t processingFlags;
+ GetProcessingFlags(msgKey, &processingFlags);
+
+ if (processingFlags & nsMsgProcessingFlags::ClassifyJunk) {
+ mClassifiedMsgKeys.AppendElement(msgKey);
+ AndProcessingFlags(msgKey, ~nsMsgProcessingFlags::ClassifyJunk);
+
+ nsAutoCString msgJunkScore;
+ msgJunkScore.AppendInt(aClassification == nsIJunkMailPlugin::JUNK
+ ? nsIJunkMailPlugin::IS_SPAM_SCORE
+ : nsIJunkMailPlugin::IS_HAM_SCORE);
+ mDatabase->SetStringProperty(msgKey, "junkscore", msgJunkScore);
+ mDatabase->SetStringProperty(msgKey, "junkscoreorigin", "plugin"_ns);
+
+ nsAutoCString strPercent;
+ strPercent.AppendInt(aJunkPercent);
+ mDatabase->SetStringProperty(msgKey, "junkpercent", strPercent);
+
+ if (aClassification == nsIJunkMailPlugin::JUNK) {
+ // IMAP has its own way of marking read.
+ if (!(mFlags & nsMsgFolderFlags::ImapBox)) {
+ bool markAsReadOnSpam;
+ (void)spamSettings->GetMarkAsReadOnSpam(&markAsReadOnSpam);
+ if (markAsReadOnSpam) {
+ rv = mDatabase->MarkRead(msgKey, true, this);
+ if (!NS_SUCCEEDED(rv))
+ NS_WARNING("failed marking spam message as read");
+ }
+ }
+ // mail folders will log junk hits with move info. Perhaps we should
+ // add a log here for non-mail folders as well, that don't override
+ // onMessageClassified
+ // rv = spamSettings->LogJunkHit(msgHdr, false);
+ // NS_ENSURE_SUCCESS(rv,rv);
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::OnMessageTraitsClassified(const nsACString& aMsgURI,
+ const nsTArray<uint32_t>& aTraits,
+ const nsTArray<uint32_t>& aPercents) {
+ if (aMsgURI.IsEmpty()) // This signifies end of batch
+ return NS_OK; // We are not handling batching
+
+ MOZ_ASSERT(aTraits.Length() == aPercents.Length());
+
+ nsresult rv;
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ rv = GetMsgDBHdrFromURI(aMsgURI, getter_AddRefs(msgHdr));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsMsgKey msgKey;
+ rv = msgHdr->GetMessageKey(&msgKey);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t processingFlags;
+ GetProcessingFlags(msgKey, &processingFlags);
+ if (!(processingFlags & nsMsgProcessingFlags::ClassifyTraits)) return NS_OK;
+
+ AndProcessingFlags(msgKey, ~nsMsgProcessingFlags::ClassifyTraits);
+
+ nsCOMPtr<nsIMsgTraitService> traitService;
+ traitService = do_GetService("@mozilla.org/msg-trait-service;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (uint32_t i = 0; i < aTraits.Length(); i++) {
+ if (aTraits[i] == nsIJunkMailPlugin::JUNK_TRAIT)
+ continue; // junk is processed by the junk listener
+ nsAutoCString traitId;
+ rv = traitService->GetId(aTraits[i], traitId);
+ traitId.InsertLiteral("bayespercent/", 0);
+ nsAutoCString strPercent;
+ strPercent.AppendInt(aPercents[i]);
+ mDatabase->SetStringPropertyByHdr(msgHdr, traitId.get(), strPercent);
+ }
+ return NS_OK;
+}
+
+/**
+ * Call the filter plugins (XXX currently just one)
+ */
+NS_IMETHODIMP
+nsMsgDBFolder::CallFilterPlugins(nsIMsgWindow* aMsgWindow, bool* aFiltersRun) {
+ NS_ENSURE_ARG_POINTER(aFiltersRun);
+
+ nsString folderName;
+ GetPrettyName(folderName);
+ MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
+ ("Running filter plugins on folder '%s'",
+ NS_ConvertUTF16toUTF8(folderName).get()));
+
+ *aFiltersRun = false;
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ nsCOMPtr<nsISpamSettings> spamSettings;
+ int32_t spamLevel = 0;
+
+ nsresult rv = GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCString serverType;
+ server->GetType(serverType);
+
+ rv = server->GetSpamSettings(getter_AddRefs(spamSettings));
+ nsCOMPtr<nsIMsgFilterPlugin> filterPlugin;
+ server->GetSpamFilterPlugin(getter_AddRefs(filterPlugin));
+ if (!filterPlugin) // it's not an error not to have the filter plugin.
+ return NS_OK;
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIJunkMailPlugin> junkMailPlugin = do_QueryInterface(filterPlugin);
+ if (!junkMailPlugin) // we currently only support the junk mail plugin
+ return NS_OK;
+
+ // if it's a news folder, then we really don't support junk in the ui
+ // yet the legacy spamLevel seems to think we should analyze it.
+ // Maybe we should upgrade that, but for now let's not analyze. We'll
+ // let an extension set an inherited property if they really want us to
+ // analyze this. We need that anyway to allow extension-based overrides.
+ // When we finalize adding junk in news to core, we'll deal with the
+ // spamLevel issue
+
+ // if this is the junk folder, or the trash folder
+ // don't analyze for spam, because we don't care
+ //
+ // if it's the sent, unsent, templates, or drafts,
+ // don't analyze for spam, because the user
+ // created that message
+ //
+ // if it's a public imap folder, or another users
+ // imap folder, don't analyze for spam, because
+ // it's not ours to analyze
+ //
+
+ bool filterForJunk = true;
+ if (serverType.EqualsLiteral("rss") ||
+ (mFlags &
+ (nsMsgFolderFlags::SpecialUse | nsMsgFolderFlags::ImapPublic |
+ nsMsgFolderFlags::Newsgroup | nsMsgFolderFlags::ImapOtherUser) &&
+ !(mFlags & nsMsgFolderFlags::Inbox)))
+ filterForJunk = false;
+
+ spamSettings->GetLevel(&spamLevel);
+ if (!spamLevel) filterForJunk = false;
+
+ /*
+ * We'll use inherited folder properties for the junk trait to override the
+ * standard server-based activation of junk processing. This provides a
+ * hook for extensions to customize the application of junk filtering.
+ * Set inherited property "dobayes.mailnews@mozilla.org#junk" to "true"
+ * to force junk processing, and "false" to skip junk processing.
+ */
+
+ nsAutoCString junkEnableOverride;
+ GetInheritedStringProperty("dobayes.mailnews@mozilla.org#junk",
+ junkEnableOverride);
+ if (junkEnableOverride.EqualsLiteral("true"))
+ filterForJunk = true;
+ else if (junkEnableOverride.EqualsLiteral("false"))
+ filterForJunk = false;
+
+ bool userHasClassified = false;
+ // if the user has not classified any messages yet, then we shouldn't bother
+ // running the junk mail controls. This creates a better first use experience.
+ // See Bug #250084.
+ junkMailPlugin->GetUserHasClassified(&userHasClassified);
+ if (!userHasClassified) filterForJunk = false;
+
+ MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
+ ("Will run Spam filter: %s", filterForJunk ? "true" : "false"));
+
+ nsCOMPtr<nsIMsgDatabase> database(mDatabase);
+ rv = GetMsgDatabase(getter_AddRefs(database));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // check if trait processing needed
+
+ nsCOMPtr<nsIMsgTraitService> traitService(
+ do_GetService("@mozilla.org/msg-trait-service;1", &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsTArray<uint32_t> proIndices;
+ rv = traitService->GetEnabledProIndices(proIndices);
+ bool filterForOther = false;
+ // We just skip this on failure, since it is rarely used.
+ if (NS_SUCCEEDED(rv)) {
+ for (uint32_t i = 0; i < proIndices.Length(); ++i) {
+ // The trait service determines which traits are globally enabled or
+ // disabled. If a trait is enabled, it can still be made inactive
+ // on a particular folder using an inherited property. To do that,
+ // set "dobayes." + trait proID as an inherited folder property with
+ // the string value "false"
+ //
+ // If any non-junk traits are active on the folder, then the bayes
+ // processing will calculate probabilities for all enabled traits.
+
+ if (proIndices[i] != nsIJunkMailPlugin::JUNK_TRAIT) {
+ filterForOther = true;
+ nsAutoCString traitId;
+ nsAutoCString property("dobayes.");
+ traitService->GetId(proIndices[i], traitId);
+ property.Append(traitId);
+ nsAutoCString isEnabledOnFolder;
+ GetInheritedStringProperty(property.get(), isEnabledOnFolder);
+ if (isEnabledOnFolder.EqualsLiteral("false")) filterForOther = false;
+ // We might have to allow a "true" override in the future, but
+ // for now there is no way for that to affect the processing
+ break;
+ }
+ }
+ }
+
+ // clang-format off
+ MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
+ ("Will run Trait classification: %s", filterForOther ? "true" : "false"));
+ // clang-format on
+
+ // Do we need to apply message filters?
+ bool filterPostPlugin = false; // Do we have a post-analysis filter?
+ nsCOMPtr<nsIMsgFilterList> filterList;
+ GetFilterList(aMsgWindow, getter_AddRefs(filterList));
+ if (filterList) {
+ uint32_t filterCount = 0;
+ filterList->GetFilterCount(&filterCount);
+ for (uint32_t index = 0; index < filterCount && !filterPostPlugin;
+ ++index) {
+ nsCOMPtr<nsIMsgFilter> filter;
+ filterList->GetFilterAt(index, getter_AddRefs(filter));
+ if (!filter) continue;
+ nsMsgFilterTypeType filterType;
+ filter->GetFilterType(&filterType);
+ if (!(filterType & nsMsgFilterType::PostPlugin)) continue;
+ bool enabled = false;
+ filter->GetEnabled(&enabled);
+ if (!enabled) continue;
+ filterPostPlugin = true;
+ }
+ }
+
+ MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
+ ("Will run Post-classification filters: %s",
+ filterPostPlugin ? "true" : "false"));
+
+ // If there is nothing to do, leave now but let NotifyHdrsNotBeingClassified
+ // generate the msgsClassified notification for all newly added messages as
+ // tracked by the NotReportedClassified processing flag.
+ if (!filterForOther && !filterForJunk && !filterPostPlugin) {
+ MOZ_LOG(FILTERLOGMODULE, LogLevel::Info, ("No filters need to be run"));
+ NotifyHdrsNotBeingClassified();
+ return NS_OK;
+ }
+
+ // get the list of new messages
+ //
+ nsTArray<nsMsgKey> newKeys;
+ rv = database->GetNewList(newKeys);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
+ ("Running filters on %" PRIu32 " new messages",
+ (uint32_t)newKeys.Length()));
+
+ nsTArray<nsMsgKey> newMessageKeys;
+ // Start from m_saveNewMsgs (and clear its current state). m_saveNewMsgs is
+ // where we stash the list of new messages when we are told to clear the list
+ // of new messages by the UI (which purges the list from the nsMsgDatabase).
+ newMessageKeys.SwapElements(m_saveNewMsgs);
+ newMessageKeys.AppendElements(newKeys);
+
+ // build up list of keys to classify
+ nsTArray<nsMsgKey> classifyMsgKeys;
+ nsCString uri;
+
+ uint32_t numNewMessages = newMessageKeys.Length();
+ for (uint32_t i = 0; i < numNewMessages; ++i) {
+ nsMsgKey msgKey = newMessageKeys[i];
+ // clang-format off
+ MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
+ ("Running filters on message with key %" PRIu32, msgKeyToInt(msgKey)));
+ // clang-format on
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ rv = database->GetMsgHdrForKey(msgKey, getter_AddRefs(msgHdr));
+ if (!NS_SUCCEEDED(rv)) continue;
+ // per-message junk tests.
+ bool filterMessageForJunk = false;
+ while (filterForJunk) // we'll break from this at the end
+ {
+ MOZ_LOG(FILTERLOGMODULE, LogLevel::Info, ("Spam filter"));
+ nsCString junkScore;
+ msgHdr->GetStringProperty("junkscore", junkScore);
+ if (!junkScore.IsEmpty()) {
+ // ignore already scored messages.
+ MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
+ ("Message already scored previously, skipping"));
+ break;
+ }
+
+ bool whiteListMessage = false;
+ spamSettings->CheckWhiteList(msgHdr, &whiteListMessage);
+ if (whiteListMessage) {
+ // mark this msg as non-junk, because we whitelisted it.
+ nsAutoCString msgJunkScore;
+ msgJunkScore.AppendInt(nsIJunkMailPlugin::IS_HAM_SCORE);
+ database->SetStringProperty(msgKey, "junkscore", msgJunkScore);
+ database->SetStringProperty(msgKey, "junkscoreorigin", "whitelist"_ns);
+ MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
+ ("Message whitelisted, skipping"));
+ break; // skip this msg since it's in the white list
+ }
+ filterMessageForJunk = true;
+
+ MOZ_LOG(FILTERLOGMODULE, LogLevel::Info, ("Message is to be classified"));
+ OrProcessingFlags(msgKey, nsMsgProcessingFlags::ClassifyJunk);
+ // Since we are junk processing, we want to defer the msgsClassified
+ // notification until the junk classification has occurred. The event
+ // is sufficiently reliable that we know this will be handled in
+ // OnMessageClassified at the end of the batch. We clear the
+ // NotReportedClassified flag since we know the message is in good hands.
+ AndProcessingFlags(msgKey, ~nsMsgProcessingFlags::NotReportedClassified);
+ break;
+ }
+
+ uint32_t processingFlags;
+ GetProcessingFlags(msgKey, &processingFlags);
+
+ bool filterMessageForOther = false;
+ // trait processing
+ if (!(processingFlags & nsMsgProcessingFlags::TraitsDone)) {
+ // don't do trait processing on this message again
+ OrProcessingFlags(msgKey, nsMsgProcessingFlags::TraitsDone);
+ if (filterForOther) {
+ filterMessageForOther = true;
+ OrProcessingFlags(msgKey, nsMsgProcessingFlags::ClassifyTraits);
+ }
+ }
+
+ if (filterMessageForJunk || filterMessageForOther)
+ classifyMsgKeys.AppendElement(newMessageKeys[i]);
+
+ // Set messages to filter post-bayes.
+ // Have we already filtered this message?
+ if (!(processingFlags & nsMsgProcessingFlags::FiltersDone)) {
+ if (filterPostPlugin) {
+ // Don't do filters on this message again.
+ // (Only set this if we are actually filtering since this is
+ // tantamount to a memory leak.)
+ MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
+ ("Filters done on this message"));
+ OrProcessingFlags(msgKey, nsMsgProcessingFlags::FiltersDone);
+ mPostBayesMessagesToFilter.AppendElement(msgHdr);
+ }
+ }
+ }
+
+ NotifyHdrsNotBeingClassified();
+ // If there weren't any new messages, just return.
+ if (newMessageKeys.IsEmpty()) return NS_OK;
+
+ // If we do not need to do any work, leave.
+ // (We needed to get the list of new messages so we could get their headers so
+ // we can send notifications about them here.)
+
+ if (!classifyMsgKeys.IsEmpty()) {
+ // Remember what classifications are the source of this decision for when
+ // we perform the notification in OnMessageClassified at the conclusion of
+ // classification.
+ mBayesJunkClassifying = filterForJunk;
+ mBayesTraitClassifying = filterForOther;
+
+ uint32_t numMessagesToClassify = classifyMsgKeys.Length();
+ MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
+ ("Running Spam classification on %" PRIu32 " messages",
+ numMessagesToClassify));
+
+ nsTArray<nsCString> messageURIs(numMessagesToClassify);
+ for (uint32_t msgIndex = 0; msgIndex < numMessagesToClassify; ++msgIndex) {
+ nsCString tmpStr;
+ rv = GenerateMessageURI(classifyMsgKeys[msgIndex], tmpStr);
+ if (NS_SUCCEEDED(rv)) {
+ messageURIs.AppendElement(tmpStr);
+ } else {
+ NS_WARNING(
+ "nsMsgDBFolder::CallFilterPlugins(): could not"
+ " generate URI for message");
+ }
+ }
+ // filterMsgs
+ *aFiltersRun = true;
+
+ // Already got proIndices, but need antiIndices too.
+ nsTArray<uint32_t> antiIndices;
+ rv = traitService->GetEnabledAntiIndices(antiIndices);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = junkMailPlugin->ClassifyTraitsInMessages(
+ messageURIs, proIndices, antiIndices, this, aMsgWindow, this);
+ } else if (filterPostPlugin) {
+ // Nothing to classify, so need to end batch ourselves. We do this so that
+ // post analysis filters will run consistently on a folder, even if
+ // disabled junk processing, which could be dynamic through whitelisting,
+ // makes the bayes analysis unnecessary.
+ OnMessageClassified(EmptyCString(), nsIJunkMailPlugin::UNCLASSIFIED, 0);
+ }
+
+ return rv;
+}
+
+/**
+ * Adds the messages in the NotReportedClassified mProcessing set to the
+ * (possibly empty) array of msgHdrsNotBeingClassified, and send the
+ * nsIMsgFolderNotificationService notification.
+ */
+nsresult nsMsgDBFolder::NotifyHdrsNotBeingClassified() {
+ if (mProcessingFlag[5].keys) {
+ nsTArray<nsMsgKey> keys;
+ mProcessingFlag[5].keys->ToMsgKeyArray(keys);
+ if (keys.Length()) {
+ nsresult rv = GetDatabase();
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsTArray<RefPtr<nsIMsgDBHdr>> msgHdrsNotBeingClassified;
+ rv = MsgGetHeadersFromKeys(mDatabase, keys, msgHdrsNotBeingClassified);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Since we know we've handled all the NotReportedClassified messages,
+ // we clear the set by deleting and recreating it.
+ delete mProcessingFlag[5].keys;
+ mProcessingFlag[5].keys = nsMsgKeySetU::Create();
+ nsCOMPtr<nsIMsgFolderNotificationService> notifier(
+ do_GetService("@mozilla.org/messenger/msgnotificationservice;1"));
+ if (notifier)
+ notifier->NotifyMsgsClassified(msgHdrsNotBeingClassified,
+ // no classification is being performed
+ false, false);
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::GetLastMessageLoaded(nsMsgKey* aMsgKey) {
+ NS_ENSURE_ARG_POINTER(aMsgKey);
+ *aMsgKey = mLastMessageLoaded;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::SetLastMessageLoaded(nsMsgKey aMsgKey) {
+ mLastMessageLoaded = aMsgKey;
+ return NS_OK;
+}
+
+// Returns true if: a) there is no need to prompt or b) the user is already
+// logged in or c) the user logged in successfully.
+bool nsMsgDBFolder::PromptForMasterPasswordIfNecessary() {
+ nsresult rv;
+ nsCOMPtr<nsIMsgAccountManager> accountManager =
+ do_GetService("@mozilla.org/messenger/account-manager;1", &rv);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ bool userNeedsToAuthenticate = false;
+ // if we're PasswordProtectLocalCache, then we need to find out if the server
+ // is authenticated.
+ (void)accountManager->GetUserNeedsToAuthenticate(&userNeedsToAuthenticate);
+ if (!userNeedsToAuthenticate) return true;
+
+ // Do we have a master password?
+ nsCOMPtr<nsIPK11TokenDB> tokenDB =
+ do_GetService(NS_PK11TOKENDB_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ nsCOMPtr<nsIPK11Token> token;
+ rv = tokenDB->GetInternalKeyToken(getter_AddRefs(token));
+ NS_ENSURE_SUCCESS(rv, false);
+
+ bool result;
+ rv = token->CheckPassword(EmptyCString(), &result);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ if (result) {
+ // We don't have a master password, so this function isn't supported,
+ // therefore just tell account manager we've authenticated and return true.
+ accountManager->SetUserNeedsToAuthenticate(false);
+ return true;
+ }
+
+ // We have a master password, so try and login to the slot.
+ rv = token->Login(false);
+ if (NS_FAILED(rv))
+ // Login failed, so we didn't get a password (e.g. prompt cancelled).
+ return false;
+
+ // Double-check that we are now logged in
+ rv = token->IsLoggedIn(&result);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ accountManager->SetUserNeedsToAuthenticate(!result);
+ return result;
+}
+
+// this gets called after the last junk mail classification has run.
+nsresult nsMsgDBFolder::PerformBiffNotifications(void) {
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ nsresult rv = GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+ int32_t numBiffMsgs = 0;
+ nsCOMPtr<nsIMsgFolder> root;
+ rv = GetRootFolder(getter_AddRefs(root));
+ root->GetNumNewMessages(true, &numBiffMsgs);
+ if (numBiffMsgs > 0) {
+ server->SetPerformingBiff(true);
+ SetBiffState(nsIMsgFolder::nsMsgBiffState_NewMail);
+ server->SetPerformingBiff(false);
+ }
+ return NS_OK;
+}
+
+nsresult nsMsgDBFolder::initializeStrings() {
+ nsresult rv;
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ mozilla::components::StringBundle::Service();
+ NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED);
+ nsCOMPtr<nsIStringBundle> bundle;
+ rv = bundleService->CreateBundle(
+ "chrome://messenger/locale/messenger.properties", getter_AddRefs(bundle));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bundle->GetStringFromName("inboxFolderName", kLocalizedInboxName);
+ bundle->GetStringFromName("trashFolderName", kLocalizedTrashName);
+ bundle->GetStringFromName("sentFolderName", kLocalizedSentName);
+ bundle->GetStringFromName("draftsFolderName", kLocalizedDraftsName);
+ bundle->GetStringFromName("templatesFolderName", kLocalizedTemplatesName);
+ bundle->GetStringFromName("junkFolderName", kLocalizedJunkName);
+ bundle->GetStringFromName("outboxFolderName", kLocalizedUnsentName);
+ bundle->GetStringFromName("archivesFolderName", kLocalizedArchivesName);
+
+ nsCOMPtr<nsIStringBundle> brandBundle;
+ rv = bundleService->CreateBundle("chrome://branding/locale/brand.properties",
+ getter_AddRefs(bundle));
+ NS_ENSURE_SUCCESS(rv, rv);
+ bundle->GetStringFromName("brandShortName", kLocalizedBrandShortName);
+ return NS_OK;
+}
+
+nsresult nsMsgDBFolder::createCollationKeyGenerator() {
+ if (!gCollationKeyGenerator) {
+ auto result = mozilla::intl::LocaleService::TryCreateComponent<Collator>();
+ if (result.isErr()) {
+ NS_WARNING("Could not create mozilla::intl::Collation.");
+ return NS_ERROR_FAILURE;
+ }
+
+ gCollationKeyGenerator = result.unwrap();
+
+ // Sort in a case-insensitive way, where "base" letters are considered
+ // equal, e.g: a = á, a = A, a ≠ b.
+ Collator::Options options{};
+ options.sensitivity = Collator::Sensitivity::Base;
+ auto optResult = gCollationKeyGenerator->SetOptions(options);
+
+ if (optResult.isErr()) {
+ NS_WARNING("Could not configure the mozilla::intl::Collation.");
+ gCollationKeyGenerator = nullptr;
+ return NS_ERROR_FAILURE;
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::Init(const nsACString& uri) {
+ mURI = uri;
+ return CreateBaseMessageURI(uri);
+}
+
+nsresult nsMsgDBFolder::CreateBaseMessageURI(const nsACString& aURI) {
+ // Each folder needs to implement this.
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::GetURI(nsACString& name) {
+ name = mURI;
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+#if 0
+typedef bool
+(*nsArrayFilter)(nsISupports* element, void* data);
+#endif
+////////////////////////////////////////////////////////////////////////////////
+
+NS_IMETHODIMP
+nsMsgDBFolder::GetSubFolders(nsTArray<RefPtr<nsIMsgFolder>>& folders) {
+ folders.ClearAndRetainStorage();
+ folders.SetCapacity(mSubFolders.Length());
+ for (nsIMsgFolder* f : mSubFolders) {
+ folders.AppendElement(f);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::FindSubFolder(const nsACString& aEscapedSubFolderName,
+ nsIMsgFolder** aFolder) {
+ // XXX use necko here
+ nsAutoCString uri;
+ uri.Append(mURI);
+ uri.Append('/');
+ uri.Append(aEscapedSubFolderName);
+
+ return GetOrCreateFolder(uri, aFolder);
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::GetHasSubFolders(bool* _retval) {
+ NS_ENSURE_ARG_POINTER(_retval);
+ *_retval = mSubFolders.Count() > 0;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::GetNumSubFolders(uint32_t* aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = mSubFolders.Count();
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::AddFolderListener(nsIFolderListener* listener) {
+ NS_ENSURE_ARG_POINTER(listener);
+ mListeners.AppendElement(listener);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::RemoveFolderListener(nsIFolderListener* listener) {
+ NS_ENSURE_ARG_POINTER(listener);
+ mListeners.RemoveElement(listener);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::SetParent(nsIMsgFolder* aParent) {
+ mParent = do_GetWeakReference(aParent);
+ if (aParent) {
+ nsresult rv;
+ // servers do not have parents, so we must not be a server
+ mIsServer = false;
+ mIsServerIsValid = true;
+
+ // also set the server itself while we're here.
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = aParent->GetServer(getter_AddRefs(server));
+ if (NS_SUCCEEDED(rv) && server) mServer = do_GetWeakReference(server);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetParent(nsIMsgFolder** aParent) {
+ NS_ENSURE_ARG_POINTER(aParent);
+ nsCOMPtr<nsIMsgFolder> parent = do_QueryReferent(mParent);
+ parent.forget(aParent);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::GetMessages(nsIMsgEnumerator** result) {
+ NS_ENSURE_ARG_POINTER(result);
+ // Make sure mDatabase is set.
+ nsresult rv = GetDatabase();
+ NS_ENSURE_SUCCESS(rv, rv);
+ return mDatabase->EnumerateMessages(result);
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::UpdateFolder(nsIMsgWindow*) { return NS_OK; }
+
+////////////////////////////////////////////////////////////////////////////////
+
+NS_IMETHODIMP nsMsgDBFolder::GetFolderURL(nsACString& url) {
+ url.Assign(EmptyCString());
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetServer(nsIMsgIncomingServer** aServer) {
+ NS_ENSURE_ARG_POINTER(aServer);
+ nsresult rv;
+ // short circuit the server if we have it.
+ nsCOMPtr<nsIMsgIncomingServer> server = do_QueryReferent(mServer, &rv);
+ if (NS_FAILED(rv)) {
+ // try again after parsing the URI
+ rv = parseURI(true);
+ server = do_QueryReferent(mServer);
+ }
+ server.forget(aServer);
+ return *aServer ? NS_OK : NS_ERROR_FAILURE;
+}
+
+nsresult nsMsgDBFolder::parseURI(bool needServer) {
+ nsresult rv;
+ nsCOMPtr<nsIURL> url;
+ rv = NS_MutateURI(NS_STANDARDURLMUTATOR_CONTRACTID)
+ .SetSpec(mURI)
+ .Finalize(url);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // empty path tells us it's a server.
+ if (!mIsServerIsValid) {
+ nsAutoCString path;
+ rv = url->GetPathQueryRef(path);
+ if (NS_SUCCEEDED(rv)) mIsServer = path.EqualsLiteral("/");
+ mIsServerIsValid = true;
+ }
+
+ // grab the name off the leaf of the server
+ if (mName.IsEmpty()) {
+ // mName:
+ // the name is the trailing directory in the path
+ nsAutoCString fileName;
+ nsAutoCString escapedFileName;
+ url->GetFileName(escapedFileName);
+ if (!escapedFileName.IsEmpty()) {
+ // XXX conversion to unicode here? is fileName in UTF8?
+ // yes, let's say it is in utf8
+ MsgUnescapeString(escapedFileName, 0, fileName);
+ NS_ASSERTION(mozilla::IsUtf8(fileName), "fileName is not in UTF-8");
+ CopyUTF8toUTF16(fileName, mName);
+ }
+ }
+
+ // grab the server by parsing the URI and looking it up
+ // in the account manager...
+ // But avoid this extra work by first asking the parent, if any
+ nsCOMPtr<nsIMsgIncomingServer> server = do_QueryReferent(mServer, &rv);
+ if (NS_FAILED(rv)) {
+ // first try asking the parent instead of the URI
+ nsCOMPtr<nsIMsgFolder> parentMsgFolder;
+ GetParent(getter_AddRefs(parentMsgFolder));
+
+ if (parentMsgFolder)
+ rv = parentMsgFolder->GetServer(getter_AddRefs(server));
+
+ // no parent. do the extra work of asking
+ if (!server && needServer) {
+ nsCOMPtr<nsIMsgAccountManager> accountManager =
+ do_GetService("@mozilla.org/messenger/account-manager;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCString serverType;
+ GetIncomingServerType(serverType);
+ if (serverType.IsEmpty()) {
+ NS_WARNING("can't determine folder's server type");
+ return NS_ERROR_FAILURE;
+ }
+
+ rv = NS_MutateURI(url).SetScheme(serverType).Finalize(url);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = accountManager->FindServerByURI(url, getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ mServer = do_GetWeakReference(server);
+ } /* !mServer */
+
+ // now try to find the local path for this folder
+ if (server) {
+ nsAutoCString newPath;
+ nsAutoCString escapedUrlPath;
+ nsAutoCString urlPath;
+ url->GetFilePath(escapedUrlPath);
+ if (!escapedUrlPath.IsEmpty()) {
+ MsgUnescapeString(escapedUrlPath, 0, urlPath);
+
+ // transform the filepath from the URI, such as
+ // "/folder1/folder2/foldern"
+ // to
+ // "folder1.sbd/folder2.sbd/foldern"
+ // (remove leading / and add .sbd to first n-1 folders)
+ // to be appended onto the server's path
+ bool isNewsFolder = false;
+ nsAutoCString scheme;
+ if (NS_SUCCEEDED(url->GetScheme(scheme))) {
+ isNewsFolder = scheme.EqualsLiteral("news") ||
+ scheme.EqualsLiteral("snews") ||
+ scheme.EqualsLiteral("nntp");
+ }
+ NS_MsgCreatePathStringFromFolderURI(urlPath.get(), newPath, scheme,
+ isNewsFolder);
+ }
+
+ // now append munged path onto server path
+ nsCOMPtr<nsIFile> serverPath;
+ rv = server->GetLocalPath(getter_AddRefs(serverPath));
+ if (NS_FAILED(rv)) return rv;
+
+ if (!mPath && serverPath) {
+ if (!newPath.IsEmpty()) {
+ // I hope this is temporary - Ultimately,
+ // NS_MsgCreatePathStringFromFolderURI will need to be fixed.
+#if defined(XP_WIN)
+ newPath.ReplaceChar('/', '\\');
+#endif
+ rv = serverPath->AppendRelativeNativePath(newPath);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "failed to append to the serverPath");
+ if (NS_FAILED(rv)) {
+ mPath = nullptr;
+ return rv;
+ }
+ }
+ mPath = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ mPath->InitWithFile(serverPath);
+ }
+ // URI is completely parsed when we've attempted to get the server
+ mHaveParsedURI = true;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::GetIsServer(bool* aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+ // make sure we've parsed the URI
+ if (!mIsServerIsValid) {
+ nsresult rv = parseURI();
+ if (NS_FAILED(rv) || !mIsServerIsValid) return NS_ERROR_FAILURE;
+ }
+
+ *aResult = mIsServer;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::GetNoSelect(bool* aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::GetImapShared(bool* aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+ return GetFlag(nsMsgFolderFlags::PersonalShared, aResult);
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::GetCanSubscribe(bool* aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+ // by default, you can't subscribe.
+ // if otherwise, override it.
+ *aResult = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::GetCanFileMessages(bool* aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ // varada - checking folder flag to see if it is the "Unsent Messages"
+ // and if so return FALSE
+ if (mFlags & (nsMsgFolderFlags::Queue | nsMsgFolderFlags::Virtual)) {
+ *aResult = false;
+ return NS_OK;
+ }
+
+ bool isServer = false;
+ nsresult rv = GetIsServer(&isServer);
+ if (NS_FAILED(rv)) return rv;
+
+ // by default, you can't file messages into servers, only to folders
+ // if otherwise, override it.
+ *aResult = !isServer;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::GetCanDeleteMessages(bool* aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::GetCanCreateSubfolders(bool* aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ // Checking folder flag to see if it is the "Unsent Messages"
+ // or a virtual folder, and if so return FALSE
+ if (mFlags & (nsMsgFolderFlags::Queue | nsMsgFolderFlags::Virtual)) {
+ *aResult = false;
+ return NS_OK;
+ }
+
+ // by default, you can create subfolders on server and folders
+ // if otherwise, override it.
+ *aResult = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::GetCanRename(bool* aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ bool isServer = false;
+ nsresult rv = GetIsServer(&isServer);
+ if (NS_FAILED(rv)) return rv;
+ // by default, you can't rename servers, only folders
+ // if otherwise, override it.
+ //
+ // check if the folder is a special folder
+ // (Trash, Drafts, Unsent Messages, Inbox, Sent, Templates, Junk, Archives)
+ // if it is, don't allow the user to rename it
+ // (which includes dnd moving it with in the same server)
+ //
+ // this errors on the side of caution. we'll return false a lot
+ // more often if we use flags,
+ // instead of checking if the folder really is being used as a
+ // special folder by looking at the "copies and folders" prefs on the
+ // identities.
+ *aResult = !(isServer || (mFlags & nsMsgFolderFlags::SpecialUse));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::GetCanCompact(bool* canCompact) {
+ NS_ENSURE_ARG_POINTER(canCompact);
+ bool isServer = false;
+ nsresult rv = GetIsServer(&isServer);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // servers (root folder) cannot be compacted
+ // virtual search folders cannot be compacted
+ *canCompact = !isServer && !(mFlags & nsMsgFolderFlags::Virtual);
+ // If *canCompact now true and folder is imap, keep *canCompact true and
+ // return; otherwise, when not imap, type of store controls it. E.g., mbox
+ // sets *canCompact true, maildir sets it false.
+ if (*canCompact && !(mFlags & nsMsgFolderFlags::ImapBox)) {
+ // Check if the storage type supports compaction
+ nsCOMPtr<nsIMsgPluggableStore> msgStore;
+ GetMsgStore(getter_AddRefs(msgStore));
+ if (msgStore) msgStore->GetSupportsCompaction(canCompact);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetPrettyName(nsAString& name) {
+ return GetName(name);
+}
+
+static bool nonEnglishApp() {
+ if (nsMsgDBFolder::gIsEnglishApp == -1) {
+ nsAutoCString locale;
+ mozilla::intl::LocaleService::GetInstance()->GetAppLocaleAsBCP47(locale);
+ nsMsgDBFolder::gIsEnglishApp =
+ (locale.EqualsLiteral("en") || StringBeginsWith(locale, "en-"_ns)) ? 1
+ : 0;
+ }
+ return nsMsgDBFolder::gIsEnglishApp ? false : true;
+}
+
+static bool hasTrashName(const nsAString& name) {
+ // Microsoft calls the folder "Deleted". If the application is non-English,
+ // we want to use the localised name instead.
+ return name.LowerCaseEqualsLiteral("trash") ||
+ (name.LowerCaseEqualsLiteral("deleted") && nonEnglishApp());
+}
+
+static bool hasDraftsName(const nsAString& name) {
+ // Some IMAP providers call the folder "Draft". If the application is
+ // non-English, we want to use the localised name instead.
+ return name.LowerCaseEqualsLiteral("drafts") ||
+ (name.LowerCaseEqualsLiteral("draft") && nonEnglishApp());
+}
+
+static bool hasSentName(const nsAString& name) {
+ // Some IMAP providers call the folder for sent messages "Outbox". That IMAP
+ // folder is not related to Thunderbird's local folder for queued messages.
+ // If we find such a folder with the 'SentMail' flag, we can safely localize
+ // its name if the application is non-English.
+ return name.LowerCaseEqualsLiteral("sent") ||
+ (name.LowerCaseEqualsLiteral("outbox") && nonEnglishApp());
+}
+
+NS_IMETHODIMP nsMsgDBFolder::SetPrettyName(const nsAString& name) {
+ nsresult rv;
+ // Keep original name.
+ mOriginalName = name;
+
+ // Set pretty name only if special flag is set and if it the default folder
+ // name
+ if (mFlags & nsMsgFolderFlags::Inbox && name.LowerCaseEqualsLiteral("inbox"))
+ rv = SetName(kLocalizedInboxName);
+ else if (mFlags & nsMsgFolderFlags::SentMail && hasSentName(name))
+ rv = SetName(kLocalizedSentName);
+ else if (mFlags & nsMsgFolderFlags::Drafts && hasDraftsName(name))
+ rv = SetName(kLocalizedDraftsName);
+ else if (mFlags & nsMsgFolderFlags::Templates &&
+ name.LowerCaseEqualsLiteral("templates"))
+ rv = SetName(kLocalizedTemplatesName);
+ else if (mFlags & nsMsgFolderFlags::Trash && hasTrashName(name))
+ rv = SetName(kLocalizedTrashName);
+ else if (mFlags & nsMsgFolderFlags::Queue &&
+ name.LowerCaseEqualsLiteral("unsent messages"))
+ rv = SetName(kLocalizedUnsentName);
+ else if (mFlags & nsMsgFolderFlags::Junk &&
+ name.LowerCaseEqualsLiteral("junk"))
+ rv = SetName(kLocalizedJunkName);
+ else if (mFlags & nsMsgFolderFlags::Archive &&
+ name.LowerCaseEqualsLiteral("archives"))
+ rv = SetName(kLocalizedArchivesName);
+ else
+ rv = SetName(name);
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::SetPrettyNameFromOriginal(void) {
+ if (mOriginalName.IsEmpty()) return NS_OK;
+ return SetPrettyName(mOriginalName);
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetName(nsAString& name) {
+ nsresult rv;
+ if (!mHaveParsedURI && mName.IsEmpty()) {
+ rv = parseURI();
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ // if it's a server, just forward the call
+ if (mIsServer) {
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = GetServer(getter_AddRefs(server));
+ if (NS_SUCCEEDED(rv) && server) return server->GetPrettyName(name);
+ }
+
+ name = mName;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::SetName(const nsAString& name) {
+ // override the URI-generated name
+ if (!mName.Equals(name)) {
+ mName = name;
+ // old/new value doesn't matter here
+ NotifyUnicharPropertyChanged(kName, name, name);
+ }
+ return NS_OK;
+}
+
+// For default, just return name
+NS_IMETHODIMP nsMsgDBFolder::GetAbbreviatedName(nsAString& aAbbreviatedName) {
+ return GetName(aAbbreviatedName);
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::GetChildNamed(const nsAString& aName, nsIMsgFolder** aChild) {
+ NS_ENSURE_ARG_POINTER(aChild);
+ nsTArray<RefPtr<nsIMsgFolder>> dummy;
+ GetSubFolders(dummy); // initialize mSubFolders
+ *aChild = nullptr;
+
+ for (nsIMsgFolder* child : mSubFolders) {
+ nsString folderName;
+ nsresult rv = child->GetName(folderName);
+ // case-insensitive compare is probably LCD across OS filesystems
+ if (NS_SUCCEEDED(rv) &&
+ folderName.Equals(aName, nsCaseInsensitiveStringComparator)) {
+ NS_ADDREF(*aChild = child);
+ return NS_OK;
+ }
+ }
+ // don't return NS_OK if we didn't find the folder
+ // see http://bugzilla.mozilla.org/show_bug.cgi?id=210089#c15
+ // and http://bugzilla.mozilla.org/show_bug.cgi?id=210089#c17
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetChildWithURI(const nsACString& uri, bool deep,
+ bool caseInsensitive,
+ nsIMsgFolder** child) {
+ NS_ENSURE_ARG_POINTER(child);
+ // will return nullptr if we can't find it
+ *child = nullptr;
+ nsTArray<RefPtr<nsIMsgFolder>> subFolders;
+ nsresult rv = GetSubFolders(subFolders);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (nsIMsgFolder* folder : subFolders) {
+ nsCString folderURI;
+ rv = folder->GetURI(folderURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+ bool equal =
+ (caseInsensitive
+ ? uri.Equals(folderURI, nsCaseInsensitiveCStringComparator)
+ : uri.Equals(folderURI));
+ if (equal) {
+ NS_ADDREF(*child = folder);
+ return NS_OK;
+ }
+ if (deep) {
+ rv = folder->GetChildWithURI(uri, deep, caseInsensitive, child);
+ if (NS_FAILED(rv)) return rv;
+
+ if (*child) return NS_OK;
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetShowDeletedMessages(bool* showDeletedMessages) {
+ NS_ENSURE_ARG_POINTER(showDeletedMessages);
+ *showDeletedMessages = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::DeleteStorage() {
+ ForceDBClosed();
+
+ // Delete the .msf file.
+ // NOTE: this doesn't remove .msf files in subfolders, but
+ // both nsMsgBrkMBoxStore::DeleteFolder() and
+ // nsMsgMaildirStore::DeleteFolder() will remove those .msf files
+ // as a side-effect of deleting the .sbd directory.
+ nsCOMPtr<nsIFile> summaryFile;
+ nsresult rv = GetSummaryFile(getter_AddRefs(summaryFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+ bool exists = false;
+ summaryFile->Exists(&exists);
+ if (exists) {
+ rv = summaryFile->Remove(false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Ask the msgStore to delete the actual storage (mbox, maildir or whatever
+ // else may be supported in future).
+ nsCOMPtr<nsIMsgPluggableStore> msgStore;
+ rv = GetMsgStore(getter_AddRefs(msgStore));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return msgStore->DeleteFolder(this);
+}
+
+NS_IMETHODIMP nsMsgDBFolder::DeleteSelf(nsIMsgWindow* msgWindow) {
+ nsCOMPtr<nsIMsgFolder> parent;
+ GetParent(getter_AddRefs(parent));
+ if (!parent) {
+ return NS_ERROR_FAILURE;
+ }
+ return parent->PropagateDelete(this, true);
+}
+
+NS_IMETHODIMP nsMsgDBFolder::CreateStorageIfMissing(
+ nsIUrlListener* /* urlListener */) {
+ NS_ASSERTION(false, "needs to be overridden");
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::PropagateDelete(nsIMsgFolder* folder,
+ bool deleteStorage) {
+ // first, find the folder we're looking to delete
+ nsresult rv = NS_OK;
+
+ int32_t count = mSubFolders.Count();
+ for (int32_t i = 0; i < count; i++) {
+ nsCOMPtr<nsIMsgFolder> child(mSubFolders[i]);
+ if (folder == child.get()) {
+ // Remove self as parent
+ child->SetParent(nullptr);
+ // maybe delete disk storage for it, and its subfolders
+ rv = child->RecursiveDelete(deleteStorage);
+ if (NS_SUCCEEDED(rv)) {
+ // Remove from list of subfolders.
+ mSubFolders.RemoveObjectAt(i);
+ NotifyFolderRemoved(child);
+ break;
+ } else // setting parent back if we failed
+ child->SetParent(this);
+ } else
+ rv = child->PropagateDelete(folder, deleteStorage);
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::RecursiveDelete(bool deleteStorage) {
+ // If deleteStorage is true, recursively deletes disk storage for this folder
+ // and all its subfolders.
+ // Regardless of deleteStorage, always unlinks them from the children lists
+ // and frees memory for the subfolders but NOT for _this_
+ // and does not remove _this_ from the parent's list of children.
+
+ nsCOMPtr<nsIFile> dbPath;
+ // first remove the deleted folder from the folder cache;
+ nsresult rv = GetFolderCacheKey(getter_AddRefs(dbPath));
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIMsgAccountManager> accountMgr =
+ do_GetService("@mozilla.org/messenger/account-manager;1", &rv);
+ nsCOMPtr<nsIMsgFolderCache> folderCache;
+ rv = accountMgr->GetFolderCache(getter_AddRefs(folderCache));
+ if (NS_SUCCEEDED(rv) && folderCache) {
+ nsCString persistentPath;
+ rv = dbPath->GetPersistentDescriptor(persistentPath);
+ if (NS_SUCCEEDED(rv)) folderCache->RemoveElement(persistentPath);
+ }
+ }
+
+ int32_t count = mSubFolders.Count();
+ while (count > 0) {
+ nsCOMPtr<nsIMsgFolder> child(mSubFolders[0]);
+ child->SetParent(nullptr);
+ rv = child->RecursiveDelete(deleteStorage);
+ if (NS_SUCCEEDED(rv)) {
+ // unlink it from this child's list
+ mSubFolders.RemoveObjectAt(0);
+ NotifyFolderRemoved(child);
+ } else {
+ // setting parent back if we failed for some reason
+ child->SetParent(this);
+ break;
+ }
+
+ count--;
+ }
+
+ // now delete the disk storage for _this_
+ if (deleteStorage && NS_SUCCEEDED(rv)) {
+ // All delete commands use deleteStorage = true, and local moves use false.
+ // IMAP moves use true, leaving this here in the hope that bug 439108
+ // works out.
+ nsCOMPtr<nsIMsgFolderNotificationService> notifier(
+ do_GetService("@mozilla.org/messenger/msgnotificationservice;1"));
+ if (notifier) notifier->NotifyFolderDeleted(this);
+ rv = DeleteStorage();
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::CreateSubfolder(const nsAString& folderName,
+ nsIMsgWindow* msgWindow) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::AddSubfolder(const nsAString& name,
+ nsIMsgFolder** child) {
+ NS_ENSURE_ARG_POINTER(child);
+
+ int32_t flags = 0;
+ nsresult rv;
+
+ nsAutoCString uri(mURI);
+ uri.Append('/');
+
+ // URI should use UTF-8
+ // (see RFC2396 Uniform Resource Identifiers (URI): Generic Syntax)
+ nsAutoCString escapedName;
+ rv = NS_MsgEscapeEncodeURLPath(name, escapedName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Ensure the containing (.sbd) dir exists.
+ nsCOMPtr<nsIFile> path;
+ rv = CreateDirectoryForFolder(getter_AddRefs(path));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // fix for #192780
+ // if this is the root folder
+ // make sure the the special folders
+ // have the right uri.
+ // on disk, host\INBOX should be a folder with the uri
+ // mailbox://user@host/Inbox" as mailbox://user@host/Inbox !=
+ // mailbox://user@host/INBOX
+ nsCOMPtr<nsIMsgFolder> rootFolder;
+ rv = GetRootFolder(getter_AddRefs(rootFolder));
+ if (NS_SUCCEEDED(rv) && rootFolder &&
+ (rootFolder.get() == (nsIMsgFolder*)this)) {
+ if (escapedName.LowerCaseEqualsLiteral("inbox"))
+ uri += "Inbox";
+ else if (escapedName.LowerCaseEqualsLiteral("unsent%20messages"))
+ uri += "Unsent%20Messages";
+ else if (escapedName.LowerCaseEqualsLiteral("drafts"))
+ uri += "Drafts";
+ else if (escapedName.LowerCaseEqualsLiteral("trash"))
+ uri += "Trash";
+ else if (escapedName.LowerCaseEqualsLiteral("sent"))
+ uri += "Sent";
+ else if (escapedName.LowerCaseEqualsLiteral("templates"))
+ uri += "Templates";
+ else if (escapedName.LowerCaseEqualsLiteral("archives"))
+ uri += "Archives";
+ else
+ uri += escapedName.get();
+ } else
+ uri += escapedName.get();
+
+ nsCOMPtr<nsIMsgFolder> msgFolder;
+ rv = GetChildWithURI(uri, false /*deep*/, true /*case Insensitive*/,
+ getter_AddRefs(msgFolder));
+ if (NS_SUCCEEDED(rv) && msgFolder) return NS_MSG_FOLDER_EXISTS;
+
+ nsCOMPtr<nsIMsgFolder> folder;
+ rv = GetOrCreateFolder(uri, getter_AddRefs(folder));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ folder->GetFlags((uint32_t*)&flags);
+ flags |= nsMsgFolderFlags::Mail;
+ folder->SetParent(this);
+
+ bool isServer;
+ rv = GetIsServer(&isServer);
+
+ // Only set these if these are top level children.
+ if (NS_SUCCEEDED(rv) && isServer) {
+ if (name.LowerCaseEqualsLiteral("inbox")) {
+ flags |= nsMsgFolderFlags::Inbox;
+ SetBiffState(nsIMsgFolder::nsMsgBiffState_Unknown);
+ } else if (name.LowerCaseEqualsLiteral("trash"))
+ flags |= nsMsgFolderFlags::Trash;
+ else if (name.LowerCaseEqualsLiteral("unsent messages") ||
+ name.LowerCaseEqualsLiteral("outbox"))
+ flags |= nsMsgFolderFlags::Queue;
+ }
+
+ folder->SetFlags(flags);
+
+ if (folder) mSubFolders.AppendObject(folder);
+
+ folder.forget(child);
+ // at this point we must be ok and we don't want to return failure in case
+ // GetIsServer failed.
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::Compact(nsIUrlListener* aListener,
+ nsIMsgWindow* aMsgWindow) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::CompactAll(nsIUrlListener* aListener,
+ nsIMsgWindow* aMsgWindow) {
+ NS_ASSERTION(false, "should be overridden by child class");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::EmptyTrash(nsIUrlListener* aListener) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsresult nsMsgDBFolder::CheckIfFolderExists(const nsAString& newFolderName,
+ nsIMsgFolder* parentFolder,
+ nsIMsgWindow* msgWindow) {
+ NS_ENSURE_ARG_POINTER(parentFolder);
+ nsTArray<RefPtr<nsIMsgFolder>> subFolders;
+ nsresult rv = parentFolder->GetSubFolders(subFolders);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (nsIMsgFolder* msgFolder : subFolders) {
+ nsString folderName;
+
+ msgFolder->GetName(folderName);
+ if (folderName.Equals(newFolderName, nsCaseInsensitiveStringComparator)) {
+ ThrowAlertMsg("folderExists", msgWindow);
+ return NS_MSG_FOLDER_EXISTS;
+ }
+ }
+ return NS_OK;
+}
+
+bool nsMsgDBFolder::ConfirmAutoFolderRename(nsIMsgWindow* msgWindow,
+ const nsString& aOldName,
+ const nsString& aNewName) {
+ nsCOMPtr<nsIStringBundle> bundle;
+ nsresult rv = GetBaseStringBundle(getter_AddRefs(bundle));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return false;
+ }
+
+ nsString folderName;
+ GetName(folderName);
+ AutoTArray<nsString, 3> formatStrings = {aOldName, folderName, aNewName};
+
+ nsString confirmString;
+ rv = bundle->FormatStringFromName("confirmDuplicateFolderRename",
+ formatStrings, confirmString);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return false;
+ }
+
+ bool confirmed = false;
+ rv = ThrowConfirmationPrompt(msgWindow, confirmString, &confirmed);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return false;
+ }
+ return confirmed;
+}
+
+nsresult nsMsgDBFolder::AddDirectorySeparator(nsIFile* path) {
+ nsAutoString leafName;
+ path->GetLeafName(leafName);
+ leafName.AppendLiteral(FOLDER_SUFFIX);
+ return path->SetLeafName(leafName);
+}
+
+/* Finds the subdirectory associated with this folder. That is if the path is
+ c:\Inbox, it will return c:\Inbox.sbd if it succeeds. If that path doesn't
+ currently exist then it will create it. Path is strictly an out parameter.
+ */
+nsresult nsMsgDBFolder::CreateDirectoryForFolder(nsIFile** resultFile) {
+ nsCOMPtr<nsIFile> path;
+ nsresult rv = GetFilePath(getter_AddRefs(path));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool isServer;
+ rv = GetIsServer(&isServer);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (isServer) {
+ // Server dir doesn't have .sbd suffix.
+ // Ensure it exists and is a directory.
+ bool pathExists;
+ path->Exists(&pathExists);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!pathExists) {
+ rv = path->Create(nsIFile::DIRECTORY_TYPE, 0700);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ bool isDir;
+ rv = path->IsDirectory(&isDir);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!isDir) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ }
+ path.forget(resultFile);
+ return NS_OK;
+ }
+
+ // Append .sbd suffix.
+ rv = AddDirectorySeparator(path);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Already exists?
+ bool exists;
+ rv = path->Exists(&exists);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (exists) {
+ bool isDir;
+ rv = path->IsDirectory(&isDir);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!isDir) {
+ // Uhoh. Not the dir we were expecting!
+ return NS_MSG_COULD_NOT_CREATE_DIRECTORY;
+ }
+ // Already been created.
+ path.forget(resultFile);
+ return NS_OK;
+ }
+
+ // Need to create it.
+ rv = path->Create(nsIFile::DIRECTORY_TYPE, 0700);
+ NS_ENSURE_SUCCESS(rv, rv);
+ path.forget(resultFile);
+ return NS_OK;
+}
+
+/* Finds the backup directory associated with this folder, stored on the temp
+ drive. If that path doesn't currently exist then it will create it. Path is
+ strictly an out parameter.
+ */
+nsresult nsMsgDBFolder::CreateBackupDirectory(nsIFile** resultFile) {
+ nsCOMPtr<nsIFile> path;
+ nsresult rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(path));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = path->Append(u"MozillaMailnews"_ns);
+ bool pathIsDirectory;
+ path->IsDirectory(&pathIsDirectory);
+
+ // If that doesn't exist, then we have to create this directory
+ if (!pathIsDirectory) {
+ bool pathExists;
+ path->Exists(&pathExists);
+ // If for some reason there's a file with the directory separator
+ // then we are going to fail.
+ rv = pathExists ? NS_MSG_COULD_NOT_CREATE_DIRECTORY
+ : path->Create(nsIFile::DIRECTORY_TYPE, 0700);
+ }
+ if (NS_SUCCEEDED(rv)) path.forget(resultFile);
+ return rv;
+}
+
+nsresult nsMsgDBFolder::GetBackupSummaryFile(nsIFile** aBackupFile,
+ const nsACString& newName) {
+ nsCOMPtr<nsIFile> backupDir;
+ nsresult rv = CreateBackupDirectory(getter_AddRefs(backupDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // We use a dummy message folder file so we can use
+ // GetSummaryFileLocation to get the db file name
+ nsCOMPtr<nsIFile> backupDBDummyFolder;
+ rv = CreateBackupDirectory(getter_AddRefs(backupDBDummyFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!newName.IsEmpty()) {
+ rv = backupDBDummyFolder->AppendNative(newName);
+ } else // if newName is null, use the folder name
+ {
+ nsCOMPtr<nsIFile> folderPath;
+ rv = GetFilePath(getter_AddRefs(folderPath));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString folderName;
+ rv = folderPath->GetNativeLeafName(folderName);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = backupDBDummyFolder->AppendNative(folderName);
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> backupDBFile;
+ rv =
+ GetSummaryFileLocation(backupDBDummyFolder, getter_AddRefs(backupDBFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ backupDBFile.forget(aBackupFile);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::Rename(const nsAString& aNewName,
+ nsIMsgWindow* msgWindow) {
+ nsCOMPtr<nsIFile> oldPathFile;
+ nsresult rv = GetFilePath(getter_AddRefs(oldPathFile));
+ if (NS_FAILED(rv)) return rv;
+ nsCOMPtr<nsIMsgFolder> parentFolder;
+ rv = GetParent(getter_AddRefs(parentFolder));
+ if (!parentFolder) return NS_ERROR_FAILURE;
+ nsCOMPtr<nsISupports> parentSupport = do_QueryInterface(parentFolder);
+ nsCOMPtr<nsIFile> oldSummaryFile;
+ rv = GetSummaryFileLocation(oldPathFile, getter_AddRefs(oldSummaryFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> dirFile;
+ int32_t count = mSubFolders.Count();
+
+ if (count > 0) rv = CreateDirectoryForFolder(getter_AddRefs(dirFile));
+
+ nsAutoString newDiskName(aNewName);
+ NS_MsgHashIfNecessary(newDiskName);
+
+ if (mName.Equals(aNewName, nsCaseInsensitiveStringComparator)) {
+ rv = ThrowAlertMsg("folderExists", msgWindow);
+ return NS_MSG_FOLDER_EXISTS;
+ } else {
+ nsCOMPtr<nsIFile> parentPathFile;
+ parentFolder->GetFilePath(getter_AddRefs(parentPathFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+ bool isDirectory = false;
+ parentPathFile->IsDirectory(&isDirectory);
+ if (!isDirectory) AddDirectorySeparator(parentPathFile);
+
+ rv = CheckIfFolderExists(aNewName, parentFolder, msgWindow);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ ForceDBClosed();
+
+ // Save of dir name before appending .msf
+ nsAutoString newNameDirStr(newDiskName);
+
+ if (!(mFlags & nsMsgFolderFlags::Virtual))
+ rv = oldPathFile->MoveTo(nullptr, newDiskName);
+ if (NS_SUCCEEDED(rv)) {
+ newDiskName.AppendLiteral(SUMMARY_SUFFIX);
+ oldSummaryFile->MoveTo(nullptr, newDiskName);
+ } else {
+ ThrowAlertMsg("folderRenameFailed", msgWindow);
+ return rv;
+ }
+
+ if (NS_SUCCEEDED(rv) && count > 0) {
+ // rename "*.sbd" directory
+ newNameDirStr.AppendLiteral(FOLDER_SUFFIX);
+ dirFile->MoveTo(nullptr, newNameDirStr);
+ }
+
+ nsCOMPtr<nsIMsgFolder> newFolder;
+ if (parentSupport) {
+ rv = parentFolder->AddSubfolder(aNewName, getter_AddRefs(newFolder));
+ if (newFolder) {
+ newFolder->SetPrettyName(EmptyString());
+ newFolder->SetPrettyName(aNewName);
+ newFolder->SetFlags(mFlags);
+ bool changed = false;
+ MatchOrChangeFilterDestination(newFolder, true /*case-insensitive*/,
+ &changed);
+ if (changed) AlertFilterChanged(msgWindow);
+
+ if (count > 0) newFolder->RenameSubFolders(msgWindow, this);
+
+ if (parentFolder) {
+ SetParent(nullptr);
+ parentFolder->PropagateDelete(this, false);
+ parentFolder->NotifyFolderAdded(newFolder);
+ }
+ newFolder->NotifyFolderEvent(kRenameCompleted);
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::RenameSubFolders(nsIMsgWindow* msgWindow,
+ nsIMsgFolder* oldFolder) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::ContainsChildNamed(const nsAString& name,
+ bool* containsChild) {
+ NS_ENSURE_ARG_POINTER(containsChild);
+ nsCOMPtr<nsIMsgFolder> child;
+ GetChildNamed(name, getter_AddRefs(child));
+ *containsChild = child != nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::IsAncestorOf(nsIMsgFolder* child,
+ bool* isAncestor) {
+ NS_ENSURE_ARG_POINTER(isAncestor);
+ nsresult rv = NS_OK;
+
+ int32_t count = mSubFolders.Count();
+
+ for (int32_t i = 0; i < count; i++) {
+ nsCOMPtr<nsIMsgFolder> folder(mSubFolders[i]);
+ if (folder.get() == child)
+ *isAncestor = true;
+ else
+ folder->IsAncestorOf(child, isAncestor);
+
+ if (*isAncestor) return NS_OK;
+ }
+ *isAncestor = false;
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GenerateUniqueSubfolderName(
+ const nsAString& prefix, nsIMsgFolder* otherFolder, nsAString& name) {
+ /* only try 256 times */
+ for (int count = 0; count < 256; count++) {
+ nsAutoString uniqueName;
+ uniqueName.Assign(prefix);
+ if (count > 0) {
+ uniqueName.AppendInt(count);
+ }
+ bool containsChild;
+ bool otherContainsChild = false;
+ ContainsChildNamed(uniqueName, &containsChild);
+ if (otherFolder)
+ otherFolder->ContainsChildNamed(uniqueName, &otherContainsChild);
+
+ if (!containsChild && !otherContainsChild) {
+ name = uniqueName;
+ break;
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::UpdateSummaryTotals(bool force) {
+ if (!mNotifyCountChanges) return NS_OK;
+
+ int32_t oldUnreadMessages = mNumUnreadMessages + mNumPendingUnreadMessages;
+ int32_t oldTotalMessages = mNumTotalMessages + mNumPendingTotalMessages;
+ // We need to read this info from the database
+ nsresult rv = ReadDBFolderInfo(force);
+
+ if (NS_SUCCEEDED(rv)) {
+ int32_t newUnreadMessages = mNumUnreadMessages + mNumPendingUnreadMessages;
+ int32_t newTotalMessages = mNumTotalMessages + mNumPendingTotalMessages;
+
+ // Need to notify listeners that total count changed.
+ if (oldTotalMessages != newTotalMessages)
+ NotifyIntPropertyChanged(kTotalMessages, oldTotalMessages,
+ newTotalMessages);
+
+ if (oldUnreadMessages != newUnreadMessages)
+ NotifyIntPropertyChanged(kTotalUnreadMessages, oldUnreadMessages,
+ newUnreadMessages);
+
+ FlushToFolderCache();
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::SummaryChanged() {
+ UpdateSummaryTotals(false);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetNumUnread(bool deep, int32_t* numUnread) {
+ NS_ENSURE_ARG_POINTER(numUnread);
+
+ bool isServer = false;
+ nsresult rv = GetIsServer(&isServer);
+ NS_ENSURE_SUCCESS(rv, rv);
+ int32_t total = isServer ? 0 : mNumUnreadMessages + mNumPendingUnreadMessages;
+
+ if (deep) {
+ if (total < 0) // deep search never returns negative counts
+ total = 0;
+ int32_t count = mSubFolders.Count();
+ for (int32_t i = 0; i < count; i++) {
+ nsCOMPtr<nsIMsgFolder> folder(mSubFolders[i]);
+ int32_t num;
+ uint32_t folderFlags;
+ folder->GetFlags(&folderFlags);
+ if (!(folderFlags & nsMsgFolderFlags::Virtual)) {
+ folder->GetNumUnread(deep, &num);
+ total += num;
+ }
+ }
+ }
+ *numUnread = total;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetTotalMessages(bool deep,
+ int32_t* totalMessages) {
+ NS_ENSURE_ARG_POINTER(totalMessages);
+
+ bool isServer = false;
+ nsresult rv = GetIsServer(&isServer);
+ NS_ENSURE_SUCCESS(rv, rv);
+ int32_t total = isServer ? 0 : mNumTotalMessages + mNumPendingTotalMessages;
+
+ if (deep) {
+ if (total < 0) // deep search never returns negative counts
+ total = 0;
+ int32_t count = mSubFolders.Count();
+ for (int32_t i = 0; i < count; i++) {
+ nsCOMPtr<nsIMsgFolder> folder(mSubFolders[i]);
+ int32_t num;
+ uint32_t folderFlags;
+ folder->GetFlags(&folderFlags);
+ if (!(folderFlags & nsMsgFolderFlags::Virtual)) {
+ folder->GetTotalMessages(deep, &num);
+ total += num;
+ }
+ }
+ }
+ *totalMessages = total;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetNumPendingUnread(int32_t* aPendingUnread) {
+ *aPendingUnread = mNumPendingUnreadMessages;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetNumPendingTotalMessages(
+ int32_t* aPendingTotal) {
+ *aPendingTotal = mNumPendingTotalMessages;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::ChangeNumPendingUnread(int32_t delta) {
+ if (delta) {
+ int32_t oldUnreadMessages = mNumUnreadMessages + mNumPendingUnreadMessages;
+ mNumPendingUnreadMessages += delta;
+ int32_t newUnreadMessages = mNumUnreadMessages + mNumPendingUnreadMessages;
+ NS_ASSERTION(newUnreadMessages >= 0,
+ "shouldn't have negative unread message count");
+ if (newUnreadMessages >= 0) {
+ nsCOMPtr<nsIMsgDatabase> db;
+ nsCOMPtr<nsIDBFolderInfo> folderInfo;
+ nsresult rv =
+ GetDBFolderInfoAndDB(getter_AddRefs(folderInfo), getter_AddRefs(db));
+ if (NS_SUCCEEDED(rv) && folderInfo)
+ folderInfo->SetImapUnreadPendingMessages(mNumPendingUnreadMessages);
+ NotifyIntPropertyChanged(kTotalUnreadMessages, oldUnreadMessages,
+ newUnreadMessages);
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::ChangeNumPendingTotalMessages(int32_t delta) {
+ if (delta) {
+ int32_t oldTotalMessages = mNumTotalMessages + mNumPendingTotalMessages;
+ mNumPendingTotalMessages += delta;
+ int32_t newTotalMessages = mNumTotalMessages + mNumPendingTotalMessages;
+
+ nsCOMPtr<nsIMsgDatabase> db;
+ nsCOMPtr<nsIDBFolderInfo> folderInfo;
+ nsresult rv =
+ GetDBFolderInfoAndDB(getter_AddRefs(folderInfo), getter_AddRefs(db));
+ if (NS_SUCCEEDED(rv) && folderInfo)
+ folderInfo->SetImapTotalPendingMessages(mNumPendingTotalMessages);
+ NotifyIntPropertyChanged(kTotalMessages, oldTotalMessages,
+ newTotalMessages);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::SetFlag(uint32_t flag) {
+ // If calling this function causes us to open the db (i.e., it was not
+ // open before), we're going to close the db before returning.
+ bool dbWasOpen = mDatabase != nullptr;
+
+ ReadDBFolderInfo(false);
+ // OnFlagChange can be expensive, so don't call it if we don't need to
+ bool flagSet;
+ nsresult rv;
+
+ if (NS_FAILED(rv = GetFlag(flag, &flagSet))) return rv;
+
+ if (!flagSet) {
+ mFlags |= flag;
+ OnFlagChange(flag);
+ }
+ if (!dbWasOpen && mDatabase) SetMsgDatabase(nullptr);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::ClearFlag(uint32_t flag) {
+ // OnFlagChange can be expensive, so don't call it if we don't need to
+ bool flagSet;
+ nsresult rv;
+
+ if (NS_FAILED(rv = GetFlag(flag, &flagSet))) return rv;
+
+ if (flagSet) {
+ mFlags &= ~flag;
+ OnFlagChange(flag);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetFlag(uint32_t flag, bool* _retval) {
+ *_retval = ((mFlags & flag) != 0);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::ToggleFlag(uint32_t flag) {
+ mFlags ^= flag;
+ OnFlagChange(flag);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::OnFlagChange(uint32_t flag) {
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIMsgDatabase> db;
+ nsCOMPtr<nsIDBFolderInfo> folderInfo;
+ rv = GetDBFolderInfoAndDB(getter_AddRefs(folderInfo), getter_AddRefs(db));
+ if (NS_SUCCEEDED(rv) && folderInfo) {
+#ifdef DEBUG_bienvenu1
+ nsString name;
+ rv = GetName(name);
+ NS_ASSERTION(Compare(name, kLocalizedTrashName) ||
+ (mFlags & nsMsgFolderFlags::Trash),
+ "lost trash flag");
+#endif
+ folderInfo->SetFlags((int32_t)mFlags);
+ if (db) db->Commit(nsMsgDBCommitType::kLargeCommit);
+
+ if (mFlags & flag)
+ NotifyIntPropertyChanged(kFolderFlag, mFlags & ~flag, mFlags);
+ else
+ NotifyIntPropertyChanged(kFolderFlag, mFlags | flag, mFlags);
+
+ if (flag & nsMsgFolderFlags::Offline) {
+ bool newValue = mFlags & nsMsgFolderFlags::Offline;
+ rv = NotifyBoolPropertyChanged(kSynchronize, !newValue, !!newValue);
+ } else if (flag & nsMsgFolderFlags::Elided) {
+ bool newValue = mFlags & nsMsgFolderFlags::Elided;
+ rv = NotifyBoolPropertyChanged(kOpen, !!newValue, !newValue);
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::SetFlags(uint32_t aFlags) {
+ if (mFlags != aFlags) {
+ uint32_t changedFlags = aFlags ^ mFlags;
+ mFlags = aFlags;
+ OnFlagChange(changedFlags);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetFolderWithFlags(uint32_t aFlags,
+ nsIMsgFolder** aResult) {
+ if ((mFlags & aFlags) == aFlags) {
+ NS_ADDREF(*aResult = this);
+ return NS_OK;
+ }
+
+ nsTArray<RefPtr<nsIMsgFolder>> dummy;
+ GetSubFolders(dummy); // initialize mSubFolders
+
+ int32_t count = mSubFolders.Count();
+ *aResult = nullptr;
+ for (int32_t i = 0; !*aResult && i < count; ++i)
+ mSubFolders[i]->GetFolderWithFlags(aFlags, aResult);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetFoldersWithFlags(
+ uint32_t aFlags, nsTArray<RefPtr<nsIMsgFolder>>& aResult) {
+ aResult.Clear();
+
+ // Ensure initialisation of mSubFolders.
+ nsTArray<RefPtr<nsIMsgFolder>> dummy;
+ GetSubFolders(dummy);
+
+ if ((mFlags & aFlags) == aFlags) {
+ aResult.AppendElement(this);
+ }
+
+ // Recurse down through children.
+ for (nsIMsgFolder* child : mSubFolders) {
+ nsTArray<RefPtr<nsIMsgFolder>> subMatches;
+ child->GetFoldersWithFlags(aFlags, subMatches);
+ aResult.AppendElements(subMatches);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::IsSpecialFolder(uint32_t aFlags,
+ bool aCheckAncestors,
+ bool* aIsSpecial) {
+ NS_ENSURE_ARG_POINTER(aIsSpecial);
+
+ if ((mFlags & aFlags) == 0) {
+ nsCOMPtr<nsIMsgFolder> parentMsgFolder;
+ GetParent(getter_AddRefs(parentMsgFolder));
+
+ if (parentMsgFolder && aCheckAncestors)
+ parentMsgFolder->IsSpecialFolder(aFlags, aCheckAncestors, aIsSpecial);
+ else
+ *aIsSpecial = false;
+ } else {
+ // The user can set their INBOX to be their SENT folder.
+ // in that case, we want this folder to act like an INBOX,
+ // and not a SENT folder
+ *aIsSpecial = !((aFlags & nsMsgFolderFlags::SentMail) &&
+ (mFlags & nsMsgFolderFlags::Inbox));
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetDeletable(bool* deletable) {
+ NS_ENSURE_ARG_POINTER(deletable);
+ *deletable = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetDisplayRecipients(bool* displayRecipients) {
+ *displayRecipients = false;
+ if (mFlags & nsMsgFolderFlags::SentMail &&
+ !(mFlags & nsMsgFolderFlags::Inbox))
+ *displayRecipients = true;
+ else if (mFlags & nsMsgFolderFlags::Queue)
+ *displayRecipients = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::AcquireSemaphore(nsISupports* semHolder) {
+ nsresult rv = NS_OK;
+ if (mSemaphoreHolder == NULL)
+ mSemaphoreHolder = semHolder; // Don't AddRef due to ownership issues.
+ else
+ rv = NS_MSG_FOLDER_BUSY;
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::ReleaseSemaphore(nsISupports* semHolder) {
+ if (!mSemaphoreHolder || mSemaphoreHolder == semHolder)
+ mSemaphoreHolder = NULL;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::TestSemaphore(nsISupports* semHolder,
+ bool* result) {
+ NS_ENSURE_ARG_POINTER(result);
+ *result = (mSemaphoreHolder == semHolder);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetLocked(bool* isLocked) {
+ *isLocked = mSemaphoreHolder != NULL;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetRelativePathName(nsACString& pathName) {
+ pathName.Truncate();
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetSizeOnDisk(int64_t* size) {
+ NS_ENSURE_ARG_POINTER(size);
+ *size = kSizeUnknown;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::SetSizeOnDisk(int64_t aSizeOnDisk) {
+ NotifyIntPropertyChanged(kFolderSize, mFolderSize, aSizeOnDisk);
+ mFolderSize = aSizeOnDisk;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetUsername(nsACString& userName) {
+ nsresult rv;
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return server->GetUsername(userName);
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetHostname(nsACString& hostName) {
+ nsresult rv;
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return server->GetHostName(hostName);
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetNewMessages(nsIMsgWindow*,
+ nsIUrlListener* /* aListener */) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetBiffState(uint32_t* aBiffState) {
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ nsresult rv = GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return server->GetBiffState(aBiffState);
+}
+
+NS_IMETHODIMP nsMsgDBFolder::SetBiffState(uint32_t aBiffState) {
+ uint32_t oldBiffState = nsMsgBiffState_Unknown;
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ nsresult rv = GetServer(getter_AddRefs(server));
+ if (NS_SUCCEEDED(rv) && server) server->GetBiffState(&oldBiffState);
+
+ if (oldBiffState != aBiffState) {
+ // Get the server and notify it and not inbox.
+ if (!mIsServer) {
+ nsCOMPtr<nsIMsgFolder> folder;
+ rv = GetRootFolder(getter_AddRefs(folder));
+ if (NS_SUCCEEDED(rv) && folder) return folder->SetBiffState(aBiffState);
+ }
+ if (server) server->SetBiffState(aBiffState);
+
+ NotifyIntPropertyChanged(kBiffState, oldBiffState, aBiffState);
+ } else if (aBiffState == oldBiffState &&
+ aBiffState == nsMsgBiffState_NewMail) {
+ // The folder has been updated, so update the MRUTime
+ SetMRUTime();
+ // biff is already set, but notify that there is additional new mail for the
+ // folder
+ NotifyIntPropertyChanged(kNewMailReceived, 0, mNumNewBiffMessages);
+ } else if (aBiffState == nsMsgBiffState_NoMail) {
+ // even if the old biff state equals the new biff state, it is still
+ // possible that we've never cleared the number of new messages for this
+ // particular folder. This happens when the new mail state got cleared by
+ // viewing a new message in folder that is different from this one. Biff
+ // state is stored per server
+ // the num. of new messages is per folder.
+ SetNumNewMessages(0);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetNumNewMessages(bool deep,
+ int32_t* aNumNewMessages) {
+ NS_ENSURE_ARG_POINTER(aNumNewMessages);
+
+ int32_t numNewMessages = (!deep || !(mFlags & nsMsgFolderFlags::Virtual))
+ ? mNumNewBiffMessages
+ : 0;
+ if (deep) {
+ int32_t count = mSubFolders.Count();
+ for (int32_t i = 0; i < count; i++) {
+ int32_t num;
+ mSubFolders[i]->GetNumNewMessages(deep, &num);
+ if (num > 0) // it's legal for counts to be negative if we don't know
+ numNewMessages += num;
+ }
+ }
+ *aNumNewMessages = numNewMessages;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::SetNumNewMessages(int32_t aNumNewMessages) {
+ if (aNumNewMessages != mNumNewBiffMessages) {
+ int32_t oldNumMessages = mNumNewBiffMessages;
+ mNumNewBiffMessages = aNumNewMessages;
+
+ nsAutoCString oldNumMessagesStr;
+ oldNumMessagesStr.AppendInt(oldNumMessages);
+ nsAutoCString newNumMessagesStr;
+ newNumMessagesStr.AppendInt(aNumNewMessages);
+ NotifyPropertyChanged(kNumNewBiffMessages, oldNumMessagesStr,
+ newNumMessagesStr);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetRootFolder(nsIMsgFolder** aRootFolder) {
+ NS_ENSURE_ARG_POINTER(aRootFolder);
+ nsresult rv;
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return server->GetRootMsgFolder(aRootFolder);
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::SetFilePath(nsIFile* aFile) {
+ mPath = aFile;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::GetFilePath(nsIFile** aFile) {
+ NS_ENSURE_ARG_POINTER(aFile);
+ nsresult rv;
+ // make a new nsIFile object in case the caller
+ // alters the underlying file object.
+ nsCOMPtr<nsIFile> file = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!mPath) {
+ rv = parseURI(true);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ rv = file->InitWithFile(mPath);
+ file.forget(aFile);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetSummaryFile(nsIFile** aSummaryFile) {
+ NS_ENSURE_ARG_POINTER(aSummaryFile);
+
+ nsresult rv;
+ nsCOMPtr<nsIFile> newSummaryLocation =
+ do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> pathFile;
+ rv = GetFilePath(getter_AddRefs(pathFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ newSummaryLocation->InitWithFile(pathFile);
+
+ nsString fileName;
+ rv = newSummaryLocation->GetLeafName(fileName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ fileName.AppendLiteral(SUMMARY_SUFFIX);
+ rv = newSummaryLocation->SetLeafName(fileName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ newSummaryLocation.forget(aSummaryFile);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::MarkMessagesRead(const nsTArray<RefPtr<nsIMsgDBHdr>>& messages,
+ bool markRead) {
+ nsresult rv = GetDatabase();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (auto message : messages) {
+ rv = message->MarkRead(markRead);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::MarkMessagesFlagged(
+ const nsTArray<RefPtr<nsIMsgDBHdr>>& messages, bool markFlagged) {
+ nsresult rv = GetDatabase();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (auto message : messages) {
+ rv = message->MarkFlagged(markFlagged);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::SetJunkScoreForMessages(
+ const nsTArray<RefPtr<nsIMsgDBHdr>>& aMessages,
+ const nsACString& junkScore) {
+ GetDatabase();
+ if (mDatabase) {
+ for (auto message : aMessages) {
+ nsMsgKey msgKey;
+ (void)message->GetMessageKey(&msgKey);
+ mDatabase->SetStringProperty(msgKey, "junkscore", junkScore);
+ mDatabase->SetStringProperty(msgKey, "junkscoreorigin", "filter"_ns);
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::ApplyRetentionSettings() { return ApplyRetentionSettings(true); }
+
+nsresult nsMsgDBFolder::ApplyRetentionSettings(bool deleteViaFolder) {
+ if (mFlags & nsMsgFolderFlags::Virtual) // ignore virtual folders.
+ return NS_OK;
+ bool weOpenedDB = !mDatabase;
+ nsCOMPtr<nsIMsgRetentionSettings> retentionSettings;
+ nsresult rv = GetRetentionSettings(getter_AddRefs(retentionSettings));
+ if (NS_SUCCEEDED(rv)) {
+ nsMsgRetainByPreference retainByPreference =
+ nsIMsgRetentionSettings::nsMsgRetainAll;
+
+ retentionSettings->GetRetainByPreference(&retainByPreference);
+ if (retainByPreference != nsIMsgRetentionSettings::nsMsgRetainAll) {
+ rv = GetDatabase();
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (mDatabase)
+ rv = mDatabase->ApplyRetentionSettings(retentionSettings,
+ deleteViaFolder);
+ }
+ }
+ // we don't want applying retention settings to keep the db open, because
+ // if we try to purge a bunch of folders, that will leave the dbs all open.
+ // So if we opened the db, close it.
+ if (weOpenedDB) CloseDBIfFolderNotOpen(false);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::DeleteMessages(nsTArray<RefPtr<nsIMsgDBHdr>> const& messages,
+ nsIMsgWindow* msgWindow, bool deleteStorage,
+ bool isMove, nsIMsgCopyServiceListener* listener,
+ bool allowUndo) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::CopyMessages(nsIMsgFolder* srcFolder,
+ nsTArray<RefPtr<nsIMsgDBHdr>> const& messages,
+ bool isMove, nsIMsgWindow* window,
+ nsIMsgCopyServiceListener* listener, bool isFolder,
+ bool allowUndo) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::CopyFolder(nsIMsgFolder* srcFolder, bool isMoveFolder,
+ nsIMsgWindow* window,
+ nsIMsgCopyServiceListener* listener) {
+ NS_ASSERTION(false, "should be overridden by child class");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::CopyFileMessage(nsIFile* aFile, nsIMsgDBHdr* messageToReplace,
+ bool isDraftOrTemplate, uint32_t aNewMsgFlags,
+ const nsACString& aNewMsgKeywords,
+ nsIMsgWindow* window,
+ nsIMsgCopyServiceListener* listener) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::CopyDataToOutputStreamForAppend(
+ nsIInputStream* aInStream, int32_t aLength,
+ nsIOutputStream* aOutputStream) {
+ if (!aInStream) return NS_OK;
+
+ uint32_t uiWritten;
+ return aOutputStream->WriteFrom(aInStream, aLength, &uiWritten);
+}
+
+NS_IMETHODIMP nsMsgDBFolder::CopyDataDone() { return NS_OK; }
+
+#define NOTIFY_LISTENERS(propertyfunc_, params_) \
+ PR_BEGIN_MACRO \
+ nsTObserverArray<nsCOMPtr<nsIFolderListener>>::ForwardIterator iter( \
+ mListeners); \
+ nsCOMPtr<nsIFolderListener> listener; \
+ while (iter.HasMore()) { \
+ listener = iter.GetNext(); \
+ listener->propertyfunc_ params_; \
+ } \
+ PR_END_MACRO
+
+NS_IMETHODIMP
+nsMsgDBFolder::NotifyPropertyChanged(const nsACString& aProperty,
+ const nsACString& aOldValue,
+ const nsACString& aNewValue) {
+ NOTIFY_LISTENERS(OnFolderPropertyChanged,
+ (this, aProperty, aOldValue, aNewValue));
+
+ // Notify listeners who listen to every folder
+ nsresult rv;
+ nsCOMPtr<nsIFolderListener> folderListenerManager =
+ do_GetService("@mozilla.org/messenger/services/session;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return folderListenerManager->OnFolderPropertyChanged(this, aProperty,
+ aOldValue, aNewValue);
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::NotifyUnicharPropertyChanged(const nsACString& aProperty,
+ const nsAString& aOldValue,
+ const nsAString& aNewValue) {
+ NOTIFY_LISTENERS(OnFolderUnicharPropertyChanged,
+ (this, aProperty, aOldValue, aNewValue));
+
+ // Notify listeners who listen to every folder
+ nsresult rv;
+ nsCOMPtr<nsIFolderListener> folderListenerManager =
+ do_GetService("@mozilla.org/messenger/services/session;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return folderListenerManager->OnFolderUnicharPropertyChanged(
+ this, aProperty, aOldValue, aNewValue);
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::NotifyIntPropertyChanged(const nsACString& aProperty,
+ int64_t aOldValue, int64_t aNewValue) {
+ // Don't send off count notifications if they are turned off.
+ if (!mNotifyCountChanges && (aProperty.Equals(kTotalMessages) ||
+ aProperty.Equals(kTotalUnreadMessages)))
+ return NS_OK;
+
+ NOTIFY_LISTENERS(OnFolderIntPropertyChanged,
+ (this, aProperty, aOldValue, aNewValue));
+
+ // Notify listeners who listen to every folder
+ nsresult rv;
+ nsCOMPtr<nsIFolderListener> folderListenerManager =
+ do_GetService("@mozilla.org/messenger/services/session;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return folderListenerManager->OnFolderIntPropertyChanged(
+ this, aProperty, aOldValue, aNewValue);
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::NotifyBoolPropertyChanged(const nsACString& aProperty,
+ bool aOldValue, bool aNewValue) {
+ NOTIFY_LISTENERS(OnFolderBoolPropertyChanged,
+ (this, aProperty, aOldValue, aNewValue));
+
+ // Notify listeners who listen to every folder
+ nsresult rv;
+ nsCOMPtr<nsIFolderListener> folderListenerManager =
+ do_GetService("@mozilla.org/messenger/services/session;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return folderListenerManager->OnFolderBoolPropertyChanged(
+ this, aProperty, aOldValue, aNewValue);
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::NotifyPropertyFlagChanged(nsIMsgDBHdr* aItem,
+ const nsACString& aProperty,
+ uint32_t aOldValue,
+ uint32_t aNewValue) {
+ NOTIFY_LISTENERS(OnFolderPropertyFlagChanged,
+ (aItem, aProperty, aOldValue, aNewValue));
+
+ // Notify listeners who listen to every folder
+ nsresult rv;
+ nsCOMPtr<nsIFolderListener> folderListenerManager =
+ do_GetService("@mozilla.org/messenger/services/session;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return folderListenerManager->OnFolderPropertyFlagChanged(
+ aItem, aProperty, aOldValue, aNewValue);
+}
+
+NS_IMETHODIMP nsMsgDBFolder::NotifyMessageAdded(nsIMsgDBHdr* msg) {
+ // Notify our directly-registered listeners.
+ NOTIFY_LISTENERS(OnMessageAdded, (this, msg));
+ // Notify listeners who listen to every folder
+ nsresult rv;
+ nsCOMPtr<nsIFolderListener> folderListenerManager =
+ do_GetService("@mozilla.org/messenger/services/session;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = folderListenerManager->OnMessageAdded(this, msg);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return NS_OK;
+}
+
+nsresult nsMsgDBFolder::NotifyMessageRemoved(nsIMsgDBHdr* msg) {
+ // Notify our directly-registered listeners.
+ NOTIFY_LISTENERS(OnMessageRemoved, (this, msg));
+ // Notify listeners who listen to every folder
+ nsresult rv;
+ nsCOMPtr<nsIFolderListener> folderListenerManager =
+ do_GetService("@mozilla.org/messenger/services/session;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = folderListenerManager->OnMessageRemoved(this, msg);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::NotifyFolderAdded(nsIMsgFolder* child) {
+ NOTIFY_LISTENERS(OnFolderAdded, (this, child));
+
+ // Notify listeners who listen to every folder
+ nsresult rv;
+ nsCOMPtr<nsIFolderListener> folderListenerManager =
+ do_GetService("@mozilla.org/messenger/services/session;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return folderListenerManager->OnFolderAdded(this, child);
+}
+
+nsresult nsMsgDBFolder::NotifyFolderRemoved(nsIMsgFolder* child) {
+ NOTIFY_LISTENERS(OnFolderRemoved, (this, child));
+
+ // Notify listeners who listen to every folder
+ nsresult rv;
+ nsCOMPtr<nsIFolderListener> folderListenerManager =
+ do_GetService("@mozilla.org/messenger/services/session;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return folderListenerManager->OnFolderRemoved(this, child);
+}
+
+nsresult nsMsgDBFolder::NotifyFolderEvent(const nsACString& aEvent) {
+ NOTIFY_LISTENERS(OnFolderEvent, (this, aEvent));
+
+ // Notify listeners who listen to every folder
+ nsresult rv;
+ nsCOMPtr<nsIFolderListener> folderListenerManager =
+ do_GetService("@mozilla.org/messenger/services/session;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return folderListenerManager->OnFolderEvent(this, aEvent);
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::GetFilterList(nsIMsgWindow* aMsgWindow,
+ nsIMsgFilterList** aResult) {
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ nsresult rv = GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return server->GetFilterList(aMsgWindow, aResult);
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::SetFilterList(nsIMsgFilterList* aFilterList) {
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ nsresult rv = GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return server->SetFilterList(aFilterList);
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::GetEditableFilterList(nsIMsgWindow* aMsgWindow,
+ nsIMsgFilterList** aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ nsresult rv = GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return server->GetEditableFilterList(aMsgWindow, aResult);
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::SetEditableFilterList(nsIMsgFilterList* aFilterList) {
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ nsresult rv = GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return server->SetEditableFilterList(aFilterList);
+}
+
+/* void enableNotifications (in long notificationType, in boolean enable); */
+NS_IMETHODIMP nsMsgDBFolder::EnableNotifications(int32_t notificationType,
+ bool enable) {
+ if (notificationType == nsIMsgFolder::allMessageCountNotifications) {
+ mNotifyCountChanges = enable;
+ if (enable) {
+ UpdateSummaryTotals(true);
+ }
+ return NS_OK;
+ }
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetMessageHeader(nsMsgKey msgKey,
+ nsIMsgDBHdr** aMsgHdr) {
+ NS_ENSURE_ARG_POINTER(aMsgHdr);
+ nsCOMPtr<nsIMsgDatabase> database;
+ nsresult rv = GetMsgDatabase(getter_AddRefs(database));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return (database) ? database->GetMsgHdrForKey(msgKey, aMsgHdr)
+ : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetDescendants(
+ nsTArray<RefPtr<nsIMsgFolder>>& aDescendants) {
+ aDescendants.Clear();
+ for (nsIMsgFolder* child : mSubFolders) {
+ aDescendants.AppendElement(child);
+ nsTArray<RefPtr<nsIMsgFolder>> grandchildren;
+ child->GetDescendants(grandchildren);
+ aDescendants.AppendElements(grandchildren);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetBaseMessageURI(nsACString& baseMessageURI) {
+ if (mBaseMessageURI.IsEmpty()) return NS_ERROR_FAILURE;
+ baseMessageURI = mBaseMessageURI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetUriForMsg(nsIMsgDBHdr* msgHdr,
+ nsACString& aURI) {
+ NS_ENSURE_ARG(msgHdr);
+ nsMsgKey msgKey;
+ msgHdr->GetMessageKey(&msgKey);
+ nsAutoCString uri;
+ uri.Assign(mBaseMessageURI);
+
+ // append a "#" followed by the message key.
+ uri.Append('#');
+ uri.AppendInt(msgKey);
+ aURI = uri;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GenerateMessageURI(nsMsgKey msgKey,
+ nsACString& aURI) {
+ nsCString uri;
+ nsresult rv = GetBaseMessageURI(uri);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // append a "#" followed by the message key.
+ uri.Append('#');
+ uri.AppendInt(msgKey);
+ aURI = uri;
+ return NS_OK;
+}
+
+nsresult nsMsgDBFolder::GetBaseStringBundle(nsIStringBundle** aBundle) {
+ NS_ENSURE_ARG_POINTER(aBundle);
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ mozilla::components::StringBundle::Service();
+ NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED);
+ nsCOMPtr<nsIStringBundle> bundle;
+ bundleService->CreateBundle("chrome://messenger/locale/messenger.properties",
+ getter_AddRefs(bundle));
+ bundle.forget(aBundle);
+ return NS_OK;
+}
+
+// Do not use this routine if you have to call it very often because
+// it creates a new bundle each time
+nsresult nsMsgDBFolder::GetStringFromBundle(const char* msgName,
+ nsString& aResult) {
+ nsresult rv;
+ nsCOMPtr<nsIStringBundle> bundle;
+ rv = GetBaseStringBundle(getter_AddRefs(bundle));
+ if (NS_SUCCEEDED(rv) && bundle)
+ rv = bundle->GetStringFromName(msgName, aResult);
+ return rv;
+}
+
+nsresult nsMsgDBFolder::ThrowConfirmationPrompt(nsIMsgWindow* msgWindow,
+ const nsAString& confirmString,
+ bool* confirmed) {
+ if (msgWindow) {
+ nsCOMPtr<nsIDocShell> docShell;
+ msgWindow->GetRootDocShell(getter_AddRefs(docShell));
+ if (docShell) {
+ nsCOMPtr<nsIPrompt> dialog(do_GetInterface(docShell));
+ if (dialog && !confirmString.IsEmpty())
+ dialog->Confirm(nullptr, nsString(confirmString).get(), confirmed);
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBFolder::GetStringWithFolderNameFromBundle(const char* msgName,
+ nsAString& aResult) {
+ nsCOMPtr<nsIStringBundle> bundle;
+ nsresult rv = GetBaseStringBundle(getter_AddRefs(bundle));
+ if (NS_SUCCEEDED(rv) && bundle) {
+ nsString folderName;
+ GetName(folderName);
+ AutoTArray<nsString, 2> formatStrings = {folderName,
+ kLocalizedBrandShortName};
+ nsString resultStr;
+ rv = bundle->FormatStringFromName(msgName, formatStrings, resultStr);
+ if (NS_SUCCEEDED(rv)) aResult.Assign(resultStr);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::ConfirmFolderDeletionForFilter(
+ nsIMsgWindow* msgWindow, bool* confirmed) {
+ nsString confirmString;
+ nsresult rv = GetStringWithFolderNameFromBundle(
+ "confirmFolderDeletionForFilter", confirmString);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return ThrowConfirmationPrompt(msgWindow, confirmString, confirmed);
+}
+
+NS_IMETHODIMP nsMsgDBFolder::ThrowAlertMsg(const char* msgName,
+ nsIMsgWindow* msgWindow) {
+ if (!msgWindow) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIStringBundle> bundle;
+ nsresult rv = GetBaseStringBundle(getter_AddRefs(bundle));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Assemble a pretty folder identifier, e.g. "Trash on bob@example.com".
+ nsAutoString ident;
+ nsAutoString folderName;
+ GetName(folderName);
+ nsAutoString serverName;
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ if (NS_SUCCEEDED(GetServer(getter_AddRefs(server)))) {
+ server->GetPrettyName(serverName);
+ bundle->FormatStringFromName("verboseFolderFormat",
+ {folderName, serverName}, ident);
+ }
+ if (ident.IsEmpty()) {
+ ident = folderName; // Fallback, just in case.
+ }
+
+ // Format the actual error message (NOTE: not all error messages use the
+ // params - extra values are just ignored).
+ nsAutoString alertString;
+ rv = bundle->FormatStringFromName(msgName, {ident, kLocalizedBrandShortName},
+ alertString);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Include the folder identifier in the alert title for good measure,
+ // because not all the error messages include the folder.
+ nsAutoString title;
+ bundle->FormatStringFromName("folderErrorAlertTitle", {ident}, title);
+
+ nsCOMPtr<mozIDOMWindowProxy> domWindow;
+ rv = msgWindow->GetDomWindow(getter_AddRefs(domWindow));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIPromptService> dlgService(
+ do_GetService(NS_PROMPTSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return dlgService->Alert(domWindow, title.IsEmpty() ? nullptr : title.get(),
+ alertString.get());
+}
+
+NS_IMETHODIMP nsMsgDBFolder::AlertFilterChanged(nsIMsgWindow* msgWindow) {
+ NS_ENSURE_ARG(msgWindow);
+ nsresult rv = NS_OK;
+ bool checkBox = false;
+ GetWarnFilterChanged(&checkBox);
+ if (!checkBox) {
+ nsCOMPtr<nsIDocShell> docShell;
+ msgWindow->GetRootDocShell(getter_AddRefs(docShell));
+ nsString alertString;
+ rv = GetStringFromBundle("alertFilterChanged", alertString);
+ nsString alertCheckbox;
+ rv = GetStringFromBundle("alertFilterCheckbox", alertCheckbox);
+ if (!alertString.IsEmpty() && !alertCheckbox.IsEmpty() && docShell) {
+ nsCOMPtr<nsIPrompt> dialog(do_GetInterface(docShell));
+ if (dialog) {
+ dialog->AlertCheck(nullptr, alertString.get(), alertCheckbox.get(),
+ &checkBox);
+ SetWarnFilterChanged(checkBox);
+ }
+ }
+ }
+ return rv;
+}
+
+nsresult nsMsgDBFolder::GetWarnFilterChanged(bool* aVal) {
+ NS_ENSURE_ARG(aVal);
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> prefBranch =
+ do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = prefBranch->GetBoolPref(PREF_MAIL_WARN_FILTER_CHANGED, aVal);
+ if (NS_FAILED(rv)) *aVal = false;
+ return NS_OK;
+}
+
+nsresult nsMsgDBFolder::SetWarnFilterChanged(bool aVal) {
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIPrefBranch> prefBranch =
+ do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return prefBranch->SetBoolPref(PREF_MAIL_WARN_FILTER_CHANGED, aVal);
+}
+
+NS_IMETHODIMP nsMsgDBFolder::NotifyCompactCompleted() {
+ NS_ASSERTION(false, "should be overridden by child class");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::CloseDBIfFolderNotOpen(bool aForceClosed) {
+ nsresult rv;
+ nsCOMPtr<nsIMsgMailSession> session =
+ do_GetService("@mozilla.org/messenger/services/session;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ bool folderOpen;
+ session->IsFolderOpenInWindow(this, &folderOpen);
+ if (!folderOpen &&
+ !(mFlags & (nsMsgFolderFlags::Trash | nsMsgFolderFlags::Inbox))) {
+ if (aForceClosed && mDatabase) mDatabase->ForceClosed();
+ SetMsgDatabase(nullptr);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::SetSortOrder(int32_t order) {
+ NS_ASSERTION(false, "not implemented");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetSortOrder(int32_t* order) {
+ NS_ENSURE_ARG_POINTER(order);
+
+ uint32_t flags;
+ nsresult rv = GetFlags(&flags);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (flags & nsMsgFolderFlags::Inbox)
+ *order = 0;
+ else if (flags & nsMsgFolderFlags::Drafts)
+ *order = 1;
+ else if (flags & nsMsgFolderFlags::Templates)
+ *order = 2;
+ else if (flags & nsMsgFolderFlags::SentMail)
+ *order = 3;
+ else if (flags & nsMsgFolderFlags::Archive)
+ *order = 4;
+ else if (flags & nsMsgFolderFlags::Junk)
+ *order = 5;
+ else if (flags & nsMsgFolderFlags::Trash)
+ *order = 6;
+ else if (flags & nsMsgFolderFlags::Virtual)
+ *order = 7;
+ else if (flags & nsMsgFolderFlags::Queue)
+ *order = 8;
+ else
+ *order = 9;
+
+ return NS_OK;
+}
+
+// static Helper function for CompareSortKeys().
+// Builds a collation key for a given folder based on "{sortOrder}{name}"
+nsresult nsMsgDBFolder::BuildFolderSortKey(nsIMsgFolder* aFolder,
+ nsTArray<uint8_t>& aKey) {
+ aKey.Clear();
+ int32_t order;
+ nsresult rv = aFolder->GetSortOrder(&order);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsAutoString orderString;
+ orderString.AppendInt(order);
+ nsString folderName;
+ rv = aFolder->GetName(folderName);
+ NS_ENSURE_SUCCESS(rv, rv);
+ orderString.Append(folderName);
+ NS_ENSURE_TRUE(gCollationKeyGenerator, NS_ERROR_NULL_POINTER);
+
+ nsTArrayU8Buffer buffer(aKey);
+
+ auto result = gCollationKeyGenerator->GetSortKey(orderString, buffer);
+ NS_ENSURE_TRUE(result.isOk(), NS_ERROR_FAILURE);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::CompareSortKeys(nsIMsgFolder* aFolder,
+ int32_t* sortOrder) {
+ nsTArray<uint8_t> sortKey1;
+ nsTArray<uint8_t> sortKey2;
+ nsresult rv = BuildFolderSortKey(this, sortKey1);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = BuildFolderSortKey(aFolder, sortKey2);
+ NS_ENSURE_SUCCESS(rv, rv);
+ *sortOrder = gCollationKeyGenerator->CompareSortKeys(sortKey1, sortKey2);
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::FetchMsgPreviewText(
+ nsTArray<nsMsgKey> const& aKeysToFetch, nsIUrlListener* aUrlListener,
+ bool* aAsyncResults) {
+ NS_ENSURE_ARG_POINTER(aAsyncResults);
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetMsgTextFromStream(
+ nsIInputStream* stream, const nsACString& aCharset, uint32_t bytesToRead,
+ uint32_t aMaxOutputLen, bool aCompressQuotes, bool aStripHTMLTags,
+ nsACString& aContentType, nsACString& aMsgText) {
+ /*
+ 1. non mime message - the message body starts after the blank line
+ following the headers.
+ 2. mime message, multipart/alternative - we could simply scan for the
+ boundary line, advance past its headers, and treat the next few lines
+ as the text.
+ 3. mime message, text/plain - body follows headers
+ 4. multipart/mixed - scan past boundary, treat next part as body.
+ */
+
+ UniquePtr<nsLineBuffer<char>> lineBuffer(new nsLineBuffer<char>);
+
+ nsAutoCString msgText;
+ nsAutoString contentType;
+ nsAutoString encoding;
+ nsAutoCString curLine;
+ nsAutoCString charset(aCharset);
+
+ // might want to use a state var instead of bools.
+ bool msgBodyIsHtml = false;
+ bool more = true;
+ bool reachedEndBody = false;
+ bool isBase64 = false;
+ bool inMsgBody = false;
+ bool justPassedEndBoundary = false;
+
+ uint32_t bytesRead = 0;
+
+ nsresult rv;
+
+ // Both are used to extract data from the headers
+ nsCOMPtr<nsIMimeHeaders> mimeHeaders(
+ do_CreateInstance(NS_IMIMEHEADERS_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIMIMEHeaderParam> mimeHdrParam(
+ do_GetService(NS_MIMEHEADERPARAM_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Stack of boundaries, used to figure out where we are
+ nsTArray<nsCString> boundaryStack;
+
+ while (!inMsgBody && bytesRead <= bytesToRead) {
+ nsAutoCString msgHeaders;
+ // We want to NS_ReadLine until we get to a blank line (the end of the
+ // headers)
+ while (more) {
+ rv = NS_ReadLine(stream, lineBuffer.get(), curLine, &more);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (curLine.IsEmpty()) break;
+ msgHeaders.Append(curLine);
+ msgHeaders.AppendLiteral("\r\n");
+ bytesRead += curLine.Length();
+ if (bytesRead > bytesToRead) break;
+ }
+
+ // There's no point in processing if we can't get the body
+ if (bytesRead > bytesToRead) break;
+
+ // Process the headers, looking for things we need
+ rv = mimeHeaders->Initialize(msgHeaders);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString contentTypeHdr;
+ mimeHeaders->ExtractHeader("Content-Type", false, contentTypeHdr);
+
+ // Get the content type
+ // If we don't have a content type, then we assign text/plain
+ // this is in violation of the RFC for multipart/digest, though
+ // Also, if we've just passed an end boundary, we're going to ignore this.
+ if (!justPassedEndBoundary && contentTypeHdr.IsEmpty())
+ contentType.AssignLiteral(u"text/plain");
+ else
+ mimeHdrParam->GetParameter(contentTypeHdr, nullptr, EmptyCString(), false,
+ nullptr, contentType);
+
+ justPassedEndBoundary = false;
+
+ // If we are multipart, then we need to get the boundary
+ if (StringBeginsWith(contentType, u"multipart/"_ns,
+ nsCaseInsensitiveStringComparator)) {
+ nsAutoString boundaryParam;
+ mimeHdrParam->GetParameter(contentTypeHdr, "boundary", EmptyCString(),
+ false, nullptr, boundaryParam);
+ if (!boundaryParam.IsEmpty()) {
+ nsAutoCString boundary("--"_ns);
+ boundary.Append(NS_ConvertUTF16toUTF8(boundaryParam));
+ boundaryStack.AppendElement(boundary);
+ }
+ }
+
+ // If we are message/rfc822, then there's another header block coming up
+ else if (contentType.LowerCaseEqualsLiteral("message/rfc822"))
+ continue;
+
+ // If we are a text part, then we want it
+ else if (StringBeginsWith(contentType, u"text/"_ns,
+ nsCaseInsensitiveStringComparator)) {
+ inMsgBody = true;
+
+ if (contentType.LowerCaseEqualsLiteral("text/html")) msgBodyIsHtml = true;
+
+ // Also get the charset if required
+ if (charset.IsEmpty()) {
+ nsAutoString charsetW;
+ mimeHdrParam->GetParameter(contentTypeHdr, "charset", EmptyCString(),
+ false, nullptr, charsetW);
+ charset.Assign(NS_ConvertUTF16toUTF8(charsetW));
+ }
+
+ // Finally, get the encoding
+ nsAutoCString encodingHdr;
+ mimeHeaders->ExtractHeader("Content-Transfer-Encoding", false,
+ encodingHdr);
+ if (!encodingHdr.IsEmpty())
+ mimeHdrParam->GetParameter(encodingHdr, nullptr, EmptyCString(), false,
+ nullptr, encoding);
+
+ if (encoding.LowerCaseEqualsLiteral(ENCODING_BASE64)) isBase64 = true;
+ }
+
+ // We need to consume the rest, until the next headers
+ uint32_t count = boundaryStack.Length();
+ nsAutoCString boundary;
+ nsAutoCString endBoundary;
+ if (count) {
+ boundary.Assign(boundaryStack.ElementAt(count - 1));
+ endBoundary.Assign(boundary);
+ endBoundary.AppendLiteral("--");
+ }
+ while (more) {
+ rv = NS_ReadLine(stream, lineBuffer.get(), curLine, &more);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (count) {
+ // If we've reached a MIME final delimiter, pop and break
+ if (StringBeginsWith(curLine, endBoundary)) {
+ if (inMsgBody) reachedEndBody = true;
+ boundaryStack.RemoveElementAt(count - 1);
+ justPassedEndBoundary = true;
+ break;
+ }
+ // If we've reached the end of this MIME part, we can break
+ if (StringBeginsWith(curLine, boundary)) {
+ if (inMsgBody) reachedEndBody = true;
+ break;
+ }
+ }
+
+ // Only append the text if we're actually in the message body
+ if (inMsgBody) {
+ msgText.Append(curLine);
+ if (!isBase64) msgText.AppendLiteral("\r\n");
+ }
+
+ bytesRead += curLine.Length();
+ if (bytesRead > bytesToRead) break;
+ }
+ }
+ lineBuffer.reset();
+
+ // if the snippet is encoded, decode it
+ if (!encoding.IsEmpty())
+ decodeMsgSnippet(NS_ConvertUTF16toUTF8(encoding), !reachedEndBody, msgText);
+
+ // In order to turn our snippet into unicode, we need to convert it from the
+ // charset we detected earlier.
+ nsString unicodeMsgBodyStr;
+ nsMsgI18NConvertToUnicode(charset, msgText, unicodeMsgBodyStr);
+
+ // now we've got a msg body. If it's html, convert it to plain text.
+ if (msgBodyIsHtml && aStripHTMLTags)
+ ConvertMsgSnippetToPlainText(unicodeMsgBodyStr, unicodeMsgBodyStr);
+
+ // We want to remove any whitespace from the beginning and end of the string
+ unicodeMsgBodyStr.Trim(" \t\r\n", true, true);
+
+ // step 3, optionally remove quoted text from the snippet
+ nsString compressedQuotesMsgStr;
+ if (aCompressQuotes)
+ compressQuotesInMsgSnippet(unicodeMsgBodyStr, compressedQuotesMsgStr);
+
+ // now convert back to utf-8 which is more convenient for storage
+ CopyUTF16toUTF8(aCompressQuotes ? compressedQuotesMsgStr : unicodeMsgBodyStr,
+ aMsgText);
+
+ // finally, truncate the string based on aMaxOutputLen
+ if (aMsgText.Length() > aMaxOutputLen) {
+ if (NS_IsAscii(aMsgText.BeginReading()))
+ aMsgText.SetLength(aMaxOutputLen);
+ else
+ nsMsgI18NShrinkUTF8Str(nsCString(aMsgText), aMaxOutputLen, aMsgText);
+ }
+
+ // Also assign the content type being returned
+ aContentType.Assign(NS_ConvertUTF16toUTF8(contentType));
+ return rv;
+}
+
+/**
+ * decodeMsgSnippet - helper function which applies the appropriate transfer
+ * decoding to the message snippet based on aEncodingType. Currently handles
+ * base64 and quoted-printable. If aEncodingType refers to an encoding we
+ * don't handle, the message data is passed back unmodified.
+ * @param aEncodingType the encoding type (base64, quoted-printable)
+ * @param aIsComplete the snippet is actually the entire message so the
+ * decoder doesn't have to worry about partial data
+ * @param aMsgSnippet in/out argument. The encoded msg snippet and then the
+ * decoded snippet
+ */
+void nsMsgDBFolder::decodeMsgSnippet(const nsACString& aEncodingType,
+ bool aIsComplete, nsCString& aMsgSnippet) {
+ if (aEncodingType.LowerCaseEqualsLiteral(ENCODING_BASE64)) {
+ int32_t base64Len = aMsgSnippet.Length();
+ if (aIsComplete) base64Len -= base64Len % 4;
+ char* decodedBody = PL_Base64Decode(aMsgSnippet.get(), base64Len, nullptr);
+ if (decodedBody) aMsgSnippet.Adopt(decodedBody);
+ } else if (aEncodingType.LowerCaseEqualsLiteral(ENCODING_QUOTED_PRINTABLE)) {
+ MsgStripQuotedPrintable(aMsgSnippet);
+ }
+}
+
+/**
+ * stripQuotesFromMsgSnippet - Reduces quoted reply text including the citation
+ * (Scott wrote:) from the message snippet to " ... ". Assumes the snippet has
+ * been decoded and converted to plain text.
+ * @param aMsgSnippet in/out argument. The string to strip quotes from.
+ */
+void nsMsgDBFolder::compressQuotesInMsgSnippet(const nsString& aMsgSnippet,
+ nsAString& aCompressedQuotes) {
+ int32_t msgBodyStrLen = aMsgSnippet.Length();
+ bool lastLineWasAQuote = false;
+ int32_t offset = 0;
+ int32_t lineFeedPos = 0;
+ while (offset < msgBodyStrLen) {
+ lineFeedPos = aMsgSnippet.FindChar('\n', offset);
+ if (lineFeedPos != -1) {
+ const nsAString& currentLine =
+ Substring(aMsgSnippet, offset, lineFeedPos - offset);
+ // this catches quoted text ("> "), nested quotes of any level (">> ",
+ // ">>> ", ...) it also catches empty line quoted text (">"). It might be
+ // over aggressive and require tweaking later. Try to strip the citation.
+ // If the current line ends with a ':' and the next line looks like a
+ // quoted reply (starts with a ">") skip the current line
+ if (StringBeginsWith(currentLine, u">"_ns) ||
+ (lineFeedPos + 1 < msgBodyStrLen && lineFeedPos &&
+ aMsgSnippet[lineFeedPos - 1] == char16_t(':') &&
+ aMsgSnippet[lineFeedPos + 1] == char16_t('>'))) {
+ lastLineWasAQuote = true;
+ } else if (!currentLine.IsEmpty()) {
+ if (lastLineWasAQuote) {
+ aCompressedQuotes += u" ... "_ns;
+ lastLineWasAQuote = false;
+ }
+
+ aCompressedQuotes += currentLine;
+ // Don't forget to substitute a space for the line feed.
+ aCompressedQuotes += char16_t(' ');
+ }
+
+ offset = lineFeedPos + 1;
+ } else {
+ aCompressedQuotes.Append(
+ Substring(aMsgSnippet, offset, msgBodyStrLen - offset));
+ break;
+ }
+ }
+}
+
+NS_IMETHODIMP nsMsgDBFolder::ConvertMsgSnippetToPlainText(
+ const nsAString& aMessageText, nsAString& aOutText) {
+ uint32_t flags = nsIDocumentEncoder::OutputLFLineBreak |
+ nsIDocumentEncoder::OutputNoScriptContent |
+ nsIDocumentEncoder::OutputNoFramesContent |
+ nsIDocumentEncoder::OutputBodyOnly;
+ nsCOMPtr<nsIParserUtils> utils = do_GetService(NS_PARSERUTILS_CONTRACTID);
+ return utils->ConvertToPlainText(aMessageText, flags, 80, aOutText);
+}
+
+nsresult nsMsgDBFolder::GetMsgPreviewTextFromStream(nsIMsgDBHdr* msgHdr,
+ nsIInputStream* stream) {
+ nsCString msgBody;
+ nsAutoCString charset;
+ msgHdr->GetCharset(getter_Copies(charset));
+ nsAutoCString contentType;
+ nsresult rv = GetMsgTextFromStream(stream, charset, 4096, 255, true, true,
+ contentType, msgBody);
+ // replaces all tabs and line returns with a space,
+ // then trims off leading and trailing white space
+ msgBody.CompressWhitespace();
+ msgHdr->SetStringProperty("preview", msgBody);
+ return rv;
+}
+
+void nsMsgDBFolder::UpdateTimestamps(bool allowUndo) {
+ if (!(mFlags & (nsMsgFolderFlags::Trash | nsMsgFolderFlags::Junk))) {
+ SetMRUTime();
+ if (allowUndo) // This is a proxy for a user-initiated act.
+ {
+ bool isArchive;
+ IsSpecialFolder(nsMsgFolderFlags::Archive, true, &isArchive);
+ if (!isArchive) {
+ SetMRMTime();
+ NotifyFolderEvent(kMRMTimeChanged);
+ }
+ }
+ }
+}
+
+void nsMsgDBFolder::SetMRUTime() {
+ uint32_t seconds;
+ PRTime2Seconds(PR_Now(), &seconds);
+ nsAutoCString nowStr;
+ nowStr.AppendInt(seconds);
+ SetStringProperty(MRU_TIME_PROPERTY, nowStr);
+}
+
+void nsMsgDBFolder::SetMRMTime() {
+ uint32_t seconds;
+ PRTime2Seconds(PR_Now(), &seconds);
+ nsAutoCString nowStr;
+ nowStr.AppendInt(seconds);
+ SetStringProperty(MRM_TIME_PROPERTY, nowStr);
+}
+
+NS_IMETHODIMP nsMsgDBFolder::AddKeywordsToMessages(
+ const nsTArray<RefPtr<nsIMsgDBHdr>>& aMessages,
+ const nsACString& aKeywords) {
+ nsresult rv = NS_OK;
+ GetDatabase();
+ if (mDatabase) {
+ nsCString keywords;
+
+ for (auto message : aMessages) {
+ message->GetStringProperty("keywords", keywords);
+ nsTArray<nsCString> keywordArray;
+ ParseString(aKeywords, ' ', keywordArray);
+ uint32_t addCount = 0;
+ for (uint32_t j = 0; j < keywordArray.Length(); j++) {
+ int32_t start, length;
+ if (!MsgFindKeyword(keywordArray[j], keywords, &start, &length)) {
+ if (!keywords.IsEmpty()) keywords.Append(' ');
+ keywords.Append(keywordArray[j]);
+ addCount++;
+ }
+ }
+ // avoid using the message key to set the string property, because
+ // in the case of filters running on incoming pop3 mail with quarantining
+ // turned on, the message key is wrong.
+ mDatabase->SetStringPropertyByHdr(message, "keywords", keywords);
+
+ if (addCount) NotifyPropertyFlagChanged(message, kKeywords, 0, addCount);
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::RemoveKeywordsFromMessages(
+ const nsTArray<RefPtr<nsIMsgDBHdr>>& aMessages,
+ const nsACString& aKeywords) {
+ nsresult rv = NS_OK;
+ GetDatabase();
+ if (mDatabase) {
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsTArray<nsCString> keywordArray;
+ ParseString(aKeywords, ' ', keywordArray);
+ nsCString keywords;
+ // If the tag is also a label, we should remove the label too...
+
+ for (auto message : aMessages) {
+ rv = message->GetStringProperty("keywords", keywords);
+ uint32_t removeCount = 0;
+ for (uint32_t j = 0; j < keywordArray.Length(); j++) {
+ int32_t startOffset, length;
+ if (MsgFindKeyword(keywordArray[j], keywords, &startOffset, &length)) {
+ // delete any leading space delimiters
+ while (startOffset && keywords.CharAt(startOffset - 1) == ' ') {
+ startOffset--;
+ length++;
+ }
+ // but if the keyword is at the start then delete the following space
+ if (!startOffset &&
+ length < static_cast<int32_t>(keywords.Length()) &&
+ keywords.CharAt(length) == ' ')
+ length++;
+ keywords.Cut(startOffset, length);
+ removeCount++;
+ }
+ }
+
+ if (removeCount) {
+ mDatabase->SetStringPropertyByHdr(message, "keywords", keywords);
+ NotifyPropertyFlagChanged(message, kKeywords, removeCount, 0);
+ }
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetCustomIdentity(nsIMsgIdentity** aIdentity) {
+ NS_ENSURE_ARG_POINTER(aIdentity);
+ *aIdentity = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::GetProcessingFlags(nsMsgKey aKey,
+ uint32_t* aFlags) {
+ NS_ENSURE_ARG_POINTER(aFlags);
+ *aFlags = 0;
+ for (uint32_t i = 0; i < nsMsgProcessingFlags::NumberOfFlags; i++)
+ if (mProcessingFlag[i].keys && mProcessingFlag[i].keys->IsMember(aKey))
+ *aFlags |= mProcessingFlag[i].bit;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::OrProcessingFlags(nsMsgKey aKey, uint32_t mask) {
+ for (uint32_t i = 0; i < nsMsgProcessingFlags::NumberOfFlags; i++)
+ if (mProcessingFlag[i].bit & mask && mProcessingFlag[i].keys)
+ mProcessingFlag[i].keys->Add(aKey);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgDBFolder::AndProcessingFlags(nsMsgKey aKey, uint32_t mask) {
+ for (uint32_t i = 0; i < nsMsgProcessingFlags::NumberOfFlags; i++)
+ if (!(mProcessingFlag[i].bit & mask) && mProcessingFlag[i].keys)
+ mProcessingFlag[i].keys->Remove(aKey);
+ return NS_OK;
+}
+
+// Each implementation must provide an override of this, connecting the folder
+// type to the corresponding incoming server type.
+NS_IMETHODIMP nsMsgDBFolder::GetIncomingServerType(
+ nsACString& aIncomingServerType) {
+ NS_ASSERTION(false, "subclasses need to override this");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+void nsMsgDBFolder::ClearProcessingFlags() {
+ for (uint32_t i = 0; i < nsMsgProcessingFlags::NumberOfFlags; i++) {
+ // There is no clear method so we need to delete and re-create.
+ delete mProcessingFlag[i].keys;
+ mProcessingFlag[i].keys = nsMsgKeySetU::Create();
+ }
+}
+
+nsresult nsMsgDBFolder::MessagesInKeyOrder(
+ nsTArray<nsMsgKey> const& aKeyArray, nsIMsgFolder* srcFolder,
+ nsTArray<RefPtr<nsIMsgDBHdr>>& messages) {
+ messages.Clear();
+ messages.SetCapacity(aKeyArray.Length());
+
+ nsCOMPtr<nsIDBFolderInfo> folderInfo;
+ nsCOMPtr<nsIMsgDatabase> db;
+ nsresult rv = srcFolder->GetDBFolderInfoAndDB(getter_AddRefs(folderInfo),
+ getter_AddRefs(db));
+ if (NS_SUCCEEDED(rv) && db) {
+ for (auto key : aKeyArray) {
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ rv = db->GetMsgHdrForKey(key, getter_AddRefs(msgHdr));
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (msgHdr) messages.AppendElement(msgHdr);
+ }
+ }
+ return rv;
+}
+
+/* static */ nsMsgKeySetU* nsMsgKeySetU::Create() {
+ nsMsgKeySetU* set = new nsMsgKeySetU;
+ if (set) {
+ set->loKeySet = nsMsgKeySet::Create();
+ set->hiKeySet = nsMsgKeySet::Create();
+ if (!(set->loKeySet && set->hiKeySet)) {
+ delete set;
+ set = nullptr;
+ }
+ }
+ return set;
+}
+
+nsMsgKeySetU::nsMsgKeySetU() : hiKeySet(nullptr) {}
+
+nsMsgKeySetU::~nsMsgKeySetU() {}
+
+const uint32_t kLowerBits = 0x7fffffff;
+
+int nsMsgKeySetU::Add(nsMsgKey aKey) {
+ int32_t intKey = static_cast<int32_t>(aKey);
+ if (intKey >= 0) return loKeySet->Add(intKey);
+ return hiKeySet->Add(intKey & kLowerBits);
+}
+
+int nsMsgKeySetU::Remove(nsMsgKey aKey) {
+ int32_t intKey = static_cast<int32_t>(aKey);
+ if (intKey >= 0) return loKeySet->Remove(intKey);
+ return hiKeySet->Remove(intKey & kLowerBits);
+}
+
+bool nsMsgKeySetU::IsMember(nsMsgKey aKey) {
+ int32_t intKey = static_cast<int32_t>(aKey);
+ if (intKey >= 0) return loKeySet->IsMember(intKey);
+ return hiKeySet->IsMember(intKey & kLowerBits);
+}
+
+nsresult nsMsgKeySetU::ToMsgKeyArray(nsTArray<nsMsgKey>& aArray) {
+ nsresult rv = loKeySet->ToMsgKeyArray(aArray);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return hiKeySet->ToMsgKeyArray(aArray);
+}
diff --git a/comm/mailnews/base/src/nsMsgDBFolder.h b/comm/mailnews/base/src/nsMsgDBFolder.h
new file mode 100644
index 0000000000..1cd01199f6
--- /dev/null
+++ b/comm/mailnews/base/src/nsMsgDBFolder.h
@@ -0,0 +1,366 @@
+/* -*- 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 nsMsgDBFolder_h__
+#define nsMsgDBFolder_h__
+
+#include "mozilla/Attributes.h"
+#include "msgCore.h"
+#include "nsIMsgFolder.h"
+#include "nsIDBFolderInfo.h"
+#include "nsIMsgDatabase.h"
+#include "nsIMsgIncomingServer.h"
+#include "nsCOMPtr.h"
+#include "nsIDBChangeListener.h"
+#include "nsIMsgPluggableStore.h"
+#include "nsIURL.h"
+#include "nsIFile.h"
+#include "nsWeakReference.h"
+#include "nsIWeakReferenceUtils.h"
+#include "nsIMsgFilterList.h"
+#include "nsIUrlListener.h"
+#include "nsIMsgHdr.h"
+#include "nsIOutputStream.h"
+#include "nsITransport.h"
+#include "nsIStringBundle.h"
+#include "nsTObserverArray.h"
+#include "nsCOMArray.h"
+#include "nsMsgKeySet.h"
+#include "nsMsgMessageFlags.h"
+#include "nsIMsgFilterPlugin.h"
+#include "mozilla/intl/Collator.h"
+
+// We declare strings for folder properties and events.
+// Properties:
+extern const nsLiteralCString kBiffState;
+extern const nsLiteralCString kCanFileMessages;
+extern const nsLiteralCString kDefaultServer;
+extern const nsLiteralCString kFlagged;
+extern const nsLiteralCString kFolderFlag;
+extern const nsLiteralCString kFolderSize;
+extern const nsLiteralCString kIsDeferred;
+extern const nsLiteralCString kIsSecure;
+extern const nsLiteralCString kJunkStatusChanged;
+extern const nsLiteralCString kKeywords;
+extern const nsLiteralCString kMRMTimeChanged;
+extern const nsLiteralCString kMsgLoaded;
+extern const nsLiteralCString kName;
+extern const nsLiteralCString kNewMailReceived;
+extern const nsLiteralCString kNewMessages;
+extern const nsLiteralCString kOpen;
+extern const nsLiteralCString kSortOrder;
+extern const nsLiteralCString kStatus;
+extern const nsLiteralCString kSynchronize;
+extern const nsLiteralCString kTotalMessages;
+extern const nsLiteralCString kTotalUnreadMessages;
+
+// Events:
+extern const nsLiteralCString kAboutToCompact;
+extern const nsLiteralCString kCompactCompleted;
+extern const nsLiteralCString kDeleteOrMoveMsgCompleted;
+extern const nsLiteralCString kDeleteOrMoveMsgFailed;
+extern const nsLiteralCString kFiltersApplied;
+extern const nsLiteralCString kFolderCreateCompleted;
+extern const nsLiteralCString kFolderCreateFailed;
+extern const nsLiteralCString kFolderLoaded;
+extern const nsLiteralCString kNumNewBiffMessages;
+extern const nsLiteralCString kRenameCompleted;
+
+using mozilla::intl::Collator;
+
+class nsIMsgFolderCacheElement;
+class nsMsgKeySetU;
+
+class nsMsgFolderService final : public nsIMsgFolderService {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMSGFOLDERSERVICE
+
+ nsMsgFolderService(){};
+
+ protected:
+ ~nsMsgFolderService(){};
+};
+
+/*
+ * nsMsgDBFolder
+ * class derived from nsMsgFolder for those folders that use an nsIMsgDatabase
+ */
+class nsMsgDBFolder : public nsSupportsWeakReference,
+ public nsIMsgFolder,
+ public nsIDBChangeListener,
+ public nsIUrlListener,
+ public nsIJunkMailClassificationListener,
+ public nsIMsgTraitClassificationListener {
+ public:
+ friend class nsMsgFolderService;
+
+ nsMsgDBFolder(void);
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIMSGFOLDER
+ NS_DECL_NSIDBCHANGELISTENER
+ NS_DECL_NSIURLLISTENER
+ NS_DECL_NSIJUNKMAILCLASSIFICATIONLISTENER
+ NS_DECL_NSIMSGTRAITCLASSIFICATIONLISTENER
+
+ NS_IMETHOD WriteToFolderCacheElem(nsIMsgFolderCacheElement* element);
+ NS_IMETHOD ReadFromFolderCacheElem(nsIMsgFolderCacheElement* element);
+
+ nsresult CreateDirectoryForFolder(nsIFile** result);
+ nsresult CreateBackupDirectory(nsIFile** result);
+ nsresult GetBackupSummaryFile(nsIFile** result, const nsACString& newName);
+ nsresult GetMsgPreviewTextFromStream(nsIMsgDBHdr* msgHdr,
+ nsIInputStream* stream);
+ nsresult HandleAutoCompactEvent(nsIMsgWindow* aMsgWindow);
+ static int gIsEnglishApp;
+
+ protected:
+ virtual ~nsMsgDBFolder();
+
+ virtual nsresult CreateBaseMessageURI(const nsACString& aURI);
+
+ void compressQuotesInMsgSnippet(const nsString& aMessageText,
+ nsAString& aCompressedQuotesStr);
+ void decodeMsgSnippet(const nsACString& aEncodingType, bool aIsComplete,
+ nsCString& aMsgSnippet);
+
+ // helper routine to parse the URI and update member variables
+ nsresult parseURI(bool needServer = false);
+ nsresult GetBaseStringBundle(nsIStringBundle** aBundle);
+ nsresult GetStringFromBundle(const char* msgName, nsString& aResult);
+ nsresult ThrowConfirmationPrompt(nsIMsgWindow* msgWindow,
+ const nsAString& confirmString,
+ bool* confirmed);
+ nsresult GetWarnFilterChanged(bool* aVal);
+ nsresult SetWarnFilterChanged(bool aVal);
+ nsresult CreateCollationKey(const nsString& aSource, uint8_t** aKey,
+ uint32_t* aLength);
+
+ // All children will override this to create the right class of object.
+ virtual nsresult CreateChildFromURI(const nsACString& uri,
+ nsIMsgFolder** folder) = 0;
+ virtual nsresult ReadDBFolderInfo(bool force);
+ virtual nsresult FlushToFolderCache();
+ virtual nsresult GetDatabase() = 0;
+ virtual nsresult SendFlagNotifications(nsIMsgDBHdr* item, uint32_t oldFlags,
+ uint32_t newFlags);
+
+ // Overriden by IMAP to handle gmail hack.
+ virtual nsresult GetOfflineFileStream(nsMsgKey msgKey, uint64_t* offset,
+ uint32_t* size,
+ nsIInputStream** aFileStream);
+
+ nsresult CheckWithNewMessagesStatus(bool messageAdded);
+ void UpdateNewMessages();
+ nsresult OnHdrAddedOrDeleted(nsIMsgDBHdr* hdrChanged, bool added);
+ nsresult CreateFileForDB(const nsAString& userLeafName, nsIFile* baseDir,
+ nsIFile** dbFile);
+
+ nsresult GetFolderCacheKey(nsIFile** aFile);
+ nsresult GetFolderCacheElemFromFile(nsIFile* file,
+ nsIMsgFolderCacheElement** cacheElement);
+ nsresult AddDirectorySeparator(nsIFile* path);
+ nsresult CheckIfFolderExists(const nsAString& newFolderName,
+ nsIMsgFolder* parentFolder,
+ nsIMsgWindow* msgWindow);
+ bool ConfirmAutoFolderRename(nsIMsgWindow* aMsgWindow,
+ const nsString& aOldName,
+ const nsString& aNewName);
+
+ // Returns true if: a) there is no need to prompt or b) the user is already
+ // logged in or c) the user logged in successfully.
+ static bool PromptForMasterPasswordIfNecessary();
+
+ // Offline support methods. Used by IMAP and News folders, but not local
+ // folders.
+ nsresult StartNewOfflineMessage();
+ nsresult WriteStartOfNewLocalMessage();
+ nsresult EndNewOfflineMessage(nsresult status);
+
+ nsresult AutoCompact(nsIMsgWindow* aWindow);
+ // this is a helper routine that ignores whether nsMsgMessageFlags::Offline is
+ // set for the folder
+ nsresult MsgFitsDownloadCriteria(nsMsgKey msgKey, bool* result);
+ nsresult GetPromptPurgeThreshold(bool* aPrompt);
+ nsresult GetPurgeThreshold(int32_t* aThreshold);
+ nsresult ApplyRetentionSettings(bool deleteViaFolder);
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY nsresult AddMarkAllReadUndoAction(
+ nsIMsgWindow* msgWindow, nsMsgKey* thoseMarked, uint32_t numMarked);
+
+ nsresult PerformBiffNotifications(
+ void); // if there are new, non spam messages, do biff
+
+ // Helper function for Move code to call to update the MRU and MRM time.
+ void UpdateTimestamps(bool allowUndo);
+ void SetMRUTime();
+ void SetMRMTime();
+ /**
+ * Clear all processing flags, presumably because message keys are no longer
+ * valid.
+ */
+ void ClearProcessingFlags();
+
+ nsresult NotifyHdrsNotBeingClassified();
+ static nsresult BuildFolderSortKey(nsIMsgFolder* aFolder,
+ nsTArray<uint8_t>& aKey);
+ /**
+ * Produce an array of messages ordered like the input keys.
+ */
+ nsresult MessagesInKeyOrder(nsTArray<nsMsgKey> const& aKeyArray,
+ nsIMsgFolder* srcFolder,
+ nsTArray<RefPtr<nsIMsgDBHdr>>& messages);
+ nsCString mURI;
+
+ nsCOMPtr<nsIMsgDatabase> mDatabase;
+ nsCOMPtr<nsIMsgDatabase> mBackupDatabase;
+ bool mAddListener;
+ bool mNewMessages;
+ bool mGettingNewMessages;
+ nsMsgKey mLastMessageLoaded;
+
+ /*
+ * Start of offline-message-writing vars.
+ * These track offline message writing for IMAP and News folders.
+ * But *not* for local folders, which do their own thing.
+ * They are set up by StartNewOfflineMessage() and cleaned up
+ * by EndNewOfflineMessage().
+ * IMAP folder also uses these vars when saving messages to disk.
+ */
+
+ // The header of the message currently being written.
+ nsCOMPtr<nsIMsgDBHdr> m_offlineHeader;
+ int32_t m_numOfflineMsgLines;
+ // Number of bytes added due to add X-Mozilla-* headers.
+ int32_t m_bytesAddedToLocalMsg;
+ // This is currently used when we do a save as of an imap or news message..
+ // Also used by IMAP/News offline messsage writing.
+ nsCOMPtr<nsIOutputStream> m_tempMessageStream;
+ // The number of bytes written to m_tempMessageStream so far.
+ uint32_t m_tempMessageStreamBytesWritten;
+
+ /*
+ * End of offline message tracking vars
+ */
+
+ nsCOMPtr<nsIMsgRetentionSettings> m_retentionSettings;
+ nsCOMPtr<nsIMsgDownloadSettings> m_downloadSettings;
+ static nsrefcnt mInstanceCount;
+
+ uint32_t mFlags;
+ nsWeakPtr mParent; // This won't be refcounted for ownership reasons.
+ int32_t mNumUnreadMessages; /* count of unread messages (-1 means unknown; -2
+ means unknown but we already tried to find
+ out.) */
+ int32_t mNumTotalMessages; /* count of existing messages. */
+ bool mNotifyCountChanges;
+ int64_t mExpungedBytes;
+ nsCOMArray<nsIMsgFolder> mSubFolders;
+ nsTObserverArray<nsCOMPtr<nsIFolderListener>> mListeners;
+
+ bool mInitializedFromCache;
+ nsISupports* mSemaphoreHolder; // set when the folder is being written to
+ // Due to ownership issues, this won't be
+ // AddRef'd.
+
+ nsWeakPtr mServer;
+
+ // These values are used for tricking the front end into thinking that we have
+ // more messages than are really in the DB. This is usually after and IMAP
+ // message copy where we don't want to do an expensive select until the user
+ // actually opens that folder
+ int32_t mNumPendingUnreadMessages;
+ int32_t mNumPendingTotalMessages;
+ int64_t mFolderSize;
+
+ int32_t mNumNewBiffMessages;
+
+ // these are previous set of new msgs, which we might
+ // want to run junk controls on. This is in addition to "new" hdrs
+ // in the db, which might get cleared because the user clicked away
+ // from the folder.
+ nsTArray<nsMsgKey> m_saveNewMsgs;
+
+ // These are the set of new messages for a folder who has had
+ // its db closed, without the user reading the folder. This
+ // happens with pop3 mail filtered to a different local folder.
+ nsTArray<nsMsgKey> m_newMsgs;
+
+ //
+ // stuff from the uri
+ //
+ bool mHaveParsedURI; // is the URI completely parsed?
+ bool mIsServerIsValid;
+ bool mIsServer;
+ nsString mName;
+ nsString mOriginalName;
+ nsCOMPtr<nsIFile> mPath;
+ nsCString mBaseMessageURI; // The uri with the message scheme
+
+ // static stuff for cross-instance objects like atoms
+ static nsrefcnt gInstanceCount;
+
+ static nsresult initializeStrings();
+ static nsresult createCollationKeyGenerator();
+
+ static nsString kLocalizedInboxName;
+ static nsString kLocalizedTrashName;
+ static nsString kLocalizedSentName;
+ static nsString kLocalizedDraftsName;
+ static nsString kLocalizedTemplatesName;
+ static nsString kLocalizedUnsentName;
+ static nsString kLocalizedJunkName;
+ static nsString kLocalizedArchivesName;
+
+ static nsString kLocalizedBrandShortName;
+
+ static mozilla::UniquePtr<mozilla::intl::Collator> gCollationKeyGenerator;
+ static bool gInitializeStringsDone;
+
+ // store of keys that have a processing flag set
+ struct {
+ uint32_t bit;
+ nsMsgKeySetU* keys;
+ } mProcessingFlag[nsMsgProcessingFlags::NumberOfFlags];
+
+ // list of nsIMsgDBHdrs for messages to process post-bayes
+ nsTArray<RefPtr<nsIMsgDBHdr>> mPostBayesMessagesToFilter;
+
+ /**
+ * The list of message keys that have been classified for msgsClassified
+ * batch notification purposes. We add to this list in OnMessageClassified
+ * when we are told about a classified message (a URI is provided), and we
+ * notify for the list and clear it when we are told all the messages in
+ * the batch were classified (a URI is not provided).
+ */
+ nsTArray<nsMsgKey> mClassifiedMsgKeys;
+ // Is the current bayes filtering doing junk classification?
+ bool mBayesJunkClassifying;
+ // Is the current bayes filtering doing trait classification?
+ bool mBayesTraitClassifying;
+};
+
+// This class is a kludge to allow nsMsgKeySet to be used with uint32_t keys
+class nsMsgKeySetU {
+ public:
+ // Creates an empty set.
+ static nsMsgKeySetU* Create();
+ ~nsMsgKeySetU();
+ // IsMember() returns whether the given key is a member of this set.
+ bool IsMember(nsMsgKey key);
+ // Add() adds the given key to the set. (Returns 1 if a change was
+ // made, 0 if it was already there, and negative on error.)
+ int Add(nsMsgKey key);
+ // Remove() removes the given article from the set.
+ int Remove(nsMsgKey key);
+ // Add the keys in the set to aArray.
+ nsresult ToMsgKeyArray(nsTArray<nsMsgKey>& aArray);
+
+ protected:
+ nsMsgKeySetU();
+ RefPtr<nsMsgKeySet> loKeySet;
+ RefPtr<nsMsgKeySet> hiKeySet;
+};
+
+#endif
diff --git a/comm/mailnews/base/src/nsMsgDBView.cpp b/comm/mailnews/base/src/nsMsgDBView.cpp
new file mode 100644
index 0000000000..6532b52e0e
--- /dev/null
+++ b/comm/mailnews/base/src/nsMsgDBView.cpp
@@ -0,0 +1,7411 @@
+/* -*- 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 <algorithm>
+#include "msgCore.h"
+#include "prmem.h"
+#include "nsArrayUtils.h"
+#include "nsIMsgCustomColumnHandler.h"
+#include "nsMsgDBView.h"
+#include "nsISupports.h"
+#include "nsIMsgFolder.h"
+#include "nsIDBFolderInfo.h"
+#include "nsIMsgDatabase.h"
+#include "nsIMsgFolder.h"
+#include "MailNewsTypes2.h"
+#include "nsIMsgImapMailFolder.h"
+#include "nsImapCore.h"
+#include "nsMsgFolderFlags.h"
+#include "nsIMsgLocalMailFolder.h"
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+#include "nsIPrefLocalizedString.h"
+#include "nsIMsgSearchSession.h"
+#include "nsIMsgCopyService.h"
+#include "nsISpamSettings.h"
+#include "nsIMsgAccountManager.h"
+#include "nsTreeColumns.h"
+#include "nsTextFormatter.h"
+#include "nsIMimeConverter.h"
+#include "nsMsgMessageFlags.h"
+#include "nsIPrompt.h"
+#include "nsIWindowWatcher.h"
+#include "nsIMsgFolderNotificationService.h"
+#include "nsServiceManagerUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "nsMemory.h"
+#include "nsIAbManager.h"
+#include "nsIAbDirectory.h"
+#include "nsIAbCard.h"
+#include "mozilla/Components.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/dom/DataTransfer.h"
+#include "mozilla/mailnews/MimeHeaderParser.h"
+#include "nsTArray.h"
+#include "mozilla/intl/OSPreferences.h"
+#include "mozilla/intl/LocaleService.h"
+#include "mozilla/intl/AppDateTimeFormat.h"
+
+using namespace mozilla::mailnews;
+
+nsString nsMsgDBView::kHighestPriorityString;
+nsString nsMsgDBView::kHighPriorityString;
+nsString nsMsgDBView::kLowestPriorityString;
+nsString nsMsgDBView::kLowPriorityString;
+nsString nsMsgDBView::kNormalPriorityString;
+
+nsString nsMsgDBView::kReadString;
+nsString nsMsgDBView::kRepliedString;
+nsString nsMsgDBView::kForwardedString;
+nsString nsMsgDBView::kRedirectedString;
+nsString nsMsgDBView::kNewString;
+
+nsString nsMsgDBView::kTodayString;
+nsString nsMsgDBView::kYesterdayString;
+nsString nsMsgDBView::kLastWeekString;
+nsString nsMsgDBView::kTwoWeeksAgoString;
+nsString nsMsgDBView::kOldMailString;
+nsString nsMsgDBView::kFutureDateString;
+
+bool nsMsgDBView::m_dateFormatsInitialized = false;
+nsDateFormatSelectorComm nsMsgDBView::m_dateFormatDefault = kDateFormatShort;
+nsDateFormatSelectorComm nsMsgDBView::m_dateFormatThisWeek = kDateFormatShort;
+nsDateFormatSelectorComm nsMsgDBView::m_dateFormatToday = kDateFormatNone;
+
+nsString nsMsgDBView::m_connectorPattern;
+nsCOMPtr<nsIStringBundle> nsMsgDBView::mMessengerStringBundle;
+
+static const uint32_t kMaxNumSortColumns = 2;
+
+static void GetCachedName(const nsCString& unparsedString,
+ int32_t displayVersion, nsACString& cachedName);
+
+static void UpdateCachedName(nsIMsgDBHdr* aHdr, const char* header_field,
+ const nsAString& newName);
+
+// viewSortInfo is context data passed into the sort comparison functions -
+// FnSortIdUint32 for comparing numeric fields, FnSortIdKey for everything
+// else. If a comparison function finds two elements with equal primary
+// ordering, it'll call SecondaryCompare() to break the deadlock.
+// SecondaryCompare() uses the same comparison functions again, but using the
+// secondary key and potentially with different criteria (eg secondary sort
+// order might be different to primary). The viewSortInfo::isSecondarySort
+// flag lets the comparison function know not to call SecondaryCompare()
+// again (and again and again)...
+class viewSortInfo {
+ public:
+ nsMsgDBView* view;
+ nsIMsgDatabase* db; // Which db to use for collation compares.
+ bool isSecondarySort;
+ bool ascendingSort;
+};
+
+NS_IMPL_ISUPPORTS(nsMsgDBViewService, nsIMsgDBViewService)
+NS_IMETHODIMP nsMsgDBViewService::InitializeDBViewStrings() {
+ nsMsgDBView::InitializeLiterals();
+ nsMsgDBView::m_connectorPattern.Truncate();
+ nsMsgDBView::mMessengerStringBundle = nullptr;
+ // Initialize date display format.
+ if (!nsMsgDBView::m_dateFormatsInitialized) {
+ nsMsgDBView::InitDisplayFormats();
+ }
+ return NS_OK;
+}
+
+NS_IMPL_ADDREF(nsMsgDBView)
+NS_IMPL_RELEASE(nsMsgDBView)
+
+NS_INTERFACE_MAP_BEGIN(nsMsgDBView)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIMsgDBView)
+ NS_INTERFACE_MAP_ENTRY(nsIMsgDBView)
+ NS_INTERFACE_MAP_ENTRY(nsIDBChangeListener)
+ NS_INTERFACE_MAP_ENTRY(nsITreeView)
+ NS_INTERFACE_MAP_ENTRY(nsIJunkMailClassificationListener)
+NS_INTERFACE_MAP_END
+
+nsMsgDBView::nsMsgDBView() {
+ // Member initializers and constructor code.
+ m_sortValid = false;
+ m_checkedCustomColumns = false;
+ m_sortOrder = nsMsgViewSortOrder::none;
+ m_sortType = nsMsgViewSortType::byNone;
+ m_viewFlags = nsMsgViewFlagsType::kNone;
+ m_secondarySort = nsMsgViewSortType::byId;
+ m_secondarySortOrder = nsMsgViewSortOrder::ascending;
+ m_cachedMsgKey = nsMsgKey_None;
+ m_currentlyDisplayedMsgKey = nsMsgKey_None;
+ m_currentlyDisplayedViewIndex = nsMsgViewIndex_None;
+ mNumSelectedRows = 0;
+ mSuppressMsgDisplay = false;
+ mSuppressCommandUpdating = false;
+ mSuppressChangeNotification = false;
+ mSummarizeFailed = false;
+ mSelectionSummarized = false;
+
+ mIsNews = false;
+ mIsRss = false;
+ mIsXFVirtual = false;
+ mDeleteModel = nsMsgImapDeleteModels::MoveToTrash;
+ m_deletingRows = false;
+ mNumMessagesRemainingInBatch = 0;
+ mShowSizeInLines = false;
+ mSortThreadsByRoot = false;
+
+ // mCommandsNeedDisablingBecauseOfSelection - A boolean that tell us if we
+ // needed to disable commands because of what's selected. If we're offline
+ // w/o a downloaded msg selected, or a dummy message was selected.
+ mCommandsNeedDisablingBecauseOfSelection = false;
+ mRemovingRow = false;
+ m_saveRestoreSelectionDepth = 0;
+ mRecentlyDeletedArrayIndex = 0;
+}
+
+void nsMsgDBView::InitializeLiterals() {
+ // Priority strings.
+ GetString(u"priorityHighest", kHighestPriorityString);
+ GetString(u"priorityHigh", kHighPriorityString);
+ GetString(u"priorityLowest", kLowestPriorityString);
+ GetString(u"priorityLow", kLowPriorityString);
+ GetString(u"priorityNormal", kNormalPriorityString);
+
+ GetString(u"read", kReadString);
+ GetString(u"replied", kRepliedString);
+ GetString(u"forwarded", kForwardedString);
+ GetString(u"redirected", kRedirectedString);
+ GetString(u"new", kNewString);
+
+ GetString(u"today", kTodayString);
+ GetString(u"yesterday", kYesterdayString);
+ GetString(u"last7Days", kLastWeekString);
+ GetString(u"last14Days", kTwoWeeksAgoString);
+ GetString(u"older", kOldMailString);
+ GetString(u"futureDate", kFutureDateString);
+}
+
+nsMsgDBView::~nsMsgDBView() {
+ if (m_db) m_db->RemoveListener(this);
+}
+
+// Helper function used to fetch strings from the messenger string bundle
+void nsMsgDBView::GetString(const char16_t* aStringName, nsAString& aValue) {
+ nsresult res = NS_ERROR_UNEXPECTED;
+
+ if (!nsMsgDBView::mMessengerStringBundle) {
+ static const char propertyURL[] = MESSENGER_STRING_URL;
+ nsCOMPtr<nsIStringBundleService> sBundleService =
+ mozilla::components::StringBundle::Service();
+
+ if (sBundleService)
+ res = sBundleService->CreateBundle(
+ propertyURL, getter_AddRefs(nsMsgDBView::mMessengerStringBundle));
+ }
+
+ if (nsMsgDBView::mMessengerStringBundle)
+ res = mMessengerStringBundle->GetStringFromName(
+ NS_ConvertUTF16toUTF8(aStringName).get(), aValue);
+
+ if (NS_FAILED(res)) {
+ aValue = aStringName;
+ }
+}
+
+// Helper function used to fetch localized strings from the prefs
+nsresult nsMsgDBView::GetPrefLocalizedString(const char* aPrefName,
+ nsString& aResult) {
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIPrefBranch> prefBranch;
+ nsCOMPtr<nsIPrefLocalizedString> pls;
+ nsString ucsval;
+
+ prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = prefBranch->GetComplexValue(
+ aPrefName, NS_GET_IID(nsIPrefLocalizedString), getter_AddRefs(pls));
+ NS_ENSURE_SUCCESS(rv, rv);
+ pls->ToString(getter_Copies(ucsval));
+ aResult = ucsval.get();
+ return rv;
+}
+
+nsresult nsMsgDBView::AppendKeywordProperties(const nsACString& keywords,
+ nsAString& properties,
+ bool* tagAdded) {
+ *tagAdded = false;
+ // Get the top most keyword's CSS selector and append that as a property.
+ nsresult rv;
+ if (!mTagService) {
+ mTagService = do_GetService("@mozilla.org/messenger/tagservice;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ nsCString topKey;
+ rv = mTagService->GetTopKey(keywords, topKey);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (topKey.IsEmpty()) return NS_OK;
+
+ nsString selector;
+ rv = mTagService->GetSelectorForKey(topKey, selector);
+ if (NS_SUCCEEDED(rv)) {
+ *tagAdded = true;
+ properties.Append(' ');
+ properties.Append(selector);
+ }
+ return rv;
+}
+
+///////////////////////////////////////////////////////////////////////////
+// nsITreeView Implementation Methods (and helper methods)
+///////////////////////////////////////////////////////////////////////////
+
+static nsresult GetDisplayNameInAddressBook(const nsACString& emailAddress,
+ nsAString& displayName) {
+ nsresult rv;
+ nsCOMPtr<nsIAbManager> abManager(
+ do_GetService("@mozilla.org/abmanager;1", &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbCard> cardForAddress;
+ rv = abManager->CardForEmailAddress(emailAddress,
+ getter_AddRefs(cardForAddress));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (cardForAddress) {
+ bool preferDisplayName = true;
+ rv = cardForAddress->GetPropertyAsBool("PreferDisplayName", true,
+ &preferDisplayName);
+
+ if (NS_FAILED(rv) || preferDisplayName)
+ rv = cardForAddress->GetDisplayName(displayName);
+ }
+
+ return rv;
+}
+
+/*
+ * The unparsedString has following format:
+ * "version|displayname"
+ */
+static void GetCachedName(const nsCString& unparsedString,
+ int32_t displayVersion, nsACString& cachedName) {
+ nsresult err;
+
+ // Get version #.
+ int32_t cachedVersion = unparsedString.ToInteger(&err);
+ if (cachedVersion != displayVersion) return;
+
+ // Get cached name.
+ int32_t pos = unparsedString.FindChar('|');
+ if (pos != kNotFound) cachedName = Substring(unparsedString, pos + 1);
+}
+
+static void UpdateCachedName(nsIMsgDBHdr* aHdr, const char* header_field,
+ const nsAString& newName) {
+ nsCString newCachedName;
+ nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID));
+ int32_t currentDisplayNameVersion = 0;
+
+ prefs->GetIntPref("mail.displayname.version", &currentDisplayNameVersion);
+
+ // Save version number.
+ newCachedName.AppendInt(currentDisplayNameVersion);
+ newCachedName.Append('|');
+
+ // Save name.
+ newCachedName.Append(NS_ConvertUTF16toUTF8(newName));
+
+ aHdr->SetStringProperty(header_field, newCachedName);
+}
+
+nsresult nsMsgDBView::FetchAuthor(nsIMsgDBHdr* aHdr, nsAString& aSenderString) {
+ nsCString unparsedAuthor;
+ bool showCondensedAddresses = false;
+ int32_t currentDisplayNameVersion = 0;
+ nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID));
+
+ prefs->GetIntPref("mail.displayname.version", &currentDisplayNameVersion);
+ prefs->GetBoolPref("mail.showCondensedAddresses", &showCondensedAddresses);
+
+ aHdr->GetStringProperty("sender_name", unparsedAuthor);
+
+ // If the author is already computed, use it.
+ if (!unparsedAuthor.IsEmpty()) {
+ nsCString cachedDisplayName;
+ GetCachedName(unparsedAuthor, currentDisplayNameVersion, cachedDisplayName);
+ if (!cachedDisplayName.IsEmpty()) {
+ CopyUTF8toUTF16(cachedDisplayName, aSenderString);
+ return NS_OK;
+ }
+ }
+
+ nsCString author;
+ (void)aHdr->GetAuthor(getter_Copies(author));
+
+ nsCString headerCharset;
+ aHdr->GetEffectiveCharset(headerCharset);
+
+ nsString name;
+ nsCString emailAddress;
+ nsCOMArray<msgIAddressObject> addresses =
+ EncodedHeader(author, headerCharset.get());
+ bool multipleAuthors = addresses.Length() > 1;
+
+ ExtractFirstAddress(addresses, name, emailAddress);
+
+ if (showCondensedAddresses)
+ GetDisplayNameInAddressBook(emailAddress, aSenderString);
+
+ if (aSenderString.IsEmpty()) {
+ // We can't use the display name in the card; use the name contained in
+ // the header or email address.
+ if (name.IsEmpty()) {
+ CopyUTF8toUTF16(emailAddress, aSenderString);
+ } else {
+ int32_t atPos;
+ if ((atPos = name.FindChar('@')) == kNotFound ||
+ name.FindChar('.', atPos) == kNotFound) {
+ aSenderString = name;
+ } else {
+ // Found @ followed by a dot, so this looks like a spoofing case.
+ aSenderString = name;
+ aSenderString.AppendLiteral(" <");
+ AppendUTF8toUTF16(emailAddress, aSenderString);
+ aSenderString.Append('>');
+ }
+ }
+ }
+
+ if (multipleAuthors) {
+ aSenderString.AppendLiteral(" ");
+ nsAutoString val;
+ GetString(u"andOthers", val);
+ aSenderString.Append(val);
+ }
+
+ UpdateCachedName(aHdr, "sender_name", aSenderString);
+
+ return NS_OK;
+}
+
+nsresult nsMsgDBView::FetchAccount(nsIMsgDBHdr* aHdr, nsAString& aAccount) {
+ nsCString accountKey;
+ nsresult rv = aHdr->GetAccountKey(getter_Copies(accountKey));
+
+ // Cache the account manager?
+ nsCOMPtr<nsIMsgAccountManager> accountManager(
+ do_GetService("@mozilla.org/messenger/account-manager;1", &rv));
+
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIMsgAccount> account;
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ if (!accountKey.IsEmpty())
+ rv = accountManager->GetAccount(accountKey, getter_AddRefs(account));
+
+ if (account) {
+ account->GetIncomingServer(getter_AddRefs(server));
+ } else {
+ nsCOMPtr<nsIMsgFolder> folder;
+ aHdr->GetFolder(getter_AddRefs(folder));
+ if (folder) folder->GetServer(getter_AddRefs(server));
+ }
+
+ if (server)
+ server->GetPrettyName(aAccount);
+ else
+ CopyASCIItoUTF16(accountKey, aAccount);
+
+ return NS_OK;
+}
+
+nsresult nsMsgDBView::FetchRecipients(nsIMsgDBHdr* aHdr,
+ nsAString& aRecipientsString) {
+ nsCString recipients;
+ int32_t currentDisplayNameVersion = 0;
+ bool showCondensedAddresses = false;
+ nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID));
+
+ prefs->GetIntPref("mail.displayname.version", &currentDisplayNameVersion);
+ prefs->GetBoolPref("mail.showCondensedAddresses", &showCondensedAddresses);
+
+ aHdr->GetStringProperty("recipient_names", recipients);
+
+ if (!recipients.IsEmpty()) {
+ nsCString cachedRecipients;
+ GetCachedName(recipients, currentDisplayNameVersion, cachedRecipients);
+
+ // Recipients have already been cached, check if the addressbook
+ // was changed after cache.
+ if (!cachedRecipients.IsEmpty()) {
+ CopyUTF8toUTF16(cachedRecipients, aRecipientsString);
+ return NS_OK;
+ }
+ }
+
+ nsCString unparsedRecipients;
+ nsresult rv = aHdr->GetRecipients(getter_Copies(unparsedRecipients));
+
+ nsCString headerCharset;
+ aHdr->GetEffectiveCharset(headerCharset);
+
+ nsTArray<nsString> names;
+ nsTArray<nsCString> emails;
+ ExtractAllAddresses(EncodedHeader(unparsedRecipients, headerCharset.get()),
+ names, UTF16ArrayAdapter<>(emails));
+
+ uint32_t numAddresses = names.Length();
+
+ nsCOMPtr<nsIAbManager> abManager(
+ do_GetService("@mozilla.org/abmanager;1", &rv));
+
+ NS_ENSURE_SUCCESS(rv, NS_OK);
+
+ // Go through each email address in the recipients and compute its
+ // display name.
+ for (uint32_t i = 0; i < numAddresses; i++) {
+ nsString recipient;
+ nsCString& curAddress = emails[i];
+ nsString& curName = names[i];
+
+ if (showCondensedAddresses)
+ GetDisplayNameInAddressBook(curAddress, recipient);
+
+ if (recipient.IsEmpty()) {
+ // We can't use the display name in the card; use the name contained in
+ // the header or email address.
+ if (curName.IsEmpty()) {
+ CopyUTF8toUTF16(curAddress, recipient);
+ } else {
+ int32_t atPos;
+ if ((atPos = curName.FindChar('@')) == kNotFound ||
+ curName.FindChar('.', atPos) == kNotFound) {
+ recipient = curName;
+ } else {
+ // Found @ followed by a dot, so this looks like a spoofing case.
+ recipient = curName;
+ recipient.AppendLiteral(" <");
+ AppendUTF8toUTF16(curAddress, recipient);
+ recipient.Append('>');
+ }
+ }
+ }
+
+ // Add ', ' between each recipient.
+ if (i != 0) aRecipientsString.AppendLiteral(u", ");
+
+ aRecipientsString.Append(recipient);
+ }
+
+ if (numAddresses == 0 && unparsedRecipients.FindChar(':') != kNotFound) {
+ // No addresses and a colon, so an empty group like
+ // "undisclosed-recipients: ;".
+ // Add group name so at least something displays.
+ nsString group;
+ CopyUTF8toUTF16(unparsedRecipients, group);
+ aRecipientsString.Assign(group);
+ }
+
+ UpdateCachedName(aHdr, "recipient_names", aRecipientsString);
+
+ return NS_OK;
+}
+
+nsresult nsMsgDBView::FetchSubject(nsIMsgDBHdr* aMsgHdr, uint32_t aFlags,
+ nsAString& aValue) {
+ if (aFlags & nsMsgMessageFlags::HasRe) {
+ nsString subject;
+ aMsgHdr->GetMime2DecodedSubject(subject);
+ aValue.AssignLiteral("Re: ");
+ aValue.Append(subject);
+ } else {
+ aMsgHdr->GetMime2DecodedSubject(aValue);
+ }
+
+ return NS_OK;
+}
+
+// In case we want to play around with the date string, I've broken it out into
+// a separate routine. Set rcvDate to true to get the Received: date instead
+// of the Date: date.
+nsresult nsMsgDBView::FetchDate(nsIMsgDBHdr* aHdr, nsAString& aDateString,
+ bool rcvDate) {
+ PRTime dateOfMsg;
+ PRTime dateOfMsgLocal;
+ uint32_t rcvDateSecs;
+ nsresult rv;
+
+ // Silently return Date: instead if Received: is unavailable.
+ if (rcvDate) {
+ rv = aHdr->GetUint32Property("dateReceived", &rcvDateSecs);
+ if (rcvDateSecs != 0) Seconds2PRTime(rcvDateSecs, &dateOfMsg);
+ }
+
+ if (!rcvDate || rcvDateSecs == 0) rv = aHdr->GetDate(&dateOfMsg);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ PRTime currentTime = PR_Now();
+ PRExplodedTime explodedCurrentTime;
+ PR_ExplodeTime(currentTime, PR_LocalTimeParameters, &explodedCurrentTime);
+ PRExplodedTime explodedMsgTime;
+ PR_ExplodeTime(dateOfMsg, PR_LocalTimeParameters, &explodedMsgTime);
+
+ // If the message is from today, don't show the date, only the time (3:15 pm).
+ // If the message is from the last week, show the day of the week
+ // (Mon 3:15 pm). In all other cases, show the full date (03/19/01 3:15 pm).
+
+ nsDateFormatSelectorComm dateFormat = m_dateFormatDefault;
+ if (explodedCurrentTime.tm_year == explodedMsgTime.tm_year &&
+ explodedCurrentTime.tm_month == explodedMsgTime.tm_month &&
+ explodedCurrentTime.tm_mday == explodedMsgTime.tm_mday) {
+ // Same day.
+ dateFormat = m_dateFormatToday;
+ } else if (currentTime > dateOfMsg) {
+ // The following chunk of code allows us to show a day instead of a number
+ // if the message was received within the last 7 days. i.e. Mon 5:10pm
+ // (depending on the mail.ui.display.dateformat.thisweek pref).
+ // The concrete format used is dependent on a preference setting
+ // (see InitDisplayFormats).
+ // Convert the times from GMT to local time
+ int64_t GMTLocalTimeShift =
+ PR_USEC_PER_SEC * int64_t(explodedCurrentTime.tm_params.tp_gmt_offset +
+ explodedCurrentTime.tm_params.tp_dst_offset);
+ currentTime += GMTLocalTimeShift;
+ dateOfMsgLocal = dateOfMsg + GMTLocalTimeShift;
+
+ // Find the most recent midnight.
+ int64_t todaysMicroSeconds = currentTime % PR_USEC_PER_DAY;
+ int64_t mostRecentMidnight = currentTime - todaysMicroSeconds;
+
+ // Most recent midnight minus 6 days.
+ int64_t mostRecentWeek = mostRecentMidnight - (PR_USEC_PER_DAY * 6);
+
+ // Was the message sent during the last week?
+ if (dateOfMsgLocal >= mostRecentWeek) dateFormat = m_dateFormatThisWeek;
+ }
+
+ mozilla::intl::DateTimeFormat::StyleBag style;
+ style.time = mozilla::Some(mozilla::intl::DateTimeFormat::Style::Short);
+ switch (dateFormat) {
+ case kDateFormatNone:
+ rv = mozilla::intl::AppDateTimeFormat::Format(style, dateOfMsg,
+ aDateString);
+ NS_ENSURE_SUCCESS(rv, rv);
+ break;
+ case kDateFormatLong:
+ style.date = mozilla::Some(mozilla::intl::DateTimeFormat::Style::Long);
+ rv = mozilla::intl::AppDateTimeFormat::Format(style, dateOfMsg,
+ aDateString);
+ NS_ENSURE_SUCCESS(rv, rv);
+ break;
+ case kDateFormatShort:
+ style.date = mozilla::Some(mozilla::intl::DateTimeFormat::Style::Short);
+ rv = mozilla::intl::AppDateTimeFormat::Format(style, dateOfMsg,
+ aDateString);
+ NS_ENSURE_SUCCESS(rv, rv);
+ break;
+ case kDateFormatWeekday: {
+ // We want weekday + time.
+ nsAutoString timeString;
+ nsAutoString weekdayString;
+ rv = mozilla::intl::AppDateTimeFormat::Format(style, dateOfMsg,
+ timeString);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mozilla::intl::DateTimeFormat::ComponentsBag components{};
+ components.weekday =
+ mozilla::Some(mozilla::intl::DateTimeFormat::Text::Short);
+ rv = mozilla::intl::AppDateTimeFormat::Format(
+ components, &explodedMsgTime, weekdayString);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (nsMsgDBView::m_connectorPattern.IsEmpty()) {
+ nsAutoCString locale;
+ AutoTArray<nsCString, 10> regionalPrefsLocales;
+ mozilla::intl::LocaleService::GetInstance()->GetRegionalPrefsLocales(
+ regionalPrefsLocales);
+ locale.Assign(regionalPrefsLocales[0]);
+ nsAutoCString str;
+ mozilla::intl::OSPreferences::GetInstance()
+ ->GetDateTimeConnectorPattern(locale, str);
+ nsMsgDBView::m_connectorPattern = NS_ConvertUTF8toUTF16(str);
+ }
+
+ nsAutoString pattern(nsMsgDBView::m_connectorPattern);
+ int32_t ind = pattern.Find(u"{1}"_ns);
+ if (ind != kNotFound) {
+ pattern.Replace(ind, 3, weekdayString);
+ }
+ ind = pattern.Find(u"{0}"_ns);
+ if (ind != kNotFound) {
+ pattern.Replace(ind, 3, timeString);
+ }
+ aDateString = pattern;
+ break;
+ }
+
+ default:
+ break;
+ }
+
+ return rv;
+}
+
+nsresult nsMsgDBView::FetchStatus(uint32_t aFlags, nsAString& aStatusString) {
+ if (aFlags & nsMsgMessageFlags::Replied)
+ aStatusString = kRepliedString;
+ else if (aFlags & nsMsgMessageFlags::Forwarded)
+ aStatusString = kForwardedString;
+ else if (aFlags & nsMsgMessageFlags::Redirected)
+ aStatusString = kRedirectedString;
+ else if (aFlags & nsMsgMessageFlags::New)
+ aStatusString = kNewString;
+ else if (aFlags & nsMsgMessageFlags::Read)
+ aStatusString = kReadString;
+
+ return NS_OK;
+}
+
+nsresult nsMsgDBView::FetchSize(nsIMsgDBHdr* aHdr, nsAString& aSizeString) {
+ nsresult rv;
+ nsAutoString formattedSizeString;
+ uint32_t msgSize = 0;
+
+ // For news, show the line count, not the size if the user wants so.
+ if (mShowSizeInLines) {
+ aHdr->GetLineCount(&msgSize);
+ formattedSizeString.AppendInt(msgSize);
+ } else {
+ uint32_t flags = 0;
+
+ aHdr->GetFlags(&flags);
+ if (flags & nsMsgMessageFlags::Partial)
+ aHdr->GetUint32Property("onlineSize", &msgSize);
+
+ if (msgSize == 0) aHdr->GetMessageSize(&msgSize);
+
+ rv = FormatFileSize(msgSize, true, formattedSizeString);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ aSizeString = formattedSizeString;
+ // The formattingString Length includes the null terminator byte!
+ if (!formattedSizeString.Last())
+ aSizeString.SetLength(formattedSizeString.Length() - 1);
+
+ return NS_OK;
+}
+
+nsresult nsMsgDBView::FetchPriority(nsIMsgDBHdr* aHdr,
+ nsAString& aPriorityString) {
+ nsMsgPriorityValue priority = nsMsgPriority::notSet;
+ aHdr->GetPriority(&priority);
+
+ switch (priority) {
+ case nsMsgPriority::highest:
+ aPriorityString = kHighestPriorityString;
+ break;
+ case nsMsgPriority::high:
+ aPriorityString = kHighPriorityString;
+ break;
+ case nsMsgPriority::low:
+ aPriorityString = kLowPriorityString;
+ break;
+ case nsMsgPriority::lowest:
+ aPriorityString = kLowestPriorityString;
+ break;
+ case nsMsgPriority::normal:
+ aPriorityString = kNormalPriorityString;
+ break;
+ default:
+ break;
+ }
+
+ return NS_OK;
+}
+
+nsresult nsMsgDBView::FetchKeywords(nsIMsgDBHdr* aHdr,
+ nsACString& keywordString) {
+ NS_ENSURE_ARG_POINTER(aHdr);
+ nsresult rv = NS_OK;
+ if (!mTagService) {
+ mTagService = do_GetService("@mozilla.org/messenger/tagservice;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ nsCString keywords;
+ aHdr->GetStringProperty("keywords", keywords);
+ keywordString = keywords;
+ return NS_OK;
+}
+
+// If the row is a collapsed thread, we optionally roll-up the keywords in all
+// the messages in the thread, otherwise, return just the keywords for the row.
+nsresult nsMsgDBView::FetchRowKeywords(nsMsgViewIndex aRow, nsIMsgDBHdr* aHdr,
+ nsACString& keywordString) {
+ nsresult rv = FetchKeywords(aHdr, keywordString);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool cascadeKeywordsUp = true;
+ nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID));
+ prefs->GetBoolPref("mailnews.display_reply_tag_colors_for_collapsed_threads",
+ &cascadeKeywordsUp);
+
+ if ((m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay) &&
+ cascadeKeywordsUp) {
+ if ((m_flags[aRow] & MSG_VIEW_FLAG_ISTHREAD) &&
+ (m_flags[aRow] & nsMsgMessageFlags::Elided)) {
+ nsCOMPtr<nsIMsgThread> thread;
+ rv = GetThreadContainingIndex(aRow, getter_AddRefs(thread));
+ if (NS_SUCCEEDED(rv) && thread) {
+ uint32_t numChildren;
+ thread->GetNumChildren(&numChildren);
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ nsCString moreKeywords;
+ for (uint32_t index = 0; index < numChildren; index++) {
+ thread->GetChildHdrAt(index, getter_AddRefs(msgHdr));
+ rv = FetchKeywords(msgHdr, moreKeywords);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!keywordString.IsEmpty() && !moreKeywords.IsEmpty())
+ keywordString.Append(' ');
+
+ keywordString.Append(moreKeywords);
+ }
+ }
+ }
+ }
+
+ return rv;
+}
+
+nsresult nsMsgDBView::FetchTags(nsIMsgDBHdr* aHdr, nsAString& aTagString) {
+ NS_ENSURE_ARG_POINTER(aHdr);
+ nsresult rv = NS_OK;
+ if (!mTagService) {
+ mTagService = do_GetService("@mozilla.org/messenger/tagservice;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ nsString tags;
+ nsCString keywords;
+ aHdr->GetStringProperty("keywords", keywords);
+
+ nsTArray<nsCString> keywordsArray;
+ ParseString(keywords, ' ', keywordsArray);
+ nsAutoString tag;
+
+ for (uint32_t i = 0; i < keywordsArray.Length(); i++) {
+ rv = mTagService->GetTagForKey(keywordsArray[i], tag);
+ if (NS_SUCCEEDED(rv) && !tag.IsEmpty()) {
+ if (!tags.IsEmpty()) tags.Append((char16_t)' ');
+
+ tags.Append(tag);
+ }
+ }
+
+ aTagString = tags;
+ return NS_OK;
+}
+
+/**
+ * Lowercase the email and remove a possible plus addressing part.
+ * E.g. John+test@example.com -> john@example.com.
+ */
+static void ToLowerCaseDropPlusAddessing(nsCString& aEmail) {
+ ToLowerCase(aEmail);
+ int32_t indPlus;
+ if ((indPlus = aEmail.FindChar('+')) == kNotFound) return;
+ int32_t indAt;
+ indAt = aEmail.FindChar('@', indPlus);
+ if (indAt == kNotFound) return;
+ aEmail.ReplaceLiteral(indPlus, indAt - indPlus, "");
+}
+
+bool nsMsgDBView::IsOutgoingMsg(nsIMsgDBHdr* aHdr) {
+ nsString author;
+ aHdr->GetMime2DecodedAuthor(author);
+
+ nsCString emailAddress;
+ nsString name;
+ ExtractFirstAddress(DecodedHeader(author), name, emailAddress);
+ ToLowerCaseDropPlusAddessing(emailAddress);
+ return mEmails.Contains(emailAddress);
+}
+
+// If you call SaveAndClearSelection make sure to call RestoreSelection(),
+// otherwise m_saveRestoreSelectionDepth will be incorrect and will lead to
+// selection msg problems.
+nsresult nsMsgDBView::SaveAndClearSelection(nsMsgKey* aCurrentMsgKey,
+ nsTArray<nsMsgKey>& aMsgKeyArray) {
+ // Always return a value in the first parameter.
+ if (aCurrentMsgKey) *aCurrentMsgKey = nsMsgKey_None;
+
+ // We don't do anything on nested Save / Restore calls.
+ m_saveRestoreSelectionDepth++;
+ if (m_saveRestoreSelectionDepth != 1) return NS_OK;
+
+ if (!mTreeSelection) return NS_OK;
+
+ // First, freeze selection.
+ mTreeSelection->SetSelectEventsSuppressed(true);
+
+ // Second, save the current index.
+ if (aCurrentMsgKey) {
+ int32_t currentIndex;
+ if (NS_SUCCEEDED(mTreeSelection->GetCurrentIndex(&currentIndex)) &&
+ currentIndex >= 0 && uint32_t(currentIndex) < GetSize())
+ *aCurrentMsgKey = m_keys[currentIndex];
+ else
+ *aCurrentMsgKey = nsMsgKey_None;
+ }
+
+ // Third, get an array of view indices for the selection.
+ nsMsgViewIndexArray selection;
+ GetIndicesForSelection(selection);
+ int32_t numIndices = selection.Length();
+ aMsgKeyArray.SetLength(numIndices);
+
+ // Now store the msg key for each selected item.
+ nsMsgKey msgKey;
+ for (int32_t index = 0; index < numIndices; index++) {
+ msgKey = m_keys[selection[index]];
+ aMsgKeyArray[index] = msgKey;
+ }
+
+ // Clear the selection, we'll manually restore it later.
+ if (mTreeSelection) mTreeSelection->ClearSelection();
+
+ return NS_OK;
+}
+
+nsresult nsMsgDBView::RestoreSelection(nsMsgKey aCurrentMsgKey,
+ nsTArray<nsMsgKey>& aMsgKeyArray) {
+ // We don't do anything on nested Save / Restore calls.
+ m_saveRestoreSelectionDepth--;
+ if (m_saveRestoreSelectionDepth) return NS_OK;
+
+ // Don't assert.
+ if (!mTreeSelection) return NS_OK;
+
+ // Turn our message keys into corresponding view indices.
+ int32_t arraySize = aMsgKeyArray.Length();
+ nsMsgViewIndex currentViewPosition = nsMsgViewIndex_None;
+ nsMsgViewIndex newViewPosition = nsMsgViewIndex_None;
+
+ // If we are threaded, we need to do a little more work
+ // we need to find (and expand) all the threads that contain messages
+ // that we had selected before.
+ if (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay) {
+ for (int32_t index = 0; index < arraySize; index++)
+ FindKey(aMsgKeyArray[index], true /* expand */);
+ }
+
+ for (int32_t index = 0; index < arraySize; index++) {
+ newViewPosition = FindKey(aMsgKeyArray[index], false);
+ // Add the index back to the selection.
+ if (newViewPosition != nsMsgViewIndex_None)
+ mTreeSelection->ToggleSelect(newViewPosition);
+ }
+
+ // Make sure the currentView was preserved.
+ if (aCurrentMsgKey != nsMsgKey_None)
+ currentViewPosition = FindKey(aCurrentMsgKey, true);
+
+ if (mJSTree) mJSTree->SetCurrentIndex(currentViewPosition);
+
+ // Make sure the current message is once again visible in the thread pane
+ // so we don't have to go search for it in the thread pane
+ if (currentViewPosition != nsMsgViewIndex_None) {
+ if (mJSTree) {
+ mJSTree->EnsureRowIsVisible(currentViewPosition);
+ } else if (mTree) {
+ mTree->EnsureRowIsVisible(currentViewPosition);
+ }
+ }
+
+ // Unfreeze selection.
+ mTreeSelection->SetSelectEventsSuppressed(false);
+ return NS_OK;
+}
+
+nsresult nsMsgDBView::GenerateURIForMsgKey(nsMsgKey aMsgKey,
+ nsIMsgFolder* folder,
+ nsACString& aURI) {
+ NS_ENSURE_ARG(folder);
+ return folder->GenerateMessageURI(aMsgKey, aURI);
+}
+
+nsresult nsMsgDBView::GetMessageEnumerator(nsIMsgEnumerator** enumerator) {
+ return m_db->EnumerateMessages(enumerator);
+}
+
+NS_IMETHODIMP
+nsMsgDBView::IsEditable(int32_t row, nsTreeColumn* col, bool* _retval) {
+ NS_ENSURE_ARG_POINTER(col);
+ NS_ENSURE_ARG_POINTER(_retval);
+ // Attempt to retrieve a custom column handler. If it exists call it and
+ // return.
+ const nsAString& colID = col->GetId();
+ nsIMsgCustomColumnHandler* colHandler = GetColumnHandler(colID);
+
+ if (colHandler) {
+ colHandler->IsEditable(row, col, _retval);
+ return NS_OK;
+ }
+
+ *_retval = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBView::SetCellValue(int32_t row, nsTreeColumn* col,
+ const nsAString& value) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBView::SetCellText(int32_t row, nsTreeColumn* col,
+ const nsAString& value) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBView::GetRowCount(int32_t* aRowCount) {
+ *aRowCount = GetSize();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBView::GetSelection(nsITreeSelection** aSelection) {
+ NS_IF_ADDREF(*aSelection = mTreeSelection);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBView::SetSelection(nsITreeSelection* aSelection) {
+ mTreeSelection = aSelection;
+ return NS_OK;
+}
+
+nsresult nsMsgDBView::UpdateDisplayMessage(nsMsgViewIndex viewPosition) {
+ nsCOMPtr<nsIMsgDBViewCommandUpdater> commandUpdater(
+ do_QueryReferent(mCommandUpdater));
+ if (!commandUpdater) return NS_OK;
+
+ if (!IsValidIndex(viewPosition)) return NS_MSG_INVALID_DBVIEW_INDEX;
+
+ // Get the subject and the folder for the message and inform the front
+ // end that we changed the message we are currently displaying.
+ nsresult rv;
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ rv = GetMsgHdrForViewIndex(viewPosition, getter_AddRefs(msgHdr));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsString subject;
+ if (viewPosition >= (nsMsgViewIndex)m_flags.Length())
+ return NS_MSG_INVALID_DBVIEW_INDEX;
+ FetchSubject(msgHdr, m_flags[viewPosition], subject);
+
+ nsCString keywords;
+ rv = msgHdr->GetStringProperty("keywords", keywords);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgFolder> folder = m_viewFolder ? m_viewFolder : m_folder;
+
+ commandUpdater->DisplayMessageChanged(folder, subject, keywords);
+
+ if (folder) {
+ if (viewPosition >= (nsMsgViewIndex)m_keys.Length())
+ return NS_MSG_INVALID_DBVIEW_INDEX;
+ rv = folder->SetLastMessageLoaded(m_keys[viewPosition]);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBView::SelectionChangedXPCOM() {
+ // If the currentSelection changed then we have a message to display -
+ // not if we are in the middle of deleting rows.
+ if (m_deletingRows) return NS_OK;
+
+ nsMsgViewIndexArray selection;
+ GetIndicesForSelection(selection);
+
+ bool commandsNeedDisablingBecauseOfSelection = false;
+
+ if (!selection.IsEmpty()) {
+ if (WeAreOffline())
+ commandsNeedDisablingBecauseOfSelection = !OfflineMsgSelected(selection);
+
+ if (!NonDummyMsgSelected(selection))
+ commandsNeedDisablingBecauseOfSelection = true;
+ }
+
+ bool selectionSummarized = false;
+ mSummarizeFailed = false;
+ // Let the front-end adjust the message pane appropriately with either
+ // the message body, or a summary of the selection.
+ nsCOMPtr<nsIMsgDBViewCommandUpdater> commandUpdater(
+ do_QueryReferent(mCommandUpdater));
+ if (commandUpdater) {
+ commandUpdater->SummarizeSelection(&selectionSummarized);
+ // Check if the selection was not summarized, but we expected it to be,
+ // and if so, remember it so GetHeadersFromSelection won't include
+ // the messages in collapsed threads.
+ if (!selectionSummarized &&
+ (selection.Length() > 1 ||
+ (selection.Length() == 1 &&
+ m_flags[selection[0]] & nsMsgMessageFlags::Elided &&
+ OperateOnMsgsInCollapsedThreads()))) {
+ mSummarizeFailed = true;
+ }
+ }
+
+ bool summaryStateChanged = selectionSummarized != mSelectionSummarized;
+ mSelectionSummarized = selectionSummarized;
+
+ if (!mTreeSelection || selection.Length() != 1 || selectionSummarized) {
+ // If we have zero or multiple items selected, we shouldn't be displaying
+ // any message.
+ m_currentlyDisplayedMsgKey = nsMsgKey_None;
+ m_currentlyDisplayedMsgUri.Truncate();
+ m_currentlyDisplayedViewIndex = nsMsgViewIndex_None;
+ }
+
+ // Determine if we need to push command update notifications out to the UI.
+ // We need to push a command update notification iff, one of the following
+ // conditions are met
+ // (1) the selection went from 0 to 1
+ // (2) it went from 1 to 0
+ // (3) it went from 1 to many
+ // (4) it went from many to 1 or 0
+ // (5) a different msg was selected - perhaps it was offline or not,
+ // matters only when we are offline
+ // (6) we did a forward/back, or went from having no history to having
+ // history - not sure how to tell this.
+ // (7) whether the selection was summarized or not changed.
+
+ // I think we're going to need to keep track of whether forward/back were
+ // enabled/should be enabled, and when this changes, force a command update.
+
+ if (!summaryStateChanged &&
+ (selection.Length() == mNumSelectedRows ||
+ (selection.Length() > 1 && mNumSelectedRows > 1)) &&
+ commandsNeedDisablingBecauseOfSelection ==
+ mCommandsNeedDisablingBecauseOfSelection) {
+ // Don't update commands if we're suppressing them, or if we're removing
+ // rows, unless it was the last row.
+ } else if (!mSuppressCommandUpdating && commandUpdater &&
+ (!mRemovingRow || GetSize() == 0)) {
+ commandUpdater->UpdateCommandStatus();
+ }
+
+ mCommandsNeedDisablingBecauseOfSelection =
+ commandsNeedDisablingBecauseOfSelection;
+ mNumSelectedRows = selection.Length();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBView::GetRowProperties(int32_t index, nsAString& properties) {
+ if (!IsValidIndex(index)) return NS_MSG_INVALID_DBVIEW_INDEX;
+
+ // This is where we tell the tree to apply styles to a particular row.
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ nsresult rv = NS_OK;
+
+ rv = GetMsgHdrForViewIndex(index, getter_AddRefs(msgHdr));
+
+ if (NS_FAILED(rv) || !msgHdr) {
+ ClearHdrCache();
+ return NS_MSG_INVALID_DBVIEW_INDEX;
+ }
+
+ if (IsOutgoingMsg(msgHdr)) properties.AppendLiteral(" outgoing");
+
+ nsCString keywordProperty;
+ FetchRowKeywords(index, msgHdr, keywordProperty);
+ bool tagAdded = false;
+ if (!keywordProperty.IsEmpty()) {
+ AppendKeywordProperties(keywordProperty, properties, &tagAdded);
+ }
+ if (tagAdded) {
+ properties.AppendLiteral(" tagged");
+ } else {
+ properties.AppendLiteral(" untagged");
+ }
+
+ uint32_t flags;
+ msgHdr->GetFlags(&flags);
+
+ if (!(flags & nsMsgMessageFlags::Read))
+ properties.AppendLiteral(" unread");
+ else
+ properties.AppendLiteral(" read");
+
+ if (flags & nsMsgMessageFlags::Replied) properties.AppendLiteral(" replied");
+
+ if (flags & nsMsgMessageFlags::Forwarded)
+ properties.AppendLiteral(" forwarded");
+
+ if (flags & nsMsgMessageFlags::Redirected)
+ properties.AppendLiteral(" redirected");
+
+ if (flags & nsMsgMessageFlags::New) properties.AppendLiteral(" new");
+
+ if (m_flags[index] & nsMsgMessageFlags::Marked)
+ properties.AppendLiteral(" flagged");
+
+ // Give the custom column handlers a chance to style the row.
+ for (int i = 0; i < m_customColumnHandlers.Count(); i++) {
+ nsString extra;
+ m_customColumnHandlers[i]->GetRowProperties(index, extra);
+ if (!extra.IsEmpty()) {
+ properties.Append(' ');
+ properties.Append(extra);
+ }
+ }
+
+ // For threaded display add the ignoreSubthread property to the
+ // subthread top row (this row). For non-threaded add it to all rows.
+ if (!(m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay) &&
+ (flags & nsMsgMessageFlags::Ignored)) {
+ properties.AppendLiteral(" ignoreSubthread");
+ } else {
+ bool ignored;
+ msgHdr->GetIsKilled(&ignored);
+ if (ignored) properties.AppendLiteral(" ignoreSubthread");
+ }
+
+ nsCOMPtr<nsIMsgLocalMailFolder> localFolder = do_QueryInterface(m_folder);
+
+ if ((flags & nsMsgMessageFlags::Offline) ||
+ (localFolder && !(flags & nsMsgMessageFlags::Partial)))
+ properties.AppendLiteral(" offline");
+
+ if (flags & nsMsgMessageFlags::Attachment)
+ properties.AppendLiteral(" attach");
+
+ if ((mDeleteModel == nsMsgImapDeleteModels::IMAPDelete) &&
+ (flags & nsMsgMessageFlags::IMAPDeleted))
+ properties.AppendLiteral(" imapdeleted");
+
+ nsCString imageSize;
+ msgHdr->GetStringProperty("imageSize", imageSize);
+ if (!imageSize.IsEmpty()) properties.AppendLiteral(" hasimage");
+
+ nsCString junkScoreStr;
+ msgHdr->GetStringProperty("junkscore", junkScoreStr);
+ if (!junkScoreStr.IsEmpty()) {
+ if (junkScoreStr.ToInteger(&rv) == nsIJunkMailPlugin::IS_SPAM_SCORE)
+ properties.AppendLiteral(" junk");
+ else
+ properties.AppendLiteral(" notjunk");
+
+ NS_ASSERTION(NS_SUCCEEDED(rv), "Converting junkScore to integer failed.");
+ }
+
+ nsCOMPtr<nsIMsgThread> thread;
+ rv = GetThreadContainingIndex(index, getter_AddRefs(thread));
+ if (NS_SUCCEEDED(rv) && thread) {
+ uint32_t numUnreadChildren;
+ thread->GetNumUnreadChildren(&numUnreadChildren);
+ if (numUnreadChildren > 0) properties.AppendLiteral(" hasUnread");
+
+ // For threaded display add the ignore/watch properties to the
+ // thread top row. For non-threaded add it to all rows.
+ if (!(m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay) ||
+ ((m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay) &&
+ (m_flags[index] & MSG_VIEW_FLAG_ISTHREAD))) {
+ thread->GetFlags(&flags);
+ if (flags & nsMsgMessageFlags::Watched)
+ properties.AppendLiteral(" watch");
+ if (flags & nsMsgMessageFlags::Ignored)
+ properties.AppendLiteral(" ignore");
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBView::GetColumnProperties(nsTreeColumn* col, nsAString& properties) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBView::GetCellProperties(int32_t aRow, nsTreeColumn* col,
+ nsAString& properties) {
+ if (!IsValidIndex(aRow)) return NS_MSG_INVALID_DBVIEW_INDEX;
+
+ // This is where we tell the tree to apply styles to a particular row
+ // i.e. if the row is an unread message...
+
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ nsresult rv = NS_OK;
+
+ rv = GetMsgHdrForViewIndex(aRow, getter_AddRefs(msgHdr));
+
+ if (NS_FAILED(rv) || !msgHdr) {
+ ClearHdrCache();
+ return NS_MSG_INVALID_DBVIEW_INDEX;
+ }
+
+ const nsAString& colID = col->GetId();
+ nsIMsgCustomColumnHandler* colHandler = GetColumnHandler(colID);
+ if (colHandler != nullptr) {
+ colHandler->GetCellProperties(aRow, col, properties);
+ } else if (colID[0] == 'c') {
+ // Correspondent.
+ if (IsOutgoingMsg(msgHdr))
+ properties.AssignLiteral("outgoing");
+ else
+ properties.AssignLiteral("incoming");
+ }
+
+ if (!properties.IsEmpty()) properties.Append(' ');
+
+ properties.Append(mMessageType);
+
+ uint32_t flags;
+ msgHdr->GetFlags(&flags);
+
+ if (!(flags & nsMsgMessageFlags::Read))
+ properties.AppendLiteral(" unread");
+ else
+ properties.AppendLiteral(" read");
+
+ if (flags & nsMsgMessageFlags::Replied) properties.AppendLiteral(" replied");
+
+ if (flags & nsMsgMessageFlags::Forwarded)
+ properties.AppendLiteral(" forwarded");
+
+ if (flags & nsMsgMessageFlags::Redirected)
+ properties.AppendLiteral(" redirected");
+
+ if (flags & nsMsgMessageFlags::New) properties.AppendLiteral(" new");
+
+ if (m_flags[aRow] & nsMsgMessageFlags::Marked)
+ properties.AppendLiteral(" flagged");
+
+ // For threaded display add the ignoreSubthread property to the
+ // subthread top row (this row). For non-threaded add it to all rows.
+ if (!(m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay) &&
+ (flags & nsMsgMessageFlags::Ignored)) {
+ properties.AppendLiteral(" ignoreSubthread");
+ } else {
+ bool ignored;
+ msgHdr->GetIsKilled(&ignored);
+ if (ignored) properties.AppendLiteral(" ignoreSubthread");
+ }
+
+ nsCOMPtr<nsIMsgLocalMailFolder> localFolder = do_QueryInterface(m_folder);
+
+ if ((flags & nsMsgMessageFlags::Offline) ||
+ (localFolder && !(flags & nsMsgMessageFlags::Partial)))
+ properties.AppendLiteral(" offline");
+
+ if (flags & nsMsgMessageFlags::Attachment)
+ properties.AppendLiteral(" attach");
+
+ if ((mDeleteModel == nsMsgImapDeleteModels::IMAPDelete) &&
+ (flags & nsMsgMessageFlags::IMAPDeleted))
+ properties.AppendLiteral(" imapdeleted");
+
+ nsCString imageSize;
+ msgHdr->GetStringProperty("imageSize", imageSize);
+ if (!imageSize.IsEmpty()) properties.AppendLiteral(" hasimage");
+
+ nsCString junkScoreStr;
+ msgHdr->GetStringProperty("junkscore", junkScoreStr);
+ if (!junkScoreStr.IsEmpty()) {
+ if (junkScoreStr.ToInteger(&rv) == nsIJunkMailPlugin::IS_SPAM_SCORE)
+ properties.AppendLiteral(" junk");
+ else
+ properties.AppendLiteral(" notjunk");
+
+ NS_ASSERTION(NS_SUCCEEDED(rv), "Converting junkScore to integer failed.");
+ }
+
+ nsCString keywords;
+ FetchRowKeywords(aRow, msgHdr, keywords);
+ bool tagAdded = false;
+ if (!keywords.IsEmpty()) {
+ AppendKeywordProperties(keywords, properties, &tagAdded);
+ }
+ if (tagAdded) {
+ properties.AppendLiteral(" tagged");
+ } else {
+ properties.AppendLiteral(" untagged");
+ }
+
+ // This is a double fetch of the keywords property since we also fetch
+ // it for the tags - do we want to do this?
+ // I'm not sure anyone uses the kw- property, though it could be nice
+ // for people wanting to extend the thread pane.
+ nsCString keywordProperty;
+ msgHdr->GetStringProperty("keywords", keywordProperty);
+ if (!keywordProperty.IsEmpty()) {
+ NS_ConvertUTF8toUTF16 keywords(keywordProperty);
+ int32_t spaceIndex = 0;
+ do {
+ spaceIndex = keywords.FindChar(' ');
+ int32_t endOfKeyword =
+ (spaceIndex == -1) ? keywords.Length() : spaceIndex;
+ properties.AppendLiteral(" kw-");
+ properties.Append(StringHead(keywords, endOfKeyword));
+ if (spaceIndex > 0) keywords.Cut(0, endOfKeyword + 1);
+ } while (spaceIndex > 0);
+ }
+
+#ifdef SUPPORT_PRIORITY_COLORS
+ // Add special styles for priority.
+ nsMsgPriorityValue priority;
+ msgHdr->GetPriority(&priority);
+ switch (priority) {
+ case nsMsgPriority::highest:
+ properties.append(" priority-highest");
+ break;
+ case nsMsgPriority::high:
+ properties.append(" priority-high");
+ break;
+ case nsMsgPriority::low:
+ properties.append(" priority-low");
+ break;
+ case nsMsgPriority::lowest:
+ properties.append(" priority-lowest");
+ break;
+ default:
+ break;
+ }
+#endif
+
+ nsCOMPtr<nsIMsgThread> thread;
+ rv = GetThreadContainingIndex(aRow, getter_AddRefs(thread));
+ if (NS_SUCCEEDED(rv) && thread) {
+ uint32_t numUnreadChildren;
+ thread->GetNumUnreadChildren(&numUnreadChildren);
+ if (numUnreadChildren > 0) properties.AppendLiteral(" hasUnread");
+
+ // For threaded display add the ignore/watch properties to the
+ // thread top row. For non-threaded add it to all rows.
+ if (!(m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay) ||
+ ((m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay) &&
+ (m_flags[aRow] & MSG_VIEW_FLAG_ISTHREAD))) {
+ thread->GetFlags(&flags);
+ if (flags & nsMsgMessageFlags::Watched)
+ properties.AppendLiteral(" watch");
+ if (flags & nsMsgMessageFlags::Ignored)
+ properties.AppendLiteral(" ignore");
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBView::IsContainer(int32_t index, bool* _retval) {
+ if (!IsValidIndex(index)) return NS_MSG_INVALID_DBVIEW_INDEX;
+
+ if (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay) {
+ uint32_t flags = m_flags[index];
+ *_retval = !!(flags & MSG_VIEW_FLAG_HASCHILDREN);
+ } else {
+ *_retval = false;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBView::IsContainerOpen(int32_t index, bool* _retval) {
+ if (!IsValidIndex(index)) return NS_MSG_INVALID_DBVIEW_INDEX;
+
+ if (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay) {
+ uint32_t flags = m_flags[index];
+ *_retval = (flags & MSG_VIEW_FLAG_HASCHILDREN) &&
+ !(flags & nsMsgMessageFlags::Elided);
+ } else {
+ *_retval = false;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBView::IsContainerEmpty(int32_t index, bool* _retval) {
+ if (!IsValidIndex(index)) return NS_MSG_INVALID_DBVIEW_INDEX;
+
+ if (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay) {
+ uint32_t flags = m_flags[index];
+ *_retval = !(flags & MSG_VIEW_FLAG_HASCHILDREN);
+ } else {
+ *_retval = false;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBView::IsSeparator(int32_t index, bool* _retval) {
+ if (!IsValidIndex(index)) return NS_MSG_INVALID_DBVIEW_INDEX;
+
+ *_retval = false;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBView::GetParentIndex(int32_t rowIndex, int32_t* _retval) {
+ *_retval = -1;
+
+ int32_t rowIndexLevel;
+ nsresult rv = GetLevel(rowIndex, &rowIndexLevel);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t i;
+ for (i = rowIndex; i >= 0; i--) {
+ int32_t l;
+ GetLevel(i, &l);
+ if (l < rowIndexLevel) {
+ *_retval = i;
+ break;
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBView::HasNextSibling(int32_t rowIndex, int32_t afterIndex,
+ bool* _retval) {
+ *_retval = false;
+
+ int32_t rowIndexLevel;
+ GetLevel(rowIndex, &rowIndexLevel);
+
+ int32_t i;
+ int32_t count;
+ GetRowCount(&count);
+ for (i = afterIndex + 1; i < count; i++) {
+ int32_t l;
+ GetLevel(i, &l);
+ if (l < rowIndexLevel) break;
+
+ if (l == rowIndexLevel) {
+ *_retval = true;
+ break;
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBView::GetLevel(int32_t index, int32_t* _retval) {
+ if (!IsValidIndex(index)) return NS_MSG_INVALID_DBVIEW_INDEX;
+
+ if (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay)
+ *_retval = m_levels[index];
+ else
+ *_retval = 0;
+
+ return NS_OK;
+}
+
+// Search view will override this since headers can span db's.
+nsresult nsMsgDBView::GetMsgHdrForViewIndex(nsMsgViewIndex index,
+ nsIMsgDBHdr** msgHdr) {
+ nsresult rv = NS_OK;
+ if (!IsValidIndex(index)) return NS_MSG_INVALID_DBVIEW_INDEX;
+
+ nsMsgKey key = m_keys[index];
+ if (key == nsMsgKey_None || !m_db) return NS_MSG_INVALID_DBVIEW_INDEX;
+
+ if (key == m_cachedMsgKey) {
+ NS_IF_ADDREF(*msgHdr = m_cachedHdr);
+ } else {
+ rv = m_db->GetMsgHdrForKey(key, msgHdr);
+ if (NS_SUCCEEDED(rv)) {
+ m_cachedHdr = *msgHdr;
+ m_cachedMsgKey = key;
+ }
+ }
+
+ return rv;
+}
+
+void nsMsgDBView::InsertMsgHdrAt(nsMsgViewIndex index, nsIMsgDBHdr* hdr,
+ nsMsgKey msgKey, uint32_t flags,
+ uint32_t level) {
+ if ((int32_t)index < 0 || index > m_keys.Length()) {
+ // Something's gone wrong in a caller, but we have no clue why.
+ // Return without adding the header to the view.
+ NS_ERROR("Index for message header insertion out of array range!");
+ return;
+ }
+
+ m_keys.InsertElementAt(index, msgKey);
+ m_flags.InsertElementAt(index, flags);
+ m_levels.InsertElementAt(index, level);
+}
+
+void nsMsgDBView::SetMsgHdrAt(nsIMsgDBHdr* hdr, nsMsgViewIndex index,
+ nsMsgKey msgKey, uint32_t flags, uint32_t level) {
+ m_keys[index] = msgKey;
+ m_flags[index] = flags;
+ m_levels[index] = level;
+}
+
+nsresult nsMsgDBView::GetFolderForViewIndex(nsMsgViewIndex index,
+ nsIMsgFolder** aFolder) {
+ NS_IF_ADDREF(*aFolder = m_folder);
+ return NS_OK;
+}
+
+nsresult nsMsgDBView::GetDBForViewIndex(nsMsgViewIndex index,
+ nsIMsgDatabase** db) {
+ NS_IF_ADDREF(*db = m_db);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBView::GetImageSrc(int32_t aRow, nsTreeColumn* aCol, nsAString& aValue) {
+ NS_ENSURE_ARG_POINTER(aCol);
+ // Attempt to retrieve a custom column handler. If it exists call it and
+ // return.
+ const nsAString& colID = aCol->GetId();
+ nsIMsgCustomColumnHandler* colHandler = GetColumnHandler(colID);
+
+ if (colHandler) {
+ colHandler->GetImageSrc(aRow, aCol, aValue);
+ return NS_OK;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBView::GetCellValue(int32_t aRow, nsTreeColumn* aCol, nsAString& aValue) {
+ if (!IsValidIndex(aRow)) return NS_MSG_INVALID_DBVIEW_INDEX;
+
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ nsresult rv = GetMsgHdrForViewIndex(aRow, getter_AddRefs(msgHdr));
+
+ if (NS_FAILED(rv) || !msgHdr) {
+ ClearHdrCache();
+ return NS_MSG_INVALID_DBVIEW_INDEX;
+ }
+
+ const nsAString& colID = aCol->GetId();
+
+ aValue.Truncate();
+ if (colID.IsEmpty()) return NS_OK;
+
+ uint32_t flags;
+ msgHdr->GetFlags(&flags);
+
+ // Provide a string "value" for cells that do not normally have text.
+ // Use empty string for the normal states "Read", "Not Starred",
+ // "No Attachment" and "Not Junk".
+ switch (colID.First()) {
+ case 'a':
+ if (colID.EqualsLiteral("attachmentCol") &&
+ flags & nsMsgMessageFlags::Attachment) {
+ GetString(u"messageHasAttachment", aValue);
+ }
+ break;
+ case 'f':
+ if (colID.EqualsLiteral("flaggedCol") &&
+ flags & nsMsgMessageFlags::Marked) {
+ GetString(u"messageHasFlag", aValue);
+ }
+ break;
+ case 'j':
+ if (colID.EqualsLiteral("junkStatusCol") && JunkControlsEnabled(aRow)) {
+ nsCString junkScoreStr;
+ msgHdr->GetStringProperty("junkscore", junkScoreStr);
+ // Only need to assign a real value for junk, it's empty already
+ // as it should be for non-junk.
+ if (!junkScoreStr.IsEmpty() &&
+ (junkScoreStr.ToInteger(&rv) == nsIJunkMailPlugin::IS_SPAM_SCORE))
+ aValue.AssignLiteral("messageJunk");
+
+ NS_ASSERTION(NS_SUCCEEDED(rv),
+ "Converting junkScore to integer failed.");
+ }
+ break;
+ case 't':
+ if (colID.EqualsLiteral("threadCol") &&
+ (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay)) {
+ // thread column
+ bool isContainer, isContainerEmpty, isContainerOpen;
+ IsContainer(aRow, &isContainer);
+ if (isContainer) {
+ IsContainerEmpty(aRow, &isContainerEmpty);
+ if (!isContainerEmpty) {
+ IsContainerOpen(aRow, &isContainerOpen);
+ GetString(
+ isContainerOpen ? u"messageExpanded" : u"messageCollapsed",
+ aValue);
+ }
+ }
+ }
+ break;
+ case 'u':
+ if (colID.EqualsLiteral("unreadButtonColHeader") &&
+ !(flags & nsMsgMessageFlags::Read)) {
+ GetString(u"messageUnread", aValue);
+ }
+ break;
+ default:
+ aValue.Assign(colID);
+ break;
+ }
+
+ return rv;
+}
+
+void nsMsgDBView::RememberDeletedMsgHdr(nsIMsgDBHdr* msgHdr) {
+ nsCString messageId;
+ msgHdr->GetMessageId(getter_Copies(messageId));
+ if (mRecentlyDeletedArrayIndex >= mRecentlyDeletedMsgIds.Length())
+ mRecentlyDeletedMsgIds.AppendElement(messageId);
+ else
+ mRecentlyDeletedMsgIds[mRecentlyDeletedArrayIndex] = messageId;
+
+ // Only remember last 20 deleted msgs.
+ mRecentlyDeletedArrayIndex = (mRecentlyDeletedArrayIndex + 1) % 20;
+}
+
+bool nsMsgDBView::WasHdrRecentlyDeleted(nsIMsgDBHdr* msgHdr) {
+ nsCString messageId;
+ msgHdr->GetMessageId(getter_Copies(messageId));
+ return mRecentlyDeletedMsgIds.Contains(messageId);
+}
+
+/**
+ * CUSTOM COLUMNS.
+ */
+
+// Add a custom column handler.
+NS_IMETHODIMP
+nsMsgDBView::AddColumnHandler(const nsAString& column,
+ nsIMsgCustomColumnHandler* handler) {
+ bool custColInSort = false;
+ size_t index = m_customColumnHandlerIDs.IndexOf(column);
+
+ nsAutoString strColID(column);
+
+ // Does not exist.
+ if (index == m_customColumnHandlerIDs.NoIndex) {
+ m_customColumnHandlerIDs.AppendElement(strColID);
+ m_customColumnHandlers.AppendObject(handler);
+ } else {
+ // Insert new handler into the appropriate place in the COMPtr array;
+ // no need to replace the column ID (it's the same).
+ m_customColumnHandlers.ReplaceObjectAt(handler, index);
+ }
+
+ // Check if the column name matches any of the columns in
+ // m_sortColumns, and if so, set m_sortColumns[i].mColHandler
+ for (uint32_t i = 0; i < m_sortColumns.Length(); i++) {
+ MsgViewSortColumnInfo& sortInfo = m_sortColumns[i];
+ if (sortInfo.mSortType == nsMsgViewSortType::byCustom &&
+ sortInfo.mCustomColumnName.Equals(column)) {
+ custColInSort = true;
+ sortInfo.mColHandler = handler;
+ }
+ }
+
+ if (m_viewFlags & nsMsgViewFlagsType::kGroupBySort)
+ // Grouped view has its own ways.
+ return NS_OK;
+
+ // This cust col is in sort columns, and all are now registered, so sort.
+ if (custColInSort && !CustomColumnsInSortAndNotRegistered())
+ Sort(m_sortType, m_sortOrder);
+
+ return NS_OK;
+}
+
+// Remove a custom column handler.
+NS_IMETHODIMP
+nsMsgDBView::RemoveColumnHandler(const nsAString& aColID) {
+ // Here we should check if the column name matches any of the columns in
+ // m_sortColumns, and if so, clear m_sortColumns[i].mColHandler.
+ size_t index = m_customColumnHandlerIDs.IndexOf(aColID);
+
+ if (index != m_customColumnHandlerIDs.NoIndex) {
+ m_customColumnHandlerIDs.RemoveElementAt(index);
+ m_customColumnHandlers.RemoveObjectAt(index);
+ // Check if the column name matches any of the columns in
+ // m_sortColumns, and if so, clear m_sortColumns[i].mColHandler.
+ for (uint32_t i = 0; i < m_sortColumns.Length(); i++) {
+ MsgViewSortColumnInfo& sortInfo = m_sortColumns[i];
+ if (sortInfo.mSortType == nsMsgViewSortType::byCustom &&
+ sortInfo.mCustomColumnName.Equals(aColID))
+ sortInfo.mColHandler = nullptr;
+ }
+
+ return NS_OK;
+ }
+
+ // Can't remove a column that isn't currently custom handled.
+ return NS_ERROR_FAILURE;
+}
+
+// TODO: NS_ENSURE_SUCCESS
+nsIMsgCustomColumnHandler* nsMsgDBView::GetCurColumnHandler() {
+ return GetColumnHandler(m_curCustomColumn);
+}
+
+NS_IMETHODIMP
+nsMsgDBView::SetCurCustomColumn(const nsAString& aColID) {
+ m_curCustomColumn = aColID;
+ if (m_viewFolder) {
+ nsCOMPtr<nsIMsgDatabase> db;
+ nsCOMPtr<nsIDBFolderInfo> folderInfo;
+ nsresult rv = m_viewFolder->GetDBFolderInfoAndDB(getter_AddRefs(folderInfo),
+ getter_AddRefs(db));
+ NS_ENSURE_SUCCESS(rv, rv);
+ folderInfo->SetProperty("customSortCol", aColID);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBView::GetCurCustomColumn(nsAString& result) {
+ result = m_curCustomColumn;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBView::GetSecondaryCustomColumn(nsAString& result) {
+ result = m_secondaryCustomColumn;
+ return NS_OK;
+}
+
+nsIMsgCustomColumnHandler* nsMsgDBView::GetColumnHandler(
+ const nsAString& colID) {
+ size_t index = m_customColumnHandlerIDs.IndexOf(colID);
+ return (index != m_customColumnHandlerIDs.NoIndex)
+ ? m_customColumnHandlers[index]
+ : nullptr;
+}
+
+NS_IMETHODIMP
+nsMsgDBView::GetColumnHandler(const nsAString& aColID,
+ nsIMsgCustomColumnHandler** aHandler) {
+ NS_ENSURE_ARG_POINTER(aHandler);
+ nsAutoString column(aColID);
+ NS_IF_ADDREF(*aHandler = GetColumnHandler(column));
+ return (*aHandler) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+// Check if any active sort columns are custom. If none are custom, return false
+// and go on as always. If any are custom, and all are not registered yet,
+// return true (so that the caller can postpone sort). When the custom column
+// observer is notified with MsgCreateDBView and registers the handler,
+// AddColumnHandler will sort once all required handlers are set.
+bool nsMsgDBView::CustomColumnsInSortAndNotRegistered() {
+ // The initial sort on view open has been started, subsequent user initiated
+ // sort callers can ignore verifying cust col registration.
+ m_checkedCustomColumns = true;
+
+ // DecodeColumnSort must have already created m_sortColumns, otherwise we
+ // can't know, but go on anyway.
+ if (!m_sortColumns.Length()) return false;
+
+ bool custColNotRegistered = false;
+ for (uint32_t i = 0; i < m_sortColumns.Length() && !custColNotRegistered;
+ i++) {
+ if (m_sortColumns[i].mSortType == nsMsgViewSortType::byCustom &&
+ m_sortColumns[i].mColHandler == nullptr)
+ custColNotRegistered = true;
+ }
+
+ return custColNotRegistered;
+}
+// END CUSTOM COLUMNS.
+
+NS_IMETHODIMP
+nsMsgDBView::GetCellText(int32_t aRow, nsTreeColumn* aCol, nsAString& aValue) {
+ const nsAString& colID = aCol->GetId();
+
+ if (!IsValidIndex(aRow)) return NS_MSG_INVALID_DBVIEW_INDEX;
+
+ aValue.Truncate();
+
+ // Attempt to retrieve a custom column handler. If it exists call it and
+ // return.
+ nsIMsgCustomColumnHandler* colHandler = GetColumnHandler(colID);
+
+ if (colHandler) {
+ colHandler->GetCellText(aRow, aCol, aValue);
+ return NS_OK;
+ }
+
+ return CellTextForColumn(aRow, colID, aValue);
+}
+
+NS_IMETHODIMP
+nsMsgDBView::CellTextForColumn(int32_t aRow, const nsAString& aColumnName,
+ nsAString& aValue) {
+ if (aColumnName.IsEmpty()) {
+ aValue.Truncate();
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ nsresult rv = GetMsgHdrForViewIndex(aRow, getter_AddRefs(msgHdr));
+
+ if (NS_FAILED(rv) || !msgHdr) {
+ ClearHdrCache();
+ return NS_MSG_INVALID_DBVIEW_INDEX;
+ }
+
+ nsCOMPtr<nsIMsgThread> thread;
+
+ switch (aColumnName.First()) {
+ case 's':
+ if (aColumnName.EqualsLiteral("subjectCol"))
+ rv = FetchSubject(msgHdr, m_flags[aRow], aValue);
+ else if (aColumnName.EqualsLiteral("senderCol"))
+ rv = FetchAuthor(msgHdr, aValue);
+ else if (aColumnName.EqualsLiteral("sizeCol"))
+ rv = FetchSize(msgHdr, aValue);
+ else if (aColumnName.EqualsLiteral("statusCol")) {
+ uint32_t flags;
+ msgHdr->GetFlags(&flags);
+ rv = FetchStatus(flags, aValue);
+ }
+ break;
+ case 'r':
+ if (aColumnName.EqualsLiteral("recipientCol"))
+ rv = FetchRecipients(msgHdr, aValue);
+ else if (aColumnName.EqualsLiteral("receivedCol"))
+ rv = FetchDate(msgHdr, aValue, true);
+ break;
+ case 'd':
+ if (aColumnName.EqualsLiteral("dateCol")) rv = FetchDate(msgHdr, aValue);
+ break;
+ case 'c':
+ if (aColumnName.EqualsLiteral("correspondentCol")) {
+ if (IsOutgoingMsg(msgHdr))
+ rv = FetchRecipients(msgHdr, aValue);
+ else
+ rv = FetchAuthor(msgHdr, aValue);
+ }
+ break;
+ case 'p':
+ if (aColumnName.EqualsLiteral("priorityCol"))
+ rv = FetchPriority(msgHdr, aValue);
+ break;
+ case 'a':
+ if (aColumnName.EqualsLiteral("accountCol"))
+ rv = FetchAccount(msgHdr, aValue);
+ break;
+ case 't':
+ // total msgs in thread column
+ if (aColumnName.EqualsLiteral("totalCol") &&
+ m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay) {
+ if (m_flags[aRow] & MSG_VIEW_FLAG_ISTHREAD) {
+ rv = GetThreadContainingIndex(aRow, getter_AddRefs(thread));
+ if (NS_SUCCEEDED(rv) && thread) {
+ nsAutoString formattedCountString;
+ uint32_t numChildren;
+ thread->GetNumChildren(&numChildren);
+ formattedCountString.AppendInt(numChildren);
+ aValue.Assign(formattedCountString);
+ }
+ }
+ } else if (aColumnName.EqualsLiteral("tagsCol")) {
+ rv = FetchTags(msgHdr, aValue);
+ }
+ break;
+ case 'u':
+ // unread msgs in thread col
+ if (aColumnName.EqualsLiteral("unreadCol") &&
+ m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay) {
+ if (m_flags[aRow] & MSG_VIEW_FLAG_ISTHREAD) {
+ rv = GetThreadContainingIndex(aRow, getter_AddRefs(thread));
+ if (NS_SUCCEEDED(rv) && thread) {
+ nsAutoString formattedCountString;
+ uint32_t numUnreadChildren;
+ thread->GetNumUnreadChildren(&numUnreadChildren);
+ if (numUnreadChildren > 0) {
+ formattedCountString.AppendInt(numUnreadChildren);
+ aValue.Assign(formattedCountString);
+ }
+ }
+ }
+ }
+ break;
+ case 'j': {
+ if (aColumnName.EqualsLiteral("junkStatusCol")) {
+ nsCString junkScoreStr;
+ msgHdr->GetStringProperty("junkscore", junkScoreStr);
+ CopyASCIItoUTF16(junkScoreStr, aValue);
+ }
+ break;
+ }
+ case 'i': {
+ if (aColumnName.EqualsLiteral("idCol")) {
+ nsAutoString keyString;
+ nsMsgKey key;
+ msgHdr->GetMessageKey(&key);
+ keyString.AppendInt((int64_t)key);
+ aValue.Assign(keyString);
+ }
+ break;
+ }
+ case 'l': {
+ if (aColumnName.EqualsLiteral("locationCol")) {
+ nsCOMPtr<nsIMsgFolder> folder;
+ nsresult rv = GetFolderForViewIndex(aRow, getter_AddRefs(folder));
+ NS_ENSURE_SUCCESS(rv, rv);
+ folder->GetPrettyName(aValue);
+ }
+ break;
+ }
+ default:
+ break;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBView::CellDataForColumns(int32_t aRow,
+ const nsTArray<nsString>& aColumnNames,
+ nsAString& aProperties, int32_t* aThreadLevel,
+ nsTArray<nsString>& _retval) {
+ nsresult rv;
+ _retval.Clear();
+
+ uint32_t count = aColumnNames.Length();
+ _retval.SetCapacity(count);
+ for (nsString column : aColumnNames) {
+ nsString text;
+ rv = CellTextForColumn(aRow, column, text);
+ if (NS_FAILED(rv)) {
+ _retval.Clear();
+ return rv;
+ }
+ _retval.AppendElement(text);
+ }
+
+ rv = GetRowProperties(aRow, aProperties);
+ if (NS_FAILED(rv)) {
+ _retval.Clear();
+ return rv;
+ }
+
+ rv = GetLevel(aRow, aThreadLevel);
+ if (NS_FAILED(rv)) {
+ _retval.Clear();
+ aProperties.Truncate();
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgDBView::SetTree(mozilla::dom::XULTreeElement* tree) {
+ mTree = tree;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBView::SetJSTree(nsIMsgJSTree* tree) {
+ mJSTree = tree;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBView::ToggleOpenState(int32_t index) {
+ uint32_t numChanged;
+ nsresult rv = ToggleExpansion(index, &numChanged);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBView::CycleHeader(nsTreeColumn* aCol) {
+ // Let HandleColumnClick() in threadPane.js handle it
+ // since it will set / clear the sort indicators.
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBView::CycleCell(int32_t row, nsTreeColumn* col) {
+ if (!IsValidIndex(row)) {
+ return NS_MSG_INVALID_DBVIEW_INDEX;
+ }
+
+ const nsAString& colID = col->GetId();
+
+ // Attempt to retrieve a custom column handler. If it exists call it and
+ // return.
+ nsIMsgCustomColumnHandler* colHandler = GetColumnHandler(colID);
+
+ if (colHandler) {
+ colHandler->CycleCell(row, col);
+ return NS_OK;
+ }
+
+ // The cyclers below don't work for the grouped header dummy row, currently.
+ // A future implementation should consider both collapsed and expanded state.
+ if (m_viewFlags & nsMsgViewFlagsType::kGroupBySort &&
+ m_flags[row] & MSG_VIEW_FLAG_DUMMY)
+ return NS_OK;
+
+ if (colID.IsEmpty()) return NS_OK;
+
+ switch (colID.First()) {
+ case 'u':
+ if (colID.EqualsLiteral("unreadButtonColHeader")) {
+ ApplyCommandToIndices(nsMsgViewCommandType::toggleMessageRead,
+ {(nsMsgViewIndex)row});
+ }
+ break;
+ case 't':
+ if (colID.EqualsLiteral("threadCol")) {
+ ExpandAndSelectThreadByIndex(row, false);
+ } else if (colID.EqualsLiteral("tagsCol")) {
+ // XXX Do we want to keep this behaviour but switch it to tags?
+ // We could enumerate over the tags and go to the next one - it looks
+ // to me like this wasn't working before tags landed, so maybe not
+ // worth bothering with.
+ }
+ break;
+ case 'f':
+ if (colID.EqualsLiteral("flaggedCol")) {
+ // toggle the flagged status of the element at row.
+ if (m_flags[row] & nsMsgMessageFlags::Marked) {
+ ApplyCommandToIndices(nsMsgViewCommandType::unflagMessages,
+ {(nsMsgViewIndex)row});
+ } else {
+ ApplyCommandToIndices(nsMsgViewCommandType::flagMessages,
+ {(nsMsgViewIndex)row});
+ }
+ }
+ break;
+ case 'j': {
+ if (!colID.EqualsLiteral("junkStatusCol") || !JunkControlsEnabled(row)) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ nsresult rv = GetMsgHdrForViewIndex(row, getter_AddRefs(msgHdr));
+ if (NS_SUCCEEDED(rv) && msgHdr) {
+ nsCString junkScoreStr;
+ rv = msgHdr->GetStringProperty("junkscore", junkScoreStr);
+ if (junkScoreStr.IsEmpty() ||
+ (junkScoreStr.ToInteger(&rv) == nsIJunkMailPlugin::IS_HAM_SCORE)) {
+ ApplyCommandToIndices(nsMsgViewCommandType::junk,
+ {(nsMsgViewIndex)row});
+ } else {
+ ApplyCommandToIndices(nsMsgViewCommandType::unjunk,
+ {(nsMsgViewIndex)row});
+ }
+ NS_ASSERTION(NS_SUCCEEDED(rv),
+ "Converting junkScore to integer failed.");
+ }
+ break;
+ }
+ default:
+ break;
+ }
+
+ return NS_OK;
+}
+
+///////////////////////////////////////////////////////////////////////////
+// end nsITreeView Implementation Methods
+///////////////////////////////////////////////////////////////////////////
+
+NS_IMETHODIMP
+nsMsgDBView::Open(nsIMsgFolder* folder, nsMsgViewSortTypeValue sortType,
+ nsMsgViewSortOrderValue sortOrder,
+ nsMsgViewFlagsTypeValue viewFlags, int32_t* pCount) {
+ m_viewFlags = viewFlags;
+ m_sortOrder = sortOrder;
+ m_sortType = sortType;
+
+ nsresult rv;
+ nsCOMPtr<nsIMsgAccountManager> accountManager =
+ do_GetService("@mozilla.org/messenger/account-manager;1", &rv);
+
+ NS_ENSURE_SUCCESS(rv, rv);
+ bool userNeedsToAuthenticate = false;
+ // If we're PasswordProtectLocalCache, then we need to find out if the
+ // server is authenticated.
+ (void)accountManager->GetUserNeedsToAuthenticate(&userNeedsToAuthenticate);
+ if (userNeedsToAuthenticate) return NS_MSG_USER_NOT_AUTHENTICATED;
+
+ if (folder) {
+ // Search view will have a null folder.
+ nsCOMPtr<nsIDBFolderInfo> folderInfo;
+ rv = folder->GetDBFolderInfoAndDB(getter_AddRefs(folderInfo),
+ getter_AddRefs(m_db));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIMsgDBService> msgDBService =
+ do_GetService("@mozilla.org/msgDatabase/msgDBService;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ msgDBService->RegisterPendingListener(folder, this);
+ m_folder = folder;
+
+ if (!m_viewFolder) {
+ // There is never a viewFolder already set except for the single folder
+ // saved search case, where the backing folder m_folder is different from
+ // the m_viewFolder with its own dbFolderInfo state.
+ m_viewFolder = folder;
+ }
+
+ SetMRUTimeForFolder(m_viewFolder);
+
+ RestoreSortInfo();
+
+ // Determine if we are in a news folder or not. If yes, we'll show lines
+ // instead of size, and special icons in the thread pane.
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = folder->GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCString type;
+ rv = server->GetType(type);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // I'm not sure this is correct, because XF virtual folders with mixed news
+ // and mail can have this set.
+ mIsNews = type.LowerCaseEqualsLiteral("nntp");
+
+ // Default to a virtual folder if folder not set, since synthetic search
+ // views may not have a folder.
+ uint32_t folderFlags = nsMsgFolderFlags::Virtual;
+ if (folder) folder->GetFlags(&folderFlags);
+
+ mIsXFVirtual = folderFlags & nsMsgFolderFlags::Virtual;
+ if (!mIsXFVirtual && type.LowerCaseEqualsLiteral("rss")) mIsRss = true;
+
+ // Special case nntp --> news since we'll break themes if we try to be
+ // consistent.
+ if (mIsNews)
+ mMessageType.AssignLiteral("news");
+ else
+ CopyUTF8toUTF16(type, mMessageType);
+
+ GetImapDeleteModel(nullptr);
+
+ nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID));
+ if (prefs) {
+ prefs->GetBoolPref("mailnews.sort_threads_by_root", &mSortThreadsByRoot);
+ if (mIsNews)
+ prefs->GetBoolPref("news.show_size_in_lines", &mShowSizeInLines);
+ }
+ }
+
+ nsTArray<RefPtr<nsIMsgIdentity>> identities;
+ rv = accountManager->GetAllIdentities(identities);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (auto identity : identities) {
+ if (!identity) continue;
+
+ nsCString email;
+ identity->GetEmail(email);
+ if (!email.IsEmpty()) {
+ ToLowerCaseDropPlusAddessing(email);
+ mEmails.PutEntry(email);
+ }
+
+ identity->GetReplyTo(email);
+ if (!email.IsEmpty()) {
+ ToLowerCaseDropPlusAddessing(email);
+ mEmails.PutEntry(email);
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBView::Close() {
+ int32_t oldSize = GetSize();
+ // This is important, because the tree will ask us for our row count, which
+ // gets determined from the number of keys.
+ m_keys.Clear();
+ // Be consistent.
+ m_flags.Clear();
+ m_levels.Clear();
+
+ // Clear these out since they no longer apply if we're switching a folder
+ mJunkHdrs.Clear();
+
+ // This needs to happen after we remove all the keys, since RowCountChanged()
+ // will call our GetRowCount().
+ if (mTree) mTree->RowCountChanged(0, -oldSize);
+ if (mJSTree) mJSTree->RowCountChanged(0, -oldSize);
+
+ ClearHdrCache();
+ if (m_db) {
+ m_db->RemoveListener(this);
+ m_db = nullptr;
+ }
+ if (m_folder) {
+ nsresult rv;
+ nsCOMPtr<nsIMsgDBService> msgDBService =
+ do_GetService("@mozilla.org/msgDatabase/msgDBService;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ msgDBService->UnregisterPendingListener(this);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBView::OpenWithHdrs(nsIMsgEnumerator* aHeaders,
+ nsMsgViewSortTypeValue aSortType,
+ nsMsgViewSortOrderValue aSortOrder,
+ nsMsgViewFlagsTypeValue aViewFlags, int32_t* aCount) {
+ NS_ASSERTION(false, "not implemented");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsMsgDBView::Init(nsIMessenger* aMessengerInstance, nsIMsgWindow* aMsgWindow,
+ nsIMsgDBViewCommandUpdater* aCmdUpdater) {
+ mMessengerWeak = do_GetWeakReference(aMessengerInstance);
+ mMsgWindowWeak = do_GetWeakReference(aMsgWindow);
+ mCommandUpdater = do_GetWeakReference(aCmdUpdater);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBView::SetSuppressCommandUpdating(bool aSuppressCommandUpdating) {
+ mSuppressCommandUpdating = aSuppressCommandUpdating;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBView::GetSuppressCommandUpdating(bool* aSuppressCommandUpdating) {
+ *aSuppressCommandUpdating = mSuppressCommandUpdating;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBView::SetSuppressMsgDisplay(bool aSuppressDisplay) {
+ mSuppressMsgDisplay = aSuppressDisplay;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBView::GetSuppressMsgDisplay(bool* aSuppressDisplay) {
+ *aSuppressDisplay = mSuppressMsgDisplay;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBView::GetUsingLines(bool* aUsingLines) {
+ *aUsingLines = mShowSizeInLines;
+ return NS_OK;
+}
+
+int CompareViewIndices(const void* v1, const void* v2, void*) {
+ nsMsgViewIndex i1 = *(nsMsgViewIndex*)v1;
+ nsMsgViewIndex i2 = *(nsMsgViewIndex*)v2;
+ return i1 - i2;
+}
+
+// Array<nsMsgViewIndex> getIndicesForSelection();
+NS_IMETHODIMP
+nsMsgDBView::GetIndicesForSelection(nsTArray<nsMsgViewIndex>& indices) {
+ indices.Clear();
+ if (mTreeSelection) {
+ int32_t viewSize = GetSize();
+ int32_t count;
+ mTreeSelection->GetCount(&count);
+ indices.SetCapacity(count);
+ int32_t selectionCount;
+ mTreeSelection->GetRangeCount(&selectionCount);
+ for (int32_t i = 0; i < selectionCount; i++) {
+ int32_t startRange = -1;
+ int32_t endRange = -1;
+ mTreeSelection->GetRangeAt(i, &startRange, &endRange);
+ if (startRange >= 0 && startRange < viewSize) {
+ for (int32_t rangeIndex = startRange;
+ rangeIndex <= endRange && rangeIndex < viewSize; rangeIndex++) {
+ indices.AppendElement(rangeIndex);
+ }
+ }
+ }
+
+ NS_ASSERTION(indices.Length() == uint32_t(count),
+ "selection count is wrong");
+ } else {
+ // If there is no tree selection object then we must be in stand alone
+ // message mode. In that case the selected indices are really just the
+ // current message key.
+ nsMsgViewIndex viewIndex = FindViewIndex(m_currentlyDisplayedMsgKey);
+ if (viewIndex != nsMsgViewIndex_None) indices.AppendElement(viewIndex);
+ }
+
+ return NS_OK;
+}
+
+// Array<nsIMsgDBHdr> getSelectedMsgHdrs();
+NS_IMETHODIMP
+nsMsgDBView::GetSelectedMsgHdrs(nsTArray<RefPtr<nsIMsgDBHdr>>& aResult) {
+ nsMsgViewIndexArray selection;
+ aResult.Clear();
+ nsresult rv = GetIndicesForSelection(selection);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return GetHeadersFromSelection(selection, aResult);
+}
+
+NS_IMETHODIMP
+nsMsgDBView::GetURIsForSelection(nsTArray<nsCString>& uris) {
+ uris.Clear();
+ AutoTArray<RefPtr<nsIMsgDBHdr>, 1> messages;
+ nsresult rv = GetSelectedMsgHdrs(messages);
+ NS_ENSURE_SUCCESS(rv, rv);
+ uris.SetCapacity(messages.Length());
+ for (nsIMsgDBHdr* msgHdr : messages) {
+ nsCString tmpUri;
+ nsCOMPtr<nsIMsgFolder> folder;
+ nsMsgKey msgKey;
+ msgHdr->GetMessageKey(&msgKey);
+ msgHdr->GetFolder(getter_AddRefs(folder));
+ rv = GenerateURIForMsgKey(msgKey, folder, tmpUri);
+ NS_ENSURE_SUCCESS(rv, rv);
+ uris.AppendElement(tmpUri);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBView::GetURIForViewIndex(nsMsgViewIndex index, nsACString& result) {
+ nsresult rv;
+ nsCOMPtr<nsIMsgFolder> folder = m_folder;
+ if (!folder) {
+ rv = GetFolderForViewIndex(index, getter_AddRefs(folder));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (index == nsMsgViewIndex_None || index >= m_flags.Length() ||
+ m_flags[index] & MSG_VIEW_FLAG_DUMMY) {
+ return NS_MSG_INVALID_DBVIEW_INDEX;
+ }
+
+ return GenerateURIForMsgKey(m_keys[index], folder, result);
+}
+
+NS_IMETHODIMP
+nsMsgDBView::DoCommandWithFolder(nsMsgViewCommandTypeValue command,
+ nsIMsgFolder* destFolder) {
+ NS_ENSURE_ARG_POINTER(destFolder);
+
+ nsMsgViewIndexArray selection;
+ GetIndicesForSelection(selection);
+
+ nsresult rv = NS_OK;
+ switch (command) {
+ case nsMsgViewCommandType::copyMessages:
+ case nsMsgViewCommandType::moveMessages:
+ rv = ApplyCommandToIndicesWithFolder(command, selection, destFolder);
+ NoteChange(0, 0, nsMsgViewNotificationCode::none);
+ break;
+ default:
+ NS_ASSERTION(false, "invalid command type");
+ rv = NS_ERROR_UNEXPECTED;
+ break;
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgDBView::DoCommand(nsMsgViewCommandTypeValue command) {
+ nsMsgViewIndexArray selection;
+ GetIndicesForSelection(selection);
+
+ nsCOMPtr<nsIMsgWindow> msgWindow(do_QueryReferent(mMsgWindowWeak));
+
+ nsresult rv = NS_OK;
+ switch (command) {
+ case nsMsgViewCommandType::downloadSelectedForOffline:
+ return DownloadForOffline(msgWindow, selection);
+ case nsMsgViewCommandType::downloadFlaggedForOffline:
+ return DownloadFlaggedForOffline(msgWindow);
+ case nsMsgViewCommandType::markMessagesRead:
+ case nsMsgViewCommandType::markMessagesUnread:
+ case nsMsgViewCommandType::toggleMessageRead:
+ case nsMsgViewCommandType::flagMessages:
+ case nsMsgViewCommandType::unflagMessages:
+ case nsMsgViewCommandType::deleteMsg:
+ case nsMsgViewCommandType::undeleteMsg:
+ case nsMsgViewCommandType::deleteNoTrash:
+ case nsMsgViewCommandType::markThreadRead:
+ case nsMsgViewCommandType::junk:
+ case nsMsgViewCommandType::unjunk:
+ rv = ApplyCommandToIndices(command, selection);
+ NoteChange(0, 0, nsMsgViewNotificationCode::none);
+ break;
+ case nsMsgViewCommandType::selectAll:
+ if (mTreeSelection) {
+ // If in threaded mode, we need to expand all before selecting.
+ if (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay)
+ rv = ExpandAll();
+
+ mTreeSelection->SelectAll();
+ if (mTree) mTree->Invalidate();
+ if (mJSTree) mJSTree->Invalidate();
+ }
+ break;
+ case nsMsgViewCommandType::selectThread:
+ rv = ExpandAndSelectThread();
+ break;
+ case nsMsgViewCommandType::selectFlagged:
+ if (!mTreeSelection) {
+ rv = NS_ERROR_UNEXPECTED;
+ } else {
+ mTreeSelection->SetSelectEventsSuppressed(true);
+ mTreeSelection->ClearSelection();
+ // XXX ExpandAll?
+ uint32_t numIndices = GetSize();
+ for (uint32_t curIndex = 0; curIndex < numIndices; curIndex++) {
+ if (m_flags[curIndex] & nsMsgMessageFlags::Marked)
+ mTreeSelection->ToggleSelect(curIndex);
+ }
+
+ mTreeSelection->SetSelectEventsSuppressed(false);
+ }
+ break;
+ case nsMsgViewCommandType::markAllRead:
+ if (m_folder) {
+ SetSuppressChangeNotifications(true);
+ rv = m_folder->MarkAllMessagesRead(msgWindow);
+ SetSuppressChangeNotifications(false);
+ if (mTree) mTree->Invalidate();
+ if (mJSTree) mJSTree->Invalidate();
+ }
+ break;
+ case nsMsgViewCommandType::toggleThreadWatched:
+ rv = ToggleWatched(selection);
+ break;
+ case nsMsgViewCommandType::expandAll:
+ rv = ExpandAll();
+ m_viewFlags |= nsMsgViewFlagsType::kExpandAll;
+ SetViewFlags(m_viewFlags);
+ if (mTree) mTree->Invalidate();
+ if (mJSTree) mJSTree->Invalidate();
+
+ break;
+ case nsMsgViewCommandType::collapseAll:
+ rv = CollapseAll();
+ m_viewFlags &= ~nsMsgViewFlagsType::kExpandAll;
+ SetViewFlags(m_viewFlags);
+ if (mTree) mTree->Invalidate();
+ if (mJSTree) mJSTree->Invalidate();
+
+ break;
+ default:
+ NS_ASSERTION(false, "invalid command type");
+ rv = NS_ERROR_UNEXPECTED;
+ break;
+ }
+
+ return rv;
+}
+
+bool nsMsgDBView::ServerSupportsFilterAfterTheFact() {
+ // Cross folder virtual folders might not have a folder set.
+ if (!m_folder) return false;
+
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ nsresult rv = m_folder->GetServer(getter_AddRefs(server));
+ // Unexpected.
+ if (NS_FAILED(rv)) return false;
+
+ // Filter after the fact is implement using search so if you can't search,
+ // you can't filter after the fact.
+ bool canSearch;
+ rv = server->GetCanSearchMessages(&canSearch);
+ // Unexpected.
+ if (NS_FAILED(rv)) return false;
+
+ return canSearch;
+}
+
+NS_IMETHODIMP
+nsMsgDBView::GetCommandStatus(nsMsgViewCommandTypeValue command,
+ bool* selectable_p,
+ nsMsgViewCommandCheckStateValue* selected_p) {
+ nsresult rv = NS_OK;
+
+ bool haveSelection;
+ int32_t rangeCount;
+ nsMsgViewIndexArray selection;
+ GetIndicesForSelection(selection);
+ // If range count is non-zero, we have at least one item selected, so we
+ // have a selection.
+ if (mTreeSelection &&
+ NS_SUCCEEDED(mTreeSelection->GetRangeCount(&rangeCount)) &&
+ rangeCount > 0) {
+ haveSelection = NonDummyMsgSelected(selection);
+ } else {
+ // If we don't have a tree selection we must be in stand alone mode.
+ haveSelection = IsValidIndex(m_currentlyDisplayedViewIndex);
+ }
+
+ switch (command) {
+ case nsMsgViewCommandType::deleteMsg:
+ case nsMsgViewCommandType::deleteNoTrash: {
+ bool canDelete;
+ if (m_folder &&
+ NS_SUCCEEDED(m_folder->GetCanDeleteMessages(&canDelete)) &&
+ !canDelete) {
+ *selectable_p = false;
+ } else {
+ *selectable_p = haveSelection;
+ }
+ break;
+ }
+ case nsMsgViewCommandType::applyFilters:
+ // Disable if no messages.
+ // XXX todo, check that we have filters, and at least one is enabled.
+ *selectable_p = GetSize();
+ if (*selectable_p) *selectable_p = ServerSupportsFilterAfterTheFact();
+
+ break;
+ case nsMsgViewCommandType::runJunkControls:
+ // Disable if no messages.
+ // XXX todo, check that we have JMC enabled?
+ *selectable_p = GetSize() && JunkControlsEnabled(nsMsgViewIndex_None);
+ break;
+ case nsMsgViewCommandType::deleteJunk: {
+ // Disable if no messages, or if we can't delete (like news and
+ // certain imap folders).
+ bool canDelete;
+ *selectable_p =
+ GetSize() && m_folder &&
+ NS_SUCCEEDED(m_folder->GetCanDeleteMessages(&canDelete)) && canDelete;
+ break;
+ }
+ case nsMsgViewCommandType::markMessagesRead:
+ case nsMsgViewCommandType::markMessagesUnread:
+ case nsMsgViewCommandType::toggleMessageRead:
+ case nsMsgViewCommandType::flagMessages:
+ case nsMsgViewCommandType::unflagMessages:
+ case nsMsgViewCommandType::toggleThreadWatched:
+ case nsMsgViewCommandType::markThreadRead:
+ case nsMsgViewCommandType::downloadSelectedForOffline:
+ *selectable_p = haveSelection;
+ break;
+ case nsMsgViewCommandType::junk:
+ case nsMsgViewCommandType::unjunk:
+ *selectable_p = haveSelection && !selection.IsEmpty() &&
+ JunkControlsEnabled(selection[0]);
+ break;
+ case nsMsgViewCommandType::cmdRequiringMsgBody:
+ *selectable_p =
+ haveSelection && (!WeAreOffline() || OfflineMsgSelected(selection));
+ break;
+ case nsMsgViewCommandType::downloadFlaggedForOffline:
+ case nsMsgViewCommandType::markAllRead:
+ *selectable_p = true;
+ break;
+ default:
+ NS_ASSERTION(false, "invalid command type");
+ rv = NS_ERROR_FAILURE;
+ }
+
+ return rv;
+}
+
+// This method needs to be overridden by the various view classes
+// that have different kinds of threads. For example, in a
+// threaded quick search db view, we'd only want to include children
+// of the thread that fit the view (IMO). And when we have threaded
+// cross folder views, we would include all the children of the
+// cross-folder thread.
+nsresult nsMsgDBView::ListCollapsedChildren(
+ nsMsgViewIndex viewIndex, nsTArray<RefPtr<nsIMsgDBHdr>>& messageArray) {
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ nsCOMPtr<nsIMsgThread> thread;
+ GetMsgHdrForViewIndex(viewIndex, getter_AddRefs(msgHdr));
+ if (!msgHdr) {
+ NS_ASSERTION(false, "couldn't find message to expand");
+ return NS_MSG_MESSAGE_NOT_FOUND;
+ }
+
+ nsresult rv = GetThreadContainingMsgHdr(msgHdr, getter_AddRefs(thread));
+ NS_ENSURE_SUCCESS(rv, rv);
+ uint32_t numChildren;
+ thread->GetNumChildren(&numChildren);
+ for (uint32_t i = 1; i < numChildren && NS_SUCCEEDED(rv); i++) {
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ rv = thread->GetChildHdrAt(i, getter_AddRefs(msgHdr));
+ if (msgHdr) {
+ messageArray.AppendElement(msgHdr);
+ }
+ }
+
+ return rv;
+}
+
+bool nsMsgDBView::OperateOnMsgsInCollapsedThreads() {
+ if (!mJSTree && mTreeSelection) {
+ RefPtr<mozilla::dom::XULTreeElement> selTree;
+ mTreeSelection->GetTree(getter_AddRefs(selTree));
+ // No tree means stand-alone message window.
+ if (!selTree) return false;
+ }
+
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIPrefBranch> prefBranch(
+ do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, false);
+
+ bool includeCollapsedMsgs = false;
+ prefBranch->GetBoolPref("mail.operate_on_msgs_in_collapsed_threads",
+ &includeCollapsedMsgs);
+ return includeCollapsedMsgs;
+}
+
+nsresult nsMsgDBView::GetHeadersFromSelection(
+ nsTArray<nsMsgViewIndex> const& selection,
+ nsTArray<RefPtr<nsIMsgDBHdr>>& hdrs) {
+ hdrs.Clear();
+ hdrs.SetCapacity(selection.Length()); // Best guess.
+ nsresult rv = NS_OK;
+
+ // Don't include collapsed messages if the front end failed to summarize
+ // the selection.
+ bool includeCollapsedMsgs =
+ OperateOnMsgsInCollapsedThreads() && !mSummarizeFailed;
+
+ for (nsMsgViewIndex viewIndex : selection) {
+ if (NS_FAILED(rv)) {
+ break;
+ }
+ if (viewIndex == nsMsgViewIndex_None) {
+ continue;
+ }
+
+ uint32_t viewIndexFlags = m_flags[viewIndex];
+ if (viewIndexFlags & MSG_VIEW_FLAG_DUMMY) {
+ // If collapsed dummy header selected, list its children.
+ if (includeCollapsedMsgs && viewIndexFlags & nsMsgMessageFlags::Elided &&
+ m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay)
+ rv = ListCollapsedChildren(viewIndex, hdrs);
+
+ continue;
+ }
+
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ rv = GetMsgHdrForViewIndex(viewIndex, getter_AddRefs(msgHdr));
+ if (NS_SUCCEEDED(rv) && msgHdr) {
+ hdrs.AppendElement(msgHdr);
+ if (includeCollapsedMsgs && viewIndexFlags & nsMsgMessageFlags::Elided &&
+ viewIndexFlags & MSG_VIEW_FLAG_HASCHILDREN &&
+ m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay) {
+ rv = ListCollapsedChildren(viewIndex, hdrs);
+ }
+ }
+ }
+
+ return rv;
+}
+
+nsresult nsMsgDBView::CopyMessages(nsIMsgWindow* window,
+ nsTArray<nsMsgViewIndex> const& selection,
+ bool isMove, nsIMsgFolder* destFolder) {
+ if (m_deletingRows) {
+ NS_ASSERTION(false, "Last move did not complete");
+ return NS_OK;
+ }
+
+ nsresult rv;
+ NS_ENSURE_ARG_POINTER(destFolder);
+
+ AutoTArray<RefPtr<nsIMsgDBHdr>, 1> hdrs;
+ rv = GetHeadersFromSelection(selection, hdrs);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ m_deletingRows = isMove && mDeleteModel != nsMsgImapDeleteModels::IMAPDelete;
+ if (m_deletingRows) {
+ mIndicesToNoteChange.AppendElements(selection);
+ }
+
+ nsCOMPtr<nsIMsgCopyService> copyService =
+ do_GetService("@mozilla.org/messenger/messagecopyservice;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return copyService->CopyMessages(m_folder /* source folder */, hdrs,
+ destFolder, isMove, nullptr /* listener */,
+ window, true /* allow Undo */);
+}
+
+nsresult nsMsgDBView::ApplyCommandToIndicesWithFolder(
+ nsMsgViewCommandTypeValue command,
+ nsTArray<nsMsgViewIndex> const& selection, nsIMsgFolder* destFolder) {
+ nsresult rv = NS_OK;
+ NS_ENSURE_ARG_POINTER(destFolder);
+
+ nsCOMPtr<nsIMsgWindow> msgWindow(do_QueryReferent(mMsgWindowWeak));
+ switch (command) {
+ case nsMsgViewCommandType::copyMessages:
+ NS_ASSERTION(!(m_folder == destFolder),
+ "The source folder and the destination folder are the same");
+ if (m_folder != destFolder)
+ rv = CopyMessages(msgWindow, selection, false /* isMove */, destFolder);
+
+ break;
+ case nsMsgViewCommandType::moveMessages:
+ NS_ASSERTION(!(m_folder == destFolder),
+ "The source folder and the destination folder are the same");
+ if (m_folder != destFolder)
+ rv = CopyMessages(msgWindow, selection, true /* isMove */, destFolder);
+
+ break;
+ default:
+ NS_ASSERTION(false, "unhandled command");
+ rv = NS_ERROR_UNEXPECTED;
+ break;
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgDBView::ApplyCommandToIndices(nsMsgViewCommandTypeValue command,
+ nsTArray<nsMsgViewIndex> const& selection) {
+ if (selection.IsEmpty()) {
+ // Return quietly, just in case/
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIMsgFolder> folder;
+ nsresult rv = GetFolderForViewIndex(selection[0], getter_AddRefs(folder));
+ nsCOMPtr<nsIMsgWindow> msgWindow(do_QueryReferent(mMsgWindowWeak));
+ if (command == nsMsgViewCommandType::deleteMsg)
+ return DeleteMessages(msgWindow, selection, false);
+
+ if (command == nsMsgViewCommandType::deleteNoTrash)
+ return DeleteMessages(msgWindow, selection, true);
+
+ nsTArray<nsMsgKey> imapUids;
+ nsCOMPtr<nsIMsgImapMailFolder> imapFolder = do_QueryInterface(folder);
+ bool thisIsImapFolder = (imapFolder != nullptr);
+ nsCOMPtr<nsIJunkMailPlugin> junkPlugin;
+
+ // If this is a junk command, get the junk plugin.
+ if (command == nsMsgViewCommandType::junk ||
+ command == nsMsgViewCommandType::unjunk) {
+ // Get the folder from the first item; we assume that
+ // all messages in the view are from the same folder (no
+ // more junk status column in the 'search messages' dialog
+ // like in earlier versions...).
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = folder->GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgFilterPlugin> filterPlugin;
+ rv = server->GetSpamFilterPlugin(getter_AddRefs(filterPlugin));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ junkPlugin = do_QueryInterface(filterPlugin, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ folder->EnableNotifications(nsIMsgFolder::allMessageCountNotifications,
+ false);
+
+ // No sense going through the code that handles messages in collasped threads
+ // for mark thread read.
+ if (command == nsMsgViewCommandType::markThreadRead) {
+ for (nsMsgViewIndex viewIndex : selection) {
+ SetThreadOfMsgReadByIndex(viewIndex, imapUids, true);
+ }
+ } else {
+ // Turn the selection into an array of msg hdrs. This may include messages
+ // in collapsed threads
+ AutoTArray<RefPtr<nsIMsgDBHdr>, 1> messages;
+ rv = GetHeadersFromSelection(selection, messages);
+ NS_ENSURE_SUCCESS(rv, rv);
+ uint32_t length = messages.Length();
+
+ if (thisIsImapFolder) {
+ imapUids.SetLength(length);
+ }
+
+ for (uint32_t i = 0; i < length; i++) {
+ nsMsgKey msgKey;
+ nsCOMPtr<nsIMsgDBHdr> msgHdr(messages[i]);
+ msgHdr->GetMessageKey(&msgKey);
+ if (thisIsImapFolder) imapUids[i] = msgKey;
+
+ switch (command) {
+ case nsMsgViewCommandType::junk:
+ mNumMessagesRemainingInBatch++;
+ mJunkHdrs.AppendElement(msgHdr);
+ rv = SetMsgHdrJunkStatus(junkPlugin.get(), msgHdr,
+ nsIJunkMailPlugin::JUNK);
+ break;
+ case nsMsgViewCommandType::unjunk:
+ mNumMessagesRemainingInBatch++;
+ mJunkHdrs.AppendElement(msgHdr);
+ rv = SetMsgHdrJunkStatus(junkPlugin.get(), msgHdr,
+ nsIJunkMailPlugin::GOOD);
+ break;
+ case nsMsgViewCommandType::toggleMessageRead:
+ case nsMsgViewCommandType::undeleteMsg:
+ case nsMsgViewCommandType::markMessagesRead:
+ case nsMsgViewCommandType::markMessagesUnread:
+ case nsMsgViewCommandType::unflagMessages:
+ case nsMsgViewCommandType::flagMessages:
+ // This is completely handled in the code below.
+ break;
+ default:
+ NS_ERROR("unhandled command");
+ break;
+ }
+ }
+
+ switch (command) {
+ case nsMsgViewCommandType::toggleMessageRead: {
+ if (messages.IsEmpty()) break;
+
+ uint32_t msgFlags;
+ messages[0]->GetFlags(&msgFlags);
+ folder->MarkMessagesRead(messages,
+ !(msgFlags & nsMsgMessageFlags::Read));
+ break;
+ }
+ case nsMsgViewCommandType::markMessagesRead:
+ case nsMsgViewCommandType::markMessagesUnread:
+ folder->MarkMessagesRead(
+ messages, command == nsMsgViewCommandType::markMessagesRead);
+ break;
+ case nsMsgViewCommandType::unflagMessages:
+ case nsMsgViewCommandType::flagMessages:
+ folder->MarkMessagesFlagged(
+ messages, command == nsMsgViewCommandType::flagMessages);
+ break;
+ default:
+ break;
+ }
+
+ // Provide junk-related batch notifications.
+ if (command == nsMsgViewCommandType::junk ||
+ command == nsMsgViewCommandType::unjunk) {
+ nsCOMPtr<nsIMsgFolderNotificationService> notifier(
+ do_GetService("@mozilla.org/messenger/msgnotificationservice;1"));
+ if (notifier) {
+ notifier->NotifyMsgsJunkStatusChanged(messages);
+ }
+ }
+ }
+
+ folder->EnableNotifications(nsIMsgFolder::allMessageCountNotifications, true);
+
+ if (thisIsImapFolder) {
+ imapMessageFlagsType flags = kNoImapMsgFlag;
+ bool addFlags = false;
+ nsCOMPtr<nsIMsgWindow> msgWindow(do_QueryReferent(mMsgWindowWeak));
+ switch (command) {
+ case nsMsgViewCommandType::markThreadRead:
+ flags |= kImapMsgSeenFlag;
+ addFlags = true;
+ break;
+ case nsMsgViewCommandType::undeleteMsg:
+ flags = kImapMsgDeletedFlag;
+ addFlags = false;
+ break;
+ case nsMsgViewCommandType::junk:
+ return imapFolder->StoreCustomKeywords(msgWindow, "Junk"_ns,
+ "NonJunk"_ns, imapUids, nullptr);
+ case nsMsgViewCommandType::unjunk: {
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ GetHdrForFirstSelectedMessage(getter_AddRefs(msgHdr));
+ uint32_t msgFlags = 0;
+ if (msgHdr) msgHdr->GetFlags(&msgFlags);
+
+ if (msgFlags & nsMsgMessageFlags::IMAPDeleted)
+ imapFolder->StoreImapFlags(kImapMsgDeletedFlag, false, imapUids,
+ nullptr);
+
+ return imapFolder->StoreCustomKeywords(msgWindow, "NonJunk"_ns,
+ "Junk"_ns, imapUids, nullptr);
+ }
+ default:
+ break;
+ }
+
+ // Can't get here without thisIsImapThreadPane == TRUE.
+ if (flags != kNoImapMsgFlag) {
+ imapFolder->StoreImapFlags(flags, addFlags, imapUids, nullptr);
+ }
+ }
+
+ return rv;
+}
+
+/**
+ * View modifications methods by index.
+ */
+
+// This method just removes the specified line from the view. It does
+// NOT delete it from the database.
+nsresult nsMsgDBView::RemoveByIndex(nsMsgViewIndex index) {
+ if (!IsValidIndex(index)) return NS_MSG_INVALID_DBVIEW_INDEX;
+
+ m_keys.RemoveElementAt(index);
+ m_flags.RemoveElementAt(index);
+ m_levels.RemoveElementAt(index);
+
+ // The call to NoteChange() has to happen after we remove the key as
+ // NoteChange() will call RowCountChanged() which will call our GetRowCount().
+ // An example where view is not the listener - D&D messages.
+ if (!m_deletingRows)
+ NoteChange(index, -1, nsMsgViewNotificationCode::insertOrDelete);
+
+ return NS_OK;
+}
+
+nsresult nsMsgDBView::DeleteMessages(nsIMsgWindow* window,
+ nsTArray<nsMsgViewIndex> const& selection,
+ bool deleteStorage) {
+ if (m_deletingRows) {
+ NS_WARNING("Last delete did not complete");
+ return NS_OK;
+ }
+
+ nsresult rv;
+ AutoTArray<RefPtr<nsIMsgDBHdr>, 1> hdrs;
+ rv = GetHeadersFromSelection(selection, hdrs);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ const char* warnCollapsedPref = "mail.warn_on_collapsed_thread_operation";
+ const char* warnShiftDelPref = "mail.warn_on_shift_delete";
+ const char* warnNewsPref = "news.warn_on_delete";
+ const char* warnTrashDelPref = "mail.warn_on_delete_from_trash";
+ const char* activePref = nullptr;
+ nsString warningName;
+ nsCOMPtr<nsIPrefBranch> prefBranch(
+ do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool trashFolder = false;
+ rv = m_folder->GetFlag(nsMsgFolderFlags::Trash, &trashFolder);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (trashFolder) {
+ bool pref = false;
+ prefBranch->GetBoolPref(warnTrashDelPref, &pref);
+ if (pref) {
+ activePref = warnTrashDelPref;
+ warningName.AssignLiteral("confirmMsgDelete.deleteFromTrash.desc");
+ }
+ }
+
+ if (!activePref && (selection.Length() != hdrs.Length())) {
+ bool pref = false;
+ prefBranch->GetBoolPref(warnCollapsedPref, &pref);
+ if (pref) {
+ activePref = warnCollapsedPref;
+ warningName.AssignLiteral("confirmMsgDelete.collapsed.desc");
+ }
+ }
+
+ if (!activePref && deleteStorage && !trashFolder) {
+ bool pref = false;
+ prefBranch->GetBoolPref(warnShiftDelPref, &pref);
+ if (pref) {
+ activePref = warnShiftDelPref;
+ warningName.AssignLiteral("confirmMsgDelete.deleteNoTrash.desc");
+ }
+ }
+
+ if (!activePref && mIsNews) {
+ bool pref = false;
+ prefBranch->GetBoolPref(warnNewsPref, &pref);
+ if (pref) {
+ activePref = warnNewsPref;
+ warningName.AssignLiteral("confirmMsgDelete.deleteNoTrash.desc");
+ }
+ }
+
+ if (activePref) {
+ nsCOMPtr<nsIPrompt> dialog;
+
+ nsCOMPtr<nsIWindowWatcher> wwatch(
+ do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = wwatch->GetNewPrompter(0, getter_AddRefs(dialog));
+ NS_ENSURE_SUCCESS(rv, rv);
+ // "Don't ask..." - unchecked by default.
+ bool dontAsk = false;
+ int32_t buttonPressed = 0;
+
+ nsString dialogTitle;
+ nsString confirmString;
+ nsString checkboxText;
+ nsString buttonApplyNowText;
+ GetString(u"confirmMsgDelete.title", dialogTitle);
+ GetString(u"confirmMsgDelete.dontAsk.label", checkboxText);
+ GetString(u"confirmMsgDelete.delete.label", buttonApplyNowText);
+
+ GetString(warningName.get(), confirmString);
+
+ const uint32_t buttonFlags =
+ (nsIPrompt::BUTTON_TITLE_IS_STRING * nsIPrompt::BUTTON_POS_0) +
+ (nsIPrompt::BUTTON_TITLE_CANCEL * nsIPrompt::BUTTON_POS_1);
+
+ rv = dialog->ConfirmEx(dialogTitle.get(), confirmString.get(), buttonFlags,
+ buttonApplyNowText.get(), nullptr, nullptr,
+ checkboxText.get(), &dontAsk, &buttonPressed);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (buttonPressed) return NS_ERROR_FAILURE;
+
+ if (dontAsk) prefBranch->SetBoolPref(activePref, false);
+ }
+
+ if (!deleteStorage) {
+ rv = m_folder->MarkMessagesRead(hdrs, true);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (mDeleteModel != nsMsgImapDeleteModels::IMAPDelete) {
+ m_deletingRows = true;
+ }
+
+ if (m_deletingRows) {
+ mIndicesToNoteChange.AppendElements(selection);
+ }
+
+ rv = m_folder->DeleteMessages(hdrs, window, deleteStorage, false, nullptr,
+ true /* allow Undo */);
+ if (NS_FAILED(rv)) {
+ m_deletingRows = false;
+ }
+
+ return rv;
+}
+
+nsresult nsMsgDBView::DownloadForOffline(
+ nsIMsgWindow* window, nsTArray<nsMsgViewIndex> const& selection) {
+ nsresult rv = NS_OK;
+ nsTArray<RefPtr<nsIMsgDBHdr>> messages;
+ for (nsMsgViewIndex viewIndex : selection) {
+ nsMsgKey key = m_keys[viewIndex];
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ rv = m_db->GetMsgHdrForKey(key, getter_AddRefs(msgHdr));
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (msgHdr) {
+ uint32_t flags;
+ msgHdr->GetFlags(&flags);
+ if (!(flags & nsMsgMessageFlags::Offline)) {
+ messages.AppendElement(msgHdr);
+ }
+ }
+ }
+
+ m_folder->DownloadMessagesForOffline(messages, window);
+ return rv;
+}
+
+nsresult nsMsgDBView::DownloadFlaggedForOffline(nsIMsgWindow* window) {
+ nsresult rv = NS_OK;
+ nsTArray<RefPtr<nsIMsgDBHdr>> messages;
+ nsCOMPtr<nsIMsgEnumerator> enumerator;
+ rv = GetMessageEnumerator(getter_AddRefs(enumerator));
+ if (NS_SUCCEEDED(rv) && enumerator) {
+ bool hasMore;
+ while (NS_SUCCEEDED(rv = enumerator->HasMoreElements(&hasMore)) &&
+ hasMore) {
+ nsCOMPtr<nsIMsgDBHdr> header;
+ rv = enumerator->GetNext(getter_AddRefs(header));
+ if (header && NS_SUCCEEDED(rv)) {
+ uint32_t flags;
+ header->GetFlags(&flags);
+ if ((flags & nsMsgMessageFlags::Marked) &&
+ !(flags & nsMsgMessageFlags::Offline)) {
+ messages.AppendElement(header);
+ }
+ }
+ }
+ }
+
+ m_folder->DownloadMessagesForOffline(messages, window);
+ return rv;
+}
+
+// Read/unread handling.
+nsresult nsMsgDBView::ToggleReadByIndex(nsMsgViewIndex index) {
+ if (!IsValidIndex(index)) return NS_MSG_INVALID_DBVIEW_INDEX;
+
+ return SetReadByIndex(index, !(m_flags[index] & nsMsgMessageFlags::Read));
+}
+
+nsresult nsMsgDBView::SetReadByIndex(nsMsgViewIndex index, bool read) {
+ nsresult rv;
+
+ if (!IsValidIndex(index)) return NS_MSG_INVALID_DBVIEW_INDEX;
+
+ if (read) {
+ OrExtraFlag(index, nsMsgMessageFlags::Read);
+ // MarkRead() will clear this flag in the db and then call OnKeyChange(),
+ // but because we are the instigator of the change we'll ignore the change.
+ // So we need to clear it in m_flags to keep the db and m_flags in sync.
+ AndExtraFlag(index, ~nsMsgMessageFlags::New);
+ } else {
+ AndExtraFlag(index, ~nsMsgMessageFlags::Read);
+ }
+
+ nsCOMPtr<nsIMsgDatabase> dbToUse;
+ rv = GetDBForViewIndex(index, getter_AddRefs(dbToUse));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = dbToUse->MarkRead(m_keys[index], read, this);
+ NoteChange(index, 1, nsMsgViewNotificationCode::changed);
+ if (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay) {
+ nsMsgViewIndex threadIndex = GetThreadIndex(index);
+ if (threadIndex != index)
+ NoteChange(threadIndex, 1, nsMsgViewNotificationCode::changed);
+ }
+
+ return rv;
+}
+
+nsresult nsMsgDBView::SetThreadOfMsgReadByIndex(
+ nsMsgViewIndex index, nsTArray<nsMsgKey>& keysMarkedRead, bool /*read*/) {
+ nsresult rv;
+
+ if (!IsValidIndex(index)) return NS_MSG_INVALID_DBVIEW_INDEX;
+
+ rv = MarkThreadOfMsgRead(m_keys[index], index, keysMarkedRead, true);
+ return rv;
+}
+
+nsresult nsMsgDBView::SetFlaggedByIndex(nsMsgViewIndex index, bool mark) {
+ nsresult rv;
+
+ if (!IsValidIndex(index)) return NS_MSG_INVALID_DBVIEW_INDEX;
+
+ nsCOMPtr<nsIMsgDatabase> dbToUse;
+ rv = GetDBForViewIndex(index, getter_AddRefs(dbToUse));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (mark)
+ OrExtraFlag(index, nsMsgMessageFlags::Marked);
+ else
+ AndExtraFlag(index, ~nsMsgMessageFlags::Marked);
+
+ rv = dbToUse->MarkMarked(m_keys[index], mark, this);
+ NoteChange(index, 1, nsMsgViewNotificationCode::changed);
+ return rv;
+}
+
+nsresult nsMsgDBView::SetMsgHdrJunkStatus(nsIJunkMailPlugin* aJunkPlugin,
+ nsIMsgDBHdr* aMsgHdr,
+ nsMsgJunkStatus aNewClassification) {
+ // Get the old junk score.
+ nsCString junkScoreStr;
+ nsresult rv = aMsgHdr->GetStringProperty("junkscore", junkScoreStr);
+
+ // And the old origin.
+ nsCString oldOriginStr;
+ rv = aMsgHdr->GetStringProperty("junkscoreorigin", oldOriginStr);
+
+ // If this was not classified by the user, say so.
+ nsMsgJunkStatus oldUserClassification;
+ if (oldOriginStr.get()[0] != 'u') {
+ oldUserClassification = nsIJunkMailPlugin::UNCLASSIFIED;
+ } else {
+ // Otherwise, pass the actual user classification.
+ if (junkScoreStr.IsEmpty())
+ oldUserClassification = nsIJunkMailPlugin::UNCLASSIFIED;
+ else if (junkScoreStr.ToInteger(&rv) == nsIJunkMailPlugin::IS_SPAM_SCORE)
+ oldUserClassification = nsIJunkMailPlugin::JUNK;
+ else
+ oldUserClassification = nsIJunkMailPlugin::GOOD;
+
+ NS_ASSERTION(NS_SUCCEEDED(rv), "Converting junkScore to integer failed.");
+ }
+
+ // Get the URI for this message so we can pass it to the plugin.
+ nsCString uri;
+ nsMsgKey msgKey;
+ nsCOMPtr<nsIMsgFolder> folder;
+ nsCOMPtr<nsIMsgDatabase> db;
+ aMsgHdr->GetMessageKey(&msgKey);
+ rv = aMsgHdr->GetFolder(getter_AddRefs(folder));
+ NS_ENSURE_SUCCESS(rv, rv);
+ GenerateURIForMsgKey(msgKey, folder, uri);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = folder->GetMsgDatabase(getter_AddRefs(db));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Tell the plugin about this change, so that it can (potentially)
+ // adjust its database appropriately.
+ nsCOMPtr<nsIMsgWindow> msgWindow(do_QueryReferent(mMsgWindowWeak));
+ rv = aJunkPlugin->SetMessageClassification(
+ uri, oldUserClassification, aNewClassification, msgWindow, this);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // This routine is only reached if the user someone touched the UI
+ // and told us the junk status of this message.
+ // Set origin first so that listeners on the junkscore will
+ // know the correct origin.
+ rv = db->SetStringProperty(msgKey, "junkscoreorigin", "user"_ns);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "SetStringPropertyByIndex failed");
+
+ // Set the junk score on the message itself.
+ nsAutoCString msgJunkScore;
+ msgJunkScore.AppendInt(aNewClassification == nsIJunkMailPlugin::JUNK
+ ? nsIJunkMailPlugin::IS_SPAM_SCORE
+ : nsIJunkMailPlugin::IS_HAM_SCORE);
+ db->SetStringProperty(msgKey, "junkscore", msgJunkScore);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return rv;
+}
+
+nsresult nsMsgDBView::GetFolderFromMsgURI(const nsACString& aMsgURI,
+ nsIMsgFolder** aFolder) {
+ NS_IF_ADDREF(*aFolder = m_folder);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBView::OnMessageClassified(const nsACString& aMsgURI,
+ nsMsgJunkStatus aClassification,
+ uint32_t aJunkPercent)
+
+{
+ // Note: we know all messages in a batch have the same
+ // classification, since unlike OnMessageClassified
+ // methods in other classes (such as nsLocalMailFolder
+ // and nsImapMailFolder), this class, nsMsgDBView, currently
+ // only triggers message classifications due to a command to
+ // mark some of the messages in the view as junk, or as not
+ // junk - so the classification is dictated to the filter,
+ // not suggested by it.
+ //
+ // For this reason the only thing we (may) have to do is
+ // perform the action on all of the junk messages.
+
+ uint32_t numJunk = mJunkHdrs.Length();
+ NS_ASSERTION(aClassification == nsIJunkMailPlugin::GOOD || numJunk,
+ "the classification of a manually-marked junk message has "
+ "been classified as junk, yet there seem to be no such "
+ "outstanding messages");
+
+ // Is this the last message in the batch?
+ if (--mNumMessagesRemainingInBatch == 0 && numJunk > 0) {
+ PerformActionsOnJunkMsgs(aClassification == nsIJunkMailPlugin::JUNK);
+ mJunkHdrs.Clear();
+ }
+
+ return NS_OK;
+}
+
+nsresult nsMsgDBView::PerformActionsOnJunkMsgs(bool msgsAreJunk) {
+ uint32_t numJunkHdrs = mJunkHdrs.Length();
+ if (!numJunkHdrs) {
+ NS_ERROR("no indices of marked-as-junk messages to act on");
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIMsgFolder> srcFolder;
+ mJunkHdrs[0]->GetFolder(getter_AddRefs(srcFolder));
+
+ bool moveMessages, changeReadState;
+ nsCOMPtr<nsIMsgFolder> targetFolder;
+
+ nsresult rv = DetermineActionsForJunkChange(msgsAreJunk, srcFolder,
+ moveMessages, changeReadState,
+ getter_AddRefs(targetFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Nothing to do, bail out.
+ if (!(moveMessages || changeReadState)) return NS_OK;
+
+ if (changeReadState) {
+ // Notes on marking junk as read:
+ // 1. There are 2 occasions on which junk messages are marked as
+ // read: after a manual marking (here and in the front end) and after
+ // automatic classification by the bayesian filter (see code for local
+ // mail folders and for imap mail folders). The server-specific
+ // markAsReadOnSpam pref only applies to the latter, the former is
+ // controlled by "mailnews.ui.junk.manualMarkAsJunkMarksRead".
+ // 2. Even though move/delete on manual mark may be
+ // turned off, we might still need to mark as read.
+
+ rv = srcFolder->MarkMessagesRead(mJunkHdrs, msgsAreJunk);
+ NoteChange(0, 0, nsMsgViewNotificationCode::none);
+ NS_ASSERTION(NS_SUCCEEDED(rv),
+ "marking marked-as-junk messages as read failed");
+ }
+
+ if (moveMessages) {
+ // Check if one of the messages to be junked is actually selected.
+ // If more than one message being junked, one must be selected.
+ // If no tree selection at all, must be in stand-alone message window.
+ bool junkedMsgSelected = numJunkHdrs > 1 || !mTreeSelection;
+ for (nsMsgViewIndex junkIndex = 0;
+ !junkedMsgSelected && junkIndex < numJunkHdrs; junkIndex++) {
+ nsMsgViewIndex hdrIndex = FindHdr(mJunkHdrs[junkIndex]);
+ if (hdrIndex != nsMsgViewIndex_None)
+ mTreeSelection->IsSelected(hdrIndex, &junkedMsgSelected);
+ }
+
+ // If a junked msg is selected, tell the FE to call
+ // SetNextMessageAfterDelete() because a delete is coming.
+ if (junkedMsgSelected) {
+ nsCOMPtr<nsIMsgDBViewCommandUpdater> commandUpdater(
+ do_QueryReferent(mCommandUpdater));
+ if (commandUpdater) {
+ rv = commandUpdater->UpdateNextMessageAfterDelete();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ nsCOMPtr<nsIMsgWindow> msgWindow(do_QueryReferent(mMsgWindowWeak));
+ if (targetFolder) {
+ nsCOMPtr<nsIMsgCopyService> copyService =
+ do_GetService("@mozilla.org/messenger/messagecopyservice;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = copyService->CopyMessages(srcFolder, mJunkHdrs, targetFolder, true,
+ nullptr, msgWindow, true);
+ } else if (msgsAreJunk) {
+ if (mDeleteModel == nsMsgImapDeleteModels::IMAPDelete) {
+ // Unfortunately the DeleteMessages in this case is interpreted by
+ // IMAP as a delete toggle. So what we have to do is to assemble a
+ // new delete array, keeping only those that are not deleted.
+ nsTArray<RefPtr<nsIMsgDBHdr>> hdrsToDelete;
+ for (nsIMsgDBHdr* msgHdr : mJunkHdrs) {
+ if (msgHdr) {
+ uint32_t flags;
+ msgHdr->GetFlags(&flags);
+ if (!(flags & nsMsgMessageFlags::IMAPDeleted)) {
+ hdrsToDelete.AppendElement(msgHdr);
+ }
+ }
+ }
+
+ if (!hdrsToDelete.IsEmpty())
+ rv = srcFolder->DeleteMessages(hdrsToDelete, msgWindow, false, false,
+ nullptr, true);
+ } else {
+ rv = srcFolder->DeleteMessages(mJunkHdrs, msgWindow, false, false,
+ nullptr, true);
+ }
+ } else if (mDeleteModel == nsMsgImapDeleteModels::IMAPDelete) {
+ nsCOMPtr<nsIMsgImapMailFolder> imapFolder(do_QueryInterface(srcFolder));
+ nsTArray<nsMsgKey> imapUids(numJunkHdrs);
+ for (nsIMsgDBHdr* msgHdr : mJunkHdrs) {
+ nsMsgKey key;
+ msgHdr->GetMessageKey(&key);
+ imapUids.AppendElement(key);
+ }
+
+ imapFolder->StoreImapFlags(kImapMsgDeletedFlag, false, imapUids, nullptr);
+ }
+
+ NoteChange(0, 0, nsMsgViewNotificationCode::none);
+
+ NS_ASSERTION(NS_SUCCEEDED(rv),
+ "move or deletion of message marked-as-junk/non junk failed");
+ }
+
+ return rv;
+}
+
+nsresult nsMsgDBView::DetermineActionsForJunkChange(
+ bool msgsAreJunk, nsIMsgFolder* srcFolder, bool& moveMessages,
+ bool& changeReadState, nsIMsgFolder** targetFolder) {
+ // There are two possible actions which may be performed
+ // on messages marked as spam: marking as read and moving
+ // somewhere. When a message is marked as non junk,
+ // it may be moved to the inbox, and marked unread.
+ moveMessages = false;
+ changeReadState = false;
+
+ // The 'somewhere', junkTargetFolder, can be a folder,
+ // but if it remains null we'll delete the messages.
+ *targetFolder = nullptr;
+
+ uint32_t folderFlags;
+ srcFolder->GetFlags(&folderFlags);
+
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ nsresult rv = srcFolder->GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIPrefBranch> prefBranch(
+ do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Handle the easy case of marking a junk message as good first.
+ // Set the move target folder to the inbox, if any.
+ if (!msgsAreJunk) {
+ if (folderFlags & nsMsgFolderFlags::Junk) {
+ prefBranch->GetBoolPref("mail.spam.markAsNotJunkMarksUnRead",
+ &changeReadState);
+ nsCOMPtr<nsIMsgFolder> rootMsgFolder;
+ rv = server->GetRootMsgFolder(getter_AddRefs(rootMsgFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rootMsgFolder->GetFolderWithFlags(nsMsgFolderFlags::Inbox, targetFolder);
+ moveMessages = *targetFolder != nullptr;
+ }
+
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsISpamSettings> spamSettings;
+ rv = server->GetSpamSettings(getter_AddRefs(spamSettings));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // When the user explicitly marks a message as junk, we can mark it as read,
+ // too. This is independent of the "markAsReadOnSpam" pref, which applies
+ // only to automatically-classified messages.
+ // Note that this behaviour should match the one in the front end for marking
+ // as junk via toolbar/context menu.
+ prefBranch->GetBoolPref("mailnews.ui.junk.manualMarkAsJunkMarksRead",
+ &changeReadState);
+
+ // Now let's determine whether we'll be taking the second action,
+ // the move / deletion (and also determine which of these two).
+ bool manualMark;
+ (void)spamSettings->GetManualMark(&manualMark);
+ if (!manualMark) return NS_OK;
+
+ int32_t manualMarkMode;
+ (void)spamSettings->GetManualMarkMode(&manualMarkMode);
+ NS_ASSERTION(manualMarkMode == nsISpamSettings::MANUAL_MARK_MODE_MOVE ||
+ manualMarkMode == nsISpamSettings::MANUAL_MARK_MODE_DELETE,
+ "bad manual mark mode");
+
+ if (manualMarkMode == nsISpamSettings::MANUAL_MARK_MODE_MOVE) {
+ // If this is a junk folder (not only "the" junk folder for this account)
+ // don't do the move.
+ if (folderFlags & nsMsgFolderFlags::Junk) return NS_OK;
+
+ nsCString spamFolderURI;
+ rv = spamSettings->GetSpamFolderURI(spamFolderURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ASSERTION(!spamFolderURI.IsEmpty(),
+ "spam folder URI is empty, can't move");
+ if (!spamFolderURI.IsEmpty()) {
+ rv = FindFolder(spamFolderURI, targetFolder);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (*targetFolder) {
+ moveMessages = true;
+ } else {
+ // XXX TODO: GetOrCreateJunkFolder will only create a folder with
+ // localized name "Junk" regardless of spamFolderURI. So if someone
+ // sets the junk folder to an existing folder of a different name,
+ // then deletes that folder, this will fail to create the correct
+ // folder.
+ rv = GetOrCreateJunkFolder(spamFolderURI, nullptr /* aListener */);
+ if (NS_SUCCEEDED(rv))
+ rv = GetExistingFolder(spamFolderURI, targetFolder);
+
+ NS_ASSERTION(NS_SUCCEEDED(rv), "GetOrCreateJunkFolder failed");
+ }
+ }
+
+ return NS_OK;
+ }
+
+ // At this point manualMarkMode == nsISpamSettings::MANUAL_MARK_MODE_DELETE).
+
+ // If this is in the trash, let's not delete.
+ if (folderFlags & nsMsgFolderFlags::Trash) return NS_OK;
+
+ return srcFolder->GetCanDeleteMessages(&moveMessages);
+}
+
+// Reversing threads involves reversing the threads but leaving the
+// expanded messages ordered relative to the thread, so we
+// make a copy of each array and copy them over.
+void nsMsgDBView::ReverseThreads() {
+ nsTArray<uint32_t> newFlagArray;
+ nsTArray<nsMsgKey> newKeyArray;
+ nsTArray<uint8_t> newLevelArray;
+
+ uint32_t viewSize = GetSize();
+ uint32_t startThread = viewSize;
+ uint32_t nextThread = viewSize;
+ uint32_t destIndex = 0;
+
+ newKeyArray.SetLength(m_keys.Length());
+ newFlagArray.SetLength(m_flags.Length());
+ newLevelArray.SetLength(m_levels.Length());
+
+ while (startThread) {
+ startThread--;
+
+ if (m_flags[startThread] & MSG_VIEW_FLAG_ISTHREAD) {
+ for (uint32_t sourceIndex = startThread; sourceIndex < nextThread;
+ sourceIndex++) {
+ newKeyArray[destIndex] = m_keys[sourceIndex];
+ newFlagArray[destIndex] = m_flags[sourceIndex];
+ newLevelArray[destIndex] = m_levels[sourceIndex];
+ destIndex++;
+ }
+ // Because we're copying in reverse order.
+ nextThread = startThread;
+ }
+ }
+
+ m_keys.SwapElements(newKeyArray);
+ m_flags.SwapElements(newFlagArray);
+ m_levels.SwapElements(newLevelArray);
+}
+
+void nsMsgDBView::ReverseSort() {
+ uint32_t topIndex = GetSize();
+
+ nsCOMArray<nsIMsgFolder>* folders = GetFolders();
+
+ // Go up half the array swapping values.
+ for (uint32_t bottomIndex = 0; bottomIndex < --topIndex; bottomIndex++) {
+ // Swap flags.
+ uint32_t tempFlags = m_flags[bottomIndex];
+ m_flags[bottomIndex] = m_flags[topIndex];
+ m_flags[topIndex] = tempFlags;
+
+ // Swap keys.
+ nsMsgKey tempKey = m_keys[bottomIndex];
+ m_keys[bottomIndex] = m_keys[topIndex];
+ m_keys[topIndex] = tempKey;
+
+ if (folders) {
+ // Swap folders -- needed when search is done across multiple folders.
+ nsIMsgFolder* bottomFolder = folders->ObjectAt(bottomIndex);
+ nsIMsgFolder* topFolder = folders->ObjectAt(topIndex);
+ folders->ReplaceObjectAt(topFolder, bottomIndex);
+ folders->ReplaceObjectAt(bottomFolder, topIndex);
+ }
+
+ // No need to swap elements in m_levels; since we only call
+ // ReverseSort in non-threaded mode, m_levels are all the same.
+ }
+}
+
+int nsMsgDBView::FnSortIdKey(const IdKey* pItem1, const IdKey* pItem2,
+ viewSortInfo* sortInfo) {
+ int32_t retVal = 0;
+
+ nsIMsgDatabase* db = sortInfo->db;
+
+ mozilla::DebugOnly<nsresult> rv =
+ db->CompareCollationKeys(pItem1->key, pItem2->key, &retVal);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "compare failed");
+
+ if (retVal) return sortInfo->ascendingSort ? retVal : -retVal;
+
+ return sortInfo->view->SecondaryCompare(pItem1->id, pItem1->folder,
+ pItem2->id, pItem2->folder, sortInfo);
+}
+
+int nsMsgDBView::FnSortIdUint32(const IdUint32* pItem1, const IdUint32* pItem2,
+ viewSortInfo* sortInfo) {
+ if (pItem1->dword > pItem2->dword) {
+ return (sortInfo->ascendingSort) ? 1 : -1;
+ }
+
+ if (pItem1->dword < pItem2->dword) {
+ return (sortInfo->ascendingSort) ? -1 : 1;
+ }
+
+ return sortInfo->view->SecondaryCompare(pItem1->id, pItem1->folder,
+ pItem2->id, pItem2->folder, sortInfo);
+}
+
+// XXX are these still correct?
+// To compensate for memory alignment required for systems such as HP-UX, these
+// values must be 4 bytes aligned. Don't break this when modifying the
+// constants.
+const int kMaxSubjectKey = 160;
+const int kMaxLocationKey = 160; // Also used for account.
+const int kMaxAuthorKey = 160;
+const int kMaxRecipientKey = 80;
+
+// There are cases when pFieldType is not set:
+// one case returns NS_ERROR_UNEXPECTED;
+// the other case now return NS_ERROR_NULL_POINTER (this is only when
+// colHandler below is null, but is very unlikely).
+// The latter case used to return NS_OK, which was incorrect.
+nsresult nsMsgDBView::GetFieldTypeAndLenForSort(
+ nsMsgViewSortTypeValue sortType, uint16_t* pMaxLen, eFieldType* pFieldType,
+ nsIMsgCustomColumnHandler* colHandler) {
+ NS_ENSURE_ARG_POINTER(pMaxLen);
+ NS_ENSURE_ARG_POINTER(pFieldType);
+
+ switch (sortType) {
+ case nsMsgViewSortType::bySubject:
+ *pFieldType = kCollationKey;
+ *pMaxLen = kMaxSubjectKey;
+ break;
+ case nsMsgViewSortType::byAccount:
+ case nsMsgViewSortType::byTags:
+ case nsMsgViewSortType::byLocation:
+ *pFieldType = kCollationKey;
+ *pMaxLen = kMaxLocationKey;
+ break;
+ case nsMsgViewSortType::byRecipient:
+ case nsMsgViewSortType::byCorrespondent:
+ *pFieldType = kCollationKey;
+ *pMaxLen = kMaxRecipientKey;
+ break;
+ case nsMsgViewSortType::byAuthor:
+ *pFieldType = kCollationKey;
+ *pMaxLen = kMaxAuthorKey;
+ break;
+ case nsMsgViewSortType::byDate:
+ case nsMsgViewSortType::byReceived:
+ case nsMsgViewSortType::byPriority:
+ case nsMsgViewSortType::byThread:
+ case nsMsgViewSortType::byId:
+ case nsMsgViewSortType::bySize:
+ case nsMsgViewSortType::byFlagged:
+ case nsMsgViewSortType::byUnread:
+ case nsMsgViewSortType::byStatus:
+ case nsMsgViewSortType::byJunkStatus:
+ case nsMsgViewSortType::byAttachments:
+ *pFieldType = kU32;
+ *pMaxLen = 0;
+ break;
+ case nsMsgViewSortType::byCustom: {
+ if (colHandler == nullptr) {
+ NS_WARNING("colHandler is null. *pFieldType is not set.");
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ bool isString;
+ colHandler->IsString(&isString);
+
+ if (isString) {
+ *pFieldType = kCollationKey;
+ // 80 - do we need a separate k?
+ *pMaxLen = kMaxRecipientKey;
+ } else {
+ *pFieldType = kU32;
+ *pMaxLen = 0;
+ }
+ break;
+ }
+ case nsMsgViewSortType::byNone:
+ // Bug 901948.
+ return NS_ERROR_INVALID_ARG;
+ default: {
+ nsAutoCString message("unexpected switch value: sortType=");
+ message.AppendInt(sortType);
+ NS_WARNING(message.get());
+ return NS_ERROR_UNEXPECTED;
+ }
+ }
+
+ return NS_OK;
+}
+
+#define MSG_STATUS_MASK \
+ (nsMsgMessageFlags::Replied | nsMsgMessageFlags::Forwarded)
+
+nsresult nsMsgDBView::GetStatusSortValue(nsIMsgDBHdr* msgHdr,
+ uint32_t* result) {
+ NS_ENSURE_ARG_POINTER(msgHdr);
+ NS_ENSURE_ARG_POINTER(result);
+
+ uint32_t messageFlags;
+ nsresult rv = msgHdr->GetFlags(&messageFlags);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (messageFlags & nsMsgMessageFlags::New) {
+ // Happily, new by definition stands alone.
+ *result = 0;
+ return NS_OK;
+ }
+
+ switch (messageFlags & MSG_STATUS_MASK) {
+ case nsMsgMessageFlags::Replied:
+ *result = 2;
+ break;
+ case nsMsgMessageFlags::Forwarded | nsMsgMessageFlags::Replied:
+ *result = 1;
+ break;
+ case nsMsgMessageFlags::Forwarded:
+ *result = 3;
+ break;
+ default:
+ *result = (messageFlags & nsMsgMessageFlags::Read) ? 4 : 5;
+ break;
+ }
+
+ return NS_OK;
+}
+
+nsresult nsMsgDBView::GetLongField(nsIMsgDBHdr* msgHdr,
+ nsMsgViewSortTypeValue sortType,
+ uint32_t* result,
+ nsIMsgCustomColumnHandler* colHandler) {
+ nsresult rv;
+ NS_ENSURE_ARG_POINTER(msgHdr);
+ NS_ENSURE_ARG_POINTER(result);
+
+ bool isRead;
+ uint32_t bits;
+
+ switch (sortType) {
+ case nsMsgViewSortType::bySize:
+ rv = (mShowSizeInLines) ? msgHdr->GetLineCount(result)
+ : msgHdr->GetMessageSize(result);
+ break;
+ case nsMsgViewSortType::byPriority:
+ nsMsgPriorityValue priority;
+ rv = msgHdr->GetPriority(&priority);
+ // Treat "none" as "normal" when sorting.
+ if (priority == nsMsgPriority::none) priority = nsMsgPriority::normal;
+
+ // We want highest priority to have lowest value
+ // so ascending sort will have highest priority first.
+ *result = nsMsgPriority::highest - priority;
+ break;
+ case nsMsgViewSortType::byStatus:
+ rv = GetStatusSortValue(msgHdr, result);
+ break;
+ case nsMsgViewSortType::byFlagged:
+ bits = 0;
+ rv = msgHdr->GetFlags(&bits);
+ // Make flagged come out on top.
+ *result = !(bits & nsMsgMessageFlags::Marked);
+ break;
+ case nsMsgViewSortType::byUnread:
+ rv = msgHdr->GetIsRead(&isRead);
+ if (NS_SUCCEEDED(rv)) *result = !isRead;
+
+ break;
+ case nsMsgViewSortType::byJunkStatus: {
+ nsCString junkScoreStr;
+ rv = msgHdr->GetStringProperty("junkscore", junkScoreStr);
+ // Unscored messages should come before messages that are scored
+ // junkScoreStr is "", and "0" - "100"; normalize to 0 - 101.
+ *result = junkScoreStr.IsEmpty() ? (0) : atoi(junkScoreStr.get()) + 1;
+ break;
+ }
+ case nsMsgViewSortType::byAttachments:
+ bits = 0;
+ rv = msgHdr->GetFlags(&bits);
+ *result = !(bits & nsMsgMessageFlags::Attachment);
+ break;
+ case nsMsgViewSortType::byDate:
+ // When sorting threads by date, we may want the date of the newest msg
+ // in the thread.
+ if (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay &&
+ !(m_viewFlags & nsMsgViewFlagsType::kGroupBySort) &&
+ !mSortThreadsByRoot) {
+ nsCOMPtr<nsIMsgThread> thread;
+ rv = GetThreadContainingMsgHdr(msgHdr, getter_AddRefs(thread));
+ if (NS_SUCCEEDED(rv)) {
+ thread->GetNewestMsgDate(result);
+ break;
+ }
+ }
+ rv = msgHdr->GetDateInSeconds(result);
+ break;
+ case nsMsgViewSortType::byReceived:
+ // When sorting threads by received date, we may want the received date
+ // of the newest msg in the thread.
+ if (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay &&
+ !(m_viewFlags & nsMsgViewFlagsType::kGroupBySort) &&
+ !mSortThreadsByRoot) {
+ nsCOMPtr<nsIMsgThread> thread;
+ rv = GetThreadContainingMsgHdr(msgHdr, getter_AddRefs(thread));
+ NS_ENSURE_SUCCESS(rv, rv);
+ thread->GetNewestMsgDate(result);
+ } else {
+ // Already in seconds.
+ rv = msgHdr->GetUint32Property("dateReceived", result);
+ if (*result == 0)
+ // Use Date instead, we have no Received property
+ rv = msgHdr->GetDateInSeconds(result);
+ }
+ break;
+ case nsMsgViewSortType::byCustom:
+ if (colHandler != nullptr) {
+ colHandler->GetSortLongForRow(msgHdr, result);
+ rv = NS_OK;
+ } else {
+ NS_ASSERTION(false,
+ "should not be here (Sort Type: byCustom (Long), but no "
+ "custom handler)");
+ rv = NS_ERROR_UNEXPECTED;
+ }
+ break;
+ case nsMsgViewSortType::byNone:
+ // Bug 901948.
+ return NS_ERROR_INVALID_ARG;
+
+ case nsMsgViewSortType::byId:
+ // Handled by caller, since caller knows the key.
+ default:
+ NS_ERROR("should not be here");
+ rv = NS_ERROR_UNEXPECTED;
+ break;
+ }
+
+ NS_ENSURE_SUCCESS(rv, rv);
+ return NS_OK;
+}
+
+MsgViewSortColumnInfo::MsgViewSortColumnInfo(
+ const MsgViewSortColumnInfo& other) {
+ mSortType = other.mSortType;
+ mSortOrder = other.mSortOrder;
+ mCustomColumnName = other.mCustomColumnName;
+ mColHandler = other.mColHandler;
+}
+
+bool MsgViewSortColumnInfo::operator==(
+ const MsgViewSortColumnInfo& other) const {
+ return (mSortType == nsMsgViewSortType::byCustom)
+ ? mCustomColumnName.Equals(other.mCustomColumnName)
+ : mSortType == other.mSortType;
+}
+
+nsresult nsMsgDBView::EncodeColumnSort(nsString& columnSortString) {
+ for (uint32_t i = 0; i < m_sortColumns.Length(); i++) {
+ MsgViewSortColumnInfo& sortInfo = m_sortColumns[i];
+ columnSortString.Append((char)sortInfo.mSortType);
+ columnSortString.Append((char)sortInfo.mSortOrder + '0');
+ if (sortInfo.mSortType == nsMsgViewSortType::byCustom) {
+ columnSortString.Append(sortInfo.mCustomColumnName);
+ columnSortString.Append((char16_t)'\r');
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult nsMsgDBView::DecodeColumnSort(nsString& columnSortString) {
+ const char16_t* stringPtr = columnSortString.BeginReading();
+ while (*stringPtr) {
+ MsgViewSortColumnInfo sortColumnInfo;
+ sortColumnInfo.mSortType = (nsMsgViewSortTypeValue)*stringPtr++;
+ sortColumnInfo.mSortOrder = (nsMsgViewSortOrderValue)(*stringPtr++) - '0';
+ if (sortColumnInfo.mSortType == nsMsgViewSortType::byCustom) {
+ while (*stringPtr && *stringPtr != '\r')
+ sortColumnInfo.mCustomColumnName.Append(*stringPtr++);
+
+ sortColumnInfo.mColHandler =
+ GetColumnHandler(sortColumnInfo.mCustomColumnName);
+
+ // Advance past '\r'.
+ if (*stringPtr) stringPtr++;
+ }
+
+ m_sortColumns.AppendElement(sortColumnInfo);
+ }
+
+ return NS_OK;
+}
+
+// Secondary Sort Key: when you select a column to sort, that
+// becomes the new Primary sort key, and all previous sort keys
+// become secondary. For example, if you first click on Date,
+// the messages are sorted by Date; then click on From, and now the
+// messages are sorted by From, and for each value of From the
+// messages are in Date order.
+
+void nsMsgDBView::PushSort(const MsgViewSortColumnInfo& newSort) {
+ // Handle byNone (bug 901948) ala a mail/base/modules/DBViewerWrapper.jsm
+ // where we don't push the secondary sort type if it's ::byNone;
+ // (and secondary sort type is NOT the same as the first sort type
+ // there). This code should behave the same way.
+
+ // We don't expect to be passed sort type ::byNone,
+ // but if we are it's safe to ignore it.
+ if (newSort.mSortType == nsMsgViewSortType::byNone) return;
+
+ // byId is a unique key (misnamed as Order Received). If we are sorting byId,
+ // we don't need to keep any secondary sort keys.
+ if (newSort.mSortType == nsMsgViewSortType::byId) m_sortColumns.Clear();
+
+ m_sortColumns.RemoveElement(newSort);
+ m_sortColumns.InsertElementAt(0, newSort);
+ if (m_sortColumns.Length() > kMaxNumSortColumns)
+ m_sortColumns.RemoveElementAt(kMaxNumSortColumns);
+}
+
+nsresult nsMsgDBView::GetCollationKey(nsIMsgDBHdr* msgHdr,
+ nsMsgViewSortTypeValue sortType,
+ nsTArray<uint8_t>& result,
+ nsIMsgCustomColumnHandler* colHandler) {
+ nsresult rv = NS_ERROR_UNEXPECTED;
+ NS_ENSURE_ARG_POINTER(msgHdr);
+
+ switch (sortType) {
+ case nsMsgViewSortType::bySubject:
+ rv = msgHdr->GetSubjectCollationKey(result);
+ break;
+ case nsMsgViewSortType::byLocation:
+ rv = GetLocationCollationKey(msgHdr, result);
+ break;
+ case nsMsgViewSortType::byRecipient: {
+ nsString recipients;
+ rv = FetchRecipients(msgHdr, recipients);
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIMsgDatabase> dbToUse = m_db;
+ // Probably a search view.
+ if (!dbToUse) {
+ rv = GetDBForHeader(msgHdr, getter_AddRefs(dbToUse));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ rv = dbToUse->CreateCollationKey(recipients, result);
+ }
+ break;
+ }
+ case nsMsgViewSortType::byAuthor: {
+ rv = msgHdr->GetAuthorCollationKey(result);
+ nsString author;
+ rv = FetchAuthor(msgHdr, author);
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIMsgDatabase> dbToUse = m_db;
+ // Probably a search view.
+ if (!dbToUse) {
+ rv = GetDBForHeader(msgHdr, getter_AddRefs(dbToUse));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ rv = dbToUse->CreateCollationKey(author, result);
+ }
+ break;
+ }
+ case nsMsgViewSortType::byAccount:
+ case nsMsgViewSortType::byTags: {
+ nsString str;
+ nsCOMPtr<nsIMsgDatabase> dbToUse = m_db;
+
+ if (!dbToUse)
+ // Probably a search view.
+ GetDBForViewIndex(0, getter_AddRefs(dbToUse));
+
+ rv = (sortType == nsMsgViewSortType::byAccount)
+ ? FetchAccount(msgHdr, str)
+ : FetchTags(msgHdr, str);
+ if (NS_SUCCEEDED(rv) && dbToUse)
+ rv = dbToUse->CreateCollationKey(str, result);
+
+ break;
+ }
+ case nsMsgViewSortType::byCustom:
+ if (colHandler != nullptr) {
+ nsAutoString strKey;
+ rv = colHandler->GetSortStringForRow(msgHdr, strKey);
+ NS_ASSERTION(NS_SUCCEEDED(rv),
+ "failed to get sort string for custom row");
+ nsAutoString strTemp(strKey);
+
+ nsCOMPtr<nsIMsgDatabase> dbToUse = m_db;
+ // Probably a search view.
+ if (!dbToUse) {
+ rv = GetDBForHeader(msgHdr, getter_AddRefs(dbToUse));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ rv = dbToUse->CreateCollationKey(strKey, result);
+ } else {
+ NS_ERROR(
+ "should not be here (Sort Type: byCustom (String), but no custom "
+ "handler)");
+ rv = NS_ERROR_UNEXPECTED;
+ }
+ break;
+ case nsMsgViewSortType::byCorrespondent: {
+ nsString value;
+ if (IsOutgoingMsg(msgHdr))
+ rv = FetchRecipients(msgHdr, value);
+ else
+ rv = FetchAuthor(msgHdr, value);
+
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIMsgDatabase> dbToUse = m_db;
+ // Probably a search view.
+ if (!dbToUse) {
+ rv = GetDBForHeader(msgHdr, getter_AddRefs(dbToUse));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ rv = dbToUse->CreateCollationKey(value, result);
+ }
+ break;
+ }
+ default:
+ rv = NS_ERROR_UNEXPECTED;
+ break;
+ }
+
+ // Bailing out with failure will stop the sort and leave us in
+ // a bad state. Try to continue on, instead.
+ NS_ASSERTION(NS_SUCCEEDED(rv), "failed to get the collation key");
+ if (NS_FAILED(rv)) {
+ result.Clear();
+ }
+
+ return NS_OK;
+}
+
+// As the location collation key is created getting folder from the msgHdr,
+// it is defined in this file and not from the db.
+nsresult nsMsgDBView::GetLocationCollationKey(nsIMsgDBHdr* msgHdr,
+ nsTArray<uint8_t>& result) {
+ nsCOMPtr<nsIMsgFolder> folder;
+
+ nsresult rv = msgHdr->GetFolder(getter_AddRefs(folder));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIMsgDatabase> dbToUse;
+ rv = folder->GetMsgDatabase(getter_AddRefs(dbToUse));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsString locationString;
+ rv = folder->GetPrettyName(locationString);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return dbToUse->CreateCollationKey(locationString, result);
+}
+
+nsresult nsMsgDBView::SaveSortInfo(nsMsgViewSortTypeValue sortType,
+ nsMsgViewSortOrderValue sortOrder) {
+ if (m_viewFolder) {
+ nsCOMPtr<nsIDBFolderInfo> folderInfo;
+ nsCOMPtr<nsIMsgDatabase> db;
+ nsresult rv = m_viewFolder->GetDBFolderInfoAndDB(getter_AddRefs(folderInfo),
+ getter_AddRefs(db));
+ if (NS_SUCCEEDED(rv) && folderInfo) {
+ // Save off sort type and order, view type and flags.
+ folderInfo->SetSortType(sortType);
+ folderInfo->SetSortOrder(sortOrder);
+
+ nsString sortColumnsString;
+ rv = EncodeColumnSort(sortColumnsString);
+ NS_ENSURE_SUCCESS(rv, rv);
+ folderInfo->SetProperty("sortColumns", sortColumnsString);
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult nsMsgDBView::RestoreSortInfo() {
+ if (!m_viewFolder) return NS_OK;
+
+ nsCOMPtr<nsIDBFolderInfo> folderInfo;
+ nsCOMPtr<nsIMsgDatabase> db;
+ nsresult rv = m_viewFolder->GetDBFolderInfoAndDB(getter_AddRefs(folderInfo),
+ getter_AddRefs(db));
+ if (NS_SUCCEEDED(rv) && folderInfo) {
+ // Restore m_sortColumns from db.
+ nsString sortColumnsString;
+ folderInfo->GetProperty("sortColumns", sortColumnsString);
+ DecodeColumnSort(sortColumnsString);
+ if (m_sortColumns.Length() > 1) {
+ m_secondarySort = m_sortColumns[1].mSortType;
+ m_secondarySortOrder = m_sortColumns[1].mSortOrder;
+ m_secondaryCustomColumn = m_sortColumns[1].mCustomColumnName;
+ }
+
+ // Restore curCustomColumn from db.
+ folderInfo->GetProperty("customSortCol", m_curCustomColumn);
+ }
+
+ return NS_OK;
+}
+
+// Called by msgDBView::Sort, at which point any persisted active custom
+// columns must be registered. If not, reset their m_sortColumns entries
+// to byDate; Sort will fill in values if necessary based on new user sort.
+void nsMsgDBView::EnsureCustomColumnsValid() {
+ if (!m_sortColumns.Length()) return;
+
+ for (uint32_t i = 0; i < m_sortColumns.Length(); i++) {
+ if (m_sortColumns[i].mSortType == nsMsgViewSortType::byCustom &&
+ m_sortColumns[i].mColHandler == nullptr) {
+ m_sortColumns[i].mSortType = nsMsgViewSortType::byDate;
+ m_sortColumns[i].mCustomColumnName.Truncate();
+ // There are only two...
+ if (i == 0 && m_sortType != nsMsgViewSortType::byCustom)
+ SetCurCustomColumn(EmptyString());
+ if (i == 1) m_secondaryCustomColumn.Truncate();
+ }
+ }
+}
+
+int32_t nsMsgDBView::SecondaryCompare(nsMsgKey key1, nsIMsgFolder* folder1,
+ nsMsgKey key2, nsIMsgFolder* folder2,
+ viewSortInfo* comparisonContext) {
+ nsMsgViewSortTypeValue sortType = comparisonContext->view->m_secondarySort;
+ bool isAscendingSort = comparisonContext->view->m_secondarySortOrder ==
+ nsMsgViewSortOrder::ascending;
+
+ // We need to make sure that in the case of the secondary sort field also
+ // matching, we don't recurse.
+ if (comparisonContext->isSecondarySort ||
+ sortType == nsMsgViewSortType::byId) {
+ if (key1 > key2) {
+ return isAscendingSort ? 1 : -1;
+ }
+
+ if (key1 < key2) {
+ return isAscendingSort ? -1 : 1;
+ }
+
+ return 0;
+ }
+
+ nsCOMPtr<nsIMsgDBHdr> hdr1, hdr2;
+ nsresult rv = folder1->GetMessageHeader(key1, getter_AddRefs(hdr1));
+ NS_ENSURE_SUCCESS(rv, 0);
+ rv = folder2->GetMessageHeader(key2, getter_AddRefs(hdr2));
+ NS_ENSURE_SUCCESS(rv, 0);
+ IdKey EntryInfo1, EntryInfo2;
+
+ uint16_t maxLen;
+ eFieldType fieldType;
+
+ // Get the custom column handler for the *secondary* sort and pass it first
+ // to GetFieldTypeAndLenForSort to get the fieldType and then either
+ // GetCollationKey or GetLongField.
+ nsIMsgCustomColumnHandler* colHandler = nullptr;
+ if (sortType == nsMsgViewSortType::byCustom &&
+ comparisonContext->view->m_sortColumns.Length() > 1) {
+ colHandler = comparisonContext->view->m_sortColumns[1].mColHandler;
+ }
+
+ // The following may leave fieldType undefined.
+ // In this case, we can return 0 right away since
+ // it is the value returned in the default case of
+ // switch (fieldType) statement below.
+ rv = GetFieldTypeAndLenForSort(sortType, &maxLen, &fieldType, colHandler);
+ NS_ENSURE_SUCCESS(rv, 0);
+
+ hdr1->GetMessageKey(&EntryInfo1.id);
+ hdr2->GetMessageKey(&EntryInfo2.id);
+
+ // Set up new viewSortInfo data for our secondary comparison.
+ viewSortInfo ctx = {
+ .view = comparisonContext->view,
+ .db = comparisonContext->db,
+ .isSecondarySort = true, // To avoid recursing back here!
+ .ascendingSort = isAscendingSort,
+ };
+
+ switch (fieldType) {
+ case kCollationKey:
+ rv = GetCollationKey(hdr1, sortType, EntryInfo1.key, colHandler);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "failed to create collation key");
+ rv = GetCollationKey(hdr2, sortType, EntryInfo2.key, colHandler);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "failed to create collation key");
+
+ return FnSortIdKey(&EntryInfo1, &EntryInfo2, &ctx);
+ case kU32:
+ if (sortType == nsMsgViewSortType::byId) {
+ EntryInfo1.dword = EntryInfo1.id;
+ EntryInfo2.dword = EntryInfo2.id;
+ } else {
+ GetLongField(hdr1, sortType, &EntryInfo1.dword, colHandler);
+ GetLongField(hdr2, sortType, &EntryInfo2.dword, colHandler);
+ }
+ return FnSortIdUint32(&EntryInfo1, &EntryInfo2, &ctx);
+ default:
+ return 0;
+ }
+}
+
+NS_IMETHODIMP nsMsgDBView::Sort(nsMsgViewSortTypeValue sortType,
+ nsMsgViewSortOrderValue sortOrder) {
+ EnsureCustomColumnsValid();
+
+ // If we're doing a stable sort, we can't just reverse the messages.
+ // Check also that the custom column we're sorting on hasn't changed.
+ // Otherwise, to be on the safe side, resort.
+ // Note: m_curCustomColumn is the desired (possibly new) custom column name,
+ // while m_sortColumns[0].mCustomColumnName is the name for the last completed
+ // sort, since these are persisted after each sort.
+ if (m_sortType == sortType && m_sortValid &&
+ (sortType != nsMsgViewSortType::byCustom ||
+ (sortType == nsMsgViewSortType::byCustom && m_sortColumns.Length() &&
+ m_sortColumns[0].mCustomColumnName.Equals(m_curCustomColumn))) &&
+ m_sortColumns.Length() < 2) {
+ // Same as it ever was. Do nothing.
+ if (m_sortOrder == sortOrder) return NS_OK;
+
+ // For secondary sort, remember the sort order on a per column basis.
+ if (m_sortColumns.Length()) m_sortColumns[0].mSortOrder = sortOrder;
+
+ SaveSortInfo(sortType, sortOrder);
+ if (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay) {
+ ReverseThreads();
+ } else {
+ ReverseSort();
+ }
+
+ m_sortOrder = sortOrder;
+ // We just reversed the sort order, we still need to invalidate the view.
+ return NS_OK;
+ }
+
+ if (sortType == nsMsgViewSortType::byThread) return NS_OK;
+
+ // If a sortType has changed, or the sortType is byCustom and a column has
+ // changed, this is the new primary sortColumnInfo.
+ // Note: m_curCustomColumn is the desired (possibly new) custom column name,
+ // while m_sortColumns[0].mCustomColumnName is the name for the last completed
+ // sort, since these are persisted after each sort.
+ if (m_sortType != sortType ||
+ (sortType == nsMsgViewSortType::byCustom && m_sortColumns.Length() &&
+ !m_sortColumns[0].mCustomColumnName.Equals(m_curCustomColumn))) {
+ // For secondary sort, remember the sort order of the original primary sort!
+ if (m_sortColumns.Length()) m_sortColumns[0].mSortOrder = m_sortOrder;
+
+ MsgViewSortColumnInfo sortColumnInfo;
+ sortColumnInfo.mSortType = sortType;
+ sortColumnInfo.mSortOrder = sortOrder;
+ if (sortType == nsMsgViewSortType::byCustom) {
+ GetCurCustomColumn(sortColumnInfo.mCustomColumnName);
+ sortColumnInfo.mColHandler = GetCurColumnHandler();
+ }
+
+ PushSort(sortColumnInfo);
+ } else {
+ // For primary sort, remember the sort order on a per column basis.
+ if (m_sortColumns.Length()) m_sortColumns[0].mSortOrder = sortOrder;
+ }
+
+ if (m_sortColumns.Length() > 1) {
+ m_secondarySort = m_sortColumns[1].mSortType;
+ m_secondarySortOrder = m_sortColumns[1].mSortOrder;
+ m_secondaryCustomColumn = m_sortColumns[1].mCustomColumnName;
+ }
+
+ SaveSortInfo(sortType, sortOrder);
+ // Figure out how much memory we'll need, and then malloc it.
+ uint16_t maxLen;
+ eFieldType fieldType;
+
+ // Get the custom column handler for the primary sort and pass it first
+ // to GetFieldTypeAndLenForSort to get the fieldType and then either
+ // GetCollationKey or GetLongField.
+ nsIMsgCustomColumnHandler* colHandler = GetCurColumnHandler();
+
+ // If we did not obtain proper fieldType, it needs to be checked
+ // because the subsequent code does not handle it very well.
+ nsresult rv =
+ GetFieldTypeAndLenForSort(sortType, &maxLen, &fieldType, colHandler);
+
+ // Don't sort if the field type is not supported: Bug 901948.
+ if (NS_FAILED(rv)) return NS_OK;
+
+ nsTArray<void*> ptrs;
+ uint32_t arraySize = GetSize();
+
+ if (!arraySize) return NS_OK;
+
+ nsCOMArray<nsIMsgFolder>* folders = GetFolders();
+ nsCOMPtr<nsIMsgDatabase> dbToUse = m_db;
+ // Probably a search view.
+ if (!dbToUse) {
+ GetDBForViewIndex(0, getter_AddRefs(dbToUse));
+ if (!dbToUse) return NS_ERROR_FAILURE;
+ }
+
+ viewSortInfo qsPrivateData{
+ .view = this,
+ .db = dbToUse,
+ .isSecondarySort = false,
+ .ascendingSort = (sortOrder == nsMsgViewSortOrder::ascending),
+ };
+
+ switch (fieldType) {
+ case kCollationKey: {
+ // Sort on a non-numeric field. We'll be calculating a collation key for
+ // each message.
+ nsTArray<IdKey> entries;
+ entries.SetLength(arraySize);
+ nsTArray<IdKey*> pPtrBase;
+ pPtrBase.SetLength(arraySize);
+ for (uint32_t i = 0; i < arraySize; ++i) {
+ IdKey* info = &entries[i];
+ pPtrBase[i] = info;
+ info->id = m_keys[i];
+ info->bits = m_flags[i];
+ info->dword = 0;
+ info->folder = folders ? folders->ObjectAt(i) : m_folder.get();
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ rv = GetMsgHdrForViewIndex(i, getter_AddRefs(msgHdr));
+ NS_ASSERTION(NS_SUCCEEDED(rv) && msgHdr, "header not found");
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = GetCollationKey(msgHdr, sortType, info->key, colHandler);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Perform the sort.
+ std::sort(pPtrBase.begin(), pPtrBase.end(),
+ [&qsPrivateData](const auto& lhs, const auto& rhs) {
+ return FnSortIdKey(lhs, rhs, &qsPrivateData) < 0;
+ });
+
+ // Now update the view state to reflect the new order.
+ for (uint32_t i = 0; i < arraySize; ++i) {
+ m_keys[i] = pPtrBase[i]->id;
+ m_flags[i] = pPtrBase[i]->bits;
+ if (folders) folders->ReplaceObjectAt(pPtrBase[i]->folder, i);
+ }
+ m_sortType = sortType;
+ m_sortOrder = sortOrder;
+ m_sortValid = true;
+ return NS_OK;
+ }
+ case kU32: {
+ // Sort on a numeric field.
+ nsTArray<IdUint32> entries;
+ entries.SetLength(arraySize);
+ nsTArray<IdUint32*> pPtrBase;
+ pPtrBase.SetLength(arraySize);
+ for (uint32_t i = 0; i < arraySize; ++i) {
+ IdUint32* info = &entries[i];
+ pPtrBase[i] = info;
+ info->id = m_keys[i];
+ info->bits = m_flags[i];
+ info->folder = folders ? folders->ObjectAt(i) : m_folder.get();
+ if (sortType == nsMsgViewSortType::byId) {
+ info->dword = info->id; // No msgHdr required.
+ } else {
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ rv = GetMsgHdrForViewIndex(i, getter_AddRefs(msgHdr));
+ NS_ASSERTION(NS_SUCCEEDED(rv) && msgHdr, "header not found");
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = GetLongField(msgHdr, sortType, &info->dword, colHandler);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ // Perform the sort.
+ std::sort(pPtrBase.begin(), pPtrBase.end(),
+ [&qsPrivateData](const auto& lhs, const auto& rhs) {
+ return FnSortIdUint32(lhs, rhs, &qsPrivateData) < 0;
+ });
+
+ // Now update the view state to reflect the new order.
+ for (uint32_t i = 0; i < arraySize; ++i) {
+ m_keys[i] = pPtrBase[i]->id;
+ m_flags[i] = pPtrBase[i]->bits;
+ if (folders) folders->ReplaceObjectAt(pPtrBase[i]->folder, i);
+ }
+ m_sortType = sortType;
+ m_sortOrder = sortOrder;
+ m_sortValid = true;
+ return NS_OK;
+ }
+ default:
+ // If we get this far, we've got a bad fieldType.
+ return NS_ERROR_UNEXPECTED;
+ }
+}
+
+nsMsgViewIndex nsMsgDBView::GetIndexOfFirstDisplayedKeyInThread(
+ nsIMsgThread* threadHdr, bool allowDummy) {
+ nsMsgViewIndex retIndex = nsMsgViewIndex_None;
+ uint32_t childIndex = 0;
+ // We could speed up the unreadOnly view by starting our search with the first
+ // unread message in the thread. Sometimes, that will be wrong, however, so
+ // let's skip it until we're sure it's necessary.
+ // (m_viewFlags & nsMsgViewFlagsType::kUnreadOnly)
+ // ? threadHdr->GetFirstUnreadKey(m_db) : threadHdr->GetChildAt(0);
+ uint32_t numThreadChildren;
+ threadHdr->GetNumChildren(&numThreadChildren);
+ while (retIndex == nsMsgViewIndex_None && childIndex < numThreadChildren) {
+ nsCOMPtr<nsIMsgDBHdr> childHdr;
+ threadHdr->GetChildHdrAt(childIndex++, getter_AddRefs(childHdr));
+ if (childHdr) retIndex = FindHdr(childHdr, 0, allowDummy);
+ }
+
+ return retIndex;
+}
+
+nsresult nsMsgDBView::GetFirstMessageHdrToDisplayInThread(
+ nsIMsgThread* threadHdr, nsIMsgDBHdr** result) {
+ nsresult rv;
+
+ if (m_viewFlags & nsMsgViewFlagsType::kUnreadOnly)
+ rv = threadHdr->GetFirstUnreadChild(result);
+ else
+ rv = threadHdr->GetChildHdrAt(0, result);
+
+ return rv;
+}
+
+// Find the view index of the thread containing the passed msgKey, if
+// the thread is in the view. MsgIndex is passed in as a shortcut if
+// it turns out the msgKey is the first message in the thread,
+// then we can avoid looking for the msgKey.
+nsMsgViewIndex nsMsgDBView::ThreadIndexOfMsg(
+ nsMsgKey msgKey, nsMsgViewIndex msgIndex /* = nsMsgViewIndex_None */,
+ int32_t* pThreadCount /* = NULL */, uint32_t* pFlags /* = NULL */) {
+ if (!(m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay))
+ return nsMsgViewIndex_None;
+
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ nsresult rv = m_db->GetMsgHdrForKey(msgKey, getter_AddRefs(msgHdr));
+ NS_ENSURE_SUCCESS(rv, nsMsgViewIndex_None);
+ return ThreadIndexOfMsgHdr(msgHdr, msgIndex, pThreadCount, pFlags);
+}
+
+nsMsgViewIndex nsMsgDBView::GetThreadIndex(nsMsgViewIndex msgIndex) {
+ if (!IsValidIndex(msgIndex)) return nsMsgViewIndex_None;
+
+ // Scan up looking for level 0 message.
+ while (m_levels[msgIndex] && msgIndex) --msgIndex;
+
+ return msgIndex;
+}
+
+nsMsgViewIndex nsMsgDBView::ThreadIndexOfMsgHdr(nsIMsgDBHdr* msgHdr,
+ nsMsgViewIndex msgIndex,
+ int32_t* pThreadCount,
+ uint32_t* pFlags) {
+ nsCOMPtr<nsIMsgThread> threadHdr;
+ nsresult rv = GetThreadContainingMsgHdr(msgHdr, getter_AddRefs(threadHdr));
+ NS_ENSURE_SUCCESS(rv, nsMsgViewIndex_None);
+
+ nsMsgViewIndex retIndex = nsMsgViewIndex_None;
+
+ if (threadHdr != nullptr) {
+ if (msgIndex == nsMsgViewIndex_None) msgIndex = FindHdr(msgHdr, 0, true);
+
+ // Hdr is not in view, need to find by thread.
+ if (msgIndex == nsMsgViewIndex_None) {
+ msgIndex = GetIndexOfFirstDisplayedKeyInThread(threadHdr, true);
+ // nsMsgKey threadKey = (msgIndex == nsMsgViewIndex_None) ? nsMsgKey_None
+ // :
+ // GetAt(msgIndex);
+ if (pFlags) threadHdr->GetFlags(pFlags);
+ }
+
+ nsMsgViewIndex startOfThread = msgIndex;
+ while ((int32_t)startOfThread >= 0 && m_levels[startOfThread] != 0)
+ startOfThread--;
+
+ retIndex = startOfThread;
+ if (pThreadCount) {
+ int32_t numChildren = 0;
+ nsMsgViewIndex threadIndex = startOfThread;
+ do {
+ threadIndex++;
+ numChildren++;
+ } while (threadIndex < m_levels.Length() && m_levels[threadIndex] != 0);
+
+ *pThreadCount = numChildren;
+ }
+ }
+
+ return retIndex;
+}
+
+nsMsgKey nsMsgDBView::GetKeyOfFirstMsgInThread(nsMsgKey key) {
+ // Just report no key for any failure. This can occur when a
+ // message is deleted from a threaded view.
+ nsCOMPtr<nsIMsgThread> pThread;
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ nsresult rv = m_db->GetMsgHdrForKey(key, getter_AddRefs(msgHdr));
+ if (NS_FAILED(rv)) return nsMsgKey_None;
+
+ rv = GetThreadContainingMsgHdr(msgHdr, getter_AddRefs(pThread));
+ if (NS_FAILED(rv)) return nsMsgKey_None;
+
+ nsMsgKey firstKeyInThread = nsMsgKey_None;
+
+ if (!pThread) return firstKeyInThread;
+
+ // ### dmb UnreadOnly - this is wrong. But didn't seem to matter in 4.x
+ pThread->GetChildKeyAt(0, &firstKeyInThread);
+ return firstKeyInThread;
+}
+
+NS_IMETHODIMP
+nsMsgDBView::GetKeyAt(nsMsgViewIndex index, nsMsgKey* result) {
+ NS_ENSURE_ARG(result);
+ *result = GetAt(index);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBView::GetFlagsAt(nsMsgViewIndex aIndex, uint32_t* aResult) {
+ NS_ENSURE_ARG(aResult);
+ if (!IsValidIndex(aIndex)) return NS_MSG_INVALID_DBVIEW_INDEX;
+
+ *aResult = m_flags[aIndex];
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBView::GetMsgHdrAt(nsMsgViewIndex aIndex, nsIMsgDBHdr** aResult) {
+ NS_ENSURE_ARG(aResult);
+ if (!IsValidIndex(aIndex)) return NS_MSG_INVALID_DBVIEW_INDEX;
+
+ return GetMsgHdrForViewIndex(aIndex, aResult);
+}
+
+nsMsgViewIndex nsMsgDBView::FindHdr(nsIMsgDBHdr* msgHdr,
+ nsMsgViewIndex startIndex,
+ bool allowDummy) {
+ nsMsgKey msgKey;
+ msgHdr->GetMessageKey(&msgKey);
+ nsMsgViewIndex viewIndex = m_keys.IndexOf(msgKey, startIndex);
+ if (viewIndex == nsMsgViewIndex_None) return viewIndex;
+
+ // If we're supposed to allow dummies, and the previous index is a dummy that
+ // is not elided, then it must be the dummy corresponding to our node and
+ // we should return that instead.
+ if (allowDummy && viewIndex &&
+ (m_flags[viewIndex - 1] & MSG_VIEW_FLAG_DUMMY) &&
+ !(m_flags[viewIndex - 1] & nsMsgMessageFlags::Elided)) {
+ viewIndex--;
+ } else if (!allowDummy && m_flags[viewIndex] & MSG_VIEW_FLAG_DUMMY) {
+ // We're not allowing dummies, and we found a dummy, look again
+ // one past the dummy.
+ return m_keys.IndexOf(msgKey, viewIndex + 1);
+ }
+
+ // Check that the message we found matches the message we were looking for.
+ if (viewIndex != nsMsgViewIndex_None) {
+ nsCOMPtr<nsIMsgDBHdr> foundMsgHdr;
+ nsresult rv = GetMsgHdrForViewIndex(viewIndex, getter_AddRefs(foundMsgHdr));
+ if (NS_FAILED(rv) || foundMsgHdr != msgHdr) {
+ viewIndex = nsMsgViewIndex_None;
+ }
+ }
+
+ return viewIndex;
+}
+
+nsMsgViewIndex nsMsgDBView::FindKey(nsMsgKey key, bool expand) {
+ nsMsgViewIndex retIndex = nsMsgViewIndex_None;
+ retIndex = (nsMsgViewIndex)(m_keys.IndexOf(key));
+ // For dummy headers, try to expand if the caller says so. And if the thread
+ // is expanded, ignore the dummy header and return the real header index.
+ if (retIndex != nsMsgViewIndex_None &&
+ m_flags[retIndex] & MSG_VIEW_FLAG_DUMMY &&
+ !(m_flags[retIndex] & nsMsgMessageFlags::Elided)) {
+ return (nsMsgViewIndex)m_keys.IndexOf(key, retIndex + 1);
+ }
+
+ if (key != nsMsgKey_None &&
+ (retIndex == nsMsgViewIndex_None ||
+ m_flags[retIndex] & MSG_VIEW_FLAG_DUMMY) &&
+ expand && m_db) {
+ nsMsgKey threadKey = GetKeyOfFirstMsgInThread(key);
+ if (threadKey != nsMsgKey_None) {
+ nsMsgViewIndex threadIndex = FindKey(threadKey, false);
+ if (threadIndex != nsMsgViewIndex_None) {
+ uint32_t flags = m_flags[threadIndex];
+ if ((flags & nsMsgMessageFlags::Elided &&
+ NS_SUCCEEDED(ExpandByIndex(threadIndex, nullptr))) ||
+ flags & MSG_VIEW_FLAG_DUMMY) {
+ retIndex = (nsMsgViewIndex)m_keys.IndexOf(key, threadIndex + 1);
+ }
+ }
+ }
+ }
+
+ return retIndex;
+}
+
+nsresult nsMsgDBView::GetThreadCount(nsMsgViewIndex index,
+ uint32_t* pThreadCount) {
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ nsresult rv = GetMsgHdrForViewIndex(index, getter_AddRefs(msgHdr));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIMsgThread> pThread;
+ rv = GetThreadContainingMsgHdr(msgHdr, getter_AddRefs(pThread));
+ if (NS_SUCCEEDED(rv) && pThread != nullptr)
+ rv = pThread->GetNumChildren(pThreadCount);
+
+ return rv;
+}
+
+// This counts the number of messages in an expanded thread, given the
+// index of the first message in the thread.
+int32_t nsMsgDBView::CountExpandedThread(nsMsgViewIndex index) {
+ int32_t numInThread = 0;
+ nsMsgViewIndex startOfThread = index;
+ while ((int32_t)startOfThread >= 0 && m_levels[startOfThread] != 0)
+ startOfThread--;
+
+ nsMsgViewIndex threadIndex = startOfThread;
+ do {
+ threadIndex++;
+ numInThread++;
+ } while (threadIndex < m_levels.Length() && m_levels[threadIndex] != 0);
+
+ return numInThread;
+}
+
+// Returns the number of lines that would be added (> 0) or removed (< 0)
+// if we were to try to expand/collapse the passed index.
+nsresult nsMsgDBView::ExpansionDelta(nsMsgViewIndex index,
+ int32_t* expansionDelta) {
+ uint32_t numChildren;
+ nsresult rv;
+
+ *expansionDelta = 0;
+ if (index >= ((nsMsgViewIndex)m_keys.Length()))
+ return NS_MSG_MESSAGE_NOT_FOUND;
+
+ char flags = m_flags[index];
+
+ if (!(m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay)) return NS_OK;
+
+ // The client can pass in the key of any message
+ // in a thread and get the expansion delta for the thread.
+
+ if (flags & nsMsgMessageFlags::Elided) {
+ rv = GetThreadCount(index, &numChildren);
+ NS_ENSURE_SUCCESS(rv, rv);
+ *expansionDelta = numChildren - 1;
+ } else {
+ numChildren = CountExpandedThread(index);
+ *expansionDelta = -(int32_t)(numChildren - 1);
+ }
+
+ return NS_OK;
+}
+
+nsresult nsMsgDBView::ToggleExpansion(nsMsgViewIndex index,
+ uint32_t* numChanged) {
+ nsresult rv;
+ NS_ENSURE_ARG(numChanged);
+ *numChanged = 0;
+ nsMsgViewIndex threadIndex = GetThreadIndex(index);
+ if (threadIndex == nsMsgViewIndex_None) {
+ NS_ASSERTION(false, "couldn't find thread");
+ return NS_MSG_MESSAGE_NOT_FOUND;
+ }
+
+ int32_t flags = m_flags[threadIndex];
+
+ // If not a thread, or doesn't have children, no expand/collapse.
+ // If we add sub-thread expand collapse, this will need to be relaxed.
+ if (!(flags & MSG_VIEW_FLAG_ISTHREAD) || !(flags & MSG_VIEW_FLAG_HASCHILDREN))
+ return NS_MSG_MESSAGE_NOT_FOUND;
+
+ if (flags & nsMsgMessageFlags::Elided)
+ rv = ExpandByIndex(threadIndex, numChanged);
+ else
+ rv = CollapseByIndex(threadIndex, numChanged);
+
+ // If we collaps/uncollapse a thread, this changes the selected URIs.
+ SelectionChangedXPCOM();
+ return rv;
+}
+
+nsresult nsMsgDBView::ExpandAndSelectThread() {
+ nsresult rv;
+
+ NS_ASSERTION(mTreeSelection, "no tree selection");
+ if (!mTreeSelection) return NS_ERROR_UNEXPECTED;
+
+ int32_t index;
+ rv = mTreeSelection->GetCurrentIndex(&index);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = ExpandAndSelectThreadByIndex(index, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return NS_OK;
+}
+
+nsresult nsMsgDBView::ExpandAndSelectThreadByIndex(nsMsgViewIndex index,
+ bool augment) {
+ if (!IsValidIndex(index)) return NS_MSG_INVALID_DBVIEW_INDEX;
+
+ nsresult rv;
+
+ nsMsgViewIndex threadIndex;
+ bool inThreadedMode = (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay);
+
+ if (inThreadedMode) {
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ rv = GetMsgHdrForViewIndex(index, getter_AddRefs(msgHdr));
+ NS_ENSURE_SUCCESS(rv, rv);
+ threadIndex = ThreadIndexOfMsgHdr(msgHdr, index);
+ if (threadIndex == nsMsgViewIndex_None) {
+ NS_ASSERTION(false, "couldn't find thread");
+ return NS_MSG_MESSAGE_NOT_FOUND;
+ }
+ } else {
+ threadIndex = index;
+ }
+
+ int32_t flags = m_flags[threadIndex];
+ int32_t count = 0;
+
+ if (inThreadedMode && flags & MSG_VIEW_FLAG_ISTHREAD &&
+ flags & MSG_VIEW_FLAG_HASCHILDREN) {
+ // If closed, expand this thread.
+ if (flags & nsMsgMessageFlags::Elided) {
+ uint32_t numExpanded;
+ rv = ExpandByIndex(threadIndex, &numExpanded);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Get the number of messages in the expanded thread so we know how many
+ // to select.
+ count = CountExpandedThread(threadIndex);
+ } else {
+ count = 1;
+ }
+
+ NS_ASSERTION(count > 0, "bad count");
+
+ // Update the selection.
+
+ NS_ASSERTION(mTreeSelection, "no tree selection");
+ if (!mTreeSelection) return NS_ERROR_UNEXPECTED;
+
+ // The count should be 1 or greater. If there was only one message in the
+ // thread, we just select it. If more, we select all of them.
+ mTreeSelection->RangedSelect(threadIndex + count - 1, threadIndex, augment);
+ return NS_OK;
+}
+
+nsresult nsMsgDBView::ExpandAll() {
+ if (mTree) mTree->BeginUpdateBatch();
+ if (mJSTree) mJSTree->BeginUpdateBatch();
+
+ for (int32_t i = GetSize() - 1; i >= 0; i--) {
+ uint32_t numExpanded;
+ uint32_t flags = m_flags[i];
+ if (flags & nsMsgMessageFlags::Elided) ExpandByIndex(i, &numExpanded);
+ }
+
+ if (mTree) mTree->EndUpdateBatch();
+ if (mJSTree) mJSTree->EndUpdateBatch();
+
+ SelectionChangedXPCOM();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBView::GetThreadContainingMsgHdr(nsIMsgDBHdr* msgHdr,
+ nsIMsgThread** pThread) {
+ NS_ENSURE_ARG_POINTER(msgHdr);
+ NS_ENSURE_ARG_POINTER(pThread);
+ if (!m_db) return NS_ERROR_FAILURE;
+ return m_db->GetThreadContainingMsgHdr(msgHdr, pThread);
+}
+
+nsresult nsMsgDBView::ExpandByIndex(nsMsgViewIndex index,
+ uint32_t* pNumExpanded) {
+ if ((uint32_t)index >= m_keys.Length()) return NS_MSG_MESSAGE_NOT_FOUND;
+
+ uint32_t flags = m_flags[index];
+ uint32_t numExpanded = 0;
+
+ NS_ASSERTION(flags & nsMsgMessageFlags::Elided,
+ "can't expand an already expanded thread");
+ flags &= ~nsMsgMessageFlags::Elided;
+
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ nsCOMPtr<nsIMsgThread> pThread;
+ nsresult rv = GetThreadContainingIndex(index, getter_AddRefs(pThread));
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (m_viewFlags & nsMsgViewFlagsType::kUnreadOnly) {
+ // Keep top level hdr in thread, even though read.
+ if (flags & nsMsgMessageFlags::Read) {
+ m_levels.AppendElement(0);
+ }
+
+ rv = ListUnreadIdsInThread(pThread, index, &numExpanded);
+ } else {
+ rv = ListIdsInThread(pThread, index, &numExpanded);
+ }
+
+ if (numExpanded > 0) {
+ m_flags[index] = flags;
+ NoteChange(index, 1, nsMsgViewNotificationCode::changed);
+ }
+
+ NoteChange(index + 1, numExpanded, nsMsgViewNotificationCode::insertOrDelete);
+
+ if (pNumExpanded != nullptr) *pNumExpanded = numExpanded;
+
+ return rv;
+}
+
+nsresult nsMsgDBView::CollapseAll() {
+ if (mJSTree) mJSTree->BeginUpdateBatch();
+ for (uint32_t i = 0; i < GetSize(); i++) {
+ uint32_t numExpanded;
+ uint32_t flags = m_flags[i];
+ if (!(flags & nsMsgMessageFlags::Elided) &&
+ (flags & MSG_VIEW_FLAG_HASCHILDREN))
+ CollapseByIndex(i, &numExpanded);
+ }
+
+ if (mJSTree) mJSTree->EndUpdateBatch();
+ SelectionChangedXPCOM();
+ return NS_OK;
+}
+
+nsresult nsMsgDBView::CollapseByIndex(nsMsgViewIndex index,
+ uint32_t* pNumCollapsed) {
+ nsresult rv;
+ int32_t flags = m_flags[index];
+ int32_t rowDelta = 0;
+
+ if (flags & nsMsgMessageFlags::Elided ||
+ !(m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay) ||
+ !(flags & MSG_VIEW_FLAG_HASCHILDREN)) {
+ return NS_OK;
+ }
+
+ if (index > m_keys.Length()) return NS_MSG_MESSAGE_NOT_FOUND;
+
+ rv = ExpansionDelta(index, &rowDelta);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ flags |= nsMsgMessageFlags::Elided;
+
+ m_flags[index] = flags;
+ NoteChange(index, 1, nsMsgViewNotificationCode::changed);
+
+ // Don't count first header in thread.
+ int32_t numRemoved = -rowDelta;
+ if (index + 1 + numRemoved > m_keys.Length()) {
+ NS_ERROR("trying to remove too many rows");
+ numRemoved -= (index + 1 + numRemoved) - m_keys.Length();
+ if (numRemoved <= 0) return NS_MSG_MESSAGE_NOT_FOUND;
+ }
+
+ // Start at first id after thread.
+ RemoveRows(index + 1, numRemoved);
+ if (pNumCollapsed != nullptr) *pNumCollapsed = numRemoved;
+
+ NoteChange(index + 1, rowDelta, nsMsgViewNotificationCode::insertOrDelete);
+
+ return rv;
+}
+
+nsresult nsMsgDBView::OnNewHeader(nsIMsgDBHdr* newHdr, nsMsgKey aParentKey,
+ bool /*ensureListed*/) {
+ nsresult rv = NS_OK;
+ // Views can override this behaviour, which is to append to view.
+ // This is the mail behaviour, but threaded views will want
+ // to insert in order...
+ if (newHdr) rv = AddHdr(newHdr);
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgDBView::GetThreadContainingIndex(nsMsgViewIndex index,
+ nsIMsgThread** resultThread) {
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ nsresult rv = GetMsgHdrForViewIndex(index, getter_AddRefs(msgHdr));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return GetThreadContainingMsgHdr(msgHdr, resultThread);
+}
+
+nsMsgViewIndex nsMsgDBView::GetIndexForThread(nsIMsgDBHdr* msgHdr) {
+ // Take advantage of the fact that we're already sorted
+ // and find the insert index via a binary search, though expanded threads
+ // make that tricky.
+
+ nsMsgViewIndex highIndex = m_keys.Length();
+ nsMsgViewIndex lowIndex = 0;
+ IdKey EntryInfo1, EntryInfo2;
+
+ nsresult rv;
+ uint16_t maxLen;
+ eFieldType fieldType;
+
+ // Get the custom column handler for the primary sort and pass it first
+ // to GetFieldTypeAndLenForSort to get the fieldType and then either
+ // GetCollationKey or GetLongField.
+ nsIMsgCustomColumnHandler* colHandler = GetCurColumnHandler();
+
+ // The following may leave fieldType undefined.
+ // In this case, we can return highIndex right away since
+ // it is the value returned in the default case of
+ // switch (fieldType) statement below.
+ rv = GetFieldTypeAndLenForSort(m_sortType, &maxLen, &fieldType, colHandler);
+ NS_ENSURE_SUCCESS(rv, highIndex);
+
+ int retStatus = 0;
+ msgHdr->GetMessageKey(&EntryInfo1.id);
+ msgHdr->GetFolder(&EntryInfo1.folder);
+ EntryInfo1.folder->Release();
+
+ viewSortInfo comparisonContext{
+ .view = this,
+ .isSecondarySort = false,
+ .ascendingSort = (m_sortOrder == nsMsgViewSortOrder::ascending),
+ };
+
+ nsCOMPtr<nsIMsgDatabase> hdrDB;
+ EntryInfo1.folder->GetMsgDatabase(getter_AddRefs(hdrDB));
+ comparisonContext.db = hdrDB.get();
+ switch (fieldType) {
+ case kCollationKey:
+ rv = GetCollationKey(msgHdr, m_sortType, EntryInfo1.key, colHandler);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "failed to create collation key");
+ break;
+ case kU32:
+ if (m_sortType == nsMsgViewSortType::byId) {
+ EntryInfo1.dword = EntryInfo1.id;
+ } else {
+ GetLongField(msgHdr, m_sortType, &EntryInfo1.dword, colHandler);
+ }
+
+ break;
+ default:
+ return highIndex;
+ }
+
+ while (highIndex > lowIndex) {
+ nsMsgViewIndex tryIndex = (lowIndex + highIndex) / 2;
+ // Need to adjust tryIndex if it's not a thread.
+ while (m_levels[tryIndex] && tryIndex) tryIndex--;
+
+ if (tryIndex < lowIndex) {
+ NS_ERROR("try index shouldn't be less than low index");
+ break;
+ }
+
+ EntryInfo2.id = m_keys[tryIndex];
+ GetFolderForViewIndex(tryIndex, &EntryInfo2.folder);
+ EntryInfo2.folder->Release();
+
+ nsCOMPtr<nsIMsgDBHdr> tryHdr;
+ nsCOMPtr<nsIMsgDatabase> db;
+ // ### this should get the db from the folder...
+ GetDBForViewIndex(tryIndex, getter_AddRefs(db));
+ if (db) db->GetMsgHdrForKey(EntryInfo2.id, getter_AddRefs(tryHdr));
+
+ if (!tryHdr) break;
+
+ if (tryHdr == msgHdr) {
+ NS_WARNING("didn't expect header to already be in view");
+ highIndex = tryIndex;
+ break;
+ }
+
+ if (fieldType == kCollationKey) {
+ rv = GetCollationKey(tryHdr, m_sortType, EntryInfo2.key, colHandler);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "failed to create collation key");
+
+ retStatus = FnSortIdKey(&EntryInfo1, &EntryInfo2, &comparisonContext);
+ } else if (fieldType == kU32) {
+ if (m_sortType == nsMsgViewSortType::byId) {
+ EntryInfo2.dword = EntryInfo2.id;
+ } else {
+ GetLongField(tryHdr, m_sortType, &EntryInfo2.dword, colHandler);
+ }
+
+ retStatus = FnSortIdUint32(&EntryInfo1, &EntryInfo2, &comparisonContext);
+ }
+
+ if (retStatus == 0) {
+ highIndex = tryIndex;
+ break;
+ }
+
+ if (retStatus < 0) {
+ highIndex = tryIndex;
+ // We already made sure tryIndex was at a thread at the top of the loop.
+ } else {
+ lowIndex = tryIndex + 1;
+ while (lowIndex < GetSize() && m_levels[lowIndex]) lowIndex++;
+ }
+ }
+
+ return highIndex;
+}
+
+nsMsgViewIndex nsMsgDBView::GetInsertIndexHelper(
+ nsIMsgDBHdr* msgHdr, nsTArray<nsMsgKey>& keys,
+ nsCOMArray<nsIMsgFolder>* folders, nsMsgViewSortOrderValue sortOrder,
+ nsMsgViewSortTypeValue sortType) {
+ nsMsgViewIndex highIndex = keys.Length();
+ nsMsgViewIndex lowIndex = 0;
+ IdKey EntryInfo1, EntryInfo2;
+
+ nsresult rv;
+ uint16_t maxLen;
+ eFieldType fieldType;
+
+ // Get the custom column handler for the primary sort and pass it first
+ // to GetFieldTypeAndLenForSort to get the fieldType and then either
+ // GetCollationKey or GetLongField.
+ nsIMsgCustomColumnHandler* colHandler = GetCurColumnHandler();
+
+ // The following may leave fieldType undefined.
+ // In this case, we can return highIndex right away since
+ // it is the value returned in the default case of
+ // switch (fieldType) statement below.
+ rv = GetFieldTypeAndLenForSort(sortType, &maxLen, &fieldType, colHandler);
+ NS_ENSURE_SUCCESS(rv, highIndex);
+
+ int retStatus = 0;
+ msgHdr->GetMessageKey(&EntryInfo1.id);
+ msgHdr->GetFolder(&EntryInfo1.folder);
+ EntryInfo1.folder->Release();
+
+ viewSortInfo comparisonContext{
+ .view = this,
+ .isSecondarySort = false,
+ .ascendingSort = (sortOrder == nsMsgViewSortOrder::ascending),
+ };
+
+ rv = EntryInfo1.folder->GetMsgDatabase(&comparisonContext.db);
+ NS_ENSURE_SUCCESS(rv, highIndex);
+ comparisonContext.db->Release();
+
+ switch (fieldType) {
+ case kCollationKey:
+ rv = GetCollationKey(msgHdr, sortType, EntryInfo1.key, colHandler);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "failed to create collation key");
+ break;
+ case kU32:
+ if (sortType == nsMsgViewSortType::byId) {
+ EntryInfo1.dword = EntryInfo1.id;
+ } else {
+ GetLongField(msgHdr, sortType, &EntryInfo1.dword, colHandler);
+ }
+
+ break;
+ default:
+ return highIndex;
+ }
+
+ while (highIndex > lowIndex) {
+ nsMsgViewIndex tryIndex = (lowIndex + highIndex - 1) / 2;
+ EntryInfo2.id = keys[tryIndex];
+ EntryInfo2.folder = folders ? folders->ObjectAt(tryIndex) : m_folder.get();
+
+ nsCOMPtr<nsIMsgDBHdr> tryHdr;
+ EntryInfo2.folder->GetMessageHeader(EntryInfo2.id, getter_AddRefs(tryHdr));
+ if (!tryHdr) break;
+
+ if (fieldType == kCollationKey) {
+ rv = GetCollationKey(tryHdr, sortType, EntryInfo2.key, colHandler);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "failed to create collation key");
+
+ retStatus = FnSortIdKey(&EntryInfo1, &EntryInfo2, &comparisonContext);
+ } else if (fieldType == kU32) {
+ if (sortType == nsMsgViewSortType::byId) {
+ EntryInfo2.dword = EntryInfo2.id;
+ } else {
+ GetLongField(tryHdr, sortType, &EntryInfo2.dword, colHandler);
+ }
+
+ retStatus = FnSortIdUint32(&EntryInfo1, &EntryInfo2, &comparisonContext);
+ }
+
+ if (retStatus == 0) {
+ highIndex = tryIndex;
+ break;
+ }
+
+ if (retStatus < 0) {
+ highIndex = tryIndex;
+ } else {
+ lowIndex = tryIndex + 1;
+ }
+ }
+
+ return highIndex;
+}
+
+nsMsgViewIndex nsMsgDBView::GetInsertIndex(nsIMsgDBHdr* msgHdr) {
+ if (!GetSize()) return 0;
+
+ if ((m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay) != 0 &&
+ !(m_viewFlags & nsMsgViewFlagsType::kGroupBySort) &&
+ m_sortOrder != nsMsgViewSortType::byId) {
+ return GetIndexForThread(msgHdr);
+ }
+
+ return GetInsertIndexHelper(msgHdr, m_keys, GetFolders(), m_sortOrder,
+ m_sortType);
+}
+
+nsresult nsMsgDBView::AddHdr(nsIMsgDBHdr* msgHdr, nsMsgViewIndex* resultIndex) {
+ uint32_t flags = 0;
+#ifdef DEBUG_bienvenu
+ NS_ASSERTION(m_keys.Length() == m_flags.Length() &&
+ (int)m_keys.Length() == m_levels.Length(),
+ "view arrays out of sync!");
+#endif
+
+ if (resultIndex) *resultIndex = nsMsgViewIndex_None;
+
+ if (!GetShowingIgnored()) {
+ nsCOMPtr<nsIMsgThread> thread;
+ GetThreadContainingMsgHdr(msgHdr, getter_AddRefs(thread));
+ if (thread) {
+ thread->GetFlags(&flags);
+ if (flags & nsMsgMessageFlags::Ignored) return NS_OK;
+ }
+
+ bool ignored;
+ msgHdr->GetIsKilled(&ignored);
+ if (ignored) return NS_OK;
+ }
+
+ nsMsgKey msgKey, threadId;
+ nsMsgKey threadParent;
+ msgHdr->GetMessageKey(&msgKey);
+ msgHdr->GetThreadId(&threadId);
+ msgHdr->GetThreadParent(&threadParent);
+
+ msgHdr->GetFlags(&flags);
+ // XXX this isn't quite right, is it?
+ // Should be checking that our thread parent key is none?
+ if (threadParent == nsMsgKey_None) flags |= MSG_VIEW_FLAG_ISTHREAD;
+
+ nsMsgViewIndex insertIndex = GetInsertIndex(msgHdr);
+ if (insertIndex == nsMsgViewIndex_None) {
+ // If unreadonly, level is 0 because we must be the only msg in the thread.
+ int32_t levelToAdd = 0;
+
+ if (m_sortOrder == nsMsgViewSortOrder::ascending) {
+ InsertMsgHdrAt(GetSize(), msgHdr, msgKey, flags, levelToAdd);
+ if (resultIndex) *resultIndex = GetSize() - 1;
+
+ // The call to NoteChange() has to happen after we add the key as
+ // NoteChange() will call RowCountChanged() which will call our
+ // GetRowCount().
+ NoteChange(GetSize() - 1, 1, nsMsgViewNotificationCode::insertOrDelete);
+ } else {
+ InsertMsgHdrAt(0, msgHdr, msgKey, flags, levelToAdd);
+ if (resultIndex) *resultIndex = 0;
+
+ // The call to NoteChange() has to happen after we insert the key as
+ // NoteChange() will call RowCountChanged() which will call our
+ // GetRowCount().
+ NoteChange(0, 1, nsMsgViewNotificationCode::insertOrDelete);
+ }
+
+ m_sortValid = false;
+ } else {
+ InsertMsgHdrAt(insertIndex, msgHdr, msgKey, flags, 0);
+ if (resultIndex) *resultIndex = insertIndex;
+
+ // The call to NoteChange() has to happen after we add the key as
+ // NoteChange() will call RowCountChanged() which will call our
+ // GetRowCount().
+ NoteChange(insertIndex, 1, nsMsgViewNotificationCode::insertOrDelete);
+ }
+
+ OnHeaderAddedOrDeleted();
+ return NS_OK;
+}
+
+bool nsMsgDBView::WantsThisThread(nsIMsgThread* /*threadHdr*/) {
+ // Default is to want all threads.
+ return true;
+}
+
+nsMsgViewIndex nsMsgDBView::FindParentInThread(
+ nsMsgKey parentKey, nsMsgViewIndex startOfThreadViewIndex) {
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ while (parentKey != nsMsgKey_None) {
+ nsMsgViewIndex parentIndex =
+ m_keys.IndexOf(parentKey, startOfThreadViewIndex);
+ if (parentIndex != nsMsgViewIndex_None) return parentIndex;
+
+ if (NS_FAILED(m_db->GetMsgHdrForKey(parentKey, getter_AddRefs(msgHdr))))
+ break;
+
+ msgHdr->GetThreadParent(&parentKey);
+ }
+
+ return startOfThreadViewIndex;
+}
+
+nsresult nsMsgDBView::ListIdsInThreadOrder(nsIMsgThread* threadHdr,
+ nsMsgKey parentKey, uint32_t level,
+ nsMsgViewIndex* viewIndex,
+ uint32_t* pNumListed) {
+ nsCOMPtr<nsIMsgEnumerator> msgEnumerator;
+ nsresult rv =
+ threadHdr->EnumerateMessages(parentKey, getter_AddRefs(msgEnumerator));
+ NS_ENSURE_SUCCESS(rv, rv);
+ uint32_t numChildren;
+ (void)threadHdr->GetNumChildren(&numChildren);
+ NS_ASSERTION(numChildren, "Empty thread in view/db");
+ // Bogus, but harmless.
+ if (!numChildren) return NS_OK;
+
+ // Account for the existing thread root.
+ numChildren--;
+
+ // Skip the first one.
+ bool hasMore;
+ while (NS_SUCCEEDED(msgEnumerator->HasMoreElements(&hasMore)) && hasMore) {
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ rv = msgEnumerator->GetNext(getter_AddRefs(msgHdr));
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (*pNumListed == numChildren) {
+ MOZ_ASSERT_UNREACHABLE("thread corrupt in db");
+ // If we've listed more messages than are in the thread, then the db
+ // is corrupt, and we should invalidate it.
+ // We'll use this rv to indicate there's something wrong with the db
+ // though for now it probably won't get paid attention to.
+ m_db->SetSummaryValid(false);
+ return NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE;
+ }
+
+ if (!(m_viewFlags & nsMsgViewFlagsType::kShowIgnored)) {
+ bool ignored;
+ msgHdr->GetIsKilled(&ignored);
+ // We are not going to process subthreads, horribly invalidating the
+ // numChildren characteristic.
+ if (ignored) continue;
+ }
+
+ nsMsgKey msgKey;
+ uint32_t msgFlags, newFlags;
+ msgHdr->GetMessageKey(&msgKey);
+ msgHdr->GetFlags(&msgFlags);
+ AdjustReadFlag(msgHdr, &msgFlags);
+ SetMsgHdrAt(msgHdr, *viewIndex, msgKey, msgFlags & ~MSG_VIEW_FLAGS, level);
+ // Turn off thread or elided bit if they got turned on (maybe from new
+ // only view?)
+ msgHdr->AndFlags(~(MSG_VIEW_FLAG_ISTHREAD | nsMsgMessageFlags::Elided),
+ &newFlags);
+ (*pNumListed)++;
+ (*viewIndex)++;
+ rv = ListIdsInThreadOrder(threadHdr, msgKey, level + 1, viewIndex,
+ pNumListed);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ return NS_OK;
+}
+
+void nsMsgDBView::InsertEmptyRows(nsMsgViewIndex viewIndex, int32_t numRows) {
+ m_keys.InsertElementsAt(viewIndex, numRows, 0);
+ m_flags.InsertElementsAt(viewIndex, numRows, 0);
+ m_levels.InsertElementsAt(viewIndex, numRows, 1);
+}
+
+void nsMsgDBView::RemoveRows(nsMsgViewIndex viewIndex, int32_t numRows) {
+ m_keys.RemoveElementsAt(viewIndex, numRows);
+ m_flags.RemoveElementsAt(viewIndex, numRows);
+ m_levels.RemoveElementsAt(viewIndex, numRows);
+}
+
+NS_IMETHODIMP
+nsMsgDBView::InsertTreeRows(nsMsgViewIndex aIndex, uint32_t aNumRows,
+ nsMsgKey aKey, nsMsgViewFlagsTypeValue aFlags,
+ uint32_t aLevel, nsIMsgFolder* aFolder) {
+ if (GetSize() < aIndex) return NS_ERROR_UNEXPECTED;
+
+ nsCOMArray<nsIMsgFolder>* folders = GetFolders();
+ if (folders) {
+ // In a search/xfvf view only, a folder is required.
+ NS_ENSURE_ARG_POINTER(aFolder);
+ for (size_t i = 0; i < aNumRows; i++)
+ // Insert into m_folders.
+ if (!folders->InsertObjectAt(aFolder, aIndex + i))
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ m_keys.InsertElementsAt(aIndex, aNumRows, aKey);
+ m_flags.InsertElementsAt(aIndex, aNumRows, aFlags);
+ m_levels.InsertElementsAt(aIndex, aNumRows, aLevel);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBView::RemoveTreeRows(nsMsgViewIndex aIndex, uint32_t aNumRows) {
+ // Prevent a crash if attempting to remove rows which don't exist.
+ if (GetSize() < aIndex + aNumRows) return NS_ERROR_UNEXPECTED;
+
+ nsMsgDBView::RemoveRows(aIndex, aNumRows);
+
+ nsCOMArray<nsIMsgFolder>* folders = GetFolders();
+ if (folders)
+ // In a search/xfvf view only, remove from m_folders.
+ if (!folders->RemoveObjectsAt(aIndex, aNumRows)) return NS_ERROR_UNEXPECTED;
+
+ return NS_OK;
+}
+
+nsresult nsMsgDBView::ListIdsInThread(nsIMsgThread* threadHdr,
+ nsMsgViewIndex startOfThreadViewIndex,
+ uint32_t* pNumListed) {
+ NS_ENSURE_ARG(threadHdr);
+ // These children ids should be in thread order.
+ nsresult rv = NS_OK;
+ uint32_t i;
+ nsMsgViewIndex viewIndex = startOfThreadViewIndex + 1;
+ *pNumListed = 0;
+
+ uint32_t numChildren;
+ threadHdr->GetNumChildren(&numChildren);
+ NS_ASSERTION(numChildren, "Empty thread in view/db");
+ if (!numChildren) return NS_OK;
+
+ // Account for the existing thread root.
+ numChildren--;
+ InsertEmptyRows(viewIndex, numChildren);
+
+ // ### need to rework this when we implemented threading in group views.
+ if (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay &&
+ !(m_viewFlags & nsMsgViewFlagsType::kGroupBySort)) {
+ nsMsgKey parentKey = m_keys[startOfThreadViewIndex];
+ // If the thread is bigger than the hdr cache, expanding the thread
+ // can be slow. Increasing the hdr cache size will help a fair amount.
+ uint32_t hdrCacheSize;
+ m_db->GetMsgHdrCacheSize(&hdrCacheSize);
+ if (numChildren > hdrCacheSize) m_db->SetMsgHdrCacheSize(numChildren);
+
+ // If this fails, *pNumListed will be 0, and we'll fall back to just
+ // enumerating the messages in the thread below.
+ rv = ListIdsInThreadOrder(threadHdr, parentKey, 1, &viewIndex, pNumListed);
+ if (numChildren > hdrCacheSize) m_db->SetMsgHdrCacheSize(hdrCacheSize);
+ }
+
+ if (!*pNumListed) {
+ uint32_t ignoredHeaders = 0;
+ // If we're not threaded, just list em out in db order.
+ for (i = 1; i <= numChildren; i++) {
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ threadHdr->GetChildHdrAt(i, getter_AddRefs(msgHdr));
+
+ if (msgHdr != nullptr) {
+ if (!(m_viewFlags & nsMsgViewFlagsType::kShowIgnored)) {
+ bool killed;
+ msgHdr->GetIsKilled(&killed);
+ if (killed) {
+ ignoredHeaders++;
+ continue;
+ }
+ }
+
+ nsMsgKey msgKey;
+ uint32_t msgFlags, newFlags;
+ msgHdr->GetMessageKey(&msgKey);
+ msgHdr->GetFlags(&msgFlags);
+ AdjustReadFlag(msgHdr, &msgFlags);
+ SetMsgHdrAt(msgHdr, viewIndex, msgKey, msgFlags & ~MSG_VIEW_FLAGS, 1);
+ // Here, we're either flat, or we're grouped - in either case,
+ // level is 1. Turn off thread or elided bit if they got turned on
+ // (maybe from new only view?).
+ if (i > 0)
+ msgHdr->AndFlags(
+ ~(MSG_VIEW_FLAG_ISTHREAD | nsMsgMessageFlags::Elided), &newFlags);
+
+ (*pNumListed)++;
+ viewIndex++;
+ }
+ }
+
+ if (ignoredHeaders + *pNumListed < numChildren) {
+ MOZ_ASSERT_UNREACHABLE("thread corrupt in db");
+ // If we've listed fewer messages than are in the thread, then the db
+ // is corrupt, and we should invalidate it.
+ // We'll use this rv to indicate there's something wrong with the db
+ // though for now it probably won't get paid attention to.
+ m_db->SetSummaryValid(false);
+ rv = NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE;
+ }
+ }
+
+ // We may have added too many elements (i.e., subthreads were cut).
+ // XXX Fix for cross folder view case.
+ if (*pNumListed < numChildren)
+ RemoveRows(viewIndex, numChildren - *pNumListed);
+
+ return rv;
+}
+
+int32_t nsMsgDBView::FindLevelInThread(nsIMsgDBHdr* msgHdr,
+ nsMsgViewIndex startOfThread,
+ nsMsgViewIndex viewIndex) {
+ nsCOMPtr<nsIMsgDBHdr> curMsgHdr = msgHdr;
+ nsMsgKey msgKey;
+ msgHdr->GetMessageKey(&msgKey);
+
+ // Look through the ancestors of the passed in msgHdr in turn, looking for
+ // them in the view, up to the start of the thread. If we find an ancestor,
+ // then our level is one greater than the level of the ancestor.
+ while (curMsgHdr) {
+ nsMsgKey parentKey;
+ curMsgHdr->GetThreadParent(&parentKey);
+ if (parentKey == nsMsgKey_None) break;
+
+ // Scan up to find view index of ancestor, if any.
+ for (nsMsgViewIndex indexToTry = viewIndex;
+ indexToTry && indexToTry-- >= startOfThread;) {
+ if (m_keys[indexToTry] == parentKey) return m_levels[indexToTry] + 1;
+ }
+
+ // If msgHdr's key is its parentKey, we'll loop forever, so protect
+ // against that corruption.
+ if (msgKey == parentKey || NS_FAILED(m_db->GetMsgHdrForKey(
+ parentKey, getter_AddRefs(curMsgHdr)))) {
+ NS_ERROR(
+ "msgKey == parentKey, or GetMsgHdrForKey failed, this used to be an "
+ "infinite loop condition");
+ curMsgHdr = nullptr;
+ } else {
+ // Need to update msgKey so the check for a msgHdr with matching
+ // key+parentKey will work after first time through loop.
+ curMsgHdr->GetMessageKey(&msgKey);
+ }
+ }
+
+ return 1;
+}
+
+// XXX Can this be combined with GetIndexForThread??
+nsMsgViewIndex nsMsgDBView::GetThreadRootIndex(nsIMsgDBHdr* msgHdr) {
+ if (!msgHdr) {
+ NS_WARNING("null msgHdr parameter");
+ return nsMsgViewIndex_None;
+ }
+
+ // Take advantage of the fact that we're already sorted
+ // and find the thread root via a binary search.
+
+ nsMsgViewIndex highIndex = m_keys.Length();
+ nsMsgViewIndex lowIndex = 0;
+ IdKey EntryInfo1, EntryInfo2;
+
+ nsresult rv;
+ uint16_t maxLen;
+ eFieldType fieldType;
+
+ // Get the custom column handler for the primary sort and pass it first
+ // to GetFieldTypeAndLenForSort to get the fieldType and then either
+ // GetCollationKey or GetLongField.
+ nsIMsgCustomColumnHandler* colHandler = GetCurColumnHandler();
+
+ // The following may leave fieldType undefined.
+ // In this case, we can return highIndex right away since
+ // it is the value returned in the default case of
+ // switch (fieldType) statement below.
+ rv = GetFieldTypeAndLenForSort(m_sortType, &maxLen, &fieldType, colHandler);
+ NS_ENSURE_SUCCESS(rv, highIndex);
+
+ int retStatus = 0;
+ msgHdr->GetMessageKey(&EntryInfo1.id);
+ msgHdr->GetFolder(&EntryInfo1.folder);
+ EntryInfo1.folder->Release();
+
+ viewSortInfo comparisonContext{
+ .view = this,
+ .isSecondarySort = false,
+ .ascendingSort = (m_sortOrder == nsMsgViewSortOrder::ascending),
+ };
+
+ nsCOMPtr<nsIMsgDatabase> hdrDB;
+ EntryInfo1.folder->GetMsgDatabase(getter_AddRefs(hdrDB));
+ comparisonContext.db = hdrDB.get();
+
+ switch (fieldType) {
+ case kCollationKey:
+ rv = GetCollationKey(msgHdr, m_sortType, EntryInfo1.key, colHandler);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "failed to create collation key");
+ break;
+ case kU32:
+ if (m_sortType == nsMsgViewSortType::byId) {
+ EntryInfo1.dword = EntryInfo1.id;
+ } else {
+ GetLongField(msgHdr, m_sortType, &EntryInfo1.dword, colHandler);
+ }
+
+ break;
+ default:
+ return highIndex;
+ }
+
+ while (highIndex > lowIndex) {
+ nsMsgViewIndex tryIndex = (lowIndex + highIndex) / 2;
+ // Need to adjust tryIndex if it's not a thread.
+ while (m_levels[tryIndex] && tryIndex) tryIndex--;
+
+ if (tryIndex < lowIndex) {
+ NS_ERROR("try index shouldn't be less than low index");
+ break;
+ }
+
+ EntryInfo2.id = m_keys[tryIndex];
+ GetFolderForViewIndex(tryIndex, &EntryInfo2.folder);
+ EntryInfo2.folder->Release();
+
+ nsCOMPtr<nsIMsgDBHdr> tryHdr;
+ nsCOMPtr<nsIMsgDatabase> db;
+ // ### this should get the db from the folder...
+ GetDBForViewIndex(tryIndex, getter_AddRefs(db));
+ if (db) db->GetMsgHdrForKey(EntryInfo2.id, getter_AddRefs(tryHdr));
+
+ if (!tryHdr) break;
+
+ if (tryHdr == msgHdr) {
+ highIndex = tryIndex;
+ break;
+ }
+
+ if (fieldType == kCollationKey) {
+ rv = GetCollationKey(tryHdr, m_sortType, EntryInfo2.key, colHandler);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "failed to create collation key");
+ retStatus = FnSortIdKey(&EntryInfo1, &EntryInfo2, &comparisonContext);
+ } else if (fieldType == kU32) {
+ if (m_sortType == nsMsgViewSortType::byId) {
+ EntryInfo2.dword = EntryInfo2.id;
+ } else {
+ GetLongField(tryHdr, m_sortType, &EntryInfo2.dword, colHandler);
+ }
+
+ retStatus = FnSortIdUint32(&EntryInfo1, &EntryInfo2, &comparisonContext);
+ }
+
+ if (retStatus == 0) {
+ highIndex = tryIndex;
+ break;
+ }
+
+ if (retStatus < 0) {
+ highIndex = tryIndex;
+ // We already made sure tryIndex was at a thread at the top of the loop.
+ } else {
+ lowIndex = tryIndex + 1;
+ while (lowIndex < GetSize() && m_levels[lowIndex]) lowIndex++;
+ }
+ }
+
+ nsCOMPtr<nsIMsgDBHdr> resultHdr;
+ GetMsgHdrForViewIndex(highIndex, getter_AddRefs(resultHdr));
+
+ if (resultHdr != msgHdr) {
+ NS_WARNING("didn't find hdr");
+ highIndex = FindHdr(msgHdr);
+#ifdef DEBUG_David_Bienvenu
+ if (highIndex != nsMsgViewIndex_None) {
+ NS_WARNING("but find hdr did");
+ printf("level of found hdr = %d\n", m_levels[highIndex]);
+ ValidateSort();
+ }
+#endif
+ return highIndex;
+ }
+
+ return msgHdr == resultHdr ? highIndex : nsMsgViewIndex_None;
+}
+
+#ifdef DEBUG_David_Bienvenu
+
+void nsMsgDBView::InitEntryInfoForIndex(nsMsgViewIndex i, IdKey& EntryInfo) {
+ nsresult rv;
+ uint16_t maxLen;
+ eFieldType fieldType;
+
+ // Get the custom column handler for the primary sort and pass it first
+ // to GetFieldTypeAndLenForSort to get the fieldType and then either
+ // GetCollationKey or GetLongField.
+ nsIMsgCustomColumnHandler* colHandler = GetCurColumnHandler();
+
+ // The following may leave fieldType undefined.
+ rv = GetFieldTypeAndLenForSort(m_sortType, &maxLen, &fieldType, colHandler);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "failed to obtain fieldType");
+
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ GetMsgHdrForViewIndex(i, getter_AddRefs(msgHdr));
+
+ msgHdr->GetMessageKey(&EntryInfo.id);
+ msgHdr->GetFolder(&EntryInfo.folder);
+ EntryInfo.folder->Release();
+
+ nsCOMPtr<nsIMsgDatabase> hdrDB;
+ EntryInfo.folder->GetMsgDatabase(getter_AddRefs(hdrDB));
+ switch (fieldType) {
+ case kCollationKey:
+ rv = GetCollationKey(msgHdr, m_sortType, EntryInfo.key, colHandler);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "failed to create collation key");
+ break;
+ case kU32:
+ if (m_sortType == nsMsgViewSortType::byId)
+ EntryInfo.dword = EntryInfo.id;
+ else
+ GetLongField(msgHdr, m_sortType, &EntryInfo.dword, colHandler);
+
+ break;
+ default:
+ NS_ERROR("invalid field type");
+ }
+}
+
+void nsMsgDBView::ValidateSort() {
+ IdKey EntryInfo1, EntryInfo2;
+ nsCOMPtr<nsIMsgDBHdr> hdr1, hdr2;
+
+ uint16_t maxLen;
+ eFieldType fieldType;
+
+ // Get the custom column handler for the primary sort and pass it first
+ // to GetFieldTypeAndLenForSort to get the fieldType and then either
+ // GetCollationKey or GetLongField.
+ nsIMsgCustomColumnHandler* colHandler = GetCurColumnHandler();
+
+ // It is not entirely clear what we should do since,
+ // if fieldType is not available, there is no way to know
+ // how to compare the field to check for sorting.
+ // So we bomb out here. It is OK since this is debug code
+ // inside #ifdef DEBUG_David_Bienvenu
+ nsresult rv =
+ GetFieldTypeAndLenForSort(m_sortType, &maxLen, &fieldType, colHandler);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "failed to obtain fieldType");
+
+ viewSortInfo comparisonContext;
+ comparisonContext.view = this;
+ comparisonContext.isSecondarySort = false;
+ comparisonContext.ascendingSort =
+ (m_sortOrder == nsMsgViewSortOrder::ascending);
+ nsCOMPtr<nsIMsgDatabase> db;
+ GetDBForViewIndex(0, getter_AddRefs(db));
+ // This is only for comparing collation keys - it could be any db.
+ comparisonContext.db = db.get();
+
+ for (nsMsgViewIndex i = 0; i < m_keys.Length();) {
+ // Ignore non threads.
+ if (m_levels[i]) {
+ i++;
+ continue;
+ }
+
+ // Find next header.
+ nsMsgViewIndex j = i + 1;
+ while (j < m_keys.Length() && m_levels[j]) j++;
+
+ if (j == m_keys.Length()) break;
+
+ InitEntryInfoForIndex(i, EntryInfo1);
+ InitEntryInfoForIndex(j, EntryInfo2);
+ const void *pValue1 = &EntryInfo1, *pValue2 = &EntryInfo2;
+ int retStatus = 0;
+ if (fieldType == kCollationKey)
+ retStatus = FnSortIdKey(&pValue1, &pValue2, &comparisonContext);
+ else if (fieldType == kU32)
+ retStatus = FnSortIdUint32(&pValue1, &pValue2, &comparisonContext);
+
+ if (retStatus &&
+ (retStatus < 0) == (m_sortOrder == nsMsgViewSortOrder::ascending)) {
+ NS_ERROR("view not sorted correctly");
+ break;
+ }
+ // j is the new i.
+ i = j;
+ }
+}
+
+#endif
+
+nsresult nsMsgDBView::ListUnreadIdsInThread(
+ nsIMsgThread* threadHdr, nsMsgViewIndex startOfThreadViewIndex,
+ uint32_t* pNumListed) {
+ NS_ENSURE_ARG(threadHdr);
+ // These children ids should be in thread order.
+ nsMsgViewIndex viewIndex = startOfThreadViewIndex + 1;
+ *pNumListed = 0;
+ nsMsgKey topLevelMsgKey = m_keys[startOfThreadViewIndex];
+
+ uint32_t numChildren;
+ threadHdr->GetNumChildren(&numChildren);
+ uint32_t i;
+ for (i = 0; i < numChildren; i++) {
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ threadHdr->GetChildHdrAt(i, getter_AddRefs(msgHdr));
+ if (msgHdr != nullptr) {
+ if (!(m_viewFlags & nsMsgViewFlagsType::kShowIgnored)) {
+ bool killed;
+ msgHdr->GetIsKilled(&killed);
+ if (killed) continue;
+ }
+
+ nsMsgKey msgKey;
+ uint32_t msgFlags;
+ msgHdr->GetMessageKey(&msgKey);
+ msgHdr->GetFlags(&msgFlags);
+ bool isRead = AdjustReadFlag(msgHdr, &msgFlags);
+ if (!isRead) {
+ // Just make sure flag is right in db.
+ m_db->MarkHdrRead(msgHdr, false, nullptr);
+ if (msgKey != topLevelMsgKey) {
+ InsertMsgHdrAt(
+ viewIndex, msgHdr, msgKey, msgFlags,
+ FindLevelInThread(msgHdr, startOfThreadViewIndex, viewIndex));
+ viewIndex++;
+ (*pNumListed)++;
+ }
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBView::OnHdrFlagsChanged(nsIMsgDBHdr* aHdrChanged, uint32_t aOldFlags,
+ uint32_t aNewFlags,
+ nsIDBChangeListener* aInstigator) {
+ // If we're not the instigator, update flags if this key is in our view.
+ if (aInstigator != this) {
+ NS_ENSURE_ARG_POINTER(aHdrChanged);
+ nsMsgKey msgKey;
+ aHdrChanged->GetMessageKey(&msgKey);
+ nsMsgViewIndex index = FindHdr(aHdrChanged);
+ if (index != nsMsgViewIndex_None) {
+ uint32_t viewOnlyFlags =
+ m_flags[index] & (MSG_VIEW_FLAGS | nsMsgMessageFlags::Elided);
+
+ // XXX what about saving the old view only flags, like IsThread and
+ // HasChildren?
+ // I think we'll want to save those away.
+ m_flags[index] = aNewFlags | viewOnlyFlags;
+ // Tell the view the extra flag changed, so it can
+ // update the previous view, if any.
+ OnExtraFlagChanged(index, aNewFlags);
+ NoteChange(index, 1, nsMsgViewNotificationCode::changed);
+ }
+
+ uint32_t deltaFlags = (aOldFlags ^ aNewFlags);
+ if (deltaFlags & (nsMsgMessageFlags::Read | nsMsgMessageFlags::New)) {
+ nsMsgViewIndex threadIndex =
+ ThreadIndexOfMsgHdr(aHdrChanged, index, nullptr, nullptr);
+
+ // May need to fix thread counts.
+ if (threadIndex != nsMsgViewIndex_None && threadIndex != index)
+ NoteChange(threadIndex, 1, nsMsgViewNotificationCode::changed);
+ }
+ }
+
+ // Don't need to propagate notifications, right?
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBView::OnHdrDeleted(nsIMsgDBHdr* aHdrChanged, nsMsgKey aParentKey,
+ int32_t aFlags, nsIDBChangeListener* aInstigator) {
+ nsMsgViewIndex deletedIndex = FindHdr(aHdrChanged);
+ if (IsValidIndex(deletedIndex)) {
+ // Check if this message is currently selected. If it is, tell the frontend
+ // to be prepared for a delete.
+ nsCOMPtr<nsIMsgDBViewCommandUpdater> commandUpdater(
+ do_QueryReferent(mCommandUpdater));
+ bool isMsgSelected = false;
+ if (mTreeSelection && commandUpdater) {
+ mTreeSelection->IsSelected(deletedIndex, &isMsgSelected);
+ if (isMsgSelected) commandUpdater->UpdateNextMessageAfterDelete();
+ }
+
+ RemoveByIndex(deletedIndex);
+
+ if (isMsgSelected) {
+ // Now tell the front end that the delete happened.
+ commandUpdater->SelectedMessageRemoved();
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBView::OnHdrAdded(nsIMsgDBHdr* aHdrChanged, nsMsgKey aParentKey,
+ int32_t aFlags, nsIDBChangeListener* aInstigator) {
+ return OnNewHeader(aHdrChanged, aParentKey, false);
+ // Probably also want to pass that parent key in, since we went to the
+ // trouble of figuring out what it is.
+}
+
+NS_IMETHODIMP
+nsMsgDBView::OnHdrPropertyChanged(nsIMsgDBHdr* aHdrToChange,
+ const nsACString& property, bool aPreChange,
+ uint32_t* aStatus,
+ nsIDBChangeListener* aInstigator) {
+ if (aPreChange) return NS_OK;
+
+ if (aHdrToChange) {
+ nsMsgViewIndex index = FindHdr(aHdrToChange);
+ if (index != nsMsgViewIndex_None)
+ NoteChange(index, 1, nsMsgViewNotificationCode::changed);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBView::OnParentChanged(nsMsgKey aKeyChanged, nsMsgKey oldParent,
+ nsMsgKey newParent,
+ nsIDBChangeListener* aInstigator) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBView::OnAnnouncerGoingAway(nsIDBChangeAnnouncer* instigator) {
+ if (m_db) {
+ m_db->RemoveListener(this);
+ m_db = nullptr;
+ }
+
+ int32_t saveSize = GetSize();
+ ClearHdrCache();
+
+ // This is important, because the tree will ask us for our
+ // row count, which get determine from the number of keys.
+ m_keys.Clear();
+ // Be consistent.
+ m_flags.Clear();
+ m_levels.Clear();
+
+ // Tell the tree all the rows have gone away.
+ if (mTree) mTree->RowCountChanged(0, -saveSize);
+ if (mJSTree) mJSTree->RowCountChanged(0, -saveSize);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBView::OnEvent(nsIMsgDatabase* aDB, const char* aEvent) {
+ if (!strcmp(aEvent, "DBOpened")) m_db = aDB;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBView::OnReadChanged(nsIDBChangeListener* aInstigator) { return NS_OK; }
+
+NS_IMETHODIMP
+nsMsgDBView::OnJunkScoreChanged(nsIDBChangeListener* aInstigator) {
+ return NS_OK;
+}
+
+void nsMsgDBView::ClearHdrCache() {
+ m_cachedHdr = nullptr;
+ m_cachedMsgKey = nsMsgKey_None;
+}
+
+NS_IMETHODIMP
+nsMsgDBView::SetSuppressChangeNotifications(bool aSuppressChangeNotifications) {
+ mSuppressChangeNotification = aSuppressChangeNotifications;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBView::GetSuppressChangeNotifications(
+ bool* aSuppressChangeNotifications) {
+ NS_ENSURE_ARG_POINTER(aSuppressChangeNotifications);
+ *aSuppressChangeNotifications = mSuppressChangeNotification;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBView::NoteChange(nsMsgViewIndex firstLineChanged, int32_t numChanged,
+ nsMsgViewNotificationCodeValue changeType) {
+ if ((mTree || mJSTree) && !mSuppressChangeNotification) {
+ switch (changeType) {
+ case nsMsgViewNotificationCode::changed:
+ if (mTree)
+ mTree->InvalidateRange(firstLineChanged,
+ firstLineChanged + numChanged - 1);
+ if (mJSTree)
+ mJSTree->InvalidateRange(firstLineChanged,
+ firstLineChanged + numChanged - 1);
+ break;
+ case nsMsgViewNotificationCode::insertOrDelete:
+ if (numChanged < 0) mRemovingRow = true;
+
+ // The caller needs to have adjusted m_keys before getting here, since
+ // RowCountChanged() will call our GetRowCount().
+ if (mTree) mTree->RowCountChanged(firstLineChanged, numChanged);
+ if (mJSTree) mJSTree->RowCountChanged(firstLineChanged, numChanged);
+ mRemovingRow = false;
+ [[fallthrough]];
+ case nsMsgViewNotificationCode::all:
+ ClearHdrCache();
+ break;
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBView::GetSortOrder(nsMsgViewSortOrderValue* aSortOrder) {
+ NS_ENSURE_ARG_POINTER(aSortOrder);
+ *aSortOrder = m_sortOrder;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBView::GetSortType(nsMsgViewSortTypeValue* aSortType) {
+ NS_ENSURE_ARG_POINTER(aSortType);
+ *aSortType = m_sortType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBView::SetSortType(nsMsgViewSortTypeValue aSortType) {
+ m_sortType = aSortType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBView::GetViewType(nsMsgViewTypeValue* aViewType) {
+ NS_ERROR("you should be overriding this");
+ return NS_ERROR_UNEXPECTED;
+}
+
+NS_IMETHODIMP
+nsMsgDBView::GetSecondarySortOrder(nsMsgViewSortOrderValue* aSortOrder) {
+ NS_ENSURE_ARG_POINTER(aSortOrder);
+ *aSortOrder = m_secondarySortOrder;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBView::SetSecondarySortOrder(nsMsgViewSortOrderValue aSortOrder) {
+ m_secondarySortOrder = aSortOrder;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBView::GetSecondarySortType(nsMsgViewSortTypeValue* aSortType) {
+ NS_ENSURE_ARG_POINTER(aSortType);
+ *aSortType = m_secondarySort;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBView::SetSecondarySortType(nsMsgViewSortTypeValue aSortType) {
+ m_secondarySort = aSortType;
+ return NS_OK;
+}
+
+nsresult nsMsgDBView::PersistFolderInfo(nsIDBFolderInfo** dbFolderInfo) {
+ nsresult rv = m_db->GetDBFolderInfo(dbFolderInfo);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // Save off sort type and order, view type and flags.
+ (*dbFolderInfo)->SetSortType(m_sortType);
+ (*dbFolderInfo)->SetSortOrder(m_sortOrder);
+ (*dbFolderInfo)->SetViewFlags(m_viewFlags);
+ nsMsgViewTypeValue viewType;
+ GetViewType(&viewType);
+ (*dbFolderInfo)->SetViewType(viewType);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgDBView::GetViewFlags(nsMsgViewFlagsTypeValue* aViewFlags) {
+ NS_ENSURE_ARG_POINTER(aViewFlags);
+ *aViewFlags = m_viewFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBView::SetViewFlags(nsMsgViewFlagsTypeValue aViewFlags) {
+ // If we're turning off threaded display, we need to expand all so that all
+ // messages will be displayed.
+ if (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay &&
+ !(aViewFlags & nsMsgViewFlagsType::kThreadedDisplay)) {
+ ExpandAll();
+ // Invalidate the sort so sorting will do something.
+ m_sortValid = false;
+ }
+
+ m_viewFlags = aViewFlags;
+
+ if (m_viewFolder) {
+ nsCOMPtr<nsIMsgDatabase> db;
+ nsCOMPtr<nsIDBFolderInfo> folderInfo;
+ nsresult rv = m_viewFolder->GetDBFolderInfoAndDB(getter_AddRefs(folderInfo),
+ getter_AddRefs(db));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return folderInfo->SetViewFlags(aViewFlags);
+ } else
+ return NS_OK;
+}
+
+nsresult nsMsgDBView::MarkThreadOfMsgRead(nsMsgKey msgId,
+ nsMsgViewIndex msgIndex,
+ nsTArray<nsMsgKey>& idsMarkedRead,
+ bool bRead) {
+ nsCOMPtr<nsIMsgThread> threadHdr;
+ nsresult rv = GetThreadContainingIndex(msgIndex, getter_AddRefs(threadHdr));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsMsgViewIndex threadIndex;
+
+ NS_ASSERTION(threadHdr, "threadHdr is null");
+ if (!threadHdr) return NS_MSG_MESSAGE_NOT_FOUND;
+
+ nsCOMPtr<nsIMsgDBHdr> firstHdr;
+ rv = threadHdr->GetChildHdrAt(0, getter_AddRefs(firstHdr));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsMsgKey firstHdrId;
+ firstHdr->GetMessageKey(&firstHdrId);
+ if (msgId != firstHdrId)
+ threadIndex = GetIndexOfFirstDisplayedKeyInThread(threadHdr);
+ else
+ threadIndex = msgIndex;
+
+ return MarkThreadRead(threadHdr, threadIndex, idsMarkedRead, bRead);
+}
+
+nsresult nsMsgDBView::MarkThreadRead(nsIMsgThread* threadHdr,
+ nsMsgViewIndex threadIndex,
+ nsTArray<nsMsgKey>& idsMarkedRead,
+ bool bRead) {
+ uint32_t numChildren;
+ threadHdr->GetNumChildren(&numChildren);
+ idsMarkedRead.SetCapacity(numChildren);
+ for (int32_t childIndex = 0; childIndex < (int32_t)numChildren;
+ childIndex++) {
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ threadHdr->GetChildHdrAt(childIndex, getter_AddRefs(msgHdr));
+ NS_ASSERTION(msgHdr, "msgHdr is null");
+ if (!msgHdr) continue;
+
+ bool isRead;
+ nsMsgKey hdrMsgId;
+ msgHdr->GetMessageKey(&hdrMsgId);
+ nsCOMPtr<nsIMsgDatabase> db;
+ nsresult rv = GetDBForHeader(msgHdr, getter_AddRefs(db));
+ NS_ENSURE_SUCCESS(rv, rv);
+ db->IsRead(hdrMsgId, &isRead);
+
+ if (isRead != bRead) {
+ // MarkHdrRead will change the unread count on the thread.
+ db->MarkHdrRead(msgHdr, bRead, nullptr);
+ // Insert at the front. Should we insert at the end?
+ idsMarkedRead.InsertElementAt(0, hdrMsgId);
+ }
+ }
+
+ return NS_OK;
+}
+
+bool nsMsgDBView::AdjustReadFlag(nsIMsgDBHdr* msgHdr, uint32_t* msgFlags) {
+ // If we're a cross-folder view, just bail on this.
+ if (GetFolders()) return *msgFlags & nsMsgMessageFlags::Read;
+
+ bool isRead = false;
+ nsMsgKey msgKey;
+ msgHdr->GetMessageKey(&msgKey);
+ m_db->IsRead(msgKey, &isRead);
+ // Just make sure flag is right in db.
+#ifdef DEBUG_David_Bienvenu
+ NS_ASSERTION(isRead == ((*msgFlags & nsMsgMessageFlags::Read) != 0),
+ "msgFlags out of sync");
+#endif
+ if (isRead)
+ *msgFlags |= nsMsgMessageFlags::Read;
+ else
+ *msgFlags &= ~nsMsgMessageFlags::Read;
+
+ m_db->MarkHdrRead(msgHdr, isRead, nullptr);
+ return isRead;
+}
+
+// Starting from startIndex, performs the passed in navigation, including
+// any marking read needed, and returns the resultId and resultIndex of the
+// destination of the navigation. If no message is found in the view,
+// it returns a resultId of nsMsgKey_None and an resultIndex of
+// nsMsgViewIndex_None.
+NS_IMETHODIMP
+nsMsgDBView::ViewNavigate(nsMsgNavigationTypeValue motion, nsMsgKey* pResultKey,
+ nsMsgViewIndex* pResultIndex,
+ nsMsgViewIndex* pThreadIndex, bool wrap) {
+ NS_ENSURE_ARG_POINTER(pResultKey);
+ NS_ENSURE_ARG_POINTER(pResultIndex);
+ NS_ENSURE_ARG_POINTER(pThreadIndex);
+
+ int32_t currentIndex;
+ nsMsgViewIndex startIndex;
+
+ if (!mTreeSelection) {
+ // We must be in stand alone message mode.
+ currentIndex = FindViewIndex(m_currentlyDisplayedMsgKey);
+ } else {
+ nsresult rv = mTreeSelection->GetCurrentIndex(&currentIndex);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ startIndex = currentIndex;
+ return nsMsgDBView::NavigateFromPos(motion, startIndex, pResultKey,
+ pResultIndex, pThreadIndex, wrap);
+}
+
+nsresult nsMsgDBView::NavigateFromPos(nsMsgNavigationTypeValue motion,
+ nsMsgViewIndex startIndex,
+ nsMsgKey* pResultKey,
+ nsMsgViewIndex* pResultIndex,
+ nsMsgViewIndex* pThreadIndex, bool wrap) {
+ nsresult rv = NS_OK;
+ nsMsgKey resultThreadKey;
+ nsMsgViewIndex curIndex;
+ nsMsgViewIndex lastIndex =
+ (GetSize() > 0) ? (nsMsgViewIndex)GetSize() - 1 : nsMsgViewIndex_None;
+ nsMsgViewIndex threadIndex = nsMsgViewIndex_None;
+
+ // If there aren't any messages in the view, bail out.
+ if (GetSize() <= 0) {
+ *pResultIndex = nsMsgViewIndex_None;
+ *pResultKey = nsMsgKey_None;
+ return NS_OK;
+ }
+ *pResultKey = nsMsgKey_None;
+
+ switch (motion) {
+ case nsMsgNavigationType::firstMessage:
+ *pResultIndex = 0;
+ *pResultKey = m_keys[0];
+ break;
+ case nsMsgNavigationType::nextMessage:
+ // Return same index and id on next on last message.
+ *pResultIndex = std::min(startIndex + 1, lastIndex);
+ *pResultKey = m_keys[*pResultIndex];
+ break;
+ case nsMsgNavigationType::previousMessage:
+ if (startIndex != nsMsgViewIndex_None && startIndex > 0) {
+ *pResultIndex = startIndex - 1;
+ }
+ if (IsValidIndex(*pResultIndex)) *pResultKey = m_keys[*pResultIndex];
+ break;
+ case nsMsgNavigationType::lastMessage:
+ *pResultIndex = lastIndex;
+ *pResultKey = m_keys[*pResultIndex];
+ break;
+ case nsMsgNavigationType::firstFlagged:
+ rv = FindFirstFlagged(pResultIndex);
+ if (IsValidIndex(*pResultIndex)) *pResultKey = m_keys[*pResultIndex];
+ break;
+ case nsMsgNavigationType::nextFlagged:
+ rv = FindNextFlagged(startIndex + 1, pResultIndex);
+ if (IsValidIndex(*pResultIndex)) *pResultKey = m_keys[*pResultIndex];
+ break;
+ case nsMsgNavigationType::previousFlagged:
+ rv = FindPrevFlagged(startIndex, pResultIndex);
+ if (IsValidIndex(*pResultIndex)) *pResultKey = m_keys[*pResultIndex];
+ break;
+ case nsMsgNavigationType::firstNew:
+ rv = FindFirstNew(pResultIndex);
+ if (IsValidIndex(*pResultIndex)) *pResultKey = m_keys[*pResultIndex];
+ break;
+ case nsMsgNavigationType::firstUnreadMessage:
+ startIndex = nsMsgViewIndex_None;
+ // Note fall through - is this motion ever used?
+ [[fallthrough]];
+ case nsMsgNavigationType::nextUnreadMessage:
+ for (curIndex = (startIndex == nsMsgViewIndex_None) ? 0 : startIndex;
+ curIndex <= lastIndex && lastIndex != nsMsgViewIndex_None;
+ curIndex++) {
+ uint32_t flags = m_flags[curIndex];
+ // Don't return start index since navigate should move.
+ if (!(flags & (nsMsgMessageFlags::Read | MSG_VIEW_FLAG_DUMMY)) &&
+ (curIndex != startIndex)) {
+ *pResultIndex = curIndex;
+ *pResultKey = m_keys[*pResultIndex];
+ break;
+ }
+
+ // Check for collapsed thread with new children.
+ if ((m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay) &&
+ flags & MSG_VIEW_FLAG_ISTHREAD &&
+ flags & nsMsgMessageFlags::Elided) {
+ nsCOMPtr<nsIMsgThread> threadHdr;
+ GetThreadContainingIndex(curIndex, getter_AddRefs(threadHdr));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ASSERTION(threadHdr, "threadHdr is null");
+ if (!threadHdr) continue;
+
+ uint32_t numUnreadChildren;
+ threadHdr->GetNumUnreadChildren(&numUnreadChildren);
+ if (numUnreadChildren > 0) {
+ uint32_t numExpanded;
+ ExpandByIndex(curIndex, &numExpanded);
+ lastIndex += numExpanded;
+ if (pThreadIndex) *pThreadIndex = curIndex;
+ }
+ }
+ }
+
+ if (curIndex > lastIndex) {
+ // Wrap around by starting at index 0.
+ if (wrap) {
+ nsMsgKey startKey = GetAt(startIndex);
+ rv = NavigateFromPos(nsMsgNavigationType::nextUnreadMessage,
+ nsMsgViewIndex_None, pResultKey, pResultIndex,
+ pThreadIndex, false);
+
+ if (*pResultKey == startKey) {
+ // wrapped around and found start message!
+ *pResultIndex = nsMsgViewIndex_None;
+ *pResultKey = nsMsgKey_None;
+ }
+ } else {
+ *pResultIndex = nsMsgViewIndex_None;
+ *pResultKey = nsMsgKey_None;
+ }
+ }
+ break;
+ case nsMsgNavigationType::previousUnreadMessage:
+ if (!IsValidIndex(startIndex)) break;
+
+ rv = FindPrevUnread(m_keys[startIndex], pResultKey, &resultThreadKey);
+ if (NS_SUCCEEDED(rv)) {
+ *pResultIndex = FindViewIndex(*pResultKey);
+ if (*pResultKey != resultThreadKey &&
+ (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay)) {
+ threadIndex = GetThreadIndex(*pResultIndex);
+ if (*pResultIndex == nsMsgViewIndex_None) {
+ nsCOMPtr<nsIMsgThread> threadHdr;
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ rv = m_db->GetMsgHdrForKey(*pResultKey, getter_AddRefs(msgHdr));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = GetThreadContainingMsgHdr(msgHdr, getter_AddRefs(threadHdr));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ASSERTION(threadHdr, "threadHdr is null");
+ if (threadHdr) break;
+
+ uint32_t numUnreadChildren;
+ threadHdr->GetNumUnreadChildren(&numUnreadChildren);
+ if (numUnreadChildren > 0) {
+ uint32_t numExpanded;
+ ExpandByIndex(threadIndex, &numExpanded);
+ }
+
+ *pResultIndex = FindViewIndex(*pResultKey);
+ }
+ }
+
+ if (pThreadIndex) *pThreadIndex = threadIndex;
+ }
+ break;
+ case nsMsgNavigationType::lastUnreadMessage:
+ break;
+ case nsMsgNavigationType::nextUnreadThread:
+ if (startIndex != nsMsgViewIndex_None) {
+ ApplyCommandToIndices(nsMsgViewCommandType::markThreadRead,
+ {startIndex});
+ }
+
+ return NavigateFromPos(nsMsgNavigationType::nextUnreadMessage, startIndex,
+ pResultKey, pResultIndex, pThreadIndex, true);
+ case nsMsgNavigationType::toggleThreadKilled: {
+ bool resultKilled;
+ nsMsgViewIndexArray selection;
+ GetIndicesForSelection(selection);
+ ToggleIgnored(selection, &threadIndex, &resultKilled);
+ if (resultKilled) {
+ return NavigateFromPos(nsMsgNavigationType::nextUnreadThread,
+ threadIndex, pResultKey, pResultIndex,
+ pThreadIndex, true);
+ } else {
+ *pResultIndex = nsMsgViewIndex_None;
+ *pResultKey = nsMsgKey_None;
+ return NS_OK;
+ }
+ }
+ case nsMsgNavigationType::toggleSubthreadKilled: {
+ bool resultKilled;
+ nsMsgViewIndexArray selection;
+ GetIndicesForSelection(selection);
+ ToggleMessageKilled(selection, &threadIndex, &resultKilled);
+ if (resultKilled) {
+ return NavigateFromPos(nsMsgNavigationType::nextUnreadMessage,
+ threadIndex, pResultKey, pResultIndex,
+ pThreadIndex, true);
+ } else {
+ *pResultIndex = nsMsgViewIndex_None;
+ *pResultKey = nsMsgKey_None;
+ return NS_OK;
+ }
+ }
+ // Check where navigate says this will take us. If we have the message
+ // in the view, return it. Otherwise, return an error.
+ case nsMsgNavigationType::back:
+ case nsMsgNavigationType::forward:
+ // Handled purely in JS.
+ *pResultIndex = nsMsgViewIndex_None;
+ *pResultKey = nsMsgKey_None;
+ break;
+ default:
+ NS_ERROR("unsupported motion");
+ break;
+ }
+
+ return NS_OK;
+}
+
+// Note that these routines do NOT expand collapsed threads! This mimics the
+// old behaviour, but it's also because we don't remember whether a thread
+// contains a flagged message the same way we remember if a thread contains
+// new messages. It would be painful to dive down into each collapsed thread
+// to update navigate status. We could cache this info, but it would still be
+// expensive the first time this status needs to get updated.
+nsresult nsMsgDBView::FindNextFlagged(nsMsgViewIndex startIndex,
+ nsMsgViewIndex* pResultIndex) {
+ nsMsgViewIndex lastIndex = (nsMsgViewIndex)GetSize() - 1;
+ nsMsgViewIndex curIndex;
+
+ *pResultIndex = nsMsgViewIndex_None;
+
+ if (GetSize() > 0) {
+ for (curIndex = startIndex; curIndex <= lastIndex; curIndex++) {
+ uint32_t flags = m_flags[curIndex];
+ if (flags & nsMsgMessageFlags::Marked) {
+ *pResultIndex = curIndex;
+ break;
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult nsMsgDBView::FindFirstNew(nsMsgViewIndex* pResultIndex) {
+ if (m_db) {
+ nsMsgKey firstNewKey = nsMsgKey_None;
+ m_db->GetFirstNew(&firstNewKey);
+ *pResultIndex = (firstNewKey != nsMsgKey_None) ? FindKey(firstNewKey, true)
+ : nsMsgViewIndex_None;
+ }
+
+ return NS_OK;
+}
+
+nsresult nsMsgDBView::FindPrevUnread(nsMsgKey startKey, nsMsgKey* pResultKey,
+ nsMsgKey* resultThreadId) {
+ nsMsgViewIndex startIndex = FindViewIndex(startKey);
+ nsMsgViewIndex curIndex = startIndex;
+ nsresult rv = NS_MSG_MESSAGE_NOT_FOUND;
+
+ if (startIndex == nsMsgViewIndex_None) return NS_MSG_MESSAGE_NOT_FOUND;
+
+ *pResultKey = nsMsgKey_None;
+ if (resultThreadId) *resultThreadId = nsMsgKey_None;
+
+ for (; (int)curIndex >= 0 && (*pResultKey == nsMsgKey_None); curIndex--) {
+ uint32_t flags = m_flags[curIndex];
+ if (curIndex != startIndex && flags & MSG_VIEW_FLAG_ISTHREAD &&
+ flags & nsMsgMessageFlags::Elided) {
+ NS_ERROR("fix this");
+ // nsMsgKey threadId = m_keys[curIndex];
+ // rv = m_db->GetUnreadKeyInThread(threadId, pResultKey, resultThreadId);
+ if (NS_SUCCEEDED(rv) && (*pResultKey != nsMsgKey_None)) break;
+ }
+
+ if (!(flags & (nsMsgMessageFlags::Read | MSG_VIEW_FLAG_DUMMY)) &&
+ (curIndex != startIndex)) {
+ *pResultKey = m_keys[curIndex];
+ rv = NS_OK;
+ break;
+ }
+ }
+
+ // Found unread message but we don't know the thread.
+ NS_ASSERTION(!(*pResultKey != nsMsgKey_None && resultThreadId &&
+ *resultThreadId == nsMsgKey_None),
+ "fix this");
+ return rv;
+}
+
+nsresult nsMsgDBView::FindFirstFlagged(nsMsgViewIndex* pResultIndex) {
+ return FindNextFlagged(0, pResultIndex);
+}
+
+nsresult nsMsgDBView::FindPrevFlagged(nsMsgViewIndex startIndex,
+ nsMsgViewIndex* pResultIndex) {
+ nsMsgViewIndex curIndex;
+
+ *pResultIndex = nsMsgViewIndex_None;
+
+ if (GetSize() > 0 && IsValidIndex(startIndex)) {
+ curIndex = startIndex;
+ do {
+ if (curIndex != 0) curIndex--;
+
+ uint32_t flags = m_flags[curIndex];
+ if (flags & nsMsgMessageFlags::Marked) {
+ *pResultIndex = curIndex;
+ break;
+ }
+ } while (curIndex != 0);
+ }
+
+ return NS_OK;
+}
+
+bool nsMsgDBView::IsValidIndex(nsMsgViewIndex index) {
+ return index != nsMsgViewIndex_None &&
+ (index < (nsMsgViewIndex)m_keys.Length());
+}
+
+nsresult nsMsgDBView::OrExtraFlag(nsMsgViewIndex index, uint32_t orflag) {
+ uint32_t flag;
+ if (!IsValidIndex(index)) return NS_MSG_INVALID_DBVIEW_INDEX;
+
+ flag = m_flags[index];
+ flag |= orflag;
+ m_flags[index] = flag;
+ OnExtraFlagChanged(index, flag);
+ return NS_OK;
+}
+
+nsresult nsMsgDBView::AndExtraFlag(nsMsgViewIndex index, uint32_t andflag) {
+ uint32_t flag;
+ if (!IsValidIndex(index)) return NS_MSG_INVALID_DBVIEW_INDEX;
+
+ flag = m_flags[index];
+ flag &= andflag;
+ m_flags[index] = flag;
+ OnExtraFlagChanged(index, flag);
+ return NS_OK;
+}
+
+nsresult nsMsgDBView::SetExtraFlag(nsMsgViewIndex index, uint32_t extraflag) {
+ if (!IsValidIndex(index)) return NS_MSG_INVALID_DBVIEW_INDEX;
+
+ m_flags[index] = extraflag;
+ OnExtraFlagChanged(index, extraflag);
+ return NS_OK;
+}
+
+nsresult nsMsgDBView::ToggleIgnored(nsTArray<nsMsgViewIndex> const& selection,
+ nsMsgViewIndex* resultIndex,
+ bool* resultToggleState) {
+ nsCOMPtr<nsIMsgThread> thread;
+
+ // Ignored state is toggled based on the first selected thread.
+ nsMsgViewIndex threadIndex =
+ GetThreadFromMsgIndex(selection[0], getter_AddRefs(thread));
+ NS_ENSURE_STATE(thread);
+ uint32_t threadFlags;
+ thread->GetFlags(&threadFlags);
+ uint32_t ignored = threadFlags & nsMsgMessageFlags::Ignored;
+
+ // Process threads in reverse order.
+ // Otherwise collapsing the threads will invalidate the indices.
+ threadIndex = nsMsgViewIndex_None;
+ uint32_t numIndices = selection.Length();
+ while (numIndices) {
+ numIndices--;
+ if (selection[numIndices] < threadIndex) {
+ threadIndex =
+ GetThreadFromMsgIndex(selection[numIndices], getter_AddRefs(thread));
+ thread->GetFlags(&threadFlags);
+ if ((threadFlags & nsMsgMessageFlags::Ignored) == ignored)
+ SetThreadIgnored(thread, threadIndex, !ignored);
+ }
+ }
+
+ if (resultIndex) *resultIndex = threadIndex;
+
+ if (resultToggleState) *resultToggleState = !ignored;
+
+ return NS_OK;
+}
+
+nsresult nsMsgDBView::ToggleMessageKilled(
+ nsTArray<nsMsgViewIndex> const& selection, nsMsgViewIndex* resultIndex,
+ bool* resultToggleState) {
+ NS_ENSURE_ARG_POINTER(resultToggleState);
+
+ nsCOMPtr<nsIMsgDBHdr> header;
+ // Ignored state is toggled based on the first selected message.
+ nsresult rv = GetMsgHdrForViewIndex(selection[0], getter_AddRefs(header));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t msgFlags;
+ header->GetFlags(&msgFlags);
+ uint32_t ignored = msgFlags & nsMsgMessageFlags::Ignored;
+
+ // Process messages in reverse order.
+ // Otherwise the indices may be invalidated.
+ nsMsgViewIndex msgIndex = nsMsgViewIndex_None;
+ uint32_t numIndices = selection.Length();
+ while (numIndices) {
+ numIndices--;
+ if (selection[numIndices] < msgIndex) {
+ msgIndex = selection[numIndices];
+ rv = GetMsgHdrForViewIndex(msgIndex, getter_AddRefs(header));
+ NS_ENSURE_SUCCESS(rv, rv);
+ header->GetFlags(&msgFlags);
+ if ((msgFlags & nsMsgMessageFlags::Ignored) == ignored)
+ SetSubthreadKilled(header, msgIndex, !ignored);
+ }
+ }
+
+ if (resultIndex) *resultIndex = msgIndex;
+
+ if (resultToggleState) *resultToggleState = !ignored;
+
+ return NS_OK;
+}
+
+nsMsgViewIndex nsMsgDBView::GetThreadFromMsgIndex(nsMsgViewIndex index,
+ nsIMsgThread** threadHdr) {
+ if (threadHdr == nullptr) return nsMsgViewIndex_None;
+ nsMsgKey msgKey = GetAt(index);
+
+ nsresult rv = GetThreadContainingIndex(index, threadHdr);
+ NS_ENSURE_SUCCESS(rv, nsMsgViewIndex_None);
+
+ if (*threadHdr == nullptr) return nsMsgViewIndex_None;
+
+ nsMsgKey threadKey;
+ (*threadHdr)->GetThreadKey(&threadKey);
+ nsMsgViewIndex threadIndex;
+ if (msgKey != threadKey)
+ threadIndex = GetIndexOfFirstDisplayedKeyInThread(*threadHdr);
+ else
+ threadIndex = index;
+ return threadIndex;
+}
+
+nsresult nsMsgDBView::ToggleWatched(nsTArray<nsMsgViewIndex> const& selection) {
+ MOZ_ASSERT(!selection.IsEmpty());
+ nsCOMPtr<nsIMsgThread> thread;
+
+ // Watched state is toggled based on the first selected thread.
+ nsMsgViewIndex threadIndex =
+ GetThreadFromMsgIndex(selection[0], getter_AddRefs(thread));
+ NS_ENSURE_STATE(thread);
+ uint32_t threadFlags;
+ thread->GetFlags(&threadFlags);
+ uint32_t watched = threadFlags & nsMsgMessageFlags::Watched;
+
+ // Process threads in reverse order for consistency with ToggleIgnored.
+ threadIndex = nsMsgViewIndex_None;
+ uint32_t numIndices = selection.Length();
+ while (numIndices) {
+ numIndices--;
+ if (selection[numIndices] < threadIndex) {
+ threadIndex =
+ GetThreadFromMsgIndex(selection[numIndices], getter_AddRefs(thread));
+ thread->GetFlags(&threadFlags);
+ if ((threadFlags & nsMsgMessageFlags::Watched) == watched)
+ SetThreadWatched(thread, threadIndex, !watched);
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult nsMsgDBView::SetThreadIgnored(nsIMsgThread* thread,
+ nsMsgViewIndex threadIndex,
+ bool ignored) {
+ if (!IsValidIndex(threadIndex)) return NS_MSG_INVALID_DBVIEW_INDEX;
+
+ NoteChange(threadIndex, 1, nsMsgViewNotificationCode::changed);
+ if (ignored) {
+ nsTArray<nsMsgKey> idsMarkedRead;
+ MarkThreadRead(thread, threadIndex, idsMarkedRead, true);
+ CollapseByIndex(threadIndex, nullptr);
+ }
+
+ if (!m_db) return NS_ERROR_FAILURE;
+
+ return m_db->MarkThreadIgnored(thread, m_keys[threadIndex], ignored, this);
+}
+
+nsresult nsMsgDBView::SetSubthreadKilled(nsIMsgDBHdr* header,
+ nsMsgViewIndex msgIndex,
+ bool ignored) {
+ if (!IsValidIndex(msgIndex)) return NS_MSG_INVALID_DBVIEW_INDEX;
+
+ NoteChange(msgIndex, 1, nsMsgViewNotificationCode::changed);
+
+ if (!m_db) return NS_ERROR_FAILURE;
+
+ nsresult rv = m_db->MarkHeaderKilled(header, ignored, this);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (ignored) {
+ nsCOMPtr<nsIMsgThread> thread;
+ nsresult rv;
+ rv = GetThreadContainingMsgHdr(header, getter_AddRefs(thread));
+ // So we didn't mark threads read.
+ if (NS_FAILED(rv)) return NS_OK;
+
+ uint32_t children, current;
+ thread->GetNumChildren(&children);
+
+ nsMsgKey headKey;
+ header->GetMessageKey(&headKey);
+
+ for (current = 0; current < children; current++) {
+ nsMsgKey newKey;
+ thread->GetChildKeyAt(current, &newKey);
+ if (newKey == headKey) break;
+ }
+
+ // Process all messages, starting with this message.
+ for (; current < children; current++) {
+ nsCOMPtr<nsIMsgDBHdr> nextHdr;
+ bool isKilled;
+
+ thread->GetChildHdrAt(current, getter_AddRefs(nextHdr));
+ nextHdr->GetIsKilled(&isKilled);
+
+ // Ideally, the messages should stop processing here.
+ // However, the children are ordered not by thread...
+ if (isKilled) nextHdr->MarkRead(true);
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult nsMsgDBView::SetThreadWatched(nsIMsgThread* thread,
+ nsMsgViewIndex index, bool watched) {
+ if (!IsValidIndex(index)) return NS_MSG_INVALID_DBVIEW_INDEX;
+
+ nsresult rv = m_db->MarkThreadWatched(thread, m_keys[index], watched, this);
+ NoteChange(index, 1, nsMsgViewNotificationCode::changed);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgDBView::GetMsgFolder(nsIMsgFolder** aMsgFolder) {
+ NS_ENSURE_ARG_POINTER(aMsgFolder);
+ NS_IF_ADDREF(*aMsgFolder = m_folder);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBView::SetViewFolder(nsIMsgFolder* aMsgFolder) {
+ m_viewFolder = aMsgFolder;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBView::GetViewFolder(nsIMsgFolder** aMsgFolder) {
+ NS_ENSURE_ARG_POINTER(aMsgFolder);
+ NS_IF_ADDREF(*aMsgFolder = m_viewFolder);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBView::GetNumSelected(uint32_t* aNumSelected) {
+ NS_ENSURE_ARG_POINTER(aNumSelected);
+
+ if (!mTreeSelection) {
+ // No tree selection can mean we're in the stand alone mode.
+ *aNumSelected = (m_currentlyDisplayedMsgKey != nsMsgKey_None) ? 1 : 0;
+ return NS_OK;
+ }
+
+ bool includeCollapsedMsgs = OperateOnMsgsInCollapsedThreads();
+
+ // We call this a lot from the front end JS, so make it fast.
+ nsresult rv = mTreeSelection->GetCount((int32_t*)aNumSelected);
+ if (!*aNumSelected || !includeCollapsedMsgs ||
+ !(m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay))
+ return rv;
+
+ int32_t numSelectedIncludingCollapsed = *aNumSelected;
+ nsMsgViewIndexArray selection;
+ GetIndicesForSelection(selection);
+ int32_t numIndices = selection.Length();
+ // Iterate over the selection, counting up the messages in collapsed
+ // threads.
+ for (int32_t i = 0; i < numIndices; i++) {
+ if (m_flags[selection[i]] & nsMsgMessageFlags::Elided) {
+ int32_t collapsedCount;
+ ExpansionDelta(selection[i], &collapsedCount);
+ numSelectedIncludingCollapsed += collapsedCount;
+ }
+ }
+
+ *aNumSelected = numSelectedIncludingCollapsed;
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgDBView::GetNumMsgsInView(int32_t* aNumMsgs) {
+ NS_ENSURE_ARG_POINTER(aNumMsgs);
+ return (m_folder) ? m_folder->GetTotalMessages(false, aNumMsgs)
+ : NS_ERROR_FAILURE;
+}
+
+/**
+ * @note For the IMAP delete model, this applies to both deleting and
+ * undeleting a message.
+ */
+NS_IMETHODIMP
+nsMsgDBView::GetMsgToSelectAfterDelete(nsMsgViewIndex* msgToSelectAfterDelete) {
+ NS_ENSURE_ARG_POINTER(msgToSelectAfterDelete);
+ *msgToSelectAfterDelete = nsMsgViewIndex_None;
+
+ bool isMultiSelect = false;
+ int32_t startFirstRange = nsMsgViewIndex_None;
+ int32_t endFirstRange = nsMsgViewIndex_None;
+ if (!mTreeSelection) {
+ // If we don't have a tree selection then we must be in stand alone mode.
+ // return the index of the current message key as the first selected index.
+ *msgToSelectAfterDelete = FindViewIndex(m_currentlyDisplayedMsgKey);
+ } else {
+ int32_t selectionCount;
+ int32_t startRange;
+ int32_t endRange;
+ nsresult rv = mTreeSelection->GetRangeCount(&selectionCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+ for (int32_t i = 0; i < selectionCount; i++) {
+ rv = mTreeSelection->GetRangeAt(i, &startRange, &endRange);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Save off the first range in case we need it later.
+ if (i == 0) {
+ startFirstRange = startRange;
+ endFirstRange = endRange;
+ } else {
+ // If the tree selection is goofy (eg adjacent or overlapping ranges),
+ // complain about it, but don't try and cope. Just live with the fact
+ // that one of the deleted messages is going to end up selected.
+ NS_WARNING_ASSERTION(
+ endFirstRange != startRange,
+ "goofy tree selection state: two ranges are adjacent!");
+ }
+
+ *msgToSelectAfterDelete =
+ std::min(*msgToSelectAfterDelete, (nsMsgViewIndex)startRange);
+ }
+
+ // Multiple selection either using Ctrl, Shift, or one of the affordances
+ // to select an entire thread.
+ isMultiSelect = (selectionCount > 1 || (endRange - startRange) > 0);
+ }
+
+ if (*msgToSelectAfterDelete == nsMsgViewIndex_None) return NS_OK;
+
+ nsCOMPtr<nsIMsgFolder> folder;
+ GetMsgFolder(getter_AddRefs(folder));
+ nsCOMPtr<nsIMsgImapMailFolder> imapFolder = do_QueryInterface(folder);
+ bool thisIsImapFolder = (imapFolder != nullptr);
+ // Need to update the imap-delete model, can change more than once in a
+ // session.
+ if (thisIsImapFolder) GetImapDeleteModel(nullptr);
+
+ // If mail.delete_matches_sort_order is true,
+ // for views sorted in descending order (newest at the top), make
+ // msgToSelectAfterDelete advance in the same direction as the sort order.
+ bool deleteMatchesSort = false;
+ if (m_sortOrder == nsMsgViewSortOrder::descending &&
+ *msgToSelectAfterDelete) {
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> prefBranch(
+ do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ prefBranch->GetBoolPref("mail.delete_matches_sort_order",
+ &deleteMatchesSort);
+ }
+
+ if (mDeleteModel == nsMsgImapDeleteModels::IMAPDelete) {
+ if (isMultiSelect) {
+ if (deleteMatchesSort)
+ *msgToSelectAfterDelete = startFirstRange - 1;
+ else
+ *msgToSelectAfterDelete = endFirstRange + 1;
+ } else {
+ if (deleteMatchesSort)
+ *msgToSelectAfterDelete -= 1;
+ else
+ *msgToSelectAfterDelete += 1;
+ }
+ } else if (deleteMatchesSort) {
+ *msgToSelectAfterDelete -= 1;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBView::GetRemoveRowOnMoveOrDelete(bool* aRemoveRowOnMoveOrDelete) {
+ NS_ENSURE_ARG_POINTER(aRemoveRowOnMoveOrDelete);
+ nsCOMPtr<nsIMsgImapMailFolder> imapFolder = do_QueryInterface(m_folder);
+ if (!imapFolder) {
+ *aRemoveRowOnMoveOrDelete = true;
+ return NS_OK;
+ }
+
+ // Need to update the imap-delete model, can change more than once in a
+ // session.
+ GetImapDeleteModel(nullptr);
+
+ // Unlike the other imap delete models, "mark as deleted" does not remove
+ // rows on delete (or move).
+ *aRemoveRowOnMoveOrDelete =
+ (mDeleteModel != nsMsgImapDeleteModels::IMAPDelete);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBView::GetCurrentlyDisplayedMessage(
+ nsMsgViewIndex* currentlyDisplayedMessage) {
+ NS_ENSURE_ARG_POINTER(currentlyDisplayedMessage);
+ *currentlyDisplayedMessage = FindViewIndex(m_currentlyDisplayedMsgKey);
+ return NS_OK;
+}
+
+// If nothing selected, return an NS_ERROR.
+NS_IMETHODIMP
+nsMsgDBView::GetHdrForFirstSelectedMessage(nsIMsgDBHdr** hdr) {
+ NS_ENSURE_ARG_POINTER(hdr);
+
+ nsresult rv;
+ nsMsgKey key;
+ rv = GetKeyForFirstSelectedMessage(&key);
+ // Don't assert, it is legal for nothing to be selected.
+ if (NS_FAILED(rv)) return rv;
+
+ if (key == nsMsgKey_None) {
+ *hdr = nullptr;
+ return NS_OK;
+ }
+
+ if (!m_db) return NS_MSG_MESSAGE_NOT_FOUND;
+
+ rv = m_db->GetMsgHdrForKey(key, hdr);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return NS_OK;
+}
+
+// If nothing selected, return an NS_ERROR.
+NS_IMETHODIMP
+nsMsgDBView::GetURIForFirstSelectedMessage(nsACString& uri) {
+ nsresult rv;
+ nsMsgViewIndex viewIndex;
+ rv = GetViewIndexForFirstSelectedMsg(&viewIndex);
+ // Don't assert, it is legal for nothing to be selected.
+ if (NS_FAILED(rv)) return rv;
+
+ return GetURIForViewIndex(viewIndex, uri);
+}
+
+NS_IMETHODIMP
+nsMsgDBView::OnDeleteCompleted(bool aSucceeded) {
+ if (m_deletingRows && aSucceeded) {
+ uint32_t numIndices = mIndicesToNoteChange.Length();
+ if (numIndices && (mTree || mJSTree)) {
+ if (numIndices > 1) mIndicesToNoteChange.Sort();
+
+ // The call to NoteChange() has to happen after we are done removing the
+ // keys as NoteChange() will call RowCountChanged() which will call our
+ // GetRowCount().
+ if (numIndices > 1) {
+ if (mTree) mTree->BeginUpdateBatch();
+ if (mJSTree) mJSTree->BeginUpdateBatch();
+ }
+
+ for (uint32_t i = 0; i < numIndices; i++)
+ NoteChange(mIndicesToNoteChange[i], -1,
+ nsMsgViewNotificationCode::insertOrDelete);
+
+ if (numIndices > 1) {
+ if (mTree) mTree->EndUpdateBatch();
+ if (mJSTree) mJSTree->EndUpdateBatch();
+ }
+ }
+
+ mIndicesToNoteChange.Clear();
+ }
+
+ m_deletingRows = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBView::GetDb(nsIMsgDatabase** aDB) {
+ NS_ENSURE_ARG_POINTER(aDB);
+ NS_IF_ADDREF(*aDB = m_db);
+ return NS_OK;
+}
+
+bool nsMsgDBView::OfflineMsgSelected(
+ nsTArray<nsMsgViewIndex> const& selection) {
+ nsCOMPtr<nsIMsgLocalMailFolder> localFolder = do_QueryInterface(m_folder);
+ if (localFolder) {
+ return true;
+ }
+
+ for (nsMsgViewIndex viewIndex : selection) {
+ // For cross-folder saved searches, we need to check if any message
+ // is in a local folder.
+ if (!m_folder) {
+ nsCOMPtr<nsIMsgFolder> folder;
+ GetFolderForViewIndex(viewIndex, getter_AddRefs(folder));
+ nsCOMPtr<nsIMsgLocalMailFolder> localFolder = do_QueryInterface(folder);
+ if (localFolder) {
+ return true;
+ }
+ }
+
+ uint32_t flags = m_flags[viewIndex];
+ if ((flags & nsMsgMessageFlags::Offline)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool nsMsgDBView::NonDummyMsgSelected(
+ nsTArray<nsMsgViewIndex> const& selection) {
+ bool includeCollapsedMsgs = OperateOnMsgsInCollapsedThreads();
+
+ for (nsMsgViewIndex viewIndex : selection) {
+ uint32_t flags = m_flags[viewIndex];
+ // We now treat having a collapsed dummy message selected as if
+ // the whole group was selected so we can apply commands to the group.
+ if (!(flags & MSG_VIEW_FLAG_DUMMY) ||
+ (flags & nsMsgMessageFlags::Elided && includeCollapsedMsgs)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+NS_IMETHODIMP
+nsMsgDBView::GetViewIndexForFirstSelectedMsg(nsMsgViewIndex* aViewIndex) {
+ NS_ENSURE_ARG_POINTER(aViewIndex);
+ // If we don't have a tree selection we must be in stand alone mode...
+ if (!mTreeSelection) {
+ *aViewIndex = m_currentlyDisplayedViewIndex;
+ return NS_OK;
+ }
+
+ int32_t startRange;
+ int32_t endRange;
+ nsresult rv = mTreeSelection->GetRangeAt(0, &startRange, &endRange);
+ // Don't assert, it is legal for nothing to be selected.
+ if (NS_FAILED(rv)) return rv;
+
+ // Check that the first index is valid, it may not be if nothing is selected.
+ if (startRange < 0 || uint32_t(startRange) >= GetSize())
+ return NS_ERROR_UNEXPECTED;
+
+ *aViewIndex = startRange;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBView::GetKeyForFirstSelectedMessage(nsMsgKey* key) {
+ NS_ENSURE_ARG_POINTER(key);
+ // If we don't have a tree selection we must be in stand alone mode...
+ if (!mTreeSelection) {
+ *key = m_currentlyDisplayedMsgKey;
+ return NS_OK;
+ }
+
+ int32_t selectionCount;
+ mTreeSelection->GetRangeCount(&selectionCount);
+ if (selectionCount == 0) {
+ *key = nsMsgKey_None;
+ return NS_OK;
+ }
+
+ int32_t startRange;
+ int32_t endRange;
+ nsresult rv = mTreeSelection->GetRangeAt(0, &startRange, &endRange);
+ // Don't assert, it is legal for nothing to be selected.
+ if (NS_FAILED(rv)) return rv;
+
+ // Check that the first index is valid, it may not be if nothing is selected.
+ if (startRange < 0 || uint32_t(startRange) >= GetSize())
+ return NS_ERROR_UNEXPECTED;
+
+ if (m_flags[startRange] & MSG_VIEW_FLAG_DUMMY)
+ return NS_MSG_INVALID_DBVIEW_INDEX;
+
+ *key = m_keys[startRange];
+ return NS_OK;
+}
+
+nsCOMArray<nsIMsgFolder>* nsMsgDBView::GetFolders() { return nullptr; }
+
+nsresult nsMsgDBView::AdjustRowCount(int32_t rowCountBeforeSort,
+ int32_t rowCountAfterSort) {
+ int32_t rowChange = rowCountAfterSort - rowCountBeforeSort;
+
+ if (rowChange) {
+ // This is not safe to use when you have a selection.
+ // RowCountChanged() will call AdjustSelection().
+ uint32_t numSelected = 0;
+ GetNumSelected(&numSelected);
+ NS_ASSERTION(
+ numSelected == 0,
+ "it is not save to call AdjustRowCount() when you have a selection");
+
+ if (mTree) mTree->RowCountChanged(0, rowChange);
+ if (mJSTree) mJSTree->RowCountChanged(0, rowChange);
+ }
+
+ return NS_OK;
+}
+
+nsresult nsMsgDBView::GetImapDeleteModel(nsIMsgFolder* folder) {
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ // For the search view.
+ if (folder)
+ folder->GetServer(getter_AddRefs(server));
+ else if (m_folder)
+ m_folder->GetServer(getter_AddRefs(server));
+
+ nsCOMPtr<nsIImapIncomingServer> imapServer = do_QueryInterface(server, &rv);
+ if (NS_SUCCEEDED(rv) && imapServer) imapServer->GetDeleteModel(&mDeleteModel);
+
+ return rv;
+}
+
+//
+// CanDrop
+//
+// Can't drop on the thread pane.
+//
+NS_IMETHODIMP
+nsMsgDBView::CanDrop(int32_t index, int32_t orient,
+ mozilla::dom::DataTransfer* dataTransfer, bool* _retval) {
+ NS_ENSURE_ARG_POINTER(_retval);
+ *_retval = false;
+
+ return NS_OK;
+}
+
+//
+// Drop
+//
+// Can't drop on the thread pane.
+//
+NS_IMETHODIMP
+nsMsgDBView::Drop(int32_t row, int32_t orient,
+ mozilla::dom::DataTransfer* dataTransfer) {
+ return NS_OK;
+}
+
+//
+// IsSorted
+//
+// ...
+//
+NS_IMETHODIMP
+nsMsgDBView::IsSorted(bool* _retval) {
+ *_retval = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBView::SelectFolderMsgByKey(nsIMsgFolder* aFolder, nsMsgKey aKey) {
+ NS_ENSURE_ARG_POINTER(aFolder);
+ if (aKey == nsMsgKey_None) return NS_ERROR_FAILURE;
+
+ // This is OK for non search views.
+
+ nsMsgViewIndex viewIndex = FindKey(aKey, true /* expand */);
+
+ if (mTree) mTreeSelection->SetCurrentIndex(viewIndex);
+
+ // Make sure the current message is once again visible in the thread pane
+ // so we don't have to go search for it in the thread pane.
+ if (mTree && viewIndex != nsMsgViewIndex_None) {
+ mTreeSelection->Select(viewIndex);
+ mTree->EnsureRowIsVisible(viewIndex);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBView::SelectMsgByKey(nsMsgKey aKey) {
+ NS_ASSERTION(aKey != nsMsgKey_None, "bad key");
+ if (aKey == nsMsgKey_None) return NS_OK;
+
+ // Use SaveAndClearSelection()
+ // and RestoreSelection() so that we'll clear the current selection
+ // but pass in a different key array so that we'll
+ // select (and load) the desired message.
+
+ AutoTArray<nsMsgKey, 1> preservedSelection;
+ nsresult rv = SaveAndClearSelection(nullptr, preservedSelection);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Now, restore our desired selection.
+ AutoTArray<nsMsgKey, 1> keyArray;
+ keyArray.AppendElement(aKey);
+
+ // If the key was not found
+ // (this can happen with "remember last selected message")
+ // nothing will be selected.
+ rv = RestoreSelection(aKey, keyArray);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBView::CloneDBView(nsIMessenger* aMessengerInstance,
+ nsIMsgWindow* aMsgWindow,
+ nsIMsgDBViewCommandUpdater* aCmdUpdater,
+ nsIMsgDBView** _retval) {
+ nsMsgDBView* newMsgDBView = new nsMsgDBView();
+
+ nsresult rv =
+ CopyDBView(newMsgDBView, aMessengerInstance, aMsgWindow, aCmdUpdater);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_IF_ADDREF(*_retval = newMsgDBView);
+ return NS_OK;
+}
+
+nsresult nsMsgDBView::CopyDBView(nsMsgDBView* aNewMsgDBView,
+ nsIMessenger* aMessengerInstance,
+ nsIMsgWindow* aMsgWindow,
+ nsIMsgDBViewCommandUpdater* aCmdUpdater) {
+ NS_ENSURE_ARG_POINTER(aNewMsgDBView);
+ if (aMsgWindow) {
+ aNewMsgDBView->mMsgWindowWeak = do_GetWeakReference(aMsgWindow);
+ aMsgWindow->SetOpenFolder(m_viewFolder ? m_viewFolder : m_folder);
+ }
+
+ aNewMsgDBView->mMessengerWeak = do_GetWeakReference(aMessengerInstance);
+ aNewMsgDBView->mCommandUpdater = do_GetWeakReference(aCmdUpdater);
+ aNewMsgDBView->m_folder = m_folder;
+ aNewMsgDBView->m_viewFlags = m_viewFlags;
+ aNewMsgDBView->m_sortOrder = m_sortOrder;
+ aNewMsgDBView->m_sortType = m_sortType;
+ aNewMsgDBView->m_curCustomColumn = m_curCustomColumn;
+ aNewMsgDBView->m_secondarySort = m_secondarySort;
+ aNewMsgDBView->m_secondarySortOrder = m_secondarySortOrder;
+ aNewMsgDBView->m_secondaryCustomColumn = m_secondaryCustomColumn;
+ aNewMsgDBView->m_db = m_db;
+ if (m_db) aNewMsgDBView->m_db->AddListener(aNewMsgDBView);
+
+ aNewMsgDBView->mIsNews = mIsNews;
+ aNewMsgDBView->mIsRss = mIsRss;
+ aNewMsgDBView->mIsXFVirtual = mIsXFVirtual;
+ aNewMsgDBView->mShowSizeInLines = mShowSizeInLines;
+ aNewMsgDBView->mDeleteModel = mDeleteModel;
+ aNewMsgDBView->m_flags = m_flags.Clone();
+ aNewMsgDBView->m_levels = m_levels.Clone();
+ aNewMsgDBView->m_keys = m_keys.Clone();
+
+ aNewMsgDBView->m_customColumnHandlerIDs = m_customColumnHandlerIDs.Clone();
+ aNewMsgDBView->m_customColumnHandlers.AppendObjects(m_customColumnHandlers);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBView::GetSearchSession(nsIMsgSearchSession** aSession) {
+ NS_ASSERTION(false, "should be overridden by child class");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsMsgDBView::SetSearchSession(nsIMsgSearchSession* aSession) {
+ NS_ASSERTION(false, "should be overridden by child class");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsMsgDBView::GetSupportsThreading(bool* aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBView::FindIndexFromKey(nsMsgKey aMsgKey, bool aExpand,
+ nsMsgViewIndex* aIndex) {
+ NS_ENSURE_ARG_POINTER(aIndex);
+ *aIndex = FindKey(aMsgKey, aExpand);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgDBView::FindIndexOfMsgHdr(nsIMsgDBHdr* aMsgHdr, bool aExpand,
+ nsMsgViewIndex* aIndex) {
+ NS_ENSURE_ARG(aMsgHdr);
+ NS_ENSURE_ARG_POINTER(aIndex);
+
+ if (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay) {
+ nsMsgViewIndex threadIndex = ThreadIndexOfMsgHdr(aMsgHdr);
+ if (threadIndex != nsMsgViewIndex_None) {
+ if (aExpand && (m_flags[threadIndex] & nsMsgMessageFlags::Elided))
+ ExpandByIndex(threadIndex, nullptr);
+
+ *aIndex = FindHdr(aMsgHdr, threadIndex);
+ } else {
+ *aIndex = nsMsgViewIndex_None;
+ }
+ } else {
+ *aIndex = FindHdr(aMsgHdr);
+ }
+
+ return NS_OK;
+}
+
+static void getDateFormatPref(nsIPrefBranch* _prefBranch,
+ const char* _prefLocalName,
+ nsDateFormatSelectorComm& _format) {
+ // Read.
+ int32_t nFormatSetting(0);
+ nsresult result = _prefBranch->GetIntPref(_prefLocalName, &nFormatSetting);
+ if (NS_SUCCEEDED(result)) {
+ // Translate.
+ nsDateFormatSelectorComm res;
+ res = static_cast<nsDateFormatSelectorComm>(nFormatSetting);
+ // Transfer if valid.
+ if (res >= kDateFormatNone && res <= kDateFormatShort)
+ _format = res;
+ else if (res == kDateFormatWeekday)
+ _format = res;
+ }
+}
+
+nsresult nsMsgDBView::InitDisplayFormats() {
+ m_dateFormatsInitialized = true;
+
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIPrefService> prefs =
+ do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIPrefBranch> dateFormatPrefs;
+ rv = prefs->GetBranch("mail.ui.display.dateformat.",
+ getter_AddRefs(dateFormatPrefs));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ getDateFormatPref(dateFormatPrefs, "default", m_dateFormatDefault);
+ getDateFormatPref(dateFormatPrefs, "thisweek", m_dateFormatThisWeek);
+ getDateFormatPref(dateFormatPrefs, "today", m_dateFormatToday);
+ return rv;
+}
+
+void nsMsgDBView::SetMRUTimeForFolder(nsIMsgFolder* folder) {
+ uint32_t seconds;
+ PRTime2Seconds(PR_Now(), &seconds);
+ nsAutoCString nowStr;
+ nowStr.AppendInt(seconds);
+ folder->SetStringProperty(MRU_TIME_PROPERTY, nowStr);
+}
+
+nsMsgDBView::nsMsgViewHdrEnumerator::nsMsgViewHdrEnumerator(nsMsgDBView* view) {
+ // We need to clone the view because the caller may clear the
+ // current view immediately. It also makes it easier to expand all
+ // if we're working on a copy.
+ nsCOMPtr<nsIMsgDBView> clonedView;
+ view->CloneDBView(nullptr, nullptr, nullptr, getter_AddRefs(clonedView));
+ m_view = static_cast<nsMsgDBView*>(clonedView.get());
+ // Make sure we enumerate over collapsed threads by expanding all.
+ m_view->ExpandAll();
+ m_curHdrIndex = 0;
+}
+
+nsMsgDBView::nsMsgViewHdrEnumerator::~nsMsgViewHdrEnumerator() {
+ if (m_view) m_view->Close();
+}
+
+NS_IMETHODIMP
+nsMsgDBView::nsMsgViewHdrEnumerator::GetNext(nsIMsgDBHdr** aItem) {
+ NS_ENSURE_ARG_POINTER(aItem);
+
+ if (m_curHdrIndex >= m_view->GetSize()) return NS_ERROR_FAILURE;
+
+ // Ignore dummy header. We won't have empty groups, so
+ // we know the view index is good.
+ if (m_view->m_flags[m_curHdrIndex] & MSG_VIEW_FLAG_DUMMY) ++m_curHdrIndex;
+
+ nsCOMPtr<nsIMsgDBHdr> nextHdr;
+
+ nsresult rv =
+ m_view->GetMsgHdrForViewIndex(m_curHdrIndex++, getter_AddRefs(nextHdr));
+ nextHdr.forget(aItem);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgDBView::nsMsgViewHdrEnumerator::HasMoreElements(bool* aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = m_curHdrIndex < m_view->GetSize();
+ return NS_OK;
+}
+
+nsresult nsMsgDBView::GetViewEnumerator(nsIMsgEnumerator** enumerator) {
+ NS_IF_ADDREF(*enumerator = new nsMsgViewHdrEnumerator(this));
+ return (*enumerator) ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
+}
+
+nsresult nsMsgDBView::GetDBForHeader(nsIMsgDBHdr* msgHdr, nsIMsgDatabase** db) {
+ nsCOMPtr<nsIMsgFolder> folder;
+ nsresult rv = msgHdr->GetFolder(getter_AddRefs(folder));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return folder->GetMsgDatabase(db);
+}
+
+/**
+ * Determine whether junk commands should be enabled on this view.
+ * Junk commands are always enabled for mail. For nntp and rss, they
+ * may be selectively enabled using an inherited folder property.
+ *
+ * @param aViewIndex view index of the message to check
+ * @return true if junk controls should be enabled
+ */
+bool nsMsgDBView::JunkControlsEnabled(nsMsgViewIndex aViewIndex) {
+ // For normal mail, junk commands are always enabled.
+ if (!(mIsNews || mIsRss || mIsXFVirtual)) return true;
+
+ // We need to check per message or folder.
+ nsCOMPtr<nsIMsgFolder> folder = m_folder;
+ if (!folder && IsValidIndex(aViewIndex))
+ GetFolderForViewIndex(aViewIndex, getter_AddRefs(folder));
+
+ if (folder) {
+ // Check if this is a mail message in search folders.
+ if (mIsXFVirtual) {
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ folder->GetServer(getter_AddRefs(server));
+ nsAutoCString type;
+ if (server) server->GetType(type);
+
+ if (!(type.LowerCaseEqualsLiteral("nntp") ||
+ type.LowerCaseEqualsLiteral("rss")))
+ return true;
+ }
+
+ // For rss and news, check the inherited folder property.
+ nsAutoCString junkEnableOverride;
+ folder->GetInheritedStringProperty("dobayes.mailnews@mozilla.org#junk",
+ junkEnableOverride);
+ if (junkEnableOverride.EqualsLiteral("true")) return true;
+ }
+
+ return false;
+}
diff --git a/comm/mailnews/base/src/nsMsgDBView.h b/comm/mailnews/base/src/nsMsgDBView.h
new file mode 100644
index 0000000000..6033a490bc
--- /dev/null
+++ b/comm/mailnews/base/src/nsMsgDBView.h
@@ -0,0 +1,558 @@
+/* -*- 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 _nsMsgDBView_H_
+#define _nsMsgDBView_H_
+
+#include "nsIMsgDBView.h"
+#include "nsIMsgWindow.h"
+#include "nsIMessenger.h"
+#include "nsIMsgDatabase.h"
+#include "nsIMsgHdr.h"
+#include "MailNewsTypes.h"
+#include "nsIDBChangeListener.h"
+#include "nsITreeView.h"
+#include "mozilla/dom/XULTreeElement.h"
+#include "nsITreeSelection.h"
+#include "nsIMsgFolder.h"
+#include "nsIMsgThread.h"
+#include "nsMsgUtils.h"
+#include "nsIImapIncomingServer.h"
+#include "nsIMsgFilterPlugin.h"
+#include "nsIStringBundle.h"
+#include "nsMsgTagService.h"
+#include "nsCOMArray.h"
+#include "nsTArray.h"
+#include "nsTHashtable.h"
+#include "nsHashKeys.h"
+#include "nsIMsgCustomColumnHandler.h"
+#include "nsIWeakReferenceUtils.h"
+#include "nsMsgEnumerator.h"
+
+#define MESSENGER_STRING_URL "chrome://messenger/locale/messenger.properties"
+
+typedef AutoTArray<nsMsgViewIndex, 1> nsMsgViewIndexArray;
+static_assert(nsMsgViewIndex(nsMsgViewIndexArray::NoIndex) ==
+ nsMsgViewIndex_None,
+ "These need to be the same value.");
+
+enum eFieldType { kCollationKey, kU32 };
+
+// This is used in an nsTArray<> to keep track of a multi-column sort.
+class MsgViewSortColumnInfo {
+ public:
+ MsgViewSortColumnInfo(const MsgViewSortColumnInfo& other);
+ MsgViewSortColumnInfo()
+ : mSortType(nsMsgViewSortType::byNone),
+ mSortOrder(nsMsgViewSortOrder::none) {}
+ bool operator==(const MsgViewSortColumnInfo& other) const;
+ nsMsgViewSortTypeValue mSortType;
+ nsMsgViewSortOrderValue mSortOrder;
+ // If mSortType == byCustom, info about the custom column sort.
+ nsString mCustomColumnName;
+ nsCOMPtr<nsIMsgCustomColumnHandler> mColHandler;
+};
+
+// Reserve some bits in the msg flags for the view-only flags.
+// NOTE: this bit space is shared by nsMsgMessageFlags (and labels).
+#define MSG_VIEW_FLAG_ISTHREAD 0x8000000
+#define MSG_VIEW_FLAG_DUMMY 0x20000000
+#define MSG_VIEW_FLAG_HASCHILDREN 0x40000000
+#define MSG_VIEW_FLAGS \
+ (MSG_VIEW_FLAG_HASCHILDREN | MSG_VIEW_FLAG_DUMMY | MSG_VIEW_FLAG_ISTHREAD)
+
+// Helper struct for sorting by numeric fields.
+// Associates a message with a key for ordering it in the view.
+struct IdUint32 {
+ nsMsgKey id;
+ uint32_t bits;
+ uint32_t dword; // The numeric key.
+ nsIMsgFolder* folder;
+};
+
+// Extends IdUint32 for sorting by a collation key field (eg subject).
+// (Also used as IdUint32 a couple of places to simplify the code, where
+// the overhead of an unused nsTArray isn't a big deal).
+struct IdKey : public IdUint32 {
+ nsTArray<uint8_t> key;
+};
+
+class nsMsgDBViewService final : public nsIMsgDBViewService {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMSGDBVIEWSERVICE
+
+ nsMsgDBViewService(){};
+
+ protected:
+ ~nsMsgDBViewService(){};
+};
+
+// This is an abstract implementation class.
+// The actual view objects will be instances of sub-classes of this class.
+class nsMsgDBView : public nsIMsgDBView,
+ public nsIDBChangeListener,
+ public nsITreeView,
+ public nsIJunkMailClassificationListener {
+ public:
+ friend class nsMsgDBViewService;
+ nsMsgDBView();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMSGDBVIEW
+ NS_DECL_NSIDBCHANGELISTENER
+ NS_DECL_NSITREEVIEW
+ NS_DECL_NSIJUNKMAILCLASSIFICATIONLISTENER
+
+ nsMsgViewIndex GetInsertIndexHelper(nsIMsgDBHdr* msgHdr,
+ nsTArray<nsMsgKey>& keys,
+ nsCOMArray<nsIMsgFolder>* folders,
+ nsMsgViewSortOrderValue sortOrder,
+ nsMsgViewSortTypeValue sortType);
+ int32_t SecondaryCompare(nsMsgKey key1, nsIMsgFolder* folder1, nsMsgKey key2,
+ nsIMsgFolder* folder2,
+ class viewSortInfo* comparisonContext);
+
+ protected:
+ virtual ~nsMsgDBView();
+
+ static nsString kHighestPriorityString;
+ static nsString kHighPriorityString;
+ static nsString kLowestPriorityString;
+ static nsString kLowPriorityString;
+ static nsString kNormalPriorityString;
+
+ static nsString kReadString;
+ static nsString kRepliedString;
+ static nsString kForwardedString;
+ static nsString kRedirectedString;
+ static nsString kNewString;
+
+ // Used for group views.
+ static nsString kTodayString;
+ static nsString kYesterdayString;
+ static nsString kLastWeekString;
+ static nsString kTwoWeeksAgoString;
+ static nsString kOldMailString;
+ static nsString kFutureDateString;
+
+ RefPtr<mozilla::dom::XULTreeElement> mTree;
+ nsCOMPtr<nsIMsgJSTree> mJSTree;
+ nsCOMPtr<nsITreeSelection> mTreeSelection;
+ // We cache this to determine when to push command status notifications.
+ uint32_t mNumSelectedRows;
+ // Set when the message pane is collapsed.
+ bool mSuppressMsgDisplay;
+ bool mSuppressCommandUpdating;
+ // Set when we're telling the outline a row is being removed. Used to
+ // suppress msg loading during delete/move operations.
+ bool mRemovingRow;
+ bool mCommandsNeedDisablingBecauseOfSelection;
+ bool mSuppressChangeNotification;
+
+ virtual const char* GetViewName(void) { return "MsgDBView"; }
+ nsresult FetchAuthor(nsIMsgDBHdr* aHdr, nsAString& aAuthorString);
+ nsresult FetchRecipients(nsIMsgDBHdr* aHdr, nsAString& aRecipientsString);
+ nsresult FetchSubject(nsIMsgDBHdr* aMsgHdr, uint32_t aFlags,
+ nsAString& aValue);
+ nsresult FetchDate(nsIMsgDBHdr* aHdr, nsAString& aDateString,
+ bool rcvDate = false);
+ nsresult FetchStatus(uint32_t aFlags, nsAString& aStatusString);
+ nsresult FetchSize(nsIMsgDBHdr* aHdr, nsAString& aSizeString);
+ nsresult FetchPriority(nsIMsgDBHdr* aHdr, nsAString& aPriorityString);
+ nsresult FetchLabel(nsIMsgDBHdr* aHdr, nsAString& aLabelString);
+ nsresult FetchTags(nsIMsgDBHdr* aHdr, nsAString& aTagString);
+ nsresult FetchKeywords(nsIMsgDBHdr* aHdr, nsACString& keywordString);
+ nsresult FetchRowKeywords(nsMsgViewIndex aRow, nsIMsgDBHdr* aHdr,
+ nsACString& keywordString);
+ nsresult FetchAccount(nsIMsgDBHdr* aHdr, nsAString& aAccount);
+ bool IsOutgoingMsg(nsIMsgDBHdr* aHdr);
+
+ // The default enumerator is over the db, but things like
+ // quick search views will enumerate just the displayed messages.
+ virtual nsresult GetMessageEnumerator(nsIMsgEnumerator** enumerator);
+ // this is a message enumerator that enumerates based on the view contents
+ virtual nsresult GetViewEnumerator(nsIMsgEnumerator** enumerator);
+
+ // Save and Restore Selection are a pair of routines you should
+ // use when performing an operation which is going to change the view
+ // and you want to remember the selection. (i.e. for sorting).
+ // Call SaveAndClearSelection and we'll give you an array of msg keys for
+ // the current selection. We also freeze and clear the selection.
+ // When you are done changing the view,
+ // call RestoreSelection passing in the same array
+ // and we'll restore the selection AND unfreeze selection in the UI.
+ nsresult SaveAndClearSelection(nsMsgKey* aCurrentMsgKey,
+ nsTArray<nsMsgKey>& aMsgKeyArray);
+ nsresult RestoreSelection(nsMsgKey aCurrentmsgKey,
+ nsTArray<nsMsgKey>& aMsgKeyArray);
+
+ // This is not safe to use when you have a selection.
+ // RowCountChanged() will call AdjustSelection().
+ // It should be called after SaveAndClearSelection() and before
+ // RestoreSelection().
+ nsresult AdjustRowCount(int32_t rowCountBeforeSort,
+ int32_t rowCountAfterSort);
+
+ nsresult GenerateURIForMsgKey(nsMsgKey aMsgKey, nsIMsgFolder* folder,
+ nsACString& aURI);
+
+ // Routines used in building up view.
+ virtual bool WantsThisThread(nsIMsgThread* thread);
+ virtual nsresult AddHdr(nsIMsgDBHdr* msgHdr,
+ nsMsgViewIndex* resultIndex = nullptr);
+ bool GetShowingIgnored() {
+ return (m_viewFlags & nsMsgViewFlagsType::kShowIgnored) != 0;
+ }
+ bool OperateOnMsgsInCollapsedThreads();
+
+ virtual nsresult OnNewHeader(nsIMsgDBHdr* aNewHdr, nsMsgKey parentKey,
+ bool ensureListed);
+ virtual nsMsgViewIndex GetInsertIndex(nsIMsgDBHdr* msgHdr);
+ nsMsgViewIndex GetIndexForThread(nsIMsgDBHdr* hdr);
+ nsMsgViewIndex GetThreadRootIndex(nsIMsgDBHdr* msgHdr);
+ virtual nsresult GetMsgHdrForViewIndex(nsMsgViewIndex index,
+ nsIMsgDBHdr** msgHdr);
+ // Given a view index, return the index of the top-level msg in the thread.
+ nsMsgViewIndex GetThreadIndex(nsMsgViewIndex msgIndex);
+
+ virtual void InsertMsgHdrAt(nsMsgViewIndex index, nsIMsgDBHdr* hdr,
+ nsMsgKey msgKey, uint32_t flags, uint32_t level);
+ virtual void SetMsgHdrAt(nsIMsgDBHdr* hdr, nsMsgViewIndex index,
+ nsMsgKey msgKey, uint32_t flags, uint32_t level);
+ virtual void InsertEmptyRows(nsMsgViewIndex viewIndex, int32_t numRows);
+ virtual void RemoveRows(nsMsgViewIndex viewIndex, int32_t numRows);
+ nsresult ToggleExpansion(nsMsgViewIndex index, uint32_t* numChanged);
+ nsresult ExpandByIndex(nsMsgViewIndex index, uint32_t* pNumExpanded);
+ nsresult CollapseByIndex(nsMsgViewIndex index, uint32_t* pNumCollapsed);
+ nsresult ExpandAll();
+ nsresult CollapseAll();
+ nsresult ExpandAndSelectThread();
+
+ // Helper routines for thread expanding and collapsing.
+ nsresult GetThreadCount(nsMsgViewIndex viewIndex, uint32_t* pThreadCount);
+ /**
+ * Retrieve the view index of the first displayed message in the given thread.
+ * @param threadHdr The thread you care about.
+ * @param allowDummy Should dummy headers be returned when the non-dummy
+ * header is available? If the root node of the thread is a dummy header
+ * and you pass false, then we will return the first child of the thread
+ * unless the thread is elided, in which case we will return the root.
+ * If you pass true, we will always return the root.
+ * @return the view index of the first message in the thread, if any.
+ */
+ nsMsgViewIndex GetIndexOfFirstDisplayedKeyInThread(nsIMsgThread* threadHdr,
+ bool allowDummy = false);
+ virtual nsresult GetFirstMessageHdrToDisplayInThread(nsIMsgThread* threadHdr,
+ nsIMsgDBHdr** result);
+ virtual nsMsgViewIndex ThreadIndexOfMsg(
+ nsMsgKey msgKey, nsMsgViewIndex msgIndex = nsMsgViewIndex_None,
+ int32_t* pThreadCount = nullptr, uint32_t* pFlags = nullptr);
+ nsMsgViewIndex ThreadIndexOfMsgHdr(
+ nsIMsgDBHdr* msgHdr, nsMsgViewIndex msgIndex = nsMsgViewIndex_None,
+ int32_t* pThreadCount = nullptr, uint32_t* pFlags = nullptr);
+ nsMsgKey GetKeyOfFirstMsgInThread(nsMsgKey key);
+ int32_t CountExpandedThread(nsMsgViewIndex index);
+ virtual nsresult ExpansionDelta(nsMsgViewIndex index,
+ int32_t* expansionDelta);
+ void ReverseSort();
+ void ReverseThreads();
+ nsresult SaveSortInfo(nsMsgViewSortTypeValue sortType,
+ nsMsgViewSortOrderValue sortOrder);
+ nsresult RestoreSortInfo();
+ nsresult PersistFolderInfo(nsIDBFolderInfo** dbFolderInfo);
+ void SetMRUTimeForFolder(nsIMsgFolder* folder);
+
+ nsMsgKey GetAt(nsMsgViewIndex index) {
+ return m_keys.SafeElementAt(index, nsMsgKey_None);
+ }
+
+ nsMsgViewIndex FindViewIndex(nsMsgKey key) { return FindKey(key, false); }
+ /**
+ * Find the message header if it is visible in this view. (Messages in
+ * threads/groups that are elided will not be
+ * @param msgHdr Message header to look for.
+ * @param startIndex The index to start looking from.
+ * @param allowDummy Are dummy headers acceptable? If yes, then for a group
+ * with a dummy header, we return the root of the thread (the dummy
+ * header), otherwise we return the actual "content" header for the
+ * message.
+ * @return The view index of the header found, if any.
+ */
+ virtual nsMsgViewIndex FindHdr(nsIMsgDBHdr* msgHdr,
+ nsMsgViewIndex startIndex = 0,
+ bool allowDummy = false);
+ virtual nsMsgViewIndex FindKey(nsMsgKey key, bool expand);
+ virtual nsresult GetDBForViewIndex(nsMsgViewIndex index, nsIMsgDatabase** db);
+ virtual nsCOMArray<nsIMsgFolder>* GetFolders();
+ virtual nsresult GetFolderFromMsgURI(const nsACString& aMsgURI,
+ nsIMsgFolder** aFolder);
+
+ virtual nsresult ListIdsInThread(nsIMsgThread* threadHdr,
+ nsMsgViewIndex viewIndex,
+ uint32_t* pNumListed);
+ nsresult ListUnreadIdsInThread(nsIMsgThread* threadHdr,
+ nsMsgViewIndex startOfThreadViewIndex,
+ uint32_t* pNumListed);
+ nsMsgViewIndex FindParentInThread(nsMsgKey parentKey,
+ nsMsgViewIndex startOfThreadViewIndex);
+ virtual nsresult ListIdsInThreadOrder(nsIMsgThread* threadHdr,
+ nsMsgKey parentKey, uint32_t level,
+ nsMsgViewIndex* viewIndex,
+ uint32_t* pNumListed);
+ uint32_t GetSize(void) { return (m_keys.Length()); }
+
+ // For commands.
+ virtual nsresult ApplyCommandToIndicesWithFolder(
+ nsMsgViewCommandTypeValue command,
+ nsTArray<nsMsgViewIndex> const& selection, nsIMsgFolder* destFolder);
+ virtual nsresult CopyMessages(nsIMsgWindow* window,
+ nsTArray<nsMsgViewIndex> const& selection,
+ bool isMove, nsIMsgFolder* destFolder);
+ virtual nsresult DeleteMessages(nsIMsgWindow* window,
+ nsTArray<nsMsgViewIndex> const& selection,
+ bool deleteStorage);
+ nsresult GetHeadersFromSelection(nsTArray<nsMsgViewIndex> const& selection,
+ nsTArray<RefPtr<nsIMsgDBHdr>>& hdrs);
+ // ListCollapsedChildren() adds to messageArray (rather than replacing it).
+ virtual nsresult ListCollapsedChildren(
+ nsMsgViewIndex viewIndex, nsTArray<RefPtr<nsIMsgDBHdr>>& messageArray);
+
+ nsresult SetMsgHdrJunkStatus(nsIJunkMailPlugin* aJunkPlugin,
+ nsIMsgDBHdr* aMsgHdr,
+ nsMsgJunkStatus aNewClassification);
+ nsresult ToggleReadByIndex(nsMsgViewIndex index);
+ nsresult SetReadByIndex(nsMsgViewIndex index, bool read);
+ nsresult SetThreadOfMsgReadByIndex(nsMsgViewIndex index,
+ nsTArray<nsMsgKey>& keysMarkedRead,
+ bool read);
+ nsresult SetFlaggedByIndex(nsMsgViewIndex index, bool mark);
+ nsresult OrExtraFlag(nsMsgViewIndex index, uint32_t orflag);
+ nsresult AndExtraFlag(nsMsgViewIndex index, uint32_t andflag);
+ nsresult SetExtraFlag(nsMsgViewIndex index, uint32_t extraflag);
+ virtual nsresult RemoveByIndex(nsMsgViewIndex index);
+ virtual void OnExtraFlagChanged(nsMsgViewIndex /*index*/,
+ uint32_t /*extraFlag*/) {}
+ virtual void OnHeaderAddedOrDeleted() {}
+ nsresult ToggleWatched(nsTArray<nsMsgViewIndex> const& selection);
+ nsresult SetThreadWatched(nsIMsgThread* thread, nsMsgViewIndex index,
+ bool watched);
+ nsresult SetThreadIgnored(nsIMsgThread* thread, nsMsgViewIndex threadIndex,
+ bool ignored);
+ nsresult SetSubthreadKilled(nsIMsgDBHdr* header, nsMsgViewIndex msgIndex,
+ bool ignored);
+ nsresult DownloadForOffline(nsIMsgWindow* window,
+ nsTArray<nsMsgViewIndex> const& selection);
+ nsresult DownloadFlaggedForOffline(nsIMsgWindow* window);
+ nsMsgViewIndex GetThreadFromMsgIndex(nsMsgViewIndex index,
+ nsIMsgThread** threadHdr);
+ /// Should junk commands be enabled for the current message in the view?
+ bool JunkControlsEnabled(nsMsgViewIndex aViewIndex);
+
+ // For sorting.
+ nsresult GetFieldTypeAndLenForSort(
+ nsMsgViewSortTypeValue sortType, uint16_t* pMaxLen,
+ eFieldType* pFieldType, nsIMsgCustomColumnHandler* colHandler = nullptr);
+ nsresult GetCollationKey(nsIMsgDBHdr* msgHdr, nsMsgViewSortTypeValue sortType,
+ nsTArray<uint8_t>& result,
+ nsIMsgCustomColumnHandler* colHandler = nullptr);
+ nsresult GetLongField(nsIMsgDBHdr* msgHdr, nsMsgViewSortTypeValue sortType,
+ uint32_t* result,
+ nsIMsgCustomColumnHandler* colHandler = nullptr);
+
+ static int FnSortIdKey(const IdKey* pItem1, const IdKey* pItem2,
+ viewSortInfo* sortInfo);
+ static int FnSortIdUint32(const IdUint32* pItem1, const IdUint32* pItem2,
+ viewSortInfo* sortInfo);
+
+ nsresult GetStatusSortValue(nsIMsgDBHdr* msgHdr, uint32_t* result);
+ nsresult GetLocationCollationKey(nsIMsgDBHdr* msgHdr,
+ nsTArray<uint8_t>& result);
+ void PushSort(const MsgViewSortColumnInfo& newSort);
+ nsresult EncodeColumnSort(nsString& columnSortString);
+ nsresult DecodeColumnSort(nsString& columnSortString);
+ // For view navigation.
+ nsresult NavigateFromPos(nsMsgNavigationTypeValue motion,
+ nsMsgViewIndex startIndex, nsMsgKey* pResultKey,
+ nsMsgViewIndex* pResultIndex,
+ nsMsgViewIndex* pThreadIndex, bool wrap);
+ nsresult FindNextFlagged(nsMsgViewIndex startIndex,
+ nsMsgViewIndex* pResultIndex);
+ nsresult FindFirstNew(nsMsgViewIndex* pResultIndex);
+ nsresult FindPrevUnread(nsMsgKey startKey, nsMsgKey* pResultKey,
+ nsMsgKey* resultThreadId);
+ nsresult FindFirstFlagged(nsMsgViewIndex* pResultIndex);
+ nsresult FindPrevFlagged(nsMsgViewIndex startIndex,
+ nsMsgViewIndex* pResultIndex);
+ nsresult MarkThreadOfMsgRead(nsMsgKey msgId, nsMsgViewIndex msgIndex,
+ nsTArray<nsMsgKey>& idsMarkedRead, bool bRead);
+ nsresult MarkThreadRead(nsIMsgThread* threadHdr, nsMsgViewIndex threadIndex,
+ nsTArray<nsMsgKey>& idsMarkedRead, bool bRead);
+ bool IsValidIndex(nsMsgViewIndex index);
+ nsresult ToggleIgnored(nsTArray<nsMsgViewIndex> const& selection,
+ nsMsgViewIndex* resultIndex, bool* resultToggleState);
+ nsresult ToggleMessageKilled(nsTArray<nsMsgViewIndex> const& selection,
+ nsMsgViewIndex* resultIndex,
+ bool* resultToggleState);
+ bool OfflineMsgSelected(nsTArray<nsMsgViewIndex> const& selection);
+ bool NonDummyMsgSelected(nsTArray<nsMsgViewIndex> const& selection);
+ static void GetString(const char16_t* aStringName, nsAString& aValue);
+ static nsresult GetPrefLocalizedString(const char* aPrefName,
+ nsString& aResult);
+ nsresult AppendKeywordProperties(const nsACString& keywords,
+ nsAString& properties, bool* tagAdded);
+ static nsresult InitLabelStrings(void);
+ nsresult CopyDBView(nsMsgDBView* aNewMsgDBView,
+ nsIMessenger* aMessengerInstance,
+ nsIMsgWindow* aMsgWindow,
+ nsIMsgDBViewCommandUpdater* aCmdUpdater);
+ static void InitializeLiterals();
+ virtual int32_t FindLevelInThread(nsIMsgDBHdr* msgHdr,
+ nsMsgViewIndex startOfThread,
+ nsMsgViewIndex viewIndex);
+ nsresult GetImapDeleteModel(nsIMsgFolder* folder);
+ nsresult UpdateDisplayMessage(nsMsgViewIndex viewPosition);
+ nsresult GetDBForHeader(nsIMsgDBHdr* msgHdr, nsIMsgDatabase** db);
+
+ bool AdjustReadFlag(nsIMsgDBHdr* msgHdr, uint32_t* msgFlags);
+ void FreeAll(nsTArray<void*>* ptrs);
+ void ClearHdrCache();
+
+ // The message held in each row.
+ nsTArray<nsMsgKey> m_keys;
+ // Flags for each row, combining nsMsgMessageFlags and MSG_VIEW_FLAGS.
+ nsTArray<uint32_t> m_flags;
+ // Threading level of each row (1=top)
+ nsTArray<uint8_t> m_levels;
+
+ nsMsgImapDeleteModel mDeleteModel;
+
+ // Cache the most recently asked for header and corresponding msgKey.
+ nsCOMPtr<nsIMsgDBHdr> m_cachedHdr;
+ nsMsgKey m_cachedMsgKey;
+
+ // We need to store the message key for the message we are currently
+ // displaying to ensure we don't try to redisplay the same message just
+ // because the selection changed (i.e. after a sort).
+ nsMsgKey m_currentlyDisplayedMsgKey;
+ nsCString m_currentlyDisplayedMsgUri;
+ nsMsgViewIndex m_currentlyDisplayedViewIndex;
+ // If we're deleting messages, we want to hold off loading messages on
+ // selection changed until the delete is done and we want to batch
+ // notifications.
+ bool m_deletingRows;
+ // For certain special folders and descendants of those folders
+ // (like the "Sent" folder, "Sent/Old Sent").
+ // The Sender column really shows recipients.
+
+ // Server types for this view's folder
+ bool mIsNews; // We have special icons for news.
+ bool mIsRss; // RSS affects enabling of junk commands.
+ bool mIsXFVirtual; // A virtual folder with multiple folders.
+
+ bool mShowSizeInLines; // For news we show lines instead of size when true.
+ bool mSortThreadsByRoot; // As opposed to by the newest message.
+ bool m_sortValid;
+ bool m_checkedCustomColumns;
+ bool mSelectionSummarized;
+ // We asked the front end to summarize the selection and it did not.
+ bool mSummarizeFailed;
+ uint8_t m_saveRestoreSelectionDepth;
+
+ nsCOMPtr<nsIMsgDatabase> m_db;
+ nsCOMPtr<nsIMsgFolder> m_folder;
+ // For virtual folders, the VF db.
+ nsCOMPtr<nsIMsgFolder> m_viewFolder;
+ nsString mMessageType;
+ nsTArray<MsgViewSortColumnInfo> m_sortColumns;
+ nsMsgViewSortTypeValue m_sortType;
+ nsMsgViewSortOrderValue m_sortOrder;
+ nsString m_curCustomColumn;
+ nsMsgViewSortTypeValue m_secondarySort;
+ nsMsgViewSortOrderValue m_secondarySortOrder;
+ nsString m_secondaryCustomColumn;
+ nsMsgViewFlagsTypeValue m_viewFlags;
+
+ // I18N date formatter service which we'll want to cache locally.
+ nsCOMPtr<nsIMsgTagService> mTagService;
+ nsWeakPtr mMessengerWeak;
+ nsWeakPtr mMsgWindowWeak;
+ // We push command update notifications to the UI from this.
+ nsWeakPtr mCommandUpdater;
+ static nsCOMPtr<nsIStringBundle> mMessengerStringBundle;
+
+ // Used to determine when to start and end junk plugin batches.
+ uint32_t mNumMessagesRemainingInBatch;
+
+ // These are the headers of the messages in the current
+ // batch/series of batches of messages manually marked
+ // as junk.
+ nsTArray<RefPtr<nsIMsgDBHdr>> mJunkHdrs;
+
+ nsTArray<uint32_t> mIndicesToNoteChange;
+
+ nsTHashtable<nsCStringHashKey> mEmails;
+
+ // The saved search views keep track of the XX most recently deleted msg ids,
+ // so that if the delete is undone, we can add the msg back to the search
+ // results, even if it no longer matches the search criteria (e.g., a saved
+ // search over unread messages). We use mRecentlyDeletedArrayIndex to treat
+ // the array as a list of the XX most recently deleted msgs.
+ nsTArray<nsCString> mRecentlyDeletedMsgIds;
+ uint32_t mRecentlyDeletedArrayIndex;
+ void RememberDeletedMsgHdr(nsIMsgDBHdr* msgHdr);
+ bool WasHdrRecentlyDeleted(nsIMsgDBHdr* msgHdr);
+
+ // These hold pointers (and IDs) for the nsIMsgCustomColumnHandler object
+ // that constitutes the custom column handler.
+ nsCOMArray<nsIMsgCustomColumnHandler> m_customColumnHandlers;
+ nsTArray<nsString> m_customColumnHandlerIDs;
+
+ nsIMsgCustomColumnHandler* GetColumnHandler(const nsAString& colID);
+ nsIMsgCustomColumnHandler* GetCurColumnHandler();
+ bool CustomColumnsInSortAndNotRegistered();
+ void EnsureCustomColumnsValid();
+
+#ifdef DEBUG_David_Bienvenu
+ void InitEntryInfoForIndex(nsMsgViewIndex i, IdKey& EntryInfo);
+ void ValidateSort();
+#endif
+
+ protected:
+ static nsresult InitDisplayFormats();
+
+ private:
+ static bool m_dateFormatsInitialized;
+ static nsDateFormatSelectorComm m_dateFormatDefault;
+ static nsDateFormatSelectorComm m_dateFormatThisWeek;
+ static nsDateFormatSelectorComm m_dateFormatToday;
+ static nsString m_connectorPattern;
+
+ bool ServerSupportsFilterAfterTheFact();
+
+ nsresult PerformActionsOnJunkMsgs(bool msgsAreJunk);
+ nsresult DetermineActionsForJunkChange(bool msgsAreJunk,
+ nsIMsgFolder* srcFolder,
+ bool& moveMessages,
+ bool& changeReadState,
+ nsIMsgFolder** targetFolder);
+
+ class nsMsgViewHdrEnumerator final : public nsBaseMsgEnumerator {
+ public:
+ explicit nsMsgViewHdrEnumerator(nsMsgDBView* view);
+
+ // nsIMsgEnumerator support.
+ NS_IMETHOD GetNext(nsIMsgDBHdr** aItem) override;
+ NS_IMETHOD HasMoreElements(bool* aResult) override;
+
+ RefPtr<nsMsgDBView> m_view;
+ nsMsgViewIndex m_curHdrIndex;
+
+ private:
+ virtual ~nsMsgViewHdrEnumerator() override;
+ };
+};
+
+#endif
diff --git a/comm/mailnews/base/src/nsMsgEnumerator.cpp b/comm/mailnews/base/src/nsMsgEnumerator.cpp
new file mode 100644
index 0000000000..5763a8d4dc
--- /dev/null
+++ b/comm/mailnews/base/src/nsMsgEnumerator.cpp
@@ -0,0 +1,138 @@
+/* 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 "nsMsgEnumerator.h"
+
+#include "mozilla/dom/IteratorResultBinding.h"
+#include "mozilla/dom/RootedDictionary.h"
+#include "mozilla/dom/ToJSValue.h"
+#include "mozilla/ResultExtensions.h"
+#include "nsContentUtils.h"
+
+#include "nsIMsgHdr.h"
+#include "nsIMsgThread.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+/**
+ * Internal class to support iteration over nsBaseMsgEnumerator in javascript.
+ */
+class JSMsgIterator final : public nsIJSIterator {
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIJSITERATOR
+
+ explicit JSMsgIterator(nsBaseMsgEnumerator* aEnumerator)
+ : mEnumerator(aEnumerator) {}
+
+ private:
+ ~JSMsgIterator() = default;
+ RefPtr<nsBaseMsgEnumerator> mEnumerator;
+};
+
+NS_IMETHODIMP JSMsgIterator::Next(JSContext* aCx,
+ JS::MutableHandle<JS::Value> aResult) {
+ // result is object of the form: {value: ..., done: ...}
+ RootedDictionary<IteratorResult> result(aCx);
+
+ // We're really using the enumerator itself as the iterator.
+ nsCOMPtr<nsIMsgDBHdr> msg;
+ if (NS_FAILED(mEnumerator->GetNext(getter_AddRefs(msg)))) {
+ result.mDone = true;
+ // Leave value unset.
+ } else {
+ result.mDone = false;
+
+ JS::Rooted<JS::Value> value(aCx);
+ MOZ_TRY(
+ nsContentUtils::WrapNative(aCx, msg, &NS_GET_IID(nsIMsgDBHdr), &value));
+ result.mValue = value;
+ }
+
+ if (!ToJSValue(aCx, result, aResult)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(JSMsgIterator, nsIJSIterator)
+
+// nsBaseMsgEnumerator implementation.
+
+NS_IMETHODIMP nsBaseMsgEnumerator::Iterator(nsIJSIterator** aResult) {
+ auto result = MakeRefPtr<JSMsgIterator>(this);
+ result.forget(aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsBaseMsgEnumerator::GetNext(nsIMsgDBHdr** aItem) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsBaseMsgEnumerator::HasMoreElements(bool* aResult) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMPL_ISUPPORTS(nsBaseMsgEnumerator, nsIMsgEnumerator)
+
+/**
+ * Internal class to support iteration over nsBaseMsgThreadEnumerator in
+ * javascript.
+ */
+class JSThreadIterator final : public nsIJSIterator {
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIJSITERATOR
+
+ explicit JSThreadIterator(nsBaseMsgThreadEnumerator* aEnumerator)
+ : mEnumerator(aEnumerator) {}
+
+ private:
+ ~JSThreadIterator() = default;
+ RefPtr<nsBaseMsgThreadEnumerator> mEnumerator;
+};
+
+NS_IMETHODIMP JSThreadIterator::Next(JSContext* aCx,
+ JS::MutableHandle<JS::Value> aResult) {
+ // result is object of the form: {value: ..., done: ...}
+ RootedDictionary<IteratorResult> result(aCx);
+
+ // We're really using the enumerator itself as the iterator.
+ nsCOMPtr<nsIMsgThread> msg;
+ if (NS_FAILED(mEnumerator->GetNext(getter_AddRefs(msg)))) {
+ result.mDone = true;
+ // Leave value unset.
+ } else {
+ result.mDone = false;
+
+ JS::Rooted<JS::Value> value(aCx);
+ MOZ_TRY(nsContentUtils::WrapNative(aCx, msg, &NS_GET_IID(nsIMsgThread),
+ &value));
+ result.mValue = value;
+ }
+
+ if (!ToJSValue(aCx, result, aResult)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(JSThreadIterator, nsIJSIterator)
+
+// nsBaseMsgThreadEnumerator implementation.
+
+NS_IMETHODIMP nsBaseMsgThreadEnumerator::Iterator(nsIJSIterator** aResult) {
+ auto result = MakeRefPtr<JSThreadIterator>(this);
+ result.forget(aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsBaseMsgThreadEnumerator::GetNext(nsIMsgThread** aItem) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsBaseMsgThreadEnumerator::HasMoreElements(bool* aResult) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMPL_ISUPPORTS(nsBaseMsgThreadEnumerator, nsIMsgThreadEnumerator)
diff --git a/comm/mailnews/base/src/nsMsgEnumerator.h b/comm/mailnews/base/src/nsMsgEnumerator.h
new file mode 100644
index 0000000000..306c3e3d72
--- /dev/null
+++ b/comm/mailnews/base/src/nsMsgEnumerator.h
@@ -0,0 +1,45 @@
+/* -*- 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 _nsMsgEnumerator_H_
+#define _nsMsgEnumerator_H_
+
+#include "nsIMsgEnumerator.h"
+
+/**
+ * A base implementation nsIMsgEnumerator for stepping over an ordered set
+ * of nsIMsgDBHdr objects.
+ * This provides the javascript iterable protocol (to support for...of
+ * constructs), but getNext() and hasMoreElements() must be implemented by
+ * derived classes.
+ */
+class nsBaseMsgEnumerator : public nsIMsgEnumerator {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMSGENUMERATOR
+ nsBaseMsgEnumerator(){};
+
+ protected:
+ virtual ~nsBaseMsgEnumerator(){};
+};
+
+/**
+ * A base implementation nsIMsgThreadEnumerator for stepping over an ordered
+ * set of nsIMsgThread objects.
+ * This provides the javascript iterable protocol (to support for...of
+ * constructs), but getNext() and hasMoreElements() must be implemented by
+ * derived classes.
+ */
+class nsBaseMsgThreadEnumerator : public nsIMsgThreadEnumerator {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMSGTHREADENUMERATOR
+ nsBaseMsgThreadEnumerator(){};
+
+ protected:
+ virtual ~nsBaseMsgThreadEnumerator(){};
+};
+
+#endif /* _nsMsgEnumerator_H_ */
diff --git a/comm/mailnews/base/src/nsMsgFileStream.cpp b/comm/mailnews/base/src/nsMsgFileStream.cpp
new file mode 100644
index 0000000000..9e18ae70f8
--- /dev/null
+++ b/comm/mailnews/base/src/nsMsgFileStream.cpp
@@ -0,0 +1,190 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIFile.h"
+#include "nsMsgFileStream.h"
+#include "prerr.h"
+#include "prerror.h"
+
+/* From nsDebugImpl.cpp: */
+static nsresult ErrorAccordingToNSPR() {
+ PRErrorCode err = PR_GetError();
+ switch (err) {
+ case PR_OUT_OF_MEMORY_ERROR:
+ return NS_ERROR_OUT_OF_MEMORY;
+ case PR_WOULD_BLOCK_ERROR:
+ return NS_BASE_STREAM_WOULD_BLOCK;
+ case PR_FILE_NOT_FOUND_ERROR:
+ return NS_ERROR_FILE_NOT_FOUND;
+ case PR_READ_ONLY_FILESYSTEM_ERROR:
+ return NS_ERROR_FILE_READ_ONLY;
+ case PR_NOT_DIRECTORY_ERROR:
+ return NS_ERROR_FILE_NOT_DIRECTORY;
+ case PR_IS_DIRECTORY_ERROR:
+ return NS_ERROR_FILE_IS_DIRECTORY;
+ case PR_LOOP_ERROR:
+ return NS_ERROR_FILE_UNRESOLVABLE_SYMLINK;
+ case PR_FILE_EXISTS_ERROR:
+ return NS_ERROR_FILE_ALREADY_EXISTS;
+ case PR_FILE_IS_LOCKED_ERROR:
+ return NS_ERROR_FILE_IS_LOCKED;
+ case PR_FILE_TOO_BIG_ERROR:
+ return NS_ERROR_FILE_TOO_BIG;
+ case PR_NO_DEVICE_SPACE_ERROR:
+ return NS_ERROR_FILE_NO_DEVICE_SPACE;
+ case PR_NAME_TOO_LONG_ERROR:
+ return NS_ERROR_FILE_NAME_TOO_LONG;
+ case PR_DIRECTORY_NOT_EMPTY_ERROR:
+ return NS_ERROR_FILE_DIR_NOT_EMPTY;
+ case PR_NO_ACCESS_RIGHTS_ERROR:
+ return NS_ERROR_FILE_ACCESS_DENIED;
+ default:
+ return NS_ERROR_FAILURE;
+ }
+}
+
+nsMsgFileStream::nsMsgFileStream() {
+ mFileDesc = nullptr;
+ mSeekedToEnd = false;
+}
+
+nsMsgFileStream::~nsMsgFileStream() {
+ if (mFileDesc) PR_Close(mFileDesc);
+}
+
+NS_IMPL_ISUPPORTS(nsMsgFileStream, nsIInputStream, nsIOutputStream,
+ nsITellableStream, nsISeekableStream)
+
+nsresult nsMsgFileStream::InitWithFile(nsIFile* file) {
+ return file->OpenNSPRFileDesc(PR_RDWR | PR_CREATE_FILE, 0664, &mFileDesc);
+}
+
+NS_IMETHODIMP
+nsMsgFileStream::Seek(int32_t whence, int64_t offset) {
+ if (mFileDesc == nullptr) return NS_BASE_STREAM_CLOSED;
+
+ bool seekingToEnd = whence == PR_SEEK_END && offset == 0;
+ if (seekingToEnd && mSeekedToEnd) return NS_OK;
+
+ int64_t cnt = PR_Seek64(mFileDesc, offset, (PRSeekWhence)whence);
+ if (cnt == int64_t(-1)) {
+ return ErrorAccordingToNSPR();
+ }
+
+ mSeekedToEnd = seekingToEnd;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgFileStream::Tell(int64_t* result) {
+ if (mFileDesc == nullptr) return NS_BASE_STREAM_CLOSED;
+
+ int64_t cnt = PR_Seek64(mFileDesc, 0, PR_SEEK_CUR);
+ if (cnt == int64_t(-1)) {
+ return ErrorAccordingToNSPR();
+ }
+ *result = cnt;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgFileStream::SetEOF() {
+ if (mFileDesc == nullptr) return NS_BASE_STREAM_CLOSED;
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/* void close (); */
+NS_IMETHODIMP nsMsgFileStream::Close() {
+ nsresult rv = NS_OK;
+ if (mFileDesc && (PR_Close(mFileDesc) == PR_FAILURE))
+ rv = NS_BASE_STREAM_OSERROR;
+ mFileDesc = nullptr;
+ return rv;
+}
+
+/* unsigned long long available (); */
+NS_IMETHODIMP nsMsgFileStream::Available(uint64_t* aResult) {
+ if (!mFileDesc) return NS_BASE_STREAM_CLOSED;
+
+ int64_t avail = PR_Available64(mFileDesc);
+ if (avail == -1) return ErrorAccordingToNSPR();
+
+ *aResult = avail;
+ return NS_OK;
+}
+
+/* [noscript] unsigned long read (in charPtr aBuf, in unsigned long aCount); */
+NS_IMETHODIMP nsMsgFileStream::Read(char* aBuf, uint32_t aCount,
+ uint32_t* aResult) {
+ if (!mFileDesc) {
+ *aResult = 0;
+ return NS_OK;
+ }
+
+ int32_t bytesRead = PR_Read(mFileDesc, aBuf, aCount);
+ if (bytesRead == -1) return ErrorAccordingToNSPR();
+
+ *aResult = bytesRead;
+ return NS_OK;
+}
+
+/* [noscript] unsigned long readSegments (in nsWriteSegmentFun aWriter, in
+ * voidPtr aClosure, in unsigned long aCount); */
+NS_IMETHODIMP nsMsgFileStream::ReadSegments(nsWriteSegmentFun aWriter,
+ void* aClosure, uint32_t aCount,
+ uint32_t* _retval) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/* boolean isNonBlocking (); */
+NS_IMETHODIMP nsMsgFileStream::IsNonBlocking(bool* aNonBlocking) {
+ *aNonBlocking = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgFileStream::Write(const char* buf, uint32_t count, uint32_t* result) {
+ if (mFileDesc == nullptr) return NS_BASE_STREAM_CLOSED;
+
+ int32_t cnt = PR_Write(mFileDesc, buf, count);
+ if (cnt == -1) {
+ return ErrorAccordingToNSPR();
+ }
+ *result = cnt;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgFileStream::Flush(void) {
+ if (mFileDesc == nullptr) return NS_BASE_STREAM_CLOSED;
+
+ int32_t cnt = PR_Sync(mFileDesc);
+ if (cnt == -1) return ErrorAccordingToNSPR();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgFileStream::WriteFrom(nsIInputStream* inStr, uint32_t count,
+ uint32_t* _retval) {
+ MOZ_ASSERT_UNREACHABLE("WriteFrom (see source comment)");
+ return NS_ERROR_NOT_IMPLEMENTED;
+ // File streams intentionally do not support this method.
+ // If you need something like this, then you should wrap
+ // the file stream using nsIBufferedOutputStream
+}
+
+NS_IMETHODIMP
+nsMsgFileStream::WriteSegments(nsReadSegmentFun reader, void* closure,
+ uint32_t count, uint32_t* _retval) {
+ MOZ_ASSERT_UNREACHABLE("WriteSegments (see source comment)");
+ return NS_ERROR_NOT_IMPLEMENTED;
+ // File streams intentionally do not support this method.
+ // If you need something like this, then you should wrap
+ // the file stream using nsIBufferedOutputStream
+}
+
+NS_IMETHODIMP nsMsgFileStream::StreamStatus() {
+ return mFileDesc ? NS_OK : NS_BASE_STREAM_CLOSED;
+}
diff --git a/comm/mailnews/base/src/nsMsgFileStream.h b/comm/mailnews/base/src/nsMsgFileStream.h
new file mode 100644
index 0000000000..11b5c24e91
--- /dev/null
+++ b/comm/mailnews/base/src/nsMsgFileStream.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/. */
+
+#include "mozilla/Attributes.h"
+#include "msgCore.h"
+#include "nsIInputStream.h"
+#include "nsIOutputStream.h"
+#include "nsISeekableStream.h"
+#include "prio.h"
+
+class nsMsgFileStream final : public nsIInputStream,
+ public nsIOutputStream,
+ public nsISeekableStream {
+ public:
+ nsMsgFileStream();
+
+ NS_DECL_ISUPPORTS
+
+ NS_IMETHOD Available(uint64_t* _retval) override;
+ NS_IMETHOD Read(char* aBuf, uint32_t aCount, uint32_t* _retval) override;
+ NS_IMETHOD ReadSegments(nsWriteSegmentFun aWriter, void* aClosure,
+ uint32_t aCount, uint32_t* _retval) override;
+ NS_DECL_NSIOUTPUTSTREAM
+ NS_DECL_NSISEEKABLESTREAM
+ NS_DECL_NSITELLABLESTREAM
+
+ nsresult InitWithFile(nsIFile* localFile);
+
+ protected:
+ ~nsMsgFileStream();
+
+ PRFileDesc* mFileDesc;
+ bool mSeekedToEnd;
+};
diff --git a/comm/mailnews/base/src/nsMsgFolderCache.cpp b/comm/mailnews/base/src/nsMsgFolderCache.cpp
new file mode 100644
index 0000000000..c843730f24
--- /dev/null
+++ b/comm/mailnews/base/src/nsMsgFolderCache.cpp
@@ -0,0 +1,570 @@
+/* -*- 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 "msgCore.h"
+#include "nsMsgFolderCache.h"
+#include "nsIMsgFolderCacheElement.h"
+#include "nsNetUtil.h"
+#include "nsStreamUtils.h"
+#include "nsIOutputStream.h"
+#include "nsIInputStream.h"
+#include "nsISafeOutputStream.h"
+#include "prprf.h"
+#include "mozilla/Logging.h"
+// Mork-related includes.
+#include "nsIMdbFactoryFactory.h"
+#include "mdb.h"
+// Includes for jsoncpp.
+#include "json/json.h"
+#include <string>
+
+using namespace mozilla;
+
+static LazyLogModule sFolderCacheLog("MsgFolderCache");
+
+// Helper functions for migration of legacy pancea.dat files.
+static nsresult importFromMork(PathString const& dbName, Json::Value& root);
+static nsresult convertTable(nsIMdbEnv* env, nsIMdbStore* store,
+ nsIMdbTable* table, Json::Value& root);
+static void applyEntry(nsCString const& name, nsCString const& val,
+ Json::Value& obj);
+
+/*
+ * nsMsgFolderCacheElement
+ * Folders are given this to let them manipulate their cache data.
+ */
+class nsMsgFolderCacheElement : public nsIMsgFolderCacheElement {
+ public:
+ nsMsgFolderCacheElement(nsMsgFolderCache* owner, nsACString const& key)
+ : mOwner(owner), mKey(key) {}
+ nsMsgFolderCacheElement() = delete;
+
+ NS_DECL_ISUPPORTS
+ NS_IMETHOD GetKey(nsACString& key) override {
+ key = mKey;
+ return NS_OK;
+ }
+
+ NS_IMETHOD GetCachedString(const char* name, nsACString& _retval) override {
+ if (!Obj().isMember(name)) return NS_ERROR_NOT_AVAILABLE;
+ Json::Value& o = Obj()[name];
+ if (o.isConvertibleTo(Json::stringValue)) {
+ _retval = o.asString().c_str();
+ return NS_OK;
+ }
+ // Leave _retval unchanged if an error occurs.
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ NS_IMETHOD GetCachedInt32(const char* name, int32_t* _retval) override {
+ if (!Obj().isMember(name)) return NS_ERROR_NOT_AVAILABLE;
+ Json::Value& o = Obj()[name];
+ if (o.isConvertibleTo(Json::intValue)) {
+ *_retval = o.asInt();
+ return NS_OK;
+ }
+ // Leave _retval unchanged if an error occurs.
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ NS_IMETHOD GetCachedUInt32(const char* name, uint32_t* _retval) override {
+ if (!Obj().isMember(name)) return NS_ERROR_NOT_AVAILABLE;
+ Json::Value& o = Obj()[name];
+ if (o.isConvertibleTo(Json::uintValue)) {
+ *_retval = o.asUInt();
+ return NS_OK;
+ }
+ // Leave _retval unchanged if an error occurs.
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ NS_IMETHOD GetCachedInt64(const char* name, int64_t* _retval) override {
+ if (!Obj().isMember(name)) return NS_ERROR_NOT_AVAILABLE;
+ Json::Value& o = Obj()[name];
+ // isConvertibleTo() doesn't seem to support Int64. Hence multiple checks.
+ if (o.isNumeric() || o.isNull() || o.isBool()) {
+ *_retval = o.asInt64();
+ return NS_OK;
+ }
+ // Leave _retval unchanged if an error occurs.
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ NS_IMETHOD SetCachedString(const char* name,
+ const nsACString& value) override {
+ if (Obj()[name] != PromiseFlatCString(value).get()) {
+ Obj()[name] = PromiseFlatCString(value).get();
+ mOwner->SetModified();
+ }
+ return NS_OK;
+ }
+
+ NS_IMETHOD SetCachedInt32(const char* name, int32_t value) override {
+ if (Obj()[name] != value) {
+ Obj()[name] = value;
+ mOwner->SetModified();
+ }
+ return NS_OK;
+ }
+
+ NS_IMETHOD SetCachedUInt32(const char* name, uint32_t value) override {
+ if (Obj()[name] != value) {
+ Obj()[name] = value;
+ mOwner->SetModified();
+ }
+ return NS_OK;
+ }
+ NS_IMETHOD SetCachedInt64(const char* name, int64_t value) override {
+ if (Obj()[name] != value) {
+ Obj()[name] = value;
+ mOwner->SetModified();
+ }
+ return NS_OK;
+ }
+
+ protected:
+ virtual ~nsMsgFolderCacheElement() {}
+ RefPtr<nsMsgFolderCache> mOwner;
+ nsAutoCString mKey;
+
+ // Helper to get the Json object for this nsFolderCacheElement,
+ // creating it if it doesn't already exist.
+ Json::Value& Obj() {
+ Json::Value& root = *mOwner->mRoot;
+ // This will create an empty object if it doesn't already exist.
+ Json::Value& v = root[mKey.get()];
+ if (v.isObject()) {
+ return v;
+ }
+ // uhoh... either the folder entry doesn't exist (expected) or
+ // the json file wasn't the structure we were expecting.
+ // We _really_ don't want jsoncpp to be throwing exceptions, so in either
+ // case we'll create a fresh new empty object there.
+ root[mKey.get()] = Json::Value(Json::objectValue);
+ return root[mKey.get()];
+ }
+};
+
+NS_IMPL_ISUPPORTS(nsMsgFolderCacheElement, nsIMsgFolderCacheElement)
+
+/*
+ * nsMsgFolderCache implementation
+ */
+
+NS_IMPL_ISUPPORTS(nsMsgFolderCache, nsIMsgFolderCache)
+
+// mRoot dynamically allocated here to avoid exposing Json in header file.
+nsMsgFolderCache::nsMsgFolderCache()
+ : mRoot(new Json::Value(Json::objectValue)),
+ mSavePending(false),
+ mSaveTimer(NS_NewTimer()) {}
+
+NS_IMETHODIMP nsMsgFolderCache::Init(nsIFile* cacheFile, nsIFile* legacyFile) {
+ mCacheFile = cacheFile;
+ // Is there a JSON file to load?
+ bool exists;
+ nsresult rv = cacheFile->Exists(&exists);
+ if (NS_SUCCEEDED(rv) && exists) {
+ rv = LoadFolderCache(cacheFile);
+ if (NS_FAILED(rv)) {
+ MOZ_LOG(
+ sFolderCacheLog, LogLevel::Error,
+ ("Failed to load %s (code 0x%x)",
+ cacheFile->HumanReadablePath().get(), static_cast<uint32_t>(rv)));
+ }
+ // Ignore error. If load fails, we'll just start off with empty cache.
+ return NS_OK;
+ }
+
+ MOZ_LOG(sFolderCacheLog, LogLevel::Debug, ("No cache file found."));
+
+ // No sign of new-style JSON file. Maybe there's an old panacea.dat we can
+ // migrate?
+ rv = legacyFile->Exists(&exists);
+ if (NS_SUCCEEDED(rv) && exists) {
+ MOZ_LOG(sFolderCacheLog, LogLevel::Debug,
+ ("Found %s. Attempting migration.",
+ legacyFile->HumanReadablePath().get()));
+ Json::Value root(Json::objectValue);
+ rv = importFromMork(legacyFile->NativePath(), root);
+ if (NS_SUCCEEDED(rv)) {
+ *mRoot = root;
+ MOZ_LOG(sFolderCacheLog, LogLevel::Debug,
+ ("Migration: Legacy cache imported"));
+ // Migrate it to JSON.
+ rv = SaveFolderCache(cacheFile);
+ if (NS_SUCCEEDED(rv)) {
+ // We're done with the legacy panacea.dat - remove it.
+ legacyFile->Remove(false);
+ } else {
+ MOZ_LOG(
+ sFolderCacheLog, LogLevel::Error,
+ ("Migration: save failed (code 0x%x)", static_cast<uint32_t>(rv)));
+ }
+ } else {
+ MOZ_LOG(
+ sFolderCacheLog, LogLevel::Error,
+ ("Migration: import failed (code 0x%x)", static_cast<uint32_t>(rv)));
+ }
+ }
+ // Never fails.
+ return NS_OK;
+}
+
+nsMsgFolderCache::~nsMsgFolderCache() {
+ Flush();
+ delete mRoot;
+}
+
+NS_IMETHODIMP nsMsgFolderCache::Flush() {
+ if (mSavePending) {
+ mSaveTimer->Cancel();
+ mSavePending = false;
+ MOZ_LOG(sFolderCacheLog, LogLevel::Debug, ("Forced save."));
+ nsresult rv = SaveFolderCache(mCacheFile);
+ if (NS_FAILED(rv)) {
+ MOZ_LOG(
+ sFolderCacheLog, LogLevel::Error,
+ ("Failed to write to %s (code 0x%x)",
+ mCacheFile->HumanReadablePath().get(), static_cast<uint32_t>(rv)));
+ }
+ }
+ return NS_OK;
+}
+
+// Read the cache data from inFile.
+// It's atomic - if a failure occurs, the cache data will be left unchanged.
+nsresult nsMsgFolderCache::LoadFolderCache(nsIFile* inFile) {
+ MOZ_LOG(sFolderCacheLog, LogLevel::Debug,
+ ("Loading %s", inFile->HumanReadablePath().get()));
+
+ nsCOMPtr<nsIInputStream> inStream;
+ nsresult rv = NS_NewLocalFileInputStream(getter_AddRefs(inStream), inFile);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCString data;
+ rv = NS_ConsumeStream(inStream, UINT32_MAX, data);
+ if (NS_FAILED(rv)) {
+ MOZ_LOG(sFolderCacheLog, LogLevel::Error, ("Read failed."));
+ return rv;
+ }
+
+ Json::Value root;
+ Json::CharReaderBuilder builder;
+ std::unique_ptr<Json::CharReader> const reader(builder.newCharReader());
+ if (!reader->parse(data.BeginReading(), data.EndReading(), &root, nullptr)) {
+ MOZ_LOG(sFolderCacheLog, LogLevel::Error, ("Error parsing JSON"));
+ return NS_ERROR_FAILURE; // parsing failed.
+ }
+ if (!root.isObject()) {
+ MOZ_LOG(sFolderCacheLog, LogLevel::Error, ("JSON root is not an object"));
+ return NS_ERROR_FAILURE; // bad format.
+ }
+ *mRoot = root;
+ return NS_OK;
+}
+
+// Write the cache data to outFile.
+nsresult nsMsgFolderCache::SaveFolderCache(nsIFile* outFile) {
+ MOZ_LOG(sFolderCacheLog, LogLevel::Debug,
+ ("Save to %s", outFile->HumanReadablePath().get()));
+
+ // Serialise the data.
+ Json::StreamWriterBuilder b;
+ // b["indentation"] = "";
+ std::string out = Json::writeString(b, *mRoot);
+
+ // Safe stream, writes to a tempfile first then moves into proper place when
+ // Finish() is called. Could use NS_NewAtomicFileOutputStream, but seems hard
+ // to justify a full filesystem flush).
+ nsCOMPtr<nsIOutputStream> outputStream;
+ nsresult rv =
+ NS_NewSafeLocalFileOutputStream(getter_AddRefs(outputStream), outFile,
+ PR_CREATE_FILE | PR_TRUNCATE | PR_WRONLY);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ const char* ptr = out.data();
+ uint32_t remaining = out.length();
+ while (remaining > 0) {
+ uint32_t written = 0;
+ rv = outputStream->Write(ptr, remaining, &written);
+ NS_ENSURE_SUCCESS(rv, rv);
+ remaining -= written;
+ ptr += written;
+ }
+ nsCOMPtr<nsISafeOutputStream> safeStream = do_QueryInterface(outputStream);
+ MOZ_ASSERT(safeStream);
+ rv = safeStream->Finish();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgFolderCache::GetCacheElement(
+ const nsACString& pathKey, bool createIfMissing,
+ nsIMsgFolderCacheElement** result) {
+ nsAutoCString key(pathKey);
+ if (mRoot->isMember(key.get()) || createIfMissing) {
+ nsCOMPtr<nsIMsgFolderCacheElement> element =
+ new nsMsgFolderCacheElement(this, pathKey);
+ element.forget(result);
+ return NS_OK;
+ }
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP nsMsgFolderCache::RemoveElement(const nsACString& key) {
+ mRoot->removeMember(PromiseFlatCString(key).get());
+ return NS_OK;
+}
+
+void nsMsgFolderCache::SetModified() {
+ if (mSavePending) {
+ return;
+ }
+ nsresult rv = mSaveTimer->InitWithNamedFuncCallback(
+ doSave, (void*)this, kSaveDelayMs, nsITimer::TYPE_ONE_SHOT,
+ "msgFolderCache::doSave");
+ if (NS_SUCCEEDED(rv)) {
+ MOZ_LOG(sFolderCacheLog, LogLevel::Debug,
+ ("AutoSave in %ds", kSaveDelayMs / 1000));
+ mSavePending = true;
+ }
+}
+
+// static
+void nsMsgFolderCache::doSave(nsITimer*, void* closure) {
+ MOZ_LOG(sFolderCacheLog, LogLevel::Debug, ("AutoSave"));
+ nsMsgFolderCache* that = static_cast<nsMsgFolderCache*>(closure);
+ nsresult rv = that->SaveFolderCache(that->mCacheFile);
+ if (NS_FAILED(rv)) {
+ MOZ_LOG(sFolderCacheLog, LogLevel::Error,
+ ("Failed writing %s (code 0x%x)",
+ that->mCacheFile->HumanReadablePath().get(),
+ static_cast<uint32_t>(rv)));
+ }
+ that->mSavePending = false;
+}
+
+// Helper to apply a legacy property to the new JSON format.
+static void applyEntry(nsCString const& name, nsCString const& val,
+ Json::Value& obj) {
+ // The old mork version stored all numbers as hex, so we need to convert
+ // them into proper Json numeric types. But there's no type info in the
+ // database so we just have to know which values to convert.
+ // We can find a list of all the numeric values by grepping the codebase
+ // for GetCacheInt32/GetCachedInt64. We treat everything else as a string.
+ // It's much harder to get a definitive list of possible keys for strings,
+ // because nsIMsgFolderCache is also used to cache nsDBFolderInfo data -
+ // see nsMsgDBFolder::GetStringProperty().
+
+ // One of the Int32 properties?
+ if (name.EqualsLiteral("hierDelim") ||
+ name.EqualsLiteral("lastSyncTimeInSec") ||
+ name.EqualsLiteral("nextUID") || name.EqualsLiteral("pendingMsgs") ||
+ name.EqualsLiteral("pendingUnreadMsgs") ||
+ name.EqualsLiteral("serverRecent") || name.EqualsLiteral("serverTotal") ||
+ name.EqualsLiteral("serverUnseen") || name.EqualsLiteral("totalMsgs") ||
+ name.EqualsLiteral("totalUnreadMsgs")) {
+ if (val.IsEmpty()) {
+ return;
+ }
+ int32_t i32;
+ if (PR_sscanf(val.get(), "%x", &i32) != 1) {
+ return;
+ }
+ obj[name.get()] = i32;
+ return;
+ }
+
+ // Flags were int32. But the upper bit can be set, meaning we'll get
+ // annoying negative values, which isn't what we want. Not so much of an
+ // issue with legacy pancea.dat as it was all hex strings anyway. But let's
+ // fix it up as we go to JSON.
+ if (name.EqualsLiteral("aclFlags") || name.EqualsLiteral("boxFlags") ||
+ name.EqualsLiteral("flags")) {
+ uint32_t u32;
+ if (PR_sscanf(val.get(), "%x", &u32) != 1) {
+ return;
+ }
+ obj[name.get()] = u32;
+ return;
+ }
+
+ // One of the Int64 properties?
+ if (name.EqualsLiteral("expungedBytes") || name.EqualsLiteral("folderSize")) {
+ if (val.IsEmpty()) {
+ return;
+ }
+ int64_t i64;
+ if (PR_sscanf(val.get(), "%llx", &i64) != 1) {
+ return;
+ }
+ obj[name.get()] = i64;
+ return;
+ }
+
+ // Assume anything else is a string.
+ obj[name.get()] = val.get();
+}
+
+// Import an old panacea.dat mork file, converting it into our JSON form.
+// The flow of this is taken from the old implementation. There are a couple
+// of steps that may not be strictly required, but have been left in anyway
+// on the grounds that it works.
+static nsresult importFromMork(PathString const& dbName, Json::Value& root) {
+ nsresult rv;
+ nsCOMPtr<nsIMdbFactoryService> factoryService =
+ do_GetService("@mozilla.org/db/mork;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIMdbFactory> factory;
+ rv = factoryService->GetMdbFactory(getter_AddRefs(factory));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMdbEnv> env;
+ rv = factory->MakeEnv(nullptr, getter_AddRefs(env));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ env->SetAutoClear(true);
+ nsCOMPtr<nsIMdbFile> dbFile;
+ rv = factory->OpenOldFile(env,
+ nullptr, // Use default heap alloc fns.
+ dbName.get(),
+ mdbBool_kTrue, // Frozen (read only).
+ getter_AddRefs(dbFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Unsure if we actually need this...
+ mdb_bool canOpen;
+ mdbYarn outFormatVersion;
+ rv = factory->CanOpenFilePort(env, dbFile, &canOpen, &outFormatVersion);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!canOpen) {
+ return NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE;
+ }
+
+ mdbOpenPolicy inOpenPolicy;
+ inOpenPolicy.mOpenPolicy_ScopePlan.mScopeStringSet_Count = 0;
+ inOpenPolicy.mOpenPolicy_MinMemory = 0;
+ inOpenPolicy.mOpenPolicy_MaxLazy = 0;
+
+ nsCOMPtr<nsIMdbThumb> thumb;
+ rv = factory->OpenFileStore(env,
+ nullptr, // Use default heap alloc fns.
+ dbFile, &inOpenPolicy, getter_AddRefs(thumb));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Unsure what this is doing. Applying appended-but-unapplied writes?
+ {
+ mdb_count outTotal; // total somethings to do in operation
+ mdb_count outCurrent; // subportion of total completed so far
+ mdb_bool outDone; // is operation finished?
+ mdb_bool outBroken; // is operation irreparably dead and broken?
+ do {
+ rv = thumb->DoMore(env, &outTotal, &outCurrent, &outDone, &outBroken);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } while (!outBroken && !outDone);
+ }
+
+ // Finally, open the store.
+ nsCOMPtr<nsIMdbStore> store;
+ rv = factory->ThumbToOpenStore(env, thumb, getter_AddRefs(store));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Resolve some tokens we'll need.
+ const char* kFoldersScope = "ns:msg:db:row:scope:folders:all";
+ mdb_token folderRowScopeToken;
+ rv = store->StringToToken(env, kFoldersScope, &folderRowScopeToken);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Find the table. Only one, and we assume id=1. Eek! But original code
+ // did this too...
+ mdbOid allFoldersTableOID{folderRowScopeToken, 1};
+ nsCOMPtr<nsIMdbTable> allFoldersTable;
+ rv = store->GetTable(env, &allFoldersTableOID,
+ getter_AddRefs(allFoldersTable));
+ NS_ENSURE_SUCCESS(rv, rv);
+ // GetTable() can return null even without an error.
+ NS_ENSURE_STATE(allFoldersTable);
+
+ rv = convertTable(env, store, allFoldersTable, root);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+// The legacy panacea.dat mork db has a single table, with a row per
+// folder. This function reads it in and writes it into our Json::Value
+// object.
+static nsresult convertTable(nsIMdbEnv* env, nsIMdbStore* store,
+ nsIMdbTable* table, Json::Value& root) {
+ MOZ_ASSERT(root.isObject());
+ MOZ_ASSERT(table);
+
+ nsresult rv;
+ nsCOMPtr<nsIMdbTableRowCursor> rowCursor;
+ rv = table->GetTableRowCursor(env, -1, getter_AddRefs(rowCursor));
+ NS_ENSURE_SUCCESS(rv, rv);
+ // For each row in the table...
+ while (true) {
+ nsCOMPtr<nsIMdbRow> row;
+ mdb_pos pos;
+ rv = rowCursor->NextRow(env, getter_AddRefs(row), &pos);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!row) {
+ break; // That's all the rows done.
+ }
+
+ nsCOMPtr<nsIMdbRowCellCursor> cellCursor;
+ rv = row->GetRowCellCursor(env, -1, getter_AddRefs(cellCursor));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ Json::Value obj(Json::objectValue);
+ // For each cell in the row...
+ nsAutoCString rowKey;
+ while (true) {
+ nsCOMPtr<nsIMdbCell> cell;
+ mdb_column column;
+ rv = cellCursor->NextCell(env, getter_AddRefs(cell), &column, nullptr);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!cell) {
+ break; // No more cells.
+ }
+
+ // Get the column name
+ nsAutoCString colName;
+ {
+ char buf[100];
+ mdbYarn colYarn{buf, 0, sizeof(buf), 0, 0, nullptr};
+ // Get the column of the cell
+ nsresult rv = store->TokenToString(env, column, &colYarn);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ colName.Assign((const char*)colYarn.mYarn_Buf, colYarn.mYarn_Fill);
+ }
+ // Get the value
+ nsAutoCString colValue;
+ {
+ mdbYarn yarn;
+ cell->AliasYarn(env, &yarn);
+ colValue.Assign((const char*)yarn.mYarn_Buf, yarn.mYarn_Fill);
+ }
+ if (colName.EqualsLiteral("key")) {
+ rowKey = colValue;
+ } else {
+ applyEntry(colName, colValue, obj);
+ }
+ }
+ if (rowKey.IsEmpty()) {
+ continue;
+ }
+
+ MOZ_LOG(sFolderCacheLog, LogLevel::Debug,
+ ("Migration: migrated key '%s' (%d properties)", rowKey.get(),
+ (int)obj.size()));
+ root[rowKey.get()] = obj;
+ }
+ return NS_OK;
+}
diff --git a/comm/mailnews/base/src/nsMsgFolderCache.h b/comm/mailnews/base/src/nsMsgFolderCache.h
new file mode 100644
index 0000000000..480814ad10
--- /dev/null
+++ b/comm/mailnews/base/src/nsMsgFolderCache.h
@@ -0,0 +1,60 @@
+/* -*- 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 nsMsgFolderCache_H
+#define nsMsgFolderCache_H
+
+#include "nsIMsgFolderCache.h"
+#include "nsIFile.h"
+#include "nsITimer.h"
+
+namespace Json {
+class Value;
+};
+
+/**
+ * nsMsgFolderCache implements the folder cache, which stores values which
+ * might be slow for the folder to calculate.
+ * It persists the cache data by dumping it out to a .json file when changes
+ * are made. To avoid huge numbers of writes, this autosaving is deferred -
+ * when a cached value is changed, it'll wait a minute or so before
+ * writing, to collect any other changes that occur during that time.
+ * If any changes are outstanding at destruction time, it'll perform an
+ * immediate save then.
+ */
+class nsMsgFolderCache : public nsIMsgFolderCache {
+ public:
+ friend class nsMsgFolderCacheElement;
+
+ nsMsgFolderCache();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMSGFOLDERCACHE
+
+ protected:
+ virtual ~nsMsgFolderCache();
+
+ nsresult LoadFolderCache(nsIFile* jsonFile);
+ nsresult SaveFolderCache(nsIFile* jsonFile);
+ // Flag that a save is required. It'll be deferred by kAutoSaveDelayMs.
+ void SetModified();
+ static constexpr uint32_t kSaveDelayMs = 1000 * 60 * 1; // 1 minute.
+ static void doSave(nsITimer*, void* closure);
+
+ // Path to the JSON file backing the cache.
+ nsCOMPtr<nsIFile> mCacheFile;
+
+ // This is our data store. Kept as a Json::Value for ease of saving, but
+ // it's actually not a bad format for access (it's basically a std::map).
+ // Using a pointer to allow forward declaration. The json headers aren't
+ // in the include path for other modules, so we don't want to expose them
+ // here.
+ Json::Value* mRoot;
+
+ bool mSavePending;
+ nsCOMPtr<nsITimer> mSaveTimer;
+};
+
+#endif
diff --git a/comm/mailnews/base/src/nsMsgFolderCompactor.cpp b/comm/mailnews/base/src/nsMsgFolderCompactor.cpp
new file mode 100644
index 0000000000..c9740bedb8
--- /dev/null
+++ b/comm/mailnews/base/src/nsMsgFolderCompactor.cpp
@@ -0,0 +1,1391 @@
+/* -*- 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 "msgCore.h" // precompiled header...
+#include "nsCOMPtr.h"
+#include "nsIMsgFolder.h"
+#include "nsIFile.h"
+#include "nsNetUtil.h"
+#include "nsIMsgHdr.h"
+#include "nsIChannel.h"
+#include "nsIStreamListener.h"
+#include "nsIMsgMessageService.h"
+#include "nsMsgUtils.h"
+#include "nsISeekableStream.h"
+#include "nsIDBFolderInfo.h"
+#include "nsIPrompt.h"
+#include "nsIMsgLocalMailFolder.h"
+#include "nsIMsgImapMailFolder.h"
+#include "nsMailHeaders.h"
+#include "nsMsgLocalFolderHdrs.h"
+#include "nsIMsgDatabase.h"
+#include "nsMsgMessageFlags.h"
+#include "nsMsgFolderFlags.h"
+#include "nsIMsgStatusFeedback.h"
+#include "nsIMsgFolderNotificationService.h"
+#include "nsMsgFolderCompactor.h"
+#include "nsIOutputStream.h"
+#include "nsIInputStream.h"
+#include "nsPrintfCString.h"
+#include "nsIStringBundle.h"
+#include "nsICopyMessageStreamListener.h"
+#include "nsIMsgWindow.h"
+#include "nsIMsgPluggableStore.h"
+#include "mozilla/Buffer.h"
+#include "HeaderReader.h"
+#include "LineReader.h"
+#include "mozilla/Components.h"
+
+static nsresult GetBaseStringBundle(nsIStringBundle** aBundle) {
+ NS_ENSURE_ARG_POINTER(aBundle);
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ mozilla::components::StringBundle::Service();
+ NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED);
+ nsCOMPtr<nsIStringBundle> bundle;
+ return bundleService->CreateBundle(
+ "chrome://messenger/locale/messenger.properties", aBundle);
+}
+
+#define COMPACTOR_READ_BUFF_SIZE 16384
+
+/**
+ * nsFolderCompactState is a helper class for nsFolderCompactor, which
+ * handles compacting the mbox for a single local folder.
+ *
+ * This class also patches X-Mozilla-* headers where required. Usually
+ * these headers are edited in-place without changing the overall size,
+ * but sometimes there's not enough room. So as compaction involves
+ * rewriting the whole file anyway, we take the opportunity to make some
+ * more space and correct those headers.
+ *
+ * NOTE (for future cleanups):
+ *
+ * This base class calls nsIMsgMessageService.copyMessages() to iterate
+ * through messages, passing itself in as a listener. Callbacks from
+ * both nsICopyMessageStreamListener and nsIStreamListener are invoked.
+ *
+ * nsOfflineStoreCompactState uses a different mechanism - see separate
+ * notes below.
+ *
+ * The way the service invokes the listener callbacks is pretty quirky
+ * and probably needs a good sorting out, but for now I'll just document what
+ * I've observed here:
+ *
+ * - The service calls OnStartRequest() at the start of the first message.
+ * - StartMessage() is called at the start of subsequent messages.
+ * - EndCopy() is called at the end of every message except the last one,
+ * where OnStopRequest() is invoked instead.
+ * - OnDataAvailable() is called to pass the message body of each message
+ * (in multiple calls if the message is big enough).
+ * - EndCopy() doesn't ever seem to be passed a failing error code from
+ * what I can see, and its own return code is ignored by upstream code.
+ */
+class nsFolderCompactState : public nsIStreamListener,
+ public nsICopyMessageStreamListener,
+ public nsIUrlListener {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSICOPYMESSAGESTREAMLISTENER
+ NS_DECL_NSIURLLISTENER
+
+ nsFolderCompactState(void);
+
+ nsresult Compact(nsIMsgFolder* folder,
+ std::function<void(nsresult, uint64_t)> completionFn,
+ nsIMsgWindow* msgWindow);
+
+ protected:
+ virtual ~nsFolderCompactState(void);
+
+ virtual nsresult InitDB(nsIMsgDatabase* db);
+ virtual nsresult StartCompacting();
+ virtual nsresult FinishCompact();
+ void CloseOutputStream();
+ void CleanupTempFilesAfterError();
+ nsresult FlushBuffer();
+
+ nsresult Init(nsIMsgFolder* aFolder, const char* aBaseMsgUri,
+ nsIMsgDatabase* aDb, nsIFile* aPath, nsIMsgWindow* aMsgWindow);
+ nsresult BuildMessageURI(const char* baseURI, nsMsgKey key, nsCString& uri);
+ nsresult ShowStatusMsg(const nsString& aMsg);
+ nsresult ReleaseFolderLock();
+ void ShowCompactingStatusMsg();
+
+ nsCString m_baseMessageUri; // base message uri
+ nsCString m_messageUri; // current message uri being copy
+ nsCOMPtr<nsIMsgFolder> m_folder; // current folder being compact
+ nsCOMPtr<nsIMsgDatabase> m_db; // new database for the compact folder
+ nsCOMPtr<nsIFile> m_file; // new mailbox for the compact folder
+ nsCOMPtr<nsIOutputStream> m_fileStream; // output file stream for writing
+ // All message keys that need to be copied over.
+ nsTArray<nsMsgKey> m_keys;
+
+ // Sum of the sizes of the messages, accumulated as we visit each msg.
+ uint64_t m_totalMsgSize{0};
+ // Number of bytes that can be expunged while compacting.
+ uint64_t m_totalExpungedBytes{0};
+ // Index of the current copied message key in key array.
+ uint32_t m_curIndex{0};
+ // Offset in mailbox of new message.
+ uint64_t m_startOfNewMsg{0};
+ mozilla::Buffer<char> m_buffer{COMPACTOR_READ_BUFF_SIZE};
+ uint32_t m_bufferCount{0};
+
+ // We'll use this if we need to output any EOLs - we try to preserve the
+ // convention found in the input data.
+ nsCString m_eolSeq{MSG_LINEBREAK};
+
+ // The status of the copying operation.
+ nsresult m_status{NS_OK};
+ nsCOMPtr<nsIMsgMessageService> m_messageService; // message service for
+ // copying
+ nsCOMPtr<nsIMsgWindow> m_window;
+ nsCOMPtr<nsIMsgDBHdr> m_curSrcHdr;
+ // Flag set when we're waiting for local folder to complete parsing.
+ bool m_parsingFolder;
+ // Flag to indicate we're starting a new message, and that no data has
+ // been written for it yet.
+ bool m_startOfMsg;
+ // Function which will be run when the folder compaction completes.
+ // Takes a result code and the number of bytes which were expunged.
+ std::function<void(nsresult, uint64_t)> m_completionFn;
+ bool m_alreadyWarnedDiskSpace{false};
+};
+
+NS_IMPL_ISUPPORTS(nsFolderCompactState, nsIRequestObserver, nsIStreamListener,
+ nsICopyMessageStreamListener, nsIUrlListener)
+
+nsFolderCompactState::nsFolderCompactState() {
+ m_parsingFolder = false;
+ m_startOfMsg = true;
+}
+
+nsFolderCompactState::~nsFolderCompactState() {
+ CloseOutputStream();
+ if (NS_FAILED(m_status)) {
+ CleanupTempFilesAfterError();
+ // if for some reason we failed remove the temp folder and database
+ }
+}
+
+void nsFolderCompactState::CloseOutputStream() {
+ if (m_fileStream) {
+ m_fileStream->Close();
+ m_fileStream = nullptr;
+ }
+}
+
+void nsFolderCompactState::CleanupTempFilesAfterError() {
+ CloseOutputStream();
+ if (m_db) m_db->ForceClosed();
+ nsCOMPtr<nsIFile> summaryFile;
+ GetSummaryFileLocation(m_file, getter_AddRefs(summaryFile));
+ m_file->Remove(false);
+ summaryFile->Remove(false);
+}
+
+nsresult nsFolderCompactState::BuildMessageURI(const char* baseURI,
+ nsMsgKey key, nsCString& uri) {
+ uri.Append(baseURI);
+ uri.Append('#');
+ uri.AppendInt(key);
+
+ return NS_OK;
+}
+
+nsresult nsFolderCompactState::InitDB(nsIMsgDatabase* db) {
+ nsCOMPtr<nsIMsgDatabase> mailDBFactory;
+ nsresult rv = db->ListAllKeys(m_keys);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgDBService> msgDBService =
+ do_GetService("@mozilla.org/msgDatabase/msgDBService;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = msgDBService->OpenMailDBFromFile(m_file, m_folder, true, false,
+ getter_AddRefs(m_db));
+
+ if (rv == NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE ||
+ rv == NS_MSG_ERROR_FOLDER_SUMMARY_MISSING)
+ // if it's out of date then reopen with upgrade.
+ return msgDBService->OpenMailDBFromFile(m_file, m_folder, true, true,
+ getter_AddRefs(m_db));
+ return rv;
+}
+
+nsresult nsFolderCompactState::Compact(
+ nsIMsgFolder* folder, std::function<void(nsresult, uint64_t)> completionFn,
+ nsIMsgWindow* msgWindow) {
+ NS_ENSURE_ARG_POINTER(folder);
+ m_completionFn = completionFn;
+ m_window = msgWindow;
+ nsresult rv;
+ nsCOMPtr<nsIMsgDatabase> db;
+ nsCOMPtr<nsIFile> path;
+ nsCString baseMessageURI;
+
+ nsCOMPtr<nsIMsgLocalMailFolder> localFolder = do_QueryInterface(folder, &rv);
+ if (NS_SUCCEEDED(rv) && localFolder) {
+ rv = localFolder->GetDatabaseWOReparse(getter_AddRefs(db));
+ if (NS_FAILED(rv) || !db) {
+ if (rv == NS_MSG_ERROR_FOLDER_SUMMARY_MISSING ||
+ rv == NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE) {
+ m_folder = folder; // will be used to compact
+ m_parsingFolder = true;
+ rv = localFolder->ParseFolder(m_window, this);
+ }
+ return rv;
+ } else {
+ bool valid;
+ rv = db->GetSummaryValid(&valid);
+ if (!valid) // we are probably parsing the folder because we selected it.
+ {
+ folder->NotifyCompactCompleted();
+ if (m_completionFn) {
+ m_completionFn(NS_OK, m_totalExpungedBytes);
+ }
+ return NS_OK;
+ }
+ }
+ } else {
+ rv = folder->GetMsgDatabase(getter_AddRefs(db));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ rv = folder->GetFilePath(getter_AddRefs(path));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ do {
+ bool exists = false;
+ rv = path->Exists(&exists);
+ if (!exists) {
+ // No need to compact if the local file does not exist.
+ // Can happen e.g. on IMAP when the folder is not marked for offline use.
+ break;
+ }
+
+ int64_t expunged = 0;
+ folder->GetExpungedBytes(&expunged);
+ if (expunged == 0) {
+ // No need to compact if nothing would be expunged.
+ break;
+ }
+
+ int64_t diskSize;
+ rv = folder->GetSizeOnDisk(&diskSize);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int64_t diskFree;
+ rv = path->GetDiskSpaceAvailable(&diskFree);
+ if (NS_FAILED(rv)) {
+ // If GetDiskSpaceAvailable() failed, better bail out fast.
+ if (rv != NS_ERROR_NOT_IMPLEMENTED) return rv;
+ // Some platforms do not have GetDiskSpaceAvailable implemented.
+ // In that case skip the preventive free space analysis and let it
+ // fail in compact later if space actually wasn't available.
+ } else {
+ // Let's try to not even start compact if there is really low free space.
+ // It may still fail later as we do not know how big exactly the folder DB
+ // will end up being. The DB already doesn't contain references to
+ // messages that are already deleted. So theoretically it shouldn't shrink
+ // with compact. But in practice, the automatic shrinking of the DB may
+ // still have not yet happened. So we cap the final size at 1KB per
+ // message.
+ db->Commit(nsMsgDBCommitType::kCompressCommit);
+
+ int64_t dbSize;
+ rv = db->GetDatabaseSize(&dbSize);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t totalMsgs;
+ rv = folder->GetTotalMessages(false, &totalMsgs);
+ NS_ENSURE_SUCCESS(rv, rv);
+ int64_t expectedDBSize =
+ std::min<int64_t>(dbSize, ((int64_t)totalMsgs) * 1024);
+ if (diskFree < diskSize - expunged + expectedDBSize) {
+ if (!m_alreadyWarnedDiskSpace) {
+ folder->ThrowAlertMsg("compactFolderInsufficientSpace", m_window);
+ m_alreadyWarnedDiskSpace = true;
+ }
+ break;
+ }
+ }
+
+ rv = folder->GetBaseMessageURI(baseMessageURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = Init(folder, baseMessageURI.get(), db, path, m_window);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool isLocked = true;
+ m_folder->GetLocked(&isLocked);
+ if (isLocked) {
+ CleanupTempFilesAfterError();
+ m_folder->ThrowAlertMsg("compactFolderDeniedLock", m_window);
+ break;
+ }
+
+ // If we got here start the real compacting.
+ nsCOMPtr<nsISupports> supports;
+ QueryInterface(NS_GET_IID(nsISupports), getter_AddRefs(supports));
+ m_folder->AcquireSemaphore(supports);
+ m_totalExpungedBytes += expunged;
+ return StartCompacting();
+
+ } while (false); // block for easy skipping the compaction using 'break'
+
+ // Skipped folder, for whatever reason.
+ folder->NotifyCompactCompleted();
+ if (m_completionFn) {
+ m_completionFn(NS_OK, m_totalExpungedBytes);
+ }
+ return NS_OK;
+}
+
+nsresult nsFolderCompactState::ShowStatusMsg(const nsString& aMsg) {
+ if (!m_window || aMsg.IsEmpty()) return NS_OK;
+
+ nsCOMPtr<nsIMsgStatusFeedback> statusFeedback;
+ nsresult rv = m_window->GetStatusFeedback(getter_AddRefs(statusFeedback));
+ if (NS_FAILED(rv) || !statusFeedback) return NS_OK;
+
+ // Try to prepend account name to the message.
+ nsString statusMessage;
+ do {
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = m_folder->GetServer(getter_AddRefs(server));
+ if (NS_FAILED(rv)) break;
+ nsAutoString accountName;
+ rv = server->GetPrettyName(accountName);
+ if (NS_FAILED(rv)) break;
+ nsCOMPtr<nsIStringBundle> bundle;
+ rv = GetBaseStringBundle(getter_AddRefs(bundle));
+ if (NS_FAILED(rv)) break;
+ AutoTArray<nsString, 2> params = {accountName, aMsg};
+ rv = bundle->FormatStringFromName("statusMessage", params, statusMessage);
+ } while (false);
+
+ // If fetching any of the needed info failed, just show the original message.
+ if (NS_FAILED(rv)) statusMessage.Assign(aMsg);
+ return statusFeedback->SetStatusString(statusMessage);
+}
+
+nsresult nsFolderCompactState::Init(nsIMsgFolder* folder,
+ const char* baseMsgUri, nsIMsgDatabase* db,
+ nsIFile* path, nsIMsgWindow* aMsgWindow) {
+ nsresult rv;
+
+ m_folder = folder;
+ m_baseMessageUri = baseMsgUri;
+ m_file = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ m_file->InitWithFile(path);
+
+ m_file->SetNativeLeafName("nstmp"_ns);
+ // Make sure we are not crunching existing nstmp file.
+ rv = m_file->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 00600);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ m_window = aMsgWindow;
+ m_totalMsgSize = 0;
+ rv = InitDB(db);
+ if (NS_FAILED(rv)) {
+ CleanupTempFilesAfterError();
+ return rv;
+ }
+
+ m_curIndex = 0;
+
+ rv = MsgNewBufferedFileOutputStream(getter_AddRefs(m_fileStream), m_file, -1,
+ 00600);
+ if (NS_FAILED(rv))
+ m_folder->ThrowAlertMsg("compactFolderWriteFailed", m_window);
+ else
+ rv = GetMessageServiceFromURI(nsDependentCString(baseMsgUri),
+ getter_AddRefs(m_messageService));
+ if (NS_FAILED(rv)) {
+ m_status = rv;
+ }
+ return rv;
+}
+
+void nsFolderCompactState::ShowCompactingStatusMsg() {
+ nsString statusString;
+ nsresult rv = m_folder->GetStringWithFolderNameFromBundle("compactingFolder",
+ statusString);
+ if (!statusString.IsEmpty() && NS_SUCCEEDED(rv)) ShowStatusMsg(statusString);
+}
+
+NS_IMETHODIMP nsFolderCompactState::OnStartRunningUrl(nsIURI* url) {
+ return NS_OK;
+}
+
+// If we had to kick off a folder parse, this will be called when it
+// completes.
+NS_IMETHODIMP nsFolderCompactState::OnStopRunningUrl(nsIURI* url,
+ nsresult status) {
+ if (m_parsingFolder) {
+ m_parsingFolder = false;
+ if (NS_SUCCEEDED(status)) {
+ // Folder reparse succeeded. Start compacting it.
+ status = Compact(m_folder, m_completionFn, m_window);
+ if (NS_SUCCEEDED(status)) {
+ return NS_OK;
+ }
+ }
+ }
+
+ // This is from bug 249754. The aim is to close the DB file to avoid
+ // running out of filehandles when large numbers of folders are compacted.
+ // But it seems like filehandle management would be better off being
+ // handled by the DB class itself (it might be already, but it's hard to
+ // tell)...
+ m_folder->SetMsgDatabase(nullptr);
+
+ if (m_completionFn) {
+ m_completionFn(status, m_totalExpungedBytes);
+ }
+ return NS_OK;
+}
+
+nsresult nsFolderCompactState::StartCompacting() {
+ nsresult rv = NS_OK;
+ // Notify that compaction is beginning. We do this even if there are no
+ // messages to be copied because the summary database still gets blown away
+ // which is still pretty interesting. (And we like consistency.)
+ nsCOMPtr<nsIMsgFolderNotificationService> notifier(
+ do_GetService("@mozilla.org/messenger/msgnotificationservice;1"));
+ if (notifier) {
+ notifier->NotifyFolderCompactStart(m_folder);
+ }
+
+ // TODO: test whether sorting the messages (m_keys) by messageOffset
+ // would improve performance on large files (less seeks).
+ // The m_keys array is in the order as stored in DB and on IMAP or News
+ // the messages stored on the mbox file are not necessarily in the same order.
+ if (m_keys.Length() > 0) {
+ nsCOMPtr<nsIURI> notUsed;
+ ShowCompactingStatusMsg();
+ NS_ADDREF_THIS();
+ rv = m_messageService->CopyMessages(m_keys, m_folder, this, false, nullptr,
+ m_window, getter_AddRefs(notUsed));
+ } else { // no messages to copy with
+ FinishCompact();
+ }
+ return rv;
+}
+
+nsresult nsFolderCompactState::FinishCompact() {
+ NS_ENSURE_TRUE(m_folder, NS_ERROR_NOT_INITIALIZED);
+ NS_ENSURE_TRUE(m_file, NS_ERROR_NOT_INITIALIZED);
+
+ // All okay time to finish up the compact process
+ nsCOMPtr<nsIFile> path;
+ nsCOMPtr<nsIDBFolderInfo> folderInfo;
+
+ // get leaf name and database name of the folder
+ nsresult rv = m_folder->GetFilePath(getter_AddRefs(path));
+ nsCOMPtr<nsIFile> folderPath =
+ do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = folderPath->InitWithFile(path);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> oldSummaryFile;
+ rv = GetSummaryFileLocation(folderPath, getter_AddRefs(oldSummaryFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsAutoCString dbName;
+ oldSummaryFile->GetNativeLeafName(dbName);
+ nsAutoCString folderName;
+ path->GetNativeLeafName(folderName);
+
+ // close down the temp file stream; preparing for deleting the old folder
+ // and its database; then rename the temp folder and database
+ if (m_fileStream) {
+ m_fileStream->Flush();
+ m_fileStream->Close();
+ m_fileStream = nullptr;
+ }
+
+ // make sure the new database is valid.
+ // Close it so we can rename the .msf file.
+ if (m_db) {
+ m_db->ForceClosed();
+ m_db = nullptr;
+ }
+
+ nsCOMPtr<nsIFile> newSummaryFile;
+ rv = GetSummaryFileLocation(m_file, getter_AddRefs(newSummaryFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIDBFolderInfo> transferInfo;
+ m_folder->GetDBTransferInfo(getter_AddRefs(transferInfo));
+
+ // close down database of the original folder
+ m_folder->ForceDBClosed();
+
+ nsCOMPtr<nsIFile> cloneFile;
+ int64_t fileSize = 0;
+ rv = m_file->Clone(getter_AddRefs(cloneFile));
+ if (NS_SUCCEEDED(rv)) rv = cloneFile->GetFileSize(&fileSize);
+ bool tempFileRightSize = ((uint64_t)fileSize == m_totalMsgSize);
+ NS_WARNING_ASSERTION(tempFileRightSize,
+ "temp file not of expected size in compact");
+
+ bool folderRenameSucceeded = false;
+ bool msfRenameSucceeded = false;
+ if (NS_SUCCEEDED(rv) && tempFileRightSize) {
+ // First we're going to try and move the old summary file out the way.
+ // We don't delete it yet, as we want to keep the files in sync.
+ nsCOMPtr<nsIFile> tempSummaryFile;
+ rv = oldSummaryFile->Clone(getter_AddRefs(tempSummaryFile));
+ if (NS_SUCCEEDED(rv))
+ rv = tempSummaryFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600);
+
+ nsAutoCString tempSummaryFileName;
+ if (NS_SUCCEEDED(rv))
+ rv = tempSummaryFile->GetNativeLeafName(tempSummaryFileName);
+
+ if (NS_SUCCEEDED(rv))
+ rv = oldSummaryFile->MoveToNative((nsIFile*)nullptr, tempSummaryFileName);
+
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "error moving compacted folder's db out of the way");
+ if (NS_SUCCEEDED(rv)) {
+ // Now we've successfully moved the summary file out the way, try moving
+ // the newly compacted message file over the old one.
+ rv = m_file->MoveToNative((nsIFile*)nullptr, folderName);
+ folderRenameSucceeded = NS_SUCCEEDED(rv);
+ NS_WARNING_ASSERTION(folderRenameSucceeded,
+ "error renaming compacted folder");
+ if (folderRenameSucceeded) {
+ // That worked, so land the new summary file in the right place.
+ nsCOMPtr<nsIFile> renamedCompactedSummaryFile;
+ newSummaryFile->Clone(getter_AddRefs(renamedCompactedSummaryFile));
+ if (renamedCompactedSummaryFile) {
+ rv = renamedCompactedSummaryFile->MoveToNative((nsIFile*)nullptr,
+ dbName);
+ msfRenameSucceeded = NS_SUCCEEDED(rv);
+ }
+ NS_WARNING_ASSERTION(msfRenameSucceeded,
+ "error renaming compacted folder's db");
+ }
+
+ if (!msfRenameSucceeded) {
+ // Do our best to put the summary file back to where it was
+ rv = tempSummaryFile->MoveToNative((nsIFile*)nullptr, dbName);
+ if (NS_SUCCEEDED(rv)) {
+ // Flagging that a renamed db no longer exists.
+ tempSummaryFile = nullptr;
+ } else {
+ NS_WARNING("error restoring uncompacted folder's db");
+ }
+ }
+ }
+ // We don't want any temporarily renamed summary file to lie around
+ if (tempSummaryFile) tempSummaryFile->Remove(false);
+ }
+
+ NS_WARNING_ASSERTION(msfRenameSucceeded, "compact failed");
+ nsresult rvReleaseFolderLock = ReleaseFolderLock();
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rvReleaseFolderLock),
+ "folder lock not released successfully");
+ rv = NS_FAILED(rv) ? rv : rvReleaseFolderLock;
+
+ // Cleanup of nstmp-named compacted files if failure
+ if (!folderRenameSucceeded) {
+ // remove the abandoned compacted version with the wrong name
+ m_file->Remove(false);
+ }
+ if (!msfRenameSucceeded) {
+ // remove the abandoned compacted summary file
+ newSummaryFile->Remove(false);
+ }
+
+ if (msfRenameSucceeded) {
+ // Transfer local db information from transferInfo
+ nsCOMPtr<nsIMsgDBService> msgDBService =
+ do_GetService("@mozilla.org/msgDatabase/msgDBService;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = msgDBService->OpenFolderDB(m_folder, true, getter_AddRefs(m_db));
+ NS_ENSURE_TRUE(m_db, NS_FAILED(rv) ? rv : NS_ERROR_FAILURE);
+ // These errors are expected.
+ rv = (rv == NS_MSG_ERROR_FOLDER_SUMMARY_MISSING ||
+ rv == NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE)
+ ? NS_OK
+ : rv;
+ m_db->SetSummaryValid(true);
+ if (transferInfo) m_folder->SetDBTransferInfo(transferInfo);
+
+ // since we're transferring info from the old db, we need to reset the
+ // expunged bytes
+ nsCOMPtr<nsIDBFolderInfo> dbFolderInfo;
+ m_db->GetDBFolderInfo(getter_AddRefs(dbFolderInfo));
+ if (dbFolderInfo) dbFolderInfo->SetExpungedBytes(0);
+ }
+ if (m_db) m_db->Close(true);
+ m_db = nullptr;
+
+ // Notify that compaction of the folder is completed.
+ nsCOMPtr<nsIMsgFolderNotificationService> notifier(
+ do_GetService("@mozilla.org/messenger/msgnotificationservice;1"));
+ if (notifier) {
+ notifier->NotifyFolderCompactFinish(m_folder);
+ }
+
+ m_folder->NotifyCompactCompleted();
+ if (m_completionFn) {
+ m_completionFn(rv, m_totalExpungedBytes);
+ }
+
+ return NS_OK;
+}
+
+nsresult nsFolderCompactState::ReleaseFolderLock() {
+ nsresult result = NS_OK;
+ if (!m_folder) return result;
+ bool haveSemaphore;
+ nsCOMPtr<nsISupports> supports;
+ QueryInterface(NS_GET_IID(nsISupports), getter_AddRefs(supports));
+ result = m_folder->TestSemaphore(supports, &haveSemaphore);
+ if (NS_SUCCEEDED(result) && haveSemaphore)
+ result = m_folder->ReleaseSemaphore(supports);
+ return result;
+}
+
+NS_IMETHODIMP
+nsFolderCompactState::OnStartRequest(nsIRequest* request) {
+ return StartMessage();
+}
+
+NS_IMETHODIMP
+nsFolderCompactState::OnStopRequest(nsIRequest* request, nsresult status) {
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ if (NS_FAILED(status)) {
+ // Set m_status to status so the destructor can remove the temp folder and
+ // database.
+ m_status = status;
+ CleanupTempFilesAfterError();
+ m_folder->NotifyCompactCompleted();
+ ReleaseFolderLock();
+ m_folder->ThrowAlertMsg("compactFolderWriteFailed", m_window);
+ } else {
+ // XXX TODO: Error checking and handling missing here.
+ EndCopy(nullptr, status);
+ if (m_curIndex >= m_keys.Length()) {
+ msgHdr = nullptr;
+ // no more to copy finish it up
+ FinishCompact();
+ } else {
+ // in case we're not getting an error, we still need to pretend we did get
+ // an error, because the compact did not successfully complete.
+ m_folder->NotifyCompactCompleted();
+ CleanupTempFilesAfterError();
+ ReleaseFolderLock();
+ }
+ }
+ NS_RELEASE_THIS(); // kill self
+ return status;
+}
+
+// Handle the message data.
+// (NOTE: nsOfflineStoreCompactState overrides this)
+NS_IMETHODIMP
+nsFolderCompactState::OnDataAvailable(nsIRequest* request,
+ nsIInputStream* inStr,
+ uint64_t sourceOffset, uint32_t count) {
+ MOZ_ASSERT(m_fileStream);
+ MOZ_ASSERT(inStr);
+
+ nsresult rv = NS_OK;
+
+ // TODO: This block should be moved in to the callback that indicates the
+ // start of a new message, but it's complicated because of the derived
+ // nsOfflineStoreCompactState and also the odd message copy listener
+ // orderings. Leaving it here for now, but it's ripe for tidying up in
+ // future.
+ if (m_startOfMsg) {
+ m_messageUri.Truncate(); // clear the previous message uri
+ if (NS_SUCCEEDED(BuildMessageURI(m_baseMessageUri.get(), m_keys[m_curIndex],
+ m_messageUri))) {
+ rv = m_messageService->MessageURIToMsgHdr(m_messageUri,
+ getter_AddRefs(m_curSrcHdr));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ while (count > 0) {
+ uint32_t maxReadCount =
+ std::min((uint32_t)m_buffer.Length() - m_bufferCount, count);
+ uint32_t readCount;
+ rv = inStr->Read(m_buffer.Elements() + m_bufferCount, maxReadCount,
+ &readCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ count -= readCount;
+ m_bufferCount += readCount;
+ if (m_bufferCount == m_buffer.Length()) {
+ rv = FlushBuffer();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+ if (m_bufferCount > 0) {
+ rv = FlushBuffer();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ return NS_OK;
+}
+
+// Helper to write data to an outputstream, until complete or error.
+static nsresult WriteSpan(nsIOutputStream* writeable,
+ mozilla::Span<const char> data) {
+ while (!data.IsEmpty()) {
+ uint32_t n;
+ nsresult rv = writeable->Write(data.Elements(), data.Length(), &n);
+ NS_ENSURE_SUCCESS(rv, rv);
+ data = data.Last(data.Length() - n);
+ }
+ return NS_OK;
+}
+
+// Flush contents of m_buffer to the output file.
+// (NOTE: not used by nsOfflineStoreCompactState)
+// More complicated than it should be because we need to fiddle with
+// some of the X-Mozilla-* headers on the fly.
+nsresult nsFolderCompactState::FlushBuffer() {
+ MOZ_ASSERT(m_fileStream);
+ nsresult rv;
+ auto buf = m_buffer.AsSpan().First(m_bufferCount);
+ if (!m_startOfMsg) {
+ // We only do header twiddling for the first chunk. So from now on we
+ // just copy data verbatim.
+ rv = WriteSpan(m_fileStream, buf);
+ NS_ENSURE_SUCCESS(rv, rv);
+ m_bufferCount = 0;
+ return NS_OK;
+ }
+
+ // This is the first chunk of a new message. We'll update the
+ // X-Mozilla-(Status|Status2|Keys) headers as we go.
+ m_startOfMsg = false;
+
+ // Sniff the data to see if we can spot any CRs.
+ // If so, we'll use CRLFs instead of platform-native EOLs.
+ auto sniffChunk = buf.First(std::min<size_t>(buf.Length(), 512));
+ auto cr = std::find(sniffChunk.cbegin(), sniffChunk.cend(), '\r');
+ if (cr != sniffChunk.cend()) {
+ m_eolSeq.Assign("\r\n"_ns);
+ }
+
+ // Add a "From " line if missing.
+ // NOTE: Ultimately we should never see "From " lines in this data - it's an
+ // mbox detail the message streaming should filter out. But for now we'll
+ // handle it optionally.
+ nsAutoCString fromLine;
+ auto l = FirstLine(buf);
+ if (l.Length() > 5 &&
+ nsDependentCSubstring(l.Elements(), 5).EqualsLiteral("From ")) {
+ fromLine = nsDependentCSubstring(l);
+ buf = buf.From(l.Length());
+ } else {
+ fromLine = "From "_ns + m_eolSeq;
+ }
+ rv = WriteSpan(m_fileStream, fromLine);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Read as many headers as we can. We might not have the complete header
+ // block our in buffer, but that's OK - the X-Mozilla-* ones should be
+ // right at the start).
+ nsTArray<HeaderReader::Hdr> headers;
+ HeaderReader rdr;
+ auto leftover = rdr.Parse(buf, [&](auto const& hdr) -> bool {
+ auto const& name = hdr.Name(buf);
+ if (!name.EqualsLiteral(HEADER_X_MOZILLA_STATUS) &&
+ !name.EqualsLiteral(HEADER_X_MOZILLA_STATUS2) &&
+ !name.EqualsLiteral(HEADER_X_MOZILLA_KEYWORDS)) {
+ headers.AppendElement(hdr);
+ }
+ return true;
+ });
+
+ // Write out X-Mozilla-* headers first - we'll create these from scratch.
+ uint32_t msgFlags = 0;
+ nsAutoCString keywords;
+ if (m_curSrcHdr) {
+ m_curSrcHdr->GetFlags(&msgFlags);
+ m_curSrcHdr->GetStringProperty("keywords", keywords);
+ // growKeywords is set if msgStore didn't have enough room to edit
+ // X-Mozilla-* headers in situ. We'll rewrite all those headers
+ // regardless but we still want to clear it.
+ uint32_t grow;
+ m_curSrcHdr->GetUint32Property("growKeywords", &grow);
+ if (grow) {
+ m_curSrcHdr->SetUint32Property("growKeywords", 0);
+ }
+ }
+
+ auto out =
+ nsPrintfCString(HEADER_X_MOZILLA_STATUS ": %4.4x", msgFlags & 0xFFFF);
+ out.Append(m_eolSeq);
+ rv = WriteSpan(m_fileStream, out);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ out = nsPrintfCString(HEADER_X_MOZILLA_STATUS2 ": %8.8x",
+ msgFlags & 0xFFFF0000);
+ out.Append(m_eolSeq);
+ rv = WriteSpan(m_fileStream, out);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Try to leave room for future in-place keyword edits.
+ while (keywords.Length() < X_MOZILLA_KEYWORDS_BLANK_LEN) {
+ keywords.Append(' ');
+ }
+ out = nsPrintfCString(HEADER_X_MOZILLA_KEYWORDS ": %s", keywords.get());
+ out.Append(m_eolSeq);
+ rv = WriteSpan(m_fileStream, out);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Write out the rest of the headers.
+ for (auto const& hdr : headers) {
+ rv = WriteSpan(m_fileStream, buf.Subspan(hdr.pos, hdr.len));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // The header parser consumes the blank line, If we've completed parsing
+ // we need to output it now.
+ // If we haven't parsed all the headers yet then the blank line will be
+ // safely copied verbatim as part of the remaining data.
+ if (rdr.IsComplete()) {
+ rv = WriteSpan(m_fileStream, m_eolSeq);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Write out everything else in the buffer verbatim.
+ if (leftover.Length() > 0) {
+ rv = WriteSpan(m_fileStream, leftover);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ m_bufferCount = 0;
+ return NS_OK;
+}
+
+/**
+ * nsOfflineStoreCompactState is a helper class for nsFolderCompactor which
+ * handles compacting the mbox for a single offline IMAP folder.
+ *
+ * nsOfflineStoreCompactState does *not* do any special X-Mozilla-* header
+ * handling, unlike the base class.
+ *
+ * NOTE (for future cleanups):
+ * This class uses a different mechanism to iterate through messages. It uses
+ * nsIMsgMessageService.streamMessage() to stream each message in turn,
+ * passing itself in as an nsIStreamListener. The nsICopyMessageStreamListener
+ * callbacks implemented in the base class are _not_ used here.
+ * For each message, the standard OnStartRequest(), OnDataAvailable()...,
+ * OnStopRequest() sequence is seen.
+ * Nothing too fancy, but it's not always clear where code from the base class
+ * is being used and when it is not, so it can be complicated to pick through.
+ *
+ */
+class nsOfflineStoreCompactState : public nsFolderCompactState {
+ public:
+ nsOfflineStoreCompactState(void);
+ virtual ~nsOfflineStoreCompactState(void);
+ NS_IMETHOD OnStopRequest(nsIRequest* request, nsresult status) override;
+ NS_IMETHODIMP OnDataAvailable(nsIRequest* request, nsIInputStream* inStr,
+ uint64_t sourceOffset, uint32_t count) override;
+
+ protected:
+ nsresult CopyNextMessage(bool& done);
+ virtual nsresult InitDB(nsIMsgDatabase* db) override;
+ virtual nsresult StartCompacting() override;
+ virtual nsresult FinishCompact() override;
+
+ char m_dataBuffer[COMPACTOR_READ_BUFF_SIZE + 1]; // temp data buffer for
+ // copying message
+ uint32_t m_offlineMsgSize;
+};
+
+nsOfflineStoreCompactState::nsOfflineStoreCompactState()
+ : m_offlineMsgSize(0) {}
+
+nsOfflineStoreCompactState::~nsOfflineStoreCompactState() {}
+
+nsresult nsOfflineStoreCompactState::InitDB(nsIMsgDatabase* db) {
+ // Start with the list of messages we have offline as the possible
+ // message to keep when compacting the offline store.
+ db->ListAllOfflineMsgs(m_keys);
+ m_db = db;
+ return NS_OK;
+}
+
+/**
+ * This will copy one message to the offline store, but if it fails to
+ * copy the next message, it will keep trying messages until it finds one
+ * it can copy, or it runs out of messages.
+ */
+nsresult nsOfflineStoreCompactState::CopyNextMessage(bool& done) {
+ while (m_curIndex < m_keys.Length()) {
+ // Filter out msgs that have the "pendingRemoval" attribute set.
+ nsCOMPtr<nsIMsgDBHdr> hdr;
+ nsCString pendingRemoval;
+ nsresult rv =
+ m_db->GetMsgHdrForKey(m_keys[m_curIndex], getter_AddRefs(hdr));
+ NS_ENSURE_SUCCESS(rv, rv);
+ hdr->GetStringProperty("pendingRemoval", pendingRemoval);
+ if (!pendingRemoval.IsEmpty()) {
+ m_curIndex++;
+ // Turn off offline flag for message, since after the compact is
+ // completed; we won't have the message in the offline store.
+ uint32_t resultFlags;
+ hdr->AndFlags(~nsMsgMessageFlags::Offline, &resultFlags);
+ // We need to clear this in case the user changes the offline retention
+ // settings.
+ hdr->SetStringProperty("pendingRemoval", ""_ns);
+ continue;
+ }
+ m_messageUri.Truncate(); // clear the previous message uri
+ rv = BuildMessageURI(m_baseMessageUri.get(), m_keys[m_curIndex],
+ m_messageUri);
+ NS_ENSURE_SUCCESS(rv, rv);
+ m_startOfMsg = true;
+ nsCOMPtr<nsISupports> thisSupports;
+ QueryInterface(NS_GET_IID(nsISupports), getter_AddRefs(thisSupports));
+ nsCOMPtr<nsIURI> dummyNull;
+ rv = m_messageService->StreamMessage(m_messageUri, thisSupports, m_window,
+ nullptr, false, EmptyCString(), true,
+ getter_AddRefs(dummyNull));
+ // if copy fails, we clear the offline flag on the source message.
+ if (NS_FAILED(rv)) {
+ nsCOMPtr<nsIMsgDBHdr> hdr;
+ m_messageService->MessageURIToMsgHdr(m_messageUri, getter_AddRefs(hdr));
+ if (hdr) {
+ uint32_t resultFlags;
+ hdr->AndFlags(~nsMsgMessageFlags::Offline, &resultFlags);
+ }
+ m_curIndex++;
+ continue;
+ } else
+ break;
+ }
+ done = m_curIndex >= m_keys.Length();
+ // In theory, we might be able to stream the next message, so
+ // return NS_OK, not rv.
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOfflineStoreCompactState::OnStopRequest(nsIRequest* request,
+ nsresult status) {
+ nsresult rv = status;
+ nsCOMPtr<nsIURI> uri;
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ nsCOMPtr<nsIMsgStatusFeedback> statusFeedback;
+ nsCOMPtr<nsIChannel> channel;
+ bool done = false;
+
+ // The NS_MSG_ERROR_MSG_NOT_OFFLINE error should allow us to continue, so we
+ // check for it specifically and don't terminate the compaction.
+ if (NS_FAILED(rv) && rv != NS_MSG_ERROR_MSG_NOT_OFFLINE) goto done;
+
+ // We know the request is an nsIChannel we can get a URI from, but this is
+ // probably bad form. See Bug 1528662.
+ channel = do_QueryInterface(request, &rv);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "error QI nsIRequest to nsIChannel failed");
+ if (NS_FAILED(rv)) goto done;
+ rv = channel->GetURI(getter_AddRefs(uri));
+ if (NS_FAILED(rv)) goto done;
+ rv = m_messageService->MessageURIToMsgHdr(m_messageUri,
+ getter_AddRefs(msgHdr));
+ if (NS_FAILED(rv)) goto done;
+
+ // This is however an unexpected condition, so let's print a warning.
+ if (rv == NS_MSG_ERROR_MSG_NOT_OFFLINE) {
+ nsAutoCString spec;
+ uri->GetSpec(spec);
+ nsPrintfCString msg("Message expectedly not available offline: %s",
+ spec.get());
+ NS_WARNING(msg.get());
+ }
+
+ if (msgHdr) {
+ if (NS_SUCCEEDED(status)) {
+ msgHdr->SetMessageOffset(m_startOfNewMsg);
+ nsCString storeToken = nsPrintfCString("%" PRIu64, m_startOfNewMsg);
+ msgHdr->SetStringProperty("storeToken", storeToken);
+ msgHdr->SetOfflineMessageSize(m_offlineMsgSize);
+ } else {
+ uint32_t resultFlags;
+ msgHdr->AndFlags(~nsMsgMessageFlags::Offline, &resultFlags);
+ }
+ }
+
+ if (m_window) {
+ m_window->GetStatusFeedback(getter_AddRefs(statusFeedback));
+ if (statusFeedback)
+ statusFeedback->ShowProgress(100 * m_curIndex / m_keys.Length());
+ }
+ // advance to next message
+ m_curIndex++;
+ rv = CopyNextMessage(done);
+ if (done) {
+ m_db->Commit(nsMsgDBCommitType::kCompressCommit);
+ msgHdr = nullptr;
+ // no more to copy finish it up
+ ReleaseFolderLock();
+ FinishCompact();
+ NS_RELEASE_THIS(); // kill self
+ }
+
+done:
+ if (NS_FAILED(rv)) {
+ m_status = rv; // set the status to rv so the destructor can remove the
+ // temp folder and database
+ ReleaseFolderLock();
+ NS_RELEASE_THIS(); // kill self
+
+ if (m_completionFn) {
+ m_completionFn(m_status, m_totalExpungedBytes);
+ }
+ return rv;
+ }
+ return rv;
+}
+
+nsresult nsOfflineStoreCompactState::FinishCompact() {
+ // All okay time to finish up the compact process
+ nsCOMPtr<nsIFile> path;
+ uint32_t flags;
+
+ // get leaf name and database name of the folder
+ m_folder->GetFlags(&flags);
+ nsresult rv = m_folder->GetFilePath(getter_AddRefs(path));
+
+ nsCString leafName;
+ path->GetNativeLeafName(leafName);
+
+ if (m_fileStream) {
+ // close down the temp file stream; preparing for deleting the old folder
+ // and its database; then rename the temp folder and database
+ m_fileStream->Flush();
+ m_fileStream->Close();
+ m_fileStream = nullptr;
+ }
+
+ // make sure the new database is valid
+ nsCOMPtr<nsIDBFolderInfo> dbFolderInfo;
+ m_db->GetDBFolderInfo(getter_AddRefs(dbFolderInfo));
+ if (dbFolderInfo) dbFolderInfo->SetExpungedBytes(0);
+ // this forces the m_folder to update mExpungedBytes from the db folder info.
+ int64_t expungedBytes;
+ m_folder->GetExpungedBytes(&expungedBytes);
+ m_folder->UpdateSummaryTotals(true);
+ m_db->SetSummaryValid(true);
+
+ // remove the old folder
+ path->Remove(false);
+
+ // rename the copied folder to be the original folder
+ m_file->MoveToNative((nsIFile*)nullptr, leafName);
+
+ ShowStatusMsg(EmptyString());
+ m_folder->NotifyCompactCompleted();
+ if (m_completionFn) {
+ m_completionFn(NS_OK, m_totalExpungedBytes);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsFolderCompactState::Init(nsICopyMessageListener* destination) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFolderCompactState::StartMessage() {
+ nsresult rv = NS_ERROR_FAILURE;
+ NS_ASSERTION(m_fileStream, "Fatal, null m_fileStream...");
+ if (m_fileStream) {
+ nsCOMPtr<nsISeekableStream> seekableStream =
+ do_QueryInterface(m_fileStream, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // this will force an internal flush, but not a sync. Tell should really do
+ // an internal flush, but it doesn't, and I'm afraid to change that
+ // nsIFileStream.cpp code anymore.
+ seekableStream->Seek(nsISeekableStream::NS_SEEK_CUR, 0);
+ // record the new message key for the message
+ int64_t curStreamPos;
+ seekableStream->Tell(&curStreamPos);
+ m_startOfNewMsg = curStreamPos;
+ rv = NS_OK;
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsFolderCompactState::EndMessage(nsMsgKey key) { return NS_OK; }
+
+// XXX TODO: This function is sadly lacking all status checking, it always
+// returns NS_OK and moves onto the next message.
+NS_IMETHODIMP
+nsFolderCompactState::EndCopy(nsIURI* uri, nsresult status) {
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ nsCOMPtr<nsIMsgDBHdr> newMsgHdr;
+
+ if (m_curIndex >= m_keys.Length()) {
+ NS_ASSERTION(false, "m_curIndex out of bounds");
+ return NS_OK;
+ }
+
+ // Take note of the end offset of the message (without the trailing blank
+ // line).
+ nsCOMPtr<nsITellableStream> tellable(do_QueryInterface(m_fileStream));
+ MOZ_ASSERT(tellable);
+ int64_t endOfMsg;
+ nsresult rv = tellable->Tell(&endOfMsg);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ /* Messages need to have trailing blank lines */
+ rv = WriteSpan(m_fileStream, m_eolSeq);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ /*
+ * Done with the current message; copying the existing message header
+ * to the new database.
+ */
+ if (m_curSrcHdr) {
+ nsMsgKey key;
+ m_curSrcHdr->GetMessageKey(&key);
+ m_db->CopyHdrFromExistingHdr(key, m_curSrcHdr, true,
+ getter_AddRefs(newMsgHdr));
+ }
+ m_curSrcHdr = nullptr;
+ if (newMsgHdr) {
+ nsCString storeToken = nsPrintfCString("%" PRIu64, m_startOfNewMsg);
+ newMsgHdr->SetStringProperty("storeToken", storeToken);
+ newMsgHdr->SetMessageOffset(m_startOfNewMsg);
+ uint64_t msgSize = endOfMsg - m_startOfNewMsg;
+ newMsgHdr->SetMessageSize(msgSize);
+
+ m_totalMsgSize += msgSize + m_eolSeq.Length();
+ }
+
+ // m_db->Commit(nsMsgDBCommitType::kLargeCommit); // no sense committing
+ // until the end
+ // advance to next message
+ m_curIndex++;
+ m_startOfMsg = true;
+ nsCOMPtr<nsIMsgStatusFeedback> statusFeedback;
+ if (m_window) {
+ m_window->GetStatusFeedback(getter_AddRefs(statusFeedback));
+ if (statusFeedback)
+ statusFeedback->ShowProgress(100 * m_curIndex / m_keys.Length());
+ }
+ return NS_OK;
+}
+
+nsresult nsOfflineStoreCompactState::StartCompacting() {
+ nsresult rv = NS_OK;
+ if (m_keys.Length() > 0 && m_curIndex == 0) {
+ NS_ADDREF_THIS(); // we own ourselves, until we're done, anyway.
+ ShowCompactingStatusMsg();
+ bool done = false;
+ rv = CopyNextMessage(done);
+ if (!done) return rv;
+ }
+ ReleaseFolderLock();
+ FinishCompact();
+ return rv;
+}
+
+NS_IMETHODIMP
+nsOfflineStoreCompactState::OnDataAvailable(nsIRequest* request,
+ nsIInputStream* inStr,
+ uint64_t sourceOffset,
+ uint32_t count) {
+ if (!m_fileStream || !inStr) return NS_ERROR_FAILURE;
+
+ nsresult rv = NS_OK;
+
+ if (m_startOfMsg) {
+ m_offlineMsgSize = 0;
+ m_messageUri.Truncate(); // clear the previous message uri
+ if (NS_SUCCEEDED(BuildMessageURI(m_baseMessageUri.get(), m_keys[m_curIndex],
+ m_messageUri))) {
+ rv = m_messageService->MessageURIToMsgHdr(m_messageUri,
+ getter_AddRefs(m_curSrcHdr));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+ uint32_t maxReadCount, readCount, writeCount;
+ uint32_t bytesWritten;
+
+ while (NS_SUCCEEDED(rv) && (int32_t)count > 0) {
+ maxReadCount =
+ count > sizeof(m_dataBuffer) - 1 ? sizeof(m_dataBuffer) - 1 : count;
+ writeCount = 0;
+ rv = inStr->Read(m_dataBuffer, maxReadCount, &readCount);
+
+ if (NS_SUCCEEDED(rv)) {
+ if (m_startOfMsg) {
+ m_startOfMsg = false;
+ // check if there's an envelope header; if not, write one.
+ if (strncmp(m_dataBuffer, "From ", 5)) {
+ m_fileStream->Write("From " CRLF, 7, &bytesWritten);
+ m_offlineMsgSize += bytesWritten;
+ }
+ }
+ m_fileStream->Write(m_dataBuffer, readCount, &bytesWritten);
+ m_offlineMsgSize += bytesWritten;
+ writeCount += bytesWritten;
+ count -= readCount;
+ if (writeCount != readCount) {
+ m_folder->ThrowAlertMsg("compactFolderWriteFailed", m_window);
+ return NS_MSG_ERROR_WRITING_MAIL_FOLDER;
+ }
+ }
+ }
+ return rv;
+}
+
+//////////////////////////////////////////////////////////////////////////////
+// nsMsgFolderCompactor implementation
+//////////////////////////////////////////////////////////////////////////////
+
+NS_IMPL_ISUPPORTS(nsMsgFolderCompactor, nsIMsgFolderCompactor)
+
+nsMsgFolderCompactor::nsMsgFolderCompactor() {}
+
+nsMsgFolderCompactor::~nsMsgFolderCompactor() {}
+
+NS_IMETHODIMP nsMsgFolderCompactor::CompactFolders(
+ const nsTArray<RefPtr<nsIMsgFolder>>& folders, nsIUrlListener* listener,
+ nsIMsgWindow* window) {
+ MOZ_ASSERT(mQueue.IsEmpty());
+ mWindow = window;
+ mListener = listener;
+ mTotalBytesGained = 0;
+ mQueue = folders.Clone();
+ mQueue.Reverse();
+
+ // Can't guarantee that anyone will keep us in scope until we're done, so...
+ MOZ_ASSERT(!mKungFuDeathGrip);
+ mKungFuDeathGrip = this;
+
+ // nsIMsgFolderCompactor idl states this isn't called...
+ // but maybe it should be?
+ // if (mListener) {
+ // mListener->OnStartRunningUrl(nullptr);
+ // }
+
+ NextFolder();
+
+ return NS_OK;
+}
+
+void nsMsgFolderCompactor::NextFolder() {
+ while (!mQueue.IsEmpty()) {
+ // Should only ever have one compactor running.
+ MOZ_ASSERT(mCompactor == nullptr);
+
+ nsCOMPtr<nsIMsgFolder> folder = mQueue.PopLastElement();
+
+ // Sanity check - should we be compacting this folder?
+ nsCOMPtr<nsIMsgPluggableStore> msgStore;
+ nsresult rv = folder->GetMsgStore(getter_AddRefs(msgStore));
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Skipping folder with no msgStore");
+ continue;
+ }
+ bool storeSupportsCompaction;
+ msgStore->GetSupportsCompaction(&storeSupportsCompaction);
+ if (!storeSupportsCompaction) {
+ NS_WARNING("Trying to compact a non-mbox folder");
+ continue; // just skip it.
+ }
+
+ nsCOMPtr<nsIMsgImapMailFolder> imapFolder(do_QueryInterface(folder));
+ if (imapFolder) {
+ uint32_t flags;
+ folder->GetFlags(&flags);
+ if (flags & nsMsgFolderFlags::Offline) {
+ mCompactor = new nsOfflineStoreCompactState();
+ }
+ } else {
+ mCompactor = new nsFolderCompactState();
+ }
+ if (!mCompactor) {
+ NS_WARNING("skipping compact of non-offline folder");
+ continue;
+ }
+ nsCString uri;
+ folder->GetURI(uri);
+
+ // Callback for when a folder compaction completes.
+ auto completionFn = [self = RefPtr<nsMsgFolderCompactor>(this),
+ compactState = mCompactor](nsresult status,
+ uint64_t expungedBytes) {
+ if (NS_SUCCEEDED(status)) {
+ self->mTotalBytesGained += expungedBytes;
+ } else {
+ // Failed. We want to keep going with the next folder, but make sure
+ // we return a failing code upon overall completion.
+ self->mOverallStatus = status;
+ NS_WARNING("folder compact failed.");
+ }
+
+ // Release our lock on the compactor - it's done.
+ self->mCompactor = nullptr;
+ self->NextFolder();
+ };
+
+ rv = mCompactor->Compact(folder, completionFn, mWindow);
+ if (NS_SUCCEEDED(rv)) {
+ // Now wait for the compactor to let us know it's finished,
+ // via the completion callback fn.
+ return;
+ }
+ mOverallStatus = rv;
+ mCompactor = nullptr;
+ NS_WARNING("folder compact failed - skipping folder");
+ }
+
+ // Done. No more folders to compact.
+
+ if (mListener) {
+ // If there were multiple failures, this will communicate only the
+ // last one, but that's OK. Main thing is to indicate that _something_
+ // went wrong.
+ mListener->OnStopRunningUrl(nullptr, mOverallStatus);
+ }
+ ShowDoneStatus();
+
+ // We're not needed any more.
+ mKungFuDeathGrip = nullptr;
+ return;
+}
+
+void nsMsgFolderCompactor::ShowDoneStatus() {
+ if (!mWindow) {
+ return;
+ }
+ nsCOMPtr<nsIStringBundle> bundle;
+ nsresult rv = GetBaseStringBundle(getter_AddRefs(bundle));
+ NS_ENSURE_SUCCESS_VOID(rv);
+ nsAutoString expungedAmount;
+ FormatFileSize(mTotalBytesGained, true, expungedAmount);
+ AutoTArray<nsString, 1> params = {expungedAmount};
+ nsString msg;
+ rv = bundle->FormatStringFromName("compactingDone", params, msg);
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ nsCOMPtr<nsIMsgStatusFeedback> statusFeedback;
+ mWindow->GetStatusFeedback(getter_AddRefs(statusFeedback));
+ if (statusFeedback) {
+ statusFeedback->SetStatusString(msg);
+ }
+}
diff --git a/comm/mailnews/base/src/nsMsgFolderCompactor.h b/comm/mailnews/base/src/nsMsgFolderCompactor.h
new file mode 100644
index 0000000000..b9de16be12
--- /dev/null
+++ b/comm/mailnews/base/src/nsMsgFolderCompactor.h
@@ -0,0 +1,48 @@
+/* -*- 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 _nsMsgFolderCompactor_h
+#define _nsMsgFolderCompactor_h
+
+#include "nsIMsgFolderCompactor.h"
+
+class nsIMsgFolder;
+class nsIMsgWindow;
+class nsFolderCompactState;
+
+/**
+ * nsMsgFolderCompactor implements nsIMsgFolderCompactor, which allows the
+ * caller to kick off a batch of folder compactions (via compactFolders()).
+ */
+class nsMsgFolderCompactor : public nsIMsgFolderCompactor {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMSGFOLDERCOMPACTOR
+
+ nsMsgFolderCompactor();
+
+ protected:
+ virtual ~nsMsgFolderCompactor();
+ // The folders waiting to be compacted.
+ nsTArray<RefPtr<nsIMsgFolder>> mQueue;
+
+ // If any individual folders fail to compact, we stash the latest fail code
+ // here (to return via listener upon overall completion).
+ nsresult mOverallStatus{NS_OK};
+
+ // If set, OnStopRunningUrl() will be called when all folders done.
+ nsCOMPtr<nsIUrlListener> mListener;
+ // If set, progress status updates will be sent here.
+ nsCOMPtr<nsIMsgWindow> mWindow;
+ RefPtr<nsMsgFolderCompactor> mKungFuDeathGrip;
+ uint64_t mTotalBytesGained{0};
+
+ // The currently-running compactor.
+ RefPtr<nsFolderCompactState> mCompactor;
+
+ void NextFolder();
+ void ShowDoneStatus();
+};
+#endif
diff --git a/comm/mailnews/base/src/nsMsgFolderNotificationService.cpp b/comm/mailnews/base/src/nsMsgFolderNotificationService.cpp
new file mode 100644
index 0000000000..7983a8ec56
--- /dev/null
+++ b/comm/mailnews/base/src/nsMsgFolderNotificationService.cpp
@@ -0,0 +1,174 @@
+/* -*- 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 "msgCore.h"
+#include "nsMsgFolderNotificationService.h"
+#include "nsIMsgHdr.h"
+#include "nsIMsgFolder.h"
+#include "nsIMsgImapMailFolder.h"
+#include "nsIImapIncomingServer.h"
+
+//
+// nsMsgFolderNotificationService
+//
+NS_IMPL_ISUPPORTS(nsMsgFolderNotificationService,
+ nsIMsgFolderNotificationService)
+
+nsMsgFolderNotificationService::nsMsgFolderNotificationService() {}
+
+nsMsgFolderNotificationService::~nsMsgFolderNotificationService() {
+ /* destructor code */
+}
+
+NS_IMETHODIMP nsMsgFolderNotificationService::GetHasListeners(
+ bool* aHasListeners) {
+ NS_ENSURE_ARG_POINTER(aHasListeners);
+ *aHasListeners = mListeners.Length() > 0;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgFolderNotificationService::AddListener(
+ nsIMsgFolderListener* aListener, msgFolderListenerFlag aFlags) {
+ NS_ENSURE_ARG_POINTER(aListener);
+ MsgFolderListener listener(aListener, aFlags);
+ mListeners.AppendElementUnlessExists(listener);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgFolderNotificationService::RemoveListener(
+ nsIMsgFolderListener* aListener) {
+ NS_ENSURE_ARG_POINTER(aListener);
+
+ mListeners.RemoveElement(aListener);
+ return NS_OK;
+}
+
+#define NOTIFY_MSGFOLDER_LISTENERS(propertyflag_, propertyfunc_, params_) \
+ PR_BEGIN_MACRO \
+ nsTObserverArray<MsgFolderListener>::ForwardIterator iter(mListeners); \
+ while (iter.HasMore()) { \
+ const MsgFolderListener& listener = iter.GetNext(); \
+ if (listener.mFlags & propertyflag_) \
+ listener.mListener->propertyfunc_ params_; \
+ } \
+ PR_END_MACRO
+
+NS_IMETHODIMP nsMsgFolderNotificationService::NotifyMsgAdded(
+ nsIMsgDBHdr* aMsg) {
+ NOTIFY_MSGFOLDER_LISTENERS(msgAdded, MsgAdded, (aMsg));
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgFolderNotificationService::NotifyMsgsClassified(
+ const nsTArray<RefPtr<nsIMsgDBHdr>>& aMsgs, bool aJunkProcessed,
+ bool aTraitProcessed) {
+ NOTIFY_MSGFOLDER_LISTENERS(msgsClassified, MsgsClassified,
+ (aMsgs, aJunkProcessed, aTraitProcessed));
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgFolderNotificationService::NotifyMsgsJunkStatusChanged(
+ const nsTArray<RefPtr<nsIMsgDBHdr>>& messages) {
+ NOTIFY_MSGFOLDER_LISTENERS(msgsJunkStatusChanged, MsgsJunkStatusChanged,
+ (messages));
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgFolderNotificationService::NotifyMsgsDeleted(
+ const nsTArray<RefPtr<nsIMsgDBHdr>>& aMsgs) {
+ NOTIFY_MSGFOLDER_LISTENERS(msgsDeleted, MsgsDeleted, (aMsgs));
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgFolderNotificationService::NotifyMsgsMoveCopyCompleted(
+ bool aMove, const nsTArray<RefPtr<nsIMsgDBHdr>>& aSrcMsgs,
+ nsIMsgFolder* aDestFolder, const nsTArray<RefPtr<nsIMsgDBHdr>>& aDestMsgs) {
+ // IMAP delete model means that a "move" isn't really a move, it is a copy,
+ // followed by storing the IMAP deleted flag on the message.
+ bool isReallyMove = aMove;
+ if (aMove && !mListeners.IsEmpty() && !aSrcMsgs.IsEmpty()) {
+ nsresult rv;
+ // Assume that all the source messages are from the same server.
+ nsCOMPtr<nsIMsgFolder> msgFolder;
+ rv = aSrcMsgs[0]->GetFolder(getter_AddRefs(msgFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgImapMailFolder> imapFolder(do_QueryInterface(msgFolder));
+ if (imapFolder) {
+ nsCOMPtr<nsIImapIncomingServer> imapServer;
+ imapFolder->GetImapIncomingServer(getter_AddRefs(imapServer));
+ if (imapServer) {
+ nsMsgImapDeleteModel deleteModel;
+ imapServer->GetDeleteModel(&deleteModel);
+ if (deleteModel == nsMsgImapDeleteModels::IMAPDelete)
+ isReallyMove = false;
+ }
+ }
+ }
+
+ NOTIFY_MSGFOLDER_LISTENERS(msgsMoveCopyCompleted, MsgsMoveCopyCompleted,
+ (isReallyMove, aSrcMsgs, aDestFolder, aDestMsgs));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgFolderNotificationService::NotifyMsgKeyChanged(nsMsgKey aOldKey,
+ nsIMsgDBHdr* aNewHdr) {
+ NOTIFY_MSGFOLDER_LISTENERS(msgKeyChanged, MsgKeyChanged, (aOldKey, aNewHdr));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgFolderNotificationService::NotifyMsgUnincorporatedMoved(
+ nsIMsgFolder* srcFolder, nsIMsgDBHdr* msg) {
+ NOTIFY_MSGFOLDER_LISTENERS(msgUnincorporatedMoved, MsgUnincorporatedMoved,
+ (srcFolder, msg));
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgFolderNotificationService::NotifyFolderAdded(
+ nsIMsgFolder* aFolder) {
+ NOTIFY_MSGFOLDER_LISTENERS(folderAdded, FolderAdded, (aFolder));
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgFolderNotificationService::NotifyFolderDeleted(
+ nsIMsgFolder* aFolder) {
+ NOTIFY_MSGFOLDER_LISTENERS(folderDeleted, FolderDeleted, (aFolder));
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgFolderNotificationService::NotifyFolderMoveCopyCompleted(
+ bool aMove, nsIMsgFolder* aSrcFolder, nsIMsgFolder* aDestFolder) {
+ NOTIFY_MSGFOLDER_LISTENERS(folderMoveCopyCompleted, FolderMoveCopyCompleted,
+ (aMove, aSrcFolder, aDestFolder));
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgFolderNotificationService::NotifyFolderRenamed(
+ nsIMsgFolder* aOrigFolder, nsIMsgFolder* aNewFolder) {
+ NOTIFY_MSGFOLDER_LISTENERS(folderRenamed, FolderRenamed,
+ (aOrigFolder, aNewFolder));
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgFolderNotificationService::NotifyFolderCompactStart(
+ nsIMsgFolder* folder) {
+ NOTIFY_MSGFOLDER_LISTENERS(folderCompactStart, FolderCompactStart, (folder));
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgFolderNotificationService::NotifyFolderCompactFinish(
+ nsIMsgFolder* folder) {
+ NOTIFY_MSGFOLDER_LISTENERS(folderCompactFinish, FolderCompactFinish,
+ (folder));
+ return NS_OK;
+}
+NS_IMETHODIMP nsMsgFolderNotificationService::NotifyFolderReindexTriggered(
+ nsIMsgFolder* folder) {
+ NOTIFY_MSGFOLDER_LISTENERS(folderReindexTriggered, FolderReindexTriggered,
+ (folder));
+ return NS_OK;
+}
diff --git a/comm/mailnews/base/src/nsMsgFolderNotificationService.h b/comm/mailnews/base/src/nsMsgFolderNotificationService.h
new file mode 100644
index 0000000000..fecf3b3064
--- /dev/null
+++ b/comm/mailnews/base/src/nsMsgFolderNotificationService.h
@@ -0,0 +1,46 @@
+/* -*- 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 nsMsgFolderNotificationService_h__
+#define nsMsgFolderNotificationService_h__
+
+#include "nsIMsgFolderNotificationService.h"
+#include "nsIMsgFolderListener.h"
+#include "nsTObserverArray.h"
+#include "nsCOMPtr.h"
+
+class nsMsgFolderNotificationService final
+ : public nsIMsgFolderNotificationService {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMSGFOLDERNOTIFICATIONSERVICE
+
+ nsMsgFolderNotificationService();
+
+ private:
+ ~nsMsgFolderNotificationService();
+ struct MsgFolderListener {
+ nsCOMPtr<nsIMsgFolderListener> mListener;
+ msgFolderListenerFlag mFlags;
+
+ MsgFolderListener(nsIMsgFolderListener* aListener,
+ msgFolderListenerFlag aFlags)
+ : mListener(aListener), mFlags(aFlags) {}
+ MsgFolderListener(const MsgFolderListener& aListener)
+ : mListener(aListener.mListener), mFlags(aListener.mFlags) {}
+ ~MsgFolderListener() {}
+
+ int operator==(nsIMsgFolderListener* aListener) const {
+ return mListener == aListener;
+ }
+ int operator==(const MsgFolderListener& aListener) const {
+ return mListener == aListener.mListener;
+ }
+ };
+
+ nsTObserverArray<MsgFolderListener> mListeners;
+};
+
+#endif
diff --git a/comm/mailnews/base/src/nsMsgGroupThread.cpp b/comm/mailnews/base/src/nsMsgGroupThread.cpp
new file mode 100644
index 0000000000..32a132d27a
--- /dev/null
+++ b/comm/mailnews/base/src/nsMsgGroupThread.cpp
@@ -0,0 +1,731 @@
+/* -*- 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 "msgCore.h"
+#include "nsMsgGroupThread.h"
+#include "nsMsgDBView.h"
+#include "nsMsgMessageFlags.h"
+#include "nsMsgUtils.h"
+#include "nsSimpleEnumerator.h"
+
+NS_IMPL_ISUPPORTS(nsMsgGroupThread, nsIMsgThread)
+
+nsMsgGroupThread::nsMsgGroupThread() { Init(); }
+
+nsMsgGroupThread::nsMsgGroupThread(nsIMsgDatabase* db) {
+ m_db = db;
+ Init();
+}
+
+void nsMsgGroupThread::Init() {
+ m_threadKey = nsMsgKey_None;
+ m_threadRootKey = nsMsgKey_None;
+ m_numUnreadChildren = 0;
+ m_flags = 0;
+ m_newestMsgDate = 0;
+ m_dummy = false;
+}
+
+nsMsgGroupThread::~nsMsgGroupThread() {}
+
+NS_IMETHODIMP nsMsgGroupThread::SetThreadKey(nsMsgKey threadKey) {
+ m_threadKey = threadKey;
+ // by definition, the initial thread key is also the thread root key.
+ m_threadRootKey = threadKey;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgGroupThread::GetThreadKey(nsMsgKey* aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = m_threadKey;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgGroupThread::GetFlags(uint32_t* aFlags) {
+ NS_ENSURE_ARG_POINTER(aFlags);
+ *aFlags = m_flags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgGroupThread::SetFlags(uint32_t aFlags) {
+ m_flags = aFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgGroupThread::SetSubject(const nsACString& aSubject) {
+ NS_ASSERTION(false, "shouldn't call this");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsMsgGroupThread::GetSubject(nsACString& result) {
+ NS_ASSERTION(false, "shouldn't call this");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsMsgGroupThread::GetNumChildren(uint32_t* aNumChildren) {
+ NS_ENSURE_ARG_POINTER(aNumChildren);
+ *aNumChildren = m_keys.Length(); // - ((m_dummy) ? 1 : 0);
+ return NS_OK;
+}
+
+uint32_t nsMsgGroupThread::NumRealChildren() {
+ return m_keys.Length() - ((m_dummy) ? 1 : 0);
+}
+
+NS_IMETHODIMP nsMsgGroupThread::GetNumUnreadChildren(
+ uint32_t* aNumUnreadChildren) {
+ NS_ENSURE_ARG_POINTER(aNumUnreadChildren);
+ *aNumUnreadChildren = m_numUnreadChildren;
+ return NS_OK;
+}
+
+void nsMsgGroupThread::InsertMsgHdrAt(nsMsgViewIndex index, nsIMsgDBHdr* hdr) {
+ nsMsgKey msgKey;
+ hdr->GetMessageKey(&msgKey);
+ m_keys.InsertElementAt(index, msgKey);
+}
+
+void nsMsgGroupThread::SetMsgHdrAt(nsMsgViewIndex index, nsIMsgDBHdr* hdr) {
+ nsMsgKey msgKey;
+ hdr->GetMessageKey(&msgKey);
+ m_keys[index] = msgKey;
+}
+
+nsMsgViewIndex nsMsgGroupThread::FindMsgHdr(nsIMsgDBHdr* hdr) {
+ nsMsgKey msgKey;
+ hdr->GetMessageKey(&msgKey);
+ return (nsMsgViewIndex)m_keys.IndexOf(msgKey);
+}
+
+NS_IMETHODIMP nsMsgGroupThread::AddChild(nsIMsgDBHdr* child,
+ nsIMsgDBHdr* inReplyTo,
+ bool threadInThread,
+ nsIDBChangeAnnouncer* announcer) {
+ NS_ASSERTION(false, "shouldn't call this");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsMsgViewIndex nsMsgGroupThread::AddMsgHdrInDateOrder(nsIMsgDBHdr* child,
+ nsMsgDBView* view) {
+ nsMsgKey newHdrKey;
+ child->GetMessageKey(&newHdrKey);
+ uint32_t insertIndex = 0;
+ // since we're sorted by date, we could do a binary search for the
+ // insert point. Or, we could start at the end...
+ if (m_keys.Length() > 0) {
+ nsMsgViewSortTypeValue sortType;
+ nsMsgViewSortOrderValue sortOrder;
+ (void)view->GetSortType(&sortType);
+ (void)view->GetSortOrder(&sortOrder);
+ // historical behavior is ascending date order unless our primary sort is
+ // on date
+ nsMsgViewSortOrderValue threadSortOrder =
+ (sortType == nsMsgViewSortType::byDate &&
+ sortOrder == nsMsgViewSortOrder::descending)
+ ? nsMsgViewSortOrder::descending
+ : nsMsgViewSortOrder::ascending;
+ // new behavior is tricky and uses the secondary sort order if the secondary
+ // sort is on the date
+ nsMsgViewSortTypeValue secondarySortType;
+ nsMsgViewSortOrderValue secondarySortOrder;
+ (void)view->GetSecondarySortType(&secondarySortType);
+ (void)view->GetSecondarySortOrder(&secondarySortOrder);
+ if (secondarySortType == nsMsgViewSortType::byDate)
+ threadSortOrder = secondarySortOrder;
+ // sort by date within group.
+ insertIndex = GetInsertIndexFromView(view, child, threadSortOrder);
+ }
+ m_keys.InsertElementAt(insertIndex, newHdrKey);
+ if (!insertIndex) m_threadRootKey = newHdrKey;
+ return insertIndex;
+}
+
+nsMsgViewIndex nsMsgGroupThread::GetInsertIndexFromView(
+ nsMsgDBView* view, nsIMsgDBHdr* child,
+ nsMsgViewSortOrderValue threadSortOrder) {
+ return view->GetInsertIndexHelper(child, m_keys, nullptr, threadSortOrder,
+ nsMsgViewSortType::byDate);
+}
+
+nsMsgViewIndex nsMsgGroupThread::AddChildFromGroupView(nsIMsgDBHdr* child,
+ nsMsgDBView* view) {
+ uint32_t newHdrFlags = 0;
+ uint32_t msgDate;
+ nsMsgKey newHdrKey = 0;
+
+ child->GetFlags(&newHdrFlags);
+ child->GetMessageKey(&newHdrKey);
+ child->GetDateInSeconds(&msgDate);
+ if (msgDate > m_newestMsgDate) SetNewestMsgDate(msgDate);
+
+ child->AndFlags(~(nsMsgMessageFlags::Watched), &newHdrFlags);
+ uint32_t numChildren;
+
+ // get the num children before we add the new header.
+ GetNumChildren(&numChildren);
+
+ // if this is an empty thread, set the root key to this header's key
+ if (numChildren == 0) m_threadRootKey = newHdrKey;
+
+ if (!(newHdrFlags & nsMsgMessageFlags::Read)) ChangeUnreadChildCount(1);
+
+ return AddMsgHdrInDateOrder(child, view);
+}
+
+nsresult nsMsgGroupThread::ReparentNonReferenceChildrenOf(
+ nsIMsgDBHdr* topLevelHdr, nsMsgKey newParentKey,
+ nsIDBChangeAnnouncer* announcer) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgGroupThread::GetChildKeyAt(uint32_t aIndex,
+ nsMsgKey* aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+ if (aIndex >= m_keys.Length()) return NS_ERROR_INVALID_ARG;
+ *aResult = m_keys[aIndex];
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgGroupThread::GetChildHdrAt(uint32_t aIndex,
+ nsIMsgDBHdr** aResult) {
+ if (aIndex >= m_keys.Length()) return NS_MSG_MESSAGE_NOT_FOUND;
+ return m_db->GetMsgHdrForKey(m_keys[aIndex], aResult);
+}
+
+NS_IMETHODIMP nsMsgGroupThread::GetChild(nsMsgKey msgKey,
+ nsIMsgDBHdr** aResult) {
+ return GetChildHdrAt(m_keys.IndexOf(msgKey), aResult);
+}
+
+NS_IMETHODIMP nsMsgGroupThread::RemoveChildAt(uint32_t aIndex) {
+ NS_ENSURE_TRUE(aIndex < m_keys.Length(), NS_MSG_MESSAGE_NOT_FOUND);
+
+ m_keys.RemoveElementAt(aIndex);
+ return NS_OK;
+}
+
+nsresult nsMsgGroupThread::RemoveChild(nsMsgKey msgKey) {
+ m_keys.RemoveElement(msgKey);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgGroupThread::RemoveChildHdr(
+ nsIMsgDBHdr* child, nsIDBChangeAnnouncer* announcer) {
+ NS_ENSURE_ARG_POINTER(child);
+
+ uint32_t flags;
+ nsMsgKey key;
+
+ child->GetFlags(&flags);
+ child->GetMessageKey(&key);
+
+ // if this was the newest msg, clear the newest msg date so we'll recalc.
+ uint32_t date;
+ child->GetDateInSeconds(&date);
+ if (date == m_newestMsgDate) SetNewestMsgDate(0);
+
+ if (!(flags & nsMsgMessageFlags::Read)) ChangeUnreadChildCount(-1);
+ nsMsgViewIndex threadIndex = FindMsgHdr(child);
+ bool wasFirstChild = threadIndex == 0;
+ nsresult rv = RemoveChildAt(threadIndex);
+ // if we're deleting the root of a dummy thread, need to update the threadKey
+ // and the dummy header at position 0
+ if (m_dummy && wasFirstChild && m_keys.Length() > 1) {
+ nsIMsgDBHdr* newRootChild;
+ rv = GetChildHdrAt(1, &newRootChild);
+ NS_ENSURE_SUCCESS(rv, rv);
+ SetMsgHdrAt(0, newRootChild);
+ }
+
+ return rv;
+}
+
+nsresult nsMsgGroupThread::ReparentChildrenOf(nsMsgKey oldParent,
+ nsMsgKey newParent,
+ nsIDBChangeAnnouncer* announcer) {
+ nsresult rv = NS_OK;
+
+ uint32_t numChildren = 0;
+ GetNumChildren(&numChildren);
+
+ if (numChildren > 0) {
+ nsCOMPtr<nsIMsgDBHdr> curHdr;
+ for (uint32_t childIndex = 0; childIndex < numChildren; childIndex++) {
+ rv = GetChildHdrAt(childIndex, getter_AddRefs(curHdr));
+ if (NS_SUCCEEDED(rv) && curHdr) {
+ nsMsgKey threadParent;
+
+ curHdr->GetThreadParent(&threadParent);
+ if (threadParent == oldParent) {
+ nsMsgKey curKey;
+
+ curHdr->SetThreadParent(newParent);
+ curHdr->GetMessageKey(&curKey);
+ if (announcer)
+ announcer->NotifyParentChangedAll(curKey, oldParent, newParent,
+ nullptr);
+ // if the old parent was the root of the thread, then only the first
+ // child gets promoted to root, and other children become children of
+ // the new root.
+ if (newParent == nsMsgKey_None) {
+ m_threadRootKey = curKey;
+ newParent = curKey;
+ }
+ }
+ }
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgGroupThread::MarkChildRead(bool bRead) {
+ ChangeUnreadChildCount(bRead ? -1 : 1);
+ return NS_OK;
+}
+
+// this could be moved into utils, because I think it's the same as the db impl.
+class nsMsgGroupThreadEnumerator : public nsBaseMsgEnumerator {
+ public:
+ // nsIMsgEnumerator support.
+ NS_IMETHOD GetNext(nsIMsgDBHdr** aItem) override;
+ NS_IMETHOD HasMoreElements(bool* aResult) override;
+
+ // nsMsgGroupThreadEnumerator methods:
+ typedef nsresult (*nsMsgGroupThreadEnumeratorFilter)(nsIMsgDBHdr* hdr,
+ void* closure);
+
+ nsMsgGroupThreadEnumerator(nsMsgGroupThread* thread, nsMsgKey startKey,
+ nsMsgGroupThreadEnumeratorFilter filter,
+ void* closure);
+ int32_t MsgKeyFirstChildIndex(nsMsgKey inMsgKey);
+
+ protected:
+ virtual ~nsMsgGroupThreadEnumerator();
+
+ nsresult Prefetch();
+
+ nsCOMPtr<nsIMsgDBHdr> mResultHdr;
+ RefPtr<nsMsgGroupThread> mThread;
+ nsMsgKey mThreadParentKey;
+ nsMsgKey mFirstMsgKey;
+ int32_t mChildIndex;
+ bool mDone;
+ bool mNeedToPrefetch;
+ nsMsgGroupThreadEnumeratorFilter mFilter;
+ void* mClosure;
+ bool mFoundChildren;
+};
+
+nsMsgGroupThreadEnumerator::nsMsgGroupThreadEnumerator(
+ nsMsgGroupThread* thread, nsMsgKey startKey,
+ nsMsgGroupThreadEnumeratorFilter filter, void* closure)
+ : mDone(false), mFilter(filter), mClosure(closure), mFoundChildren(false) {
+ mThreadParentKey = startKey;
+ mChildIndex = 0;
+ mThread = thread;
+ mNeedToPrefetch = true;
+ mFirstMsgKey = nsMsgKey_None;
+
+ nsresult rv = mThread->GetRootHdr(getter_AddRefs(mResultHdr));
+ if (NS_SUCCEEDED(rv) && mResultHdr) mResultHdr->GetMessageKey(&mFirstMsgKey);
+
+ uint32_t numChildren;
+ mThread->GetNumChildren(&numChildren);
+
+ if (mThreadParentKey != nsMsgKey_None) {
+ nsMsgKey msgKey = nsMsgKey_None;
+ for (uint32_t childIndex = 0; childIndex < numChildren; childIndex++) {
+ rv = mThread->GetChildHdrAt(childIndex, getter_AddRefs(mResultHdr));
+ if (NS_SUCCEEDED(rv) && mResultHdr) {
+ mResultHdr->GetMessageKey(&msgKey);
+ if (msgKey == startKey) {
+ mChildIndex = MsgKeyFirstChildIndex(msgKey);
+ mDone = (mChildIndex < 0);
+ break;
+ }
+
+ if (mDone) break;
+ } else
+ NS_ASSERTION(false, "couldn't get child from thread");
+ }
+ }
+
+#ifdef DEBUG_bienvenu1
+ nsCOMPtr<nsIMsgDBHdr> child;
+ for (uint32_t childIndex = 0; childIndex < numChildren; childIndex++) {
+ rv = mThread->GetChildHdrAt(childIndex, getter_AddRefs(child));
+ if (NS_SUCCEEDED(rv) && child) {
+ nsMsgKey threadParent;
+ nsMsgKey msgKey;
+ // we're only doing one level of threading, so check if caller is
+ // asking for children of the first message in the thread or not.
+ // if not, we will tell him there are no children.
+ child->GetMessageKey(&msgKey);
+ child->GetThreadParent(&threadParent);
+
+ printf("index = %ld key = %ld parent = %lx\n", childIndex, msgKey,
+ threadParent);
+ }
+ }
+#endif
+}
+
+nsMsgGroupThreadEnumerator::~nsMsgGroupThreadEnumerator() {}
+
+int32_t nsMsgGroupThreadEnumerator::MsgKeyFirstChildIndex(nsMsgKey inMsgKey) {
+ // look through rest of thread looking for a child of this message.
+ // If the inMsgKey is the first message in the thread, then all children
+ // without parents are considered to be children of inMsgKey.
+ // Otherwise, only true children qualify.
+ uint32_t numChildren;
+ nsCOMPtr<nsIMsgDBHdr> curHdr;
+ int32_t firstChildIndex = -1;
+
+ mThread->GetNumChildren(&numChildren);
+
+ for (uint32_t curChildIndex = 0; curChildIndex < numChildren;
+ curChildIndex++) {
+ nsresult rv = mThread->GetChildHdrAt(curChildIndex, getter_AddRefs(curHdr));
+ if (NS_SUCCEEDED(rv) && curHdr) {
+ nsMsgKey parentKey;
+
+ curHdr->GetThreadParent(&parentKey);
+ if (parentKey == inMsgKey) {
+ firstChildIndex = curChildIndex;
+ break;
+ }
+ }
+ }
+#ifdef DEBUG_bienvenu1
+ printf("first child index of %ld = %ld\n", inMsgKey, firstChildIndex);
+#endif
+ return firstChildIndex;
+}
+
+NS_IMETHODIMP nsMsgGroupThreadEnumerator::GetNext(nsIMsgDBHdr** aItem) {
+ NS_ENSURE_ARG_POINTER(aItem);
+ nsresult rv = NS_OK;
+
+ if (mNeedToPrefetch) rv = Prefetch();
+
+ if (NS_SUCCEEDED(rv) && mResultHdr) {
+ NS_ADDREF(*aItem = mResultHdr);
+ mNeedToPrefetch = true;
+ }
+ return rv;
+}
+
+nsresult nsMsgGroupThreadEnumerator::Prefetch() {
+ nsresult rv = NS_OK; // XXX or should this default to an error?
+ mResultHdr = nullptr;
+ if (mThreadParentKey == nsMsgKey_None) {
+ rv = mThread->GetRootHdr(getter_AddRefs(mResultHdr));
+ NS_ASSERTION(NS_SUCCEEDED(rv) && mResultHdr,
+ "better be able to get root hdr");
+ mChildIndex = 0; // since root can be anywhere, set mChildIndex to 0.
+ } else if (!mDone) {
+ uint32_t numChildren;
+ mThread->GetNumChildren(&numChildren);
+
+ while ((uint32_t)mChildIndex < numChildren) {
+ rv = mThread->GetChildHdrAt(mChildIndex++, getter_AddRefs(mResultHdr));
+ if (NS_SUCCEEDED(rv) && mResultHdr) {
+ nsMsgKey parentKey;
+ nsMsgKey curKey;
+
+ if (mFilter && NS_FAILED(mFilter(mResultHdr, mClosure))) {
+ mResultHdr = nullptr;
+ continue;
+ }
+
+ mResultHdr->GetThreadParent(&parentKey);
+ mResultHdr->GetMessageKey(&curKey);
+ // if the parent is the same as the msg we're enumerating over,
+ // or the parentKey isn't set, and we're iterating over the top
+ // level message in the thread, then leave mResultHdr set to cur msg.
+ if (parentKey == mThreadParentKey ||
+ (parentKey == nsMsgKey_None && mThreadParentKey == mFirstMsgKey &&
+ curKey != mThreadParentKey))
+ break;
+ mResultHdr = nullptr;
+ } else
+ NS_ASSERTION(false, "better be able to get child");
+ }
+ // if (!mResultHdr && mThreadParentKey == mFirstMsgKey && !mFoundChildren &&
+ // numChildren > 1) {
+ // mThread->ReparentMsgsWithInvalidParent(numChildren, mThreadParentKey);
+ // }
+ }
+ if (!mResultHdr) {
+ mDone = true;
+ return NS_ERROR_FAILURE;
+ }
+ if (NS_FAILED(rv)) {
+ mDone = true;
+ return rv;
+ } else
+ mNeedToPrefetch = false;
+ mFoundChildren = true;
+
+#ifdef DEBUG_bienvenu1
+ nsMsgKey debugMsgKey;
+ mResultHdr->GetMessageKey(&debugMsgKey);
+ printf("next for %ld = %ld\n", mThreadParentKey, debugMsgKey);
+#endif
+
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgGroupThreadEnumerator::HasMoreElements(bool* aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+ if (mNeedToPrefetch) Prefetch();
+ *aResult = !mDone;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgGroupThread::EnumerateMessages(nsMsgKey parentKey,
+ nsIMsgEnumerator** result) {
+ NS_ADDREF(*result = new nsMsgGroupThreadEnumerator(this, parentKey, nullptr,
+ nullptr));
+ return NS_OK;
+}
+
+#if 0
+nsresult nsMsgGroupThread::ReparentMsgsWithInvalidParent(uint32_t numChildren, nsMsgKey threadParentKey)
+{
+ nsresult ret = NS_OK;
+ // run through looking for messages that don't have a correct parent,
+ // i.e., a parent that's in the thread!
+ for (int32_t childIndex = 0; childIndex < (int32_t) numChildren; childIndex++)
+ {
+ nsCOMPtr<nsIMsgDBHdr> curChild;
+ ret = GetChildHdrAt(childIndex, getter_AddRefs(curChild));
+ if (NS_SUCCEEDED(ret) && curChild)
+ {
+ nsMsgKey parentKey;
+ nsCOMPtr<nsIMsgDBHdr> parent;
+
+ curChild->GetThreadParent(&parentKey);
+
+ if (parentKey != nsMsgKey_None)
+ {
+ GetChild(parentKey, getter_AddRefs(parent));
+ if (!parent)
+ curChild->SetThreadParent(threadParentKey);
+ }
+ }
+ }
+ return ret;
+}
+#endif
+
+NS_IMETHODIMP nsMsgGroupThread::GetRootHdr(nsIMsgDBHdr** result) {
+ NS_ENSURE_ARG_POINTER(result);
+
+ *result = nullptr;
+ int32_t resultIndex = -1;
+
+ if (m_threadRootKey != nsMsgKey_None) {
+ nsresult ret = GetChildHdrForKey(m_threadRootKey, result, &resultIndex);
+ if (NS_SUCCEEDED(ret) && *result)
+ return ret;
+ else {
+ printf("need to reset thread root key\n");
+ uint32_t numChildren;
+ nsMsgKey threadParentKey = nsMsgKey_None;
+ GetNumChildren(&numChildren);
+
+ for (uint32_t childIndex = 0; childIndex < numChildren; childIndex++) {
+ nsCOMPtr<nsIMsgDBHdr> curChild;
+ ret = GetChildHdrAt(childIndex, getter_AddRefs(curChild));
+ if (NS_SUCCEEDED(ret) && curChild) {
+ nsMsgKey parentKey;
+
+ curChild->GetThreadParent(&parentKey);
+ if (parentKey == nsMsgKey_None) {
+ NS_ASSERTION(!(*result), "two top level msgs, not good");
+ curChild->GetMessageKey(&threadParentKey);
+ m_threadRootKey = threadParentKey;
+ curChild.forget(result);
+ }
+ }
+ }
+ if (*result) {
+ return NS_OK;
+ }
+ }
+ // if we can't get the thread root key, we'll just get the first hdr.
+ // there's a bug where sometimes we weren't resetting the thread root key
+ // when removing the thread root key.
+ }
+ return GetChildHdrAt(0, result);
+}
+
+nsresult nsMsgGroupThread::ChangeUnreadChildCount(int32_t delta) {
+ m_numUnreadChildren += delta;
+ return NS_OK;
+}
+
+nsresult nsMsgGroupThread::GetChildHdrForKey(nsMsgKey desiredKey,
+ nsIMsgDBHdr** result,
+ int32_t* resultIndex) {
+ NS_ENSURE_ARG_POINTER(result);
+
+ nsresult rv = NS_OK; // XXX or should this default to an error?
+ uint32_t numChildren = 0;
+ GetNumChildren(&numChildren);
+
+ uint32_t childIndex;
+ for (childIndex = 0; childIndex < numChildren; childIndex++) {
+ nsCOMPtr<nsIMsgDBHdr> child;
+ rv = GetChildHdrAt(childIndex, getter_AddRefs(child));
+ if (NS_SUCCEEDED(rv) && child) {
+ nsMsgKey msgKey;
+ // we're only doing one level of threading, so check if caller is
+ // asking for children of the first message in the thread or not.
+ // if not, we will tell him there are no children.
+ child->GetMessageKey(&msgKey);
+
+ if (msgKey == desiredKey) {
+ child.forget(result);
+ break;
+ }
+ }
+ }
+ if (resultIndex) *resultIndex = (int32_t)childIndex;
+
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgGroupThread::GetFirstUnreadChild(nsIMsgDBHdr** result) {
+ NS_ENSURE_ARG_POINTER(result);
+
+ uint32_t numChildren = 0;
+ GetNumChildren(&numChildren);
+
+ for (uint32_t childIndex = 0; childIndex < numChildren; childIndex++) {
+ nsCOMPtr<nsIMsgDBHdr> child;
+ nsresult rv = GetChildHdrAt(childIndex, getter_AddRefs(child));
+ if (NS_SUCCEEDED(rv) && child) {
+ nsMsgKey msgKey;
+ child->GetMessageKey(&msgKey);
+
+ bool isRead;
+ rv = m_db->IsRead(msgKey, &isRead);
+ if (NS_SUCCEEDED(rv) && !isRead) {
+ child.forget(result);
+ break;
+ }
+ }
+ }
+
+ return (*result) ? NS_OK : NS_ERROR_NULL_POINTER;
+}
+
+NS_IMETHODIMP nsMsgGroupThread::GetNewestMsgDate(uint32_t* aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ // if this hasn't been set, figure it out by enumerating the msgs in the
+ // thread.
+ if (!m_newestMsgDate) {
+ nsresult rv = NS_OK;
+
+ uint32_t numChildren = 0;
+ GetNumChildren(&numChildren);
+
+ for (uint32_t childIndex = 0; childIndex < numChildren; childIndex++) {
+ nsCOMPtr<nsIMsgDBHdr> child;
+ rv = GetChildHdrAt(childIndex, getter_AddRefs(child));
+ if (NS_SUCCEEDED(rv) && child) {
+ uint32_t msgDate;
+ child->GetDateInSeconds(&msgDate);
+ if (msgDate > m_newestMsgDate) m_newestMsgDate = msgDate;
+ }
+ }
+ }
+ *aResult = m_newestMsgDate;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgGroupThread::SetNewestMsgDate(uint32_t aNewestMsgDate) {
+ m_newestMsgDate = aNewestMsgDate;
+ return NS_OK;
+}
+
+nsMsgXFGroupThread::nsMsgXFGroupThread() {}
+
+nsMsgXFGroupThread::~nsMsgXFGroupThread() {}
+
+NS_IMETHODIMP nsMsgXFGroupThread::GetNumChildren(uint32_t* aNumChildren) {
+ NS_ENSURE_ARG_POINTER(aNumChildren);
+ *aNumChildren = m_folders.Length();
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgXFGroupThread::GetChildHdrAt(uint32_t aIndex,
+ nsIMsgDBHdr** aResult) {
+ if (aIndex >= m_folders.Length()) return NS_MSG_MESSAGE_NOT_FOUND;
+ return m_folders.ObjectAt(aIndex)->GetMessageHeader(m_keys[aIndex], aResult);
+}
+
+NS_IMETHODIMP nsMsgXFGroupThread::GetChildKeyAt(uint32_t aIndex,
+ nsMsgKey* aResult) {
+ NS_ASSERTION(false, "shouldn't call this");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsMsgXFGroupThread::RemoveChildAt(uint32_t aIndex) {
+ NS_ENSURE_TRUE(aIndex < m_folders.Length(), NS_MSG_MESSAGE_NOT_FOUND);
+
+ nsresult rv = nsMsgGroupThread::RemoveChildAt(aIndex);
+ NS_ENSURE_SUCCESS(rv, rv);
+ m_folders.RemoveElementAt(aIndex);
+ return NS_OK;
+}
+
+void nsMsgXFGroupThread::InsertMsgHdrAt(nsMsgViewIndex index,
+ nsIMsgDBHdr* hdr) {
+ nsCOMPtr<nsIMsgFolder> folder;
+ hdr->GetFolder(getter_AddRefs(folder));
+ m_folders.InsertObjectAt(folder, index);
+ nsMsgGroupThread::InsertMsgHdrAt(index, hdr);
+}
+
+void nsMsgXFGroupThread::SetMsgHdrAt(nsMsgViewIndex index, nsIMsgDBHdr* hdr) {
+ nsCOMPtr<nsIMsgFolder> folder;
+ hdr->GetFolder(getter_AddRefs(folder));
+ m_folders.ReplaceObjectAt(folder, index);
+ nsMsgGroupThread::SetMsgHdrAt(index, hdr);
+}
+
+nsMsgViewIndex nsMsgXFGroupThread::FindMsgHdr(nsIMsgDBHdr* hdr) {
+ nsMsgKey msgKey;
+ hdr->GetMessageKey(&msgKey);
+ nsCOMPtr<nsIMsgFolder> folder;
+ hdr->GetFolder(getter_AddRefs(folder));
+ size_t index = 0;
+ while (true) {
+ index = m_keys.IndexOf(msgKey, index);
+ if (index == m_keys.NoIndex || m_folders[index] == folder) break;
+ index++;
+ }
+ return (nsMsgViewIndex)index;
+}
+
+nsMsgViewIndex nsMsgXFGroupThread::AddMsgHdrInDateOrder(nsIMsgDBHdr* child,
+ nsMsgDBView* view) {
+ nsMsgViewIndex insertIndex =
+ nsMsgGroupThread::AddMsgHdrInDateOrder(child, view);
+ nsCOMPtr<nsIMsgFolder> folder;
+ child->GetFolder(getter_AddRefs(folder));
+ m_folders.InsertObjectAt(folder, insertIndex);
+ return insertIndex;
+}
+nsMsgViewIndex nsMsgXFGroupThread::GetInsertIndexFromView(
+ nsMsgDBView* view, nsIMsgDBHdr* child,
+ nsMsgViewSortOrderValue threadSortOrder) {
+ return view->GetInsertIndexHelper(child, m_keys, &m_folders, threadSortOrder,
+ nsMsgViewSortType::byDate);
+}
diff --git a/comm/mailnews/base/src/nsMsgGroupThread.h b/comm/mailnews/base/src/nsMsgGroupThread.h
new file mode 100644
index 0000000000..735b9580e3
--- /dev/null
+++ b/comm/mailnews/base/src/nsMsgGroupThread.h
@@ -0,0 +1,88 @@
+/* -*- 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 "mozilla/Attributes.h"
+#include "msgCore.h"
+#include "nsCOMArray.h"
+#include "nsIMsgThread.h"
+#include "MailNewsTypes.h"
+#include "nsTArray.h"
+#include "nsIMsgDatabase.h"
+#include "nsIMsgHdr.h"
+#include "nsMsgDBView.h"
+
+class nsMsgGroupView;
+
+class nsMsgGroupThread : public nsIMsgThread {
+ public:
+ friend class nsMsgGroupView;
+
+ nsMsgGroupThread();
+ explicit nsMsgGroupThread(nsIMsgDatabase* db);
+
+ NS_DECL_NSIMSGTHREAD
+ NS_DECL_ISUPPORTS
+
+ protected:
+ virtual ~nsMsgGroupThread();
+
+ void Init();
+ nsMsgViewIndex AddChildFromGroupView(nsIMsgDBHdr* child, nsMsgDBView* view);
+ nsresult RemoveChild(nsMsgKey msgKey);
+ nsresult RerootThread(nsIMsgDBHdr* newParentOfOldRoot, nsIMsgDBHdr* oldRoot,
+ nsIDBChangeAnnouncer* announcer);
+
+ virtual nsMsgViewIndex AddMsgHdrInDateOrder(nsIMsgDBHdr* child,
+ nsMsgDBView* view);
+ virtual nsMsgViewIndex GetInsertIndexFromView(
+ nsMsgDBView* view, nsIMsgDBHdr* child,
+ nsMsgViewSortOrderValue threadSortOrder);
+ nsresult ReparentNonReferenceChildrenOf(nsIMsgDBHdr* topLevelHdr,
+ nsMsgKey newParentKey,
+ nsIDBChangeAnnouncer* announcer);
+
+ nsresult ReparentChildrenOf(nsMsgKey oldParent, nsMsgKey newParent,
+ nsIDBChangeAnnouncer* announcer);
+ nsresult ChangeUnreadChildCount(int32_t delta);
+ nsresult GetChildHdrForKey(nsMsgKey desiredKey, nsIMsgDBHdr** result,
+ int32_t* resultIndex);
+ uint32_t NumRealChildren();
+ virtual void InsertMsgHdrAt(nsMsgViewIndex index, nsIMsgDBHdr* hdr);
+ virtual void SetMsgHdrAt(nsMsgViewIndex index, nsIMsgDBHdr* hdr);
+ virtual nsMsgViewIndex FindMsgHdr(nsIMsgDBHdr* hdr);
+
+ nsMsgKey m_threadKey;
+ uint32_t m_numUnreadChildren;
+ uint32_t m_flags;
+ nsMsgKey m_threadRootKey;
+ uint32_t m_newestMsgDate;
+ nsTArray<nsMsgKey> m_keys;
+ bool m_dummy; // top level msg is a dummy, e.g., grouped by age.
+ nsCOMPtr<nsIMsgDatabase> m_db; // should we make a weak ref or just a ptr?
+};
+
+class nsMsgXFGroupThread : public nsMsgGroupThread {
+ public:
+ nsMsgXFGroupThread();
+
+ NS_IMETHOD GetNumChildren(uint32_t* aNumChildren) override;
+ NS_IMETHOD GetChildKeyAt(uint32_t aIndex, nsMsgKey* aResult) override;
+ NS_IMETHOD GetChildHdrAt(uint32_t aIndex, nsIMsgDBHdr** aResult) override;
+ NS_IMETHOD RemoveChildAt(uint32_t aIndex) override;
+
+ protected:
+ virtual ~nsMsgXFGroupThread();
+
+ virtual void InsertMsgHdrAt(nsMsgViewIndex index, nsIMsgDBHdr* hdr) override;
+ virtual void SetMsgHdrAt(nsMsgViewIndex index, nsIMsgDBHdr* hdr) override;
+ virtual nsMsgViewIndex FindMsgHdr(nsIMsgDBHdr* hdr) override;
+ virtual nsMsgViewIndex AddMsgHdrInDateOrder(nsIMsgDBHdr* child,
+ nsMsgDBView* view) override;
+ virtual nsMsgViewIndex GetInsertIndexFromView(
+ nsMsgDBView* view, nsIMsgDBHdr* child,
+ nsMsgViewSortOrderValue threadSortOrder) override;
+
+ nsCOMArray<nsIMsgFolder> m_folders;
+};
diff --git a/comm/mailnews/base/src/nsMsgGroupView.cpp b/comm/mailnews/base/src/nsMsgGroupView.cpp
new file mode 100644
index 0000000000..ca42d01a5a
--- /dev/null
+++ b/comm/mailnews/base/src/nsMsgGroupView.cpp
@@ -0,0 +1,941 @@
+/* -*- 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 "msgCore.h"
+#include "nsMsgUtils.h"
+#include "nsMsgGroupView.h"
+#include "nsIMsgHdr.h"
+#include "nsIMsgThread.h"
+#include "nsIDBFolderInfo.h"
+#include "nsIMsgSearchSession.h"
+#include "nsMsgGroupThread.h"
+#include "nsTreeColumns.h"
+#include "nsMsgMessageFlags.h"
+#include <plhash.h>
+#include "mozilla/Attributes.h"
+
+// Allocate this more to avoid reallocation on new mail.
+#define MSGHDR_CACHE_LOOK_AHEAD_SIZE 25
+// Max msghdr cache entries.
+#define MSGHDR_CACHE_MAX_SIZE 8192
+#define MSGHDR_CACHE_DEFAULT_SIZE 100
+
+nsMsgGroupView::nsMsgGroupView() { m_dayChanged = false; }
+
+nsMsgGroupView::~nsMsgGroupView() {}
+
+NS_IMETHODIMP
+nsMsgGroupView::Open(nsIMsgFolder* aFolder, nsMsgViewSortTypeValue aSortType,
+ nsMsgViewSortOrderValue aSortOrder,
+ nsMsgViewFlagsTypeValue aViewFlags, int32_t* aCount) {
+ nsresult rv =
+ nsMsgDBView::Open(aFolder, aSortType, aSortOrder, aViewFlags, aCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIDBFolderInfo> dbFolderInfo;
+ PersistFolderInfo(getter_AddRefs(dbFolderInfo));
+
+ nsCOMPtr<nsIMsgEnumerator> headers;
+ rv = m_db->EnumerateMessages(getter_AddRefs(headers));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return OpenWithHdrs(headers, aSortType, aSortOrder, aViewFlags, aCount);
+}
+
+void nsMsgGroupView::InternalClose() {
+ m_groupsTable.Clear();
+ // Nothing to do if we're not grouped.
+ if (!(m_viewFlags & nsMsgViewFlagsType::kGroupBySort)) return;
+
+ bool rcvDate = false;
+
+ if (m_sortType == nsMsgViewSortType::byReceived) rcvDate = true;
+
+ if (m_db && ((m_sortType == nsMsgViewSortType::byDate) ||
+ (m_sortType == nsMsgViewSortType::byReceived))) {
+ nsCOMPtr<nsIDBFolderInfo> dbFolderInfo;
+ m_db->GetDBFolderInfo(getter_AddRefs(dbFolderInfo));
+ if (dbFolderInfo) {
+ uint32_t expandFlags = 0;
+ uint32_t num = GetSize();
+
+ for (uint32_t i = 0; i < num; i++) {
+ if (m_flags[i] & MSG_VIEW_FLAG_ISTHREAD &&
+ !(m_flags[i] & nsMsgMessageFlags::Elided)) {
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ GetMsgHdrForViewIndex(i, getter_AddRefs(msgHdr));
+ if (msgHdr) {
+ uint32_t ageBucket;
+ nsresult rv = GetAgeBucketValue(msgHdr, &ageBucket, rcvDate);
+ if (NS_SUCCEEDED(rv)) expandFlags |= 1 << ageBucket;
+ }
+ }
+ }
+ dbFolderInfo->SetUint32Property("dateGroupFlags", expandFlags);
+ }
+ }
+}
+
+NS_IMETHODIMP
+nsMsgGroupView::Close() {
+ InternalClose();
+ return nsMsgDBView::Close();
+}
+
+// Set rcvDate to true to get the Received: date instead of the Date: date.
+nsresult nsMsgGroupView::GetAgeBucketValue(nsIMsgDBHdr* aMsgHdr,
+ uint32_t* aAgeBucket, bool rcvDate) {
+ NS_ENSURE_ARG_POINTER(aMsgHdr);
+ NS_ENSURE_ARG_POINTER(aAgeBucket);
+
+ PRTime dateOfMsg;
+ nsresult rv;
+ if (!rcvDate)
+ rv = aMsgHdr->GetDate(&dateOfMsg);
+ else {
+ uint32_t rcvDateSecs;
+ rv = aMsgHdr->GetUint32Property("dateReceived", &rcvDateSecs);
+ Seconds2PRTime(rcvDateSecs, &dateOfMsg);
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ PRTime currentTime = PR_Now();
+ PRExplodedTime currentExplodedTime;
+ PR_ExplodeTime(currentTime, PR_LocalTimeParameters, &currentExplodedTime);
+ PRExplodedTime explodedMsgTime;
+ PR_ExplodeTime(dateOfMsg, PR_LocalTimeParameters, &explodedMsgTime);
+
+ if (m_lastCurExplodedTime.tm_mday &&
+ m_lastCurExplodedTime.tm_mday != currentExplodedTime.tm_mday)
+ // This will cause us to rebuild the view.
+ m_dayChanged = true;
+
+ m_lastCurExplodedTime = currentExplodedTime;
+ if (currentExplodedTime.tm_year == explodedMsgTime.tm_year &&
+ currentExplodedTime.tm_month == explodedMsgTime.tm_month &&
+ currentExplodedTime.tm_mday == explodedMsgTime.tm_mday) {
+ // Same day.
+ *aAgeBucket = 1;
+ }
+ // Figure out how many days ago this msg arrived.
+ else if (currentTime > dateOfMsg) {
+ // Setting the time variables to local time.
+ int64_t GMTLocalTimeShift = currentExplodedTime.tm_params.tp_gmt_offset +
+ currentExplodedTime.tm_params.tp_dst_offset;
+ GMTLocalTimeShift *= PR_USEC_PER_SEC;
+ currentTime += GMTLocalTimeShift;
+ dateOfMsg += GMTLocalTimeShift;
+
+ // The most recent midnight, counting from current time.
+ int64_t mostRecentMidnight = currentTime - currentTime % PR_USEC_PER_DAY;
+ int64_t yesterday = mostRecentMidnight - PR_USEC_PER_DAY;
+ // Most recent midnight minus 6 days.
+ int64_t mostRecentWeek = mostRecentMidnight - (PR_USEC_PER_DAY * 6);
+
+ // Was the message sent yesterday?
+ if (dateOfMsg >= yesterday)
+ *aAgeBucket = 2;
+ else if (dateOfMsg >= mostRecentWeek)
+ *aAgeBucket = 3;
+ else {
+ int64_t lastTwoWeeks = mostRecentMidnight - PR_USEC_PER_DAY * 13;
+ *aAgeBucket = (dateOfMsg >= lastTwoWeeks) ? 4 : 5;
+ }
+ } else {
+ // All that remains is a future date.
+ *aAgeBucket = 6;
+ }
+ return NS_OK;
+}
+
+nsresult nsMsgGroupView::HashHdr(nsIMsgDBHdr* msgHdr, nsString& aHashKey) {
+ nsCString cStringKey;
+ aHashKey.Truncate();
+ nsresult rv = NS_OK;
+ bool rcvDate = false;
+
+ switch (m_sortType) {
+ case nsMsgViewSortType::bySubject:
+ (void)msgHdr->GetSubject(cStringKey);
+ CopyASCIItoUTF16(cStringKey, aHashKey);
+ break;
+ case nsMsgViewSortType::byAuthor:
+ rv = nsMsgDBView::FetchAuthor(msgHdr, aHashKey);
+ break;
+ case nsMsgViewSortType::byRecipient:
+ (void)msgHdr->GetRecipients(getter_Copies(cStringKey));
+ CopyASCIItoUTF16(cStringKey, aHashKey);
+ break;
+ case nsMsgViewSortType::byAccount:
+ case nsMsgViewSortType::byTags: {
+ nsCOMPtr<nsIMsgDatabase> dbToUse = m_db;
+ if (!dbToUse)
+ // Probably a search view.
+ GetDBForViewIndex(0, getter_AddRefs(dbToUse));
+
+ rv = (m_sortType == nsMsgViewSortType::byAccount)
+ ? FetchAccount(msgHdr, aHashKey)
+ : FetchTags(msgHdr, aHashKey);
+ } break;
+ case nsMsgViewSortType::byAttachments: {
+ uint32_t flags;
+ msgHdr->GetFlags(&flags);
+ aHashKey.Assign(flags & nsMsgMessageFlags::Attachment ? '1' : '0');
+ break;
+ }
+ case nsMsgViewSortType::byFlagged: {
+ uint32_t flags;
+ msgHdr->GetFlags(&flags);
+ aHashKey.Assign(flags & nsMsgMessageFlags::Marked ? '1' : '0');
+ break;
+ }
+ case nsMsgViewSortType::byPriority: {
+ nsMsgPriorityValue priority;
+ msgHdr->GetPriority(&priority);
+ aHashKey.AppendInt(priority);
+ } break;
+ case nsMsgViewSortType::byStatus: {
+ uint32_t status = 0;
+ GetStatusSortValue(msgHdr, &status);
+ aHashKey.AppendInt(status);
+ } break;
+ case nsMsgViewSortType::byReceived:
+ rcvDate = true;
+ [[fallthrough]];
+ case nsMsgViewSortType::byDate: {
+ uint32_t ageBucket;
+ rv = GetAgeBucketValue(msgHdr, &ageBucket, rcvDate);
+ if (NS_SUCCEEDED(rv)) aHashKey.AppendInt(ageBucket);
+
+ break;
+ }
+ case nsMsgViewSortType::byCustom: {
+ nsIMsgCustomColumnHandler* colHandler = GetCurColumnHandler();
+ if (colHandler) {
+ bool isString;
+ colHandler->IsString(&isString);
+ if (isString) {
+ rv = colHandler->GetSortStringForRow(msgHdr, aHashKey);
+ } else {
+ uint32_t intKey;
+ rv = colHandler->GetSortLongForRow(msgHdr, &intKey);
+ aHashKey.AppendInt(intKey);
+ }
+ }
+ break;
+ }
+ case nsMsgViewSortType::byCorrespondent:
+ if (IsOutgoingMsg(msgHdr))
+ rv = FetchRecipients(msgHdr, aHashKey);
+ else
+ rv = FetchAuthor(msgHdr, aHashKey);
+
+ break;
+ default:
+ NS_ASSERTION(false, "no hash key for this type");
+ rv = NS_ERROR_FAILURE;
+ }
+ return rv;
+}
+
+nsMsgGroupThread* nsMsgGroupView::CreateGroupThread(nsIMsgDatabase* db) {
+ return new nsMsgGroupThread(db);
+}
+
+nsMsgGroupThread* nsMsgGroupView::AddHdrToThread(nsIMsgDBHdr* msgHdr,
+ bool* pNewThread) {
+ nsMsgKey msgKey;
+ uint32_t msgFlags;
+ msgHdr->GetMessageKey(&msgKey);
+ msgHdr->GetFlags(&msgFlags);
+ nsString hashKey;
+ nsresult rv = HashHdr(msgHdr, hashKey);
+ if (NS_FAILED(rv)) return nullptr;
+
+ // if (m_sortType == nsMsgViewSortType::byDate)
+ // msgKey = ((nsPRUint32Key *)hashKey)->GetValue();
+ nsCOMPtr<nsIMsgThread> msgThread;
+ m_groupsTable.Get(hashKey, getter_AddRefs(msgThread));
+ bool newThread = !msgThread;
+ *pNewThread = newThread;
+ // Index of first message in thread in view.
+ nsMsgViewIndex viewIndexOfThread;
+ // Index of newly added header in thread.
+ nsMsgViewIndex threadInsertIndex;
+
+ nsMsgGroupThread* foundThread =
+ static_cast<nsMsgGroupThread*>(msgThread.get());
+ if (foundThread) {
+ // Find the view index of the root node of the thread in the view.
+ viewIndexOfThread = GetIndexOfFirstDisplayedKeyInThread(foundThread, true);
+ if (viewIndexOfThread == nsMsgViewIndex_None) {
+ // Something is wrong with the group table. Remove the old group and
+ // insert a new one.
+ m_groupsTable.Remove(hashKey);
+ foundThread = nullptr;
+ *pNewThread = newThread = true;
+ }
+ }
+
+ // If the thread does not already exist, create one
+ if (!foundThread) {
+ foundThread = CreateGroupThread(m_db);
+ msgThread = foundThread;
+ m_groupsTable.InsertOrUpdate(hashKey, msgThread);
+ if (GroupViewUsesDummyRow()) {
+ foundThread->m_dummy = true;
+ msgFlags |= MSG_VIEW_FLAG_DUMMY | MSG_VIEW_FLAG_HASCHILDREN;
+ }
+
+ viewIndexOfThread = GetInsertIndex(msgHdr);
+ if (viewIndexOfThread == nsMsgViewIndex_None)
+ viewIndexOfThread = m_keys.Length();
+
+ // Add the thread root node to the view.
+ InsertMsgHdrAt(
+ viewIndexOfThread, msgHdr, msgKey,
+ msgFlags | MSG_VIEW_FLAG_ISTHREAD | nsMsgMessageFlags::Elided, 0);
+
+ // For dummy rows, Have the header serve as the dummy node (it will be
+ // added again for its actual content later).
+ if (GroupViewUsesDummyRow()) foundThread->InsertMsgHdrAt(0, msgHdr);
+
+ // Calculate the (integer thread key); this really only needs to be done for
+ // the byDate case where the expanded state of the groups can be easily
+ // persisted and restored because of the bounded, consecutive value space
+ // occupied. We calculate an integer value in all cases mainly because
+ // it's the sanest choice available...
+ // (The thread key needs to be an integer, so parse hash keys that are
+ // stringified integers to real integers, and hash actual strings into
+ // integers.)
+ if ((m_sortType == nsMsgViewSortType::byAttachments) ||
+ (m_sortType == nsMsgViewSortType::byFlagged) ||
+ (m_sortType == nsMsgViewSortType::byPriority) ||
+ (m_sortType == nsMsgViewSortType::byStatus) ||
+ (m_sortType == nsMsgViewSortType::byReceived) ||
+ (m_sortType == nsMsgViewSortType::byDate))
+ foundThread->m_threadKey =
+ atoi(NS_LossyConvertUTF16toASCII(hashKey).get());
+ else
+ foundThread->m_threadKey =
+ (nsMsgKey)PL_HashString(NS_LossyConvertUTF16toASCII(hashKey).get());
+ }
+
+ // Add the message to the thread as an actual content-bearing header.
+ // (If we use dummy rows, it was already added to the thread during creation.)
+ threadInsertIndex = foundThread->AddChildFromGroupView(msgHdr, this);
+ // Check if new hdr became thread root.
+ if (!newThread && threadInsertIndex == 0) {
+ // Update the root node's header (in the view) to be the same as the root
+ // node in the thread.
+ SetMsgHdrAt(msgHdr, viewIndexOfThread, msgKey,
+ (msgFlags & ~(nsMsgMessageFlags::Elided)) |
+ // Maintain elided flag and dummy flag.
+ (m_flags[viewIndexOfThread] &
+ (nsMsgMessageFlags::Elided | MSG_VIEW_FLAG_DUMMY)) |
+ // Ensure thread and has-children flags are set.
+ MSG_VIEW_FLAG_ISTHREAD | MSG_VIEW_FLAG_HASCHILDREN,
+ 0);
+ // Update the content-bearing copy in the thread to match. (the root and
+ // first nodes in the thread should always be the same header.)
+ // Note: the guy who used to be the root will still exist. If our list of
+ // nodes was [A A], a new node B is introduced which sorts to be the first
+ // node, giving us [B A A], our copy makes that [B B A], and things are
+ // right in the world (since we want the first two headers to be the same
+ // since one is our dummy and one is real.)
+ if (GroupViewUsesDummyRow()) {
+ // Replace the old duplicate dummy header.
+ // We do not update the content-bearing copy in the view to match; we
+ // leave that up to OnNewHeader, which is the piece of code who gets to
+ // care about whether the thread's children are shown or not (elided).
+ foundThread->SetMsgHdrAt(1, msgHdr);
+ }
+ }
+
+ return foundThread;
+}
+
+NS_IMETHODIMP
+nsMsgGroupView::OpenWithHdrs(nsIMsgEnumerator* aHeaders,
+ nsMsgViewSortTypeValue aSortType,
+ nsMsgViewSortOrderValue aSortOrder,
+ nsMsgViewFlagsTypeValue aViewFlags,
+ int32_t* aCount) {
+ nsresult rv = NS_OK;
+
+ m_groupsTable.Clear();
+ if (aSortType == nsMsgViewSortType::byThread ||
+ aSortType == nsMsgViewSortType::byId ||
+ aSortType == nsMsgViewSortType::byNone ||
+ aSortType == nsMsgViewSortType::bySize)
+ return NS_ERROR_INVALID_ARG;
+
+ m_sortType = aSortType;
+ m_sortOrder = aSortOrder;
+ m_viewFlags = aViewFlags | nsMsgViewFlagsType::kThreadedDisplay |
+ nsMsgViewFlagsType::kGroupBySort;
+ SaveSortInfo(m_sortType, m_sortOrder);
+
+ if (m_sortType == nsMsgViewSortType::byCustom) {
+ // If the desired sort is a custom column and there is no handler found,
+ // it hasn't been registered yet; after the custom column observer is
+ // notified with MsgCreateDBView and registers the handler, it will come
+ // back and build the view.
+ nsIMsgCustomColumnHandler* colHandler = GetCurColumnHandler();
+ if (!colHandler) return rv;
+ }
+
+ bool hasMore;
+ nsCOMPtr<nsISupports> supports;
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ while (NS_SUCCEEDED(rv) &&
+ NS_SUCCEEDED(rv = aHeaders->HasMoreElements(&hasMore)) && hasMore) {
+ rv = aHeaders->GetNext(getter_AddRefs(msgHdr));
+ if (NS_SUCCEEDED(rv) && msgHdr) {
+ bool notUsed;
+ AddHdrToThread(msgHdr, &notUsed);
+ }
+ }
+ uint32_t expandFlags = 0;
+ bool expandAll = m_viewFlags & nsMsgViewFlagsType::kExpandAll;
+ uint32_t viewFlag =
+ (m_sortType == nsMsgViewSortType::byDate) ? MSG_VIEW_FLAG_DUMMY : 0;
+ if (viewFlag && m_db) {
+ nsCOMPtr<nsIDBFolderInfo> dbFolderInfo;
+ nsresult rv = m_db->GetDBFolderInfo(getter_AddRefs(dbFolderInfo));
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (dbFolderInfo)
+ dbFolderInfo->GetUint32Property("dateGroupFlags", 0, &expandFlags);
+ }
+ // Go through the view updating the flags for threads with more than one
+ // message, and if grouped by date, expanding threads that were expanded
+ // before.
+ for (uint32_t viewIndex = 0; viewIndex < m_keys.Length(); viewIndex++) {
+ nsCOMPtr<nsIMsgThread> thread;
+ GetThreadContainingIndex(viewIndex, getter_AddRefs(thread));
+ if (thread) {
+ uint32_t numChildren;
+ thread->GetNumChildren(&numChildren);
+ if (numChildren > 1 || viewFlag)
+ OrExtraFlag(viewIndex, viewFlag | MSG_VIEW_FLAG_HASCHILDREN);
+ if (expandAll || expandFlags) {
+ nsMsgGroupThread* groupThread =
+ static_cast<nsMsgGroupThread*>((nsIMsgThread*)thread);
+ if (expandAll || expandFlags & (1 << groupThread->m_threadKey)) {
+ uint32_t numExpanded;
+ ExpandByIndex(viewIndex, &numExpanded);
+ viewIndex += numExpanded;
+ }
+ }
+ }
+ }
+ *aCount = m_keys.Length();
+ return rv;
+}
+
+// We wouldn't need this if we never instantiated this directly,
+// but instead used nsMsgThreadedDBView with the grouping flag set.
+// Or, we could get rid of the nsMsgThreadedDBView impl of this method.
+NS_IMETHODIMP
+nsMsgGroupView::GetViewType(nsMsgViewTypeValue* aViewType) {
+ NS_ENSURE_ARG_POINTER(aViewType);
+ *aViewType = nsMsgViewType::eShowAllThreads;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgGroupView::CopyDBView(nsMsgDBView* aNewMsgDBView,
+ nsIMessenger* aMessengerInstance,
+ nsIMsgWindow* aMsgWindow,
+ nsIMsgDBViewCommandUpdater* aCmdUpdater) {
+ nsMsgDBView::CopyDBView(aNewMsgDBView, aMessengerInstance, aMsgWindow,
+ aCmdUpdater);
+ nsMsgGroupView* newMsgDBView = (nsMsgGroupView*)aNewMsgDBView;
+
+ // If grouped, we need to clone the group thread hash table.
+ if (m_viewFlags & nsMsgViewFlagsType::kGroupBySort) {
+ for (auto iter = m_groupsTable.Iter(); !iter.Done(); iter.Next()) {
+ newMsgDBView->m_groupsTable.InsertOrUpdate(iter.Key(), iter.UserData());
+ }
+ }
+ return NS_OK;
+}
+
+// E.g., if the day has changed, we need to close and re-open the view.
+// Or, if we're switching between grouping and threading in a cross-folder
+// saved search. In that case, we needed to build an enumerator based on the
+// old view type, and internally close the view based on its old type, but
+// rebuild the new view based on the new view type. So we pass the new
+// view flags to OpenWithHdrs.
+nsresult nsMsgGroupView::RebuildView(nsMsgViewFlagsTypeValue newFlags) {
+ nsCOMPtr<nsIMsgEnumerator> headers;
+ if (NS_SUCCEEDED(GetMessageEnumerator(getter_AddRefs(headers)))) {
+ int32_t count;
+ m_dayChanged = false;
+ AutoTArray<nsMsgKey, 1> preservedSelection;
+ nsMsgKey curSelectedKey;
+ SaveAndClearSelection(&curSelectedKey, preservedSelection);
+ InternalClose();
+ int32_t oldSize = GetSize();
+ // This is important, because the tree will ask us for our row count,
+ // which gets determined from the number of keys.
+ m_keys.Clear();
+ // Be consistent.
+ m_flags.Clear();
+ m_levels.Clear();
+
+ // This needs to happen after we remove all the keys, since
+ // RowCountChanged() will call our GetRowCount().
+ if (mTree) mTree->RowCountChanged(0, -oldSize);
+ if (mJSTree) mJSTree->RowCountChanged(0, -oldSize);
+
+ SetSuppressChangeNotifications(true);
+ nsresult rv =
+ OpenWithHdrs(headers, m_sortType, m_sortOrder, newFlags, &count);
+ SetSuppressChangeNotifications(false);
+ if (mTree) mTree->RowCountChanged(0, GetSize());
+ if (mJSTree) mJSTree->RowCountChanged(0, GetSize());
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Now, restore our desired selection.
+ AutoTArray<nsMsgKey, 1> keyArray;
+ keyArray.AppendElement(curSelectedKey);
+
+ return RestoreSelection(curSelectedKey, keyArray);
+ }
+ return NS_OK;
+}
+
+nsresult nsMsgGroupView::OnNewHeader(nsIMsgDBHdr* newHdr, nsMsgKey aParentKey,
+ bool ensureListed) {
+ if (!(m_viewFlags & nsMsgViewFlagsType::kGroupBySort))
+ return nsMsgDBView::OnNewHeader(newHdr, aParentKey, ensureListed);
+
+ // Check if we're adding a header, and the current day has changed.
+ // If it has, we're just going to close and re-open the view so things
+ // will be correctly categorized.
+ if (m_dayChanged) return RebuildView(m_viewFlags);
+
+ bool newThread;
+ nsMsgGroupThread* thread = AddHdrToThread(newHdr, &newThread);
+ if (thread) {
+ // Find the view index of (the root node of) the thread.
+ nsMsgViewIndex threadIndex = ThreadIndexOfMsgHdr(newHdr);
+ // May need to fix thread counts.
+ if (threadIndex != nsMsgViewIndex_None) {
+ if (newThread) {
+ // AddHdrToThread creates the header elided, so we need to un-elide it
+ // if we want it expanded.
+ if (m_viewFlags & nsMsgViewFlagsType::kExpandAll)
+ m_flags[threadIndex] &= ~nsMsgMessageFlags::Elided;
+ } else {
+ m_flags[threadIndex] |=
+ MSG_VIEW_FLAG_HASCHILDREN | MSG_VIEW_FLAG_ISTHREAD;
+ }
+
+ int32_t numRowsToInvalidate = 1;
+ // If the thread is expanded (not elided), we should add the header to
+ // the view.
+ if (!(m_flags[threadIndex] & nsMsgMessageFlags::Elided)) {
+ uint32_t msgIndexInThread = thread->FindMsgHdr(newHdr);
+ bool insertedAtThreadRoot = !msgIndexInThread;
+ // Add any new display node and potentially fix-up changes in the root.
+ // (If this is a new thread and we are not using a dummy row, the only
+ // node to display is the root node which has already been added by
+ // AddHdrToThread. And since there is just the one, no change in root
+ // could have occurred, so we have nothing to do.)
+ if (!newThread || GroupViewUsesDummyRow()) {
+ // We never want to insert/update the root node, because
+ // AddHdrToThread has already done that for us (in all cases).
+ if (insertedAtThreadRoot) msgIndexInThread++;
+ // If this header is the new parent of the thread... AND
+ // If we are not using a dummy row, this means we need to append our
+ // old node as the first child of the new root.
+ // (If we are using a dummy row, the old node's "content" node already
+ // exists (at position threadIndex + 1) and we need to insert the
+ // "content" copy of the new root node there, pushing our old
+ // "content" node down.)
+ // Example mini-diagrams, wrapping the to-add thing with ()
+ // No dummy row; we had: [A], now we have [B], we want [B (A)].
+ // Dummy row; we had: [A A], now we have [B A], we want [B (B) A].
+ // (Coming into this we're adding 'B')
+ if (!newThread && insertedAtThreadRoot && !GroupViewUsesDummyRow()) {
+ // Grab a copy of the old root node ('A') from the thread so we can
+ // insert it. (offset msgIndexInThread=1 is the right thing; we are
+ // non-dummy.)
+ thread->GetChildHdrAt(msgIndexInThread, &newHdr);
+ }
+ // Nothing to do for dummy case, we're already inserting 'B'.
+
+ nsMsgKey msgKey;
+ uint32_t msgFlags;
+ newHdr->GetMessageKey(&msgKey);
+ newHdr->GetFlags(&msgFlags);
+ InsertMsgHdrAt(threadIndex + msgIndexInThread, newHdr, msgKey,
+ msgFlags, 1);
+ }
+ // The call to NoteChange() has to happen after we add the key
+ // as NoteChange() will call RowCountChanged() which will call our
+ // GetRowCount().
+ // (msgIndexInThread states - new thread: 0, old thread at root: 1).
+ if (newThread && GroupViewUsesDummyRow())
+ NoteChange(threadIndex, 2, nsMsgViewNotificationCode::insertOrDelete);
+ else
+ NoteChange(threadIndex + msgIndexInThread, 1,
+ nsMsgViewNotificationCode::insertOrDelete);
+
+ numRowsToInvalidate = msgIndexInThread;
+ } else if (newThread) {
+ // We still need the addition notification for new threads when elided.
+ NoteChange(threadIndex, 1, nsMsgViewNotificationCode::insertOrDelete);
+ }
+
+ NoteChange(threadIndex, numRowsToInvalidate,
+ nsMsgViewNotificationCode::changed);
+ }
+ }
+
+ // If thread is expanded, we need to add hdr to view...
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgGroupView::OnHdrFlagsChanged(nsIMsgDBHdr* aHdrChanged, uint32_t aOldFlags,
+ uint32_t aNewFlags,
+ nsIDBChangeListener* aInstigator) {
+ if (!(m_viewFlags & nsMsgViewFlagsType::kGroupBySort))
+ return nsMsgDBView::OnHdrFlagsChanged(aHdrChanged, aOldFlags, aNewFlags,
+ aInstigator);
+
+ nsCOMPtr<nsIMsgThread> thread;
+
+ // Check if we're adding a header, and the current day has changed.
+ // If it has, we're just going to close and re-open the view so things
+ // will be correctly categorized.
+ if (m_dayChanged) return RebuildView(m_viewFlags);
+
+ nsresult rv = GetThreadContainingMsgHdr(aHdrChanged, getter_AddRefs(thread));
+ NS_ENSURE_SUCCESS(rv, rv);
+ uint32_t deltaFlags = (aOldFlags ^ aNewFlags);
+ if (deltaFlags & nsMsgMessageFlags::Read)
+ thread->MarkChildRead(aNewFlags & nsMsgMessageFlags::Read);
+
+ return nsMsgDBView::OnHdrFlagsChanged(aHdrChanged, aOldFlags, aNewFlags,
+ aInstigator);
+}
+
+NS_IMETHODIMP
+nsMsgGroupView::OnHdrDeleted(nsIMsgDBHdr* aHdrDeleted, nsMsgKey aParentKey,
+ int32_t aFlags, nsIDBChangeListener* aInstigator) {
+ if (!(m_viewFlags & nsMsgViewFlagsType::kGroupBySort))
+ return nsMsgDBView::OnHdrDeleted(aHdrDeleted, aParentKey, aFlags,
+ aInstigator);
+
+ // Check if we're adding a header, and the current day has changed.
+ // If it has, we're just going to close and re-open the view so things
+ // will be correctly categorized.
+ if (m_dayChanged) return RebuildView(m_viewFlags);
+
+ nsCOMPtr<nsIMsgThread> thread;
+ nsMsgKey keyDeleted;
+ aHdrDeleted->GetMessageKey(&keyDeleted);
+
+ nsresult rv = GetThreadContainingMsgHdr(aHdrDeleted, getter_AddRefs(thread));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsMsgViewIndex viewIndexOfThread =
+ GetIndexOfFirstDisplayedKeyInThread(thread, true); // Yes to dummy node.
+
+ thread->RemoveChildHdr(aHdrDeleted, nullptr);
+
+ nsMsgGroupThread* groupThread =
+ static_cast<nsMsgGroupThread*>((nsIMsgThread*)thread);
+
+ bool rootDeleted = viewIndexOfThread != nsMsgKey_None &&
+ m_keys[viewIndexOfThread] == keyDeleted;
+ rv = nsMsgDBView::OnHdrDeleted(aHdrDeleted, aParentKey, aFlags, aInstigator);
+ if (groupThread->m_dummy) {
+ if (!groupThread->NumRealChildren()) {
+ // Get rid of dummy.
+ thread->RemoveChildAt(0);
+ if (viewIndexOfThread != nsMsgKey_None) {
+ RemoveByIndex(viewIndexOfThread);
+ if (m_deletingRows)
+ mIndicesToNoteChange.AppendElement(viewIndexOfThread);
+ }
+ } else if (rootDeleted) {
+ // Reflect new thread root into view.dummy row.
+ nsCOMPtr<nsIMsgDBHdr> hdr;
+ thread->GetChildHdrAt(0, getter_AddRefs(hdr));
+ if (hdr) {
+ nsMsgKey msgKey;
+ hdr->GetMessageKey(&msgKey);
+ SetMsgHdrAt(hdr, viewIndexOfThread, msgKey, m_flags[viewIndexOfThread],
+ 0);
+ }
+ }
+ }
+ if (!groupThread->m_keys.Length()) {
+ nsString hashKey;
+ rv = HashHdr(aHdrDeleted, hashKey);
+ if (NS_SUCCEEDED(rv)) m_groupsTable.Remove(hashKey);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgGroupView::GetRowProperties(int32_t aRow, nsAString& aProperties) {
+ if (!IsValidIndex(aRow)) return NS_MSG_INVALID_DBVIEW_INDEX;
+
+ if (m_flags[aRow] & MSG_VIEW_FLAG_DUMMY) {
+ aProperties.AssignLiteral("dummy");
+ return NS_OK;
+ }
+
+ return nsMsgDBView::GetRowProperties(aRow, aProperties);
+}
+
+NS_IMETHODIMP
+nsMsgGroupView::GetCellProperties(int32_t aRow, nsTreeColumn* aCol,
+ nsAString& aProperties) {
+ if (!IsValidIndex(aRow)) return NS_MSG_INVALID_DBVIEW_INDEX;
+
+ if (m_flags[aRow] & MSG_VIEW_FLAG_DUMMY) {
+ aProperties.AssignLiteral("dummy read");
+
+ if (!(m_flags[aRow] & nsMsgMessageFlags::Elided)) return NS_OK;
+
+ // Set unread property if a collapsed group thread has unread.
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ nsresult rv = GetMsgHdrForViewIndex(aRow, getter_AddRefs(msgHdr));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsString hashKey;
+ rv = HashHdr(msgHdr, hashKey);
+ if (NS_FAILED(rv)) return NS_OK;
+
+ nsCOMPtr<nsIMsgThread> msgThread;
+ m_groupsTable.Get(hashKey, getter_AddRefs(msgThread));
+ nsMsgGroupThread* groupThread =
+ static_cast<nsMsgGroupThread*>(msgThread.get());
+ if (!groupThread) return NS_OK;
+
+ uint32_t numUnrMsg = 0;
+ groupThread->GetNumUnreadChildren(&numUnrMsg);
+ if (numUnrMsg > 0) aProperties.AppendLiteral(" hasUnread");
+
+ return NS_OK;
+ }
+
+ return nsMsgDBView::GetCellProperties(aRow, aCol, aProperties);
+}
+
+NS_IMETHODIMP
+nsMsgGroupView::CellTextForColumn(int32_t aRow, const nsAString& aColumnName,
+ nsAString& aValue) {
+ if (!IsValidIndex(aRow)) return NS_MSG_INVALID_DBVIEW_INDEX;
+
+ if (!(m_flags[aRow] & MSG_VIEW_FLAG_DUMMY) ||
+ aColumnName.EqualsLiteral("unreadCol"))
+ return nsMsgDBView::CellTextForColumn(aRow, aColumnName, aValue);
+
+ // We only treat "subject" and "total" here.
+ bool isSubject;
+ if (!(isSubject = aColumnName.EqualsLiteral("subjectCol")) &&
+ !aColumnName.EqualsLiteral("totalCol"))
+ return NS_OK;
+
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ nsresult rv = GetMsgHdrForViewIndex(aRow, getter_AddRefs(msgHdr));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsString hashKey;
+ rv = HashHdr(msgHdr, hashKey);
+ if (NS_FAILED(rv)) return NS_OK;
+ nsCOMPtr<nsIMsgThread> msgThread;
+ m_groupsTable.Get(hashKey, getter_AddRefs(msgThread));
+ nsMsgGroupThread* groupThread =
+ static_cast<nsMsgGroupThread*>(msgThread.get());
+ if (isSubject) {
+ uint32_t flags;
+ bool rcvDate = false;
+ msgHdr->GetFlags(&flags);
+ aValue.Truncate();
+ switch (m_sortType) {
+ case nsMsgViewSortType::byReceived:
+ rcvDate = true;
+ [[fallthrough]];
+ case nsMsgViewSortType::byDate: {
+ uint32_t ageBucket = 0;
+ GetAgeBucketValue(msgHdr, &ageBucket, rcvDate);
+ switch (ageBucket) {
+ case 1:
+ aValue.Assign(nsMsgDBView::kTodayString);
+ break;
+ case 2:
+ aValue.Assign(nsMsgDBView::kYesterdayString);
+ break;
+ case 3:
+ aValue.Assign(nsMsgDBView::kLastWeekString);
+ break;
+ case 4:
+ aValue.Assign(nsMsgDBView::kTwoWeeksAgoString);
+ break;
+ case 5:
+ aValue.Assign(nsMsgDBView::kOldMailString);
+ break;
+ default:
+ // Future date, error/spoofed.
+ aValue.Assign(nsMsgDBView::kFutureDateString);
+ break;
+ }
+ break;
+ }
+ case nsMsgViewSortType::bySubject:
+ FetchSubject(msgHdr, m_flags[aRow], aValue);
+ break;
+ case nsMsgViewSortType::byAuthor:
+ FetchAuthor(msgHdr, aValue);
+ break;
+ case nsMsgViewSortType::byStatus:
+ rv = FetchStatus(m_flags[aRow], aValue);
+ if (aValue.IsEmpty()) {
+ GetString(u"messagesWithNoStatus", aValue);
+ }
+ break;
+ case nsMsgViewSortType::byTags:
+ rv = FetchTags(msgHdr, aValue);
+ if (aValue.IsEmpty()) {
+ GetString(u"untaggedMessages", aValue);
+ }
+ break;
+ case nsMsgViewSortType::byPriority:
+ FetchPriority(msgHdr, aValue);
+ if (aValue.IsEmpty()) {
+ GetString(u"noPriority", aValue);
+ }
+ break;
+ case nsMsgViewSortType::byAccount:
+ FetchAccount(msgHdr, aValue);
+ break;
+ case nsMsgViewSortType::byRecipient:
+ FetchRecipients(msgHdr, aValue);
+ break;
+ case nsMsgViewSortType::byAttachments:
+ GetString(flags & nsMsgMessageFlags::Attachment ? u"attachments"
+ : u"noAttachments",
+ aValue);
+ break;
+ case nsMsgViewSortType::byFlagged:
+ GetString(
+ flags & nsMsgMessageFlags::Marked ? u"groupFlagged" : u"notFlagged",
+ aValue);
+ break;
+ // byLocation is a special case; we don't want to have duplicate
+ // all this logic in nsMsgSearchDBView, and its hash key is what we
+ // want anyways, so just copy it across.
+ case nsMsgViewSortType::byLocation:
+ case nsMsgViewSortType::byCorrespondent:
+ aValue = hashKey;
+ break;
+ case nsMsgViewSortType::byCustom: {
+ nsIMsgCustomColumnHandler* colHandler = GetCurColumnHandler();
+ if (colHandler) {
+ bool isString;
+ colHandler->IsString(&isString);
+ if (isString) {
+ rv = colHandler->GetSortStringForRow(msgHdr.get(), aValue);
+ } else {
+ uint32_t intKey;
+ rv = colHandler->GetSortLongForRow(msgHdr.get(), &intKey);
+ aValue.AppendInt(intKey);
+ }
+ }
+ if (aValue.IsEmpty()) aValue.Assign('*');
+ break;
+ }
+
+ default:
+ NS_ASSERTION(false, "we don't sort by group for this type");
+ break;
+ }
+
+ if (groupThread) {
+ // Get number of messages in group.
+ nsAutoString formattedCountMsg;
+ uint32_t numMsg = groupThread->NumRealChildren();
+ formattedCountMsg.AppendInt(numMsg);
+
+ // Get number of unread messages.
+ nsAutoString formattedCountUnrMsg;
+ uint32_t numUnrMsg = 0;
+ groupThread->GetNumUnreadChildren(&numUnrMsg);
+ formattedCountUnrMsg.AppendInt(numUnrMsg);
+
+ // Add text to header.
+ aValue.AppendLiteral(u" (");
+ if (numUnrMsg) {
+ aValue.Append(formattedCountUnrMsg);
+ aValue.Append(u'/');
+ }
+
+ aValue.Append(formattedCountMsg);
+ aValue.Append(u')');
+ }
+ } else {
+ nsAutoString formattedCountString;
+ uint32_t numChildren = (groupThread) ? groupThread->NumRealChildren() : 0;
+ formattedCountString.AppendInt(numChildren);
+ aValue.Assign(formattedCountString);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgGroupView::GetThreadContainingMsgHdr(nsIMsgDBHdr* msgHdr,
+ nsIMsgThread** pThread) {
+ if (!(m_viewFlags & nsMsgViewFlagsType::kGroupBySort))
+ return nsMsgDBView::GetThreadContainingMsgHdr(msgHdr, pThread);
+
+ nsString hashKey;
+ nsresult rv = HashHdr(msgHdr, hashKey);
+ *pThread = nullptr;
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIMsgThread> thread;
+ m_groupsTable.Get(hashKey, getter_AddRefs(thread));
+ thread.forget(pThread);
+ }
+
+ return (*pThread) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+int32_t nsMsgGroupView::FindLevelInThread(nsIMsgDBHdr* msgHdr,
+ nsMsgViewIndex startOfThread,
+ nsMsgViewIndex viewIndex) {
+ if (!(m_viewFlags & nsMsgViewFlagsType::kGroupBySort))
+ return nsMsgDBView::FindLevelInThread(msgHdr, startOfThread, viewIndex);
+
+ return (startOfThread == viewIndex) ? 0 : 1;
+}
+
+bool nsMsgGroupView::GroupViewUsesDummyRow() {
+ // Return true to always use a header row as root grouped parent row.
+ return true;
+}
+
+NS_IMETHODIMP
+nsMsgGroupView::AddColumnHandler(const nsAString& column,
+ nsIMsgCustomColumnHandler* handler) {
+ nsMsgDBView::AddColumnHandler(column, handler);
+
+ // If the sortType is byCustom and the desired custom column is the one just
+ // registered, build the view.
+ if (m_viewFlags & nsMsgViewFlagsType::kGroupBySort &&
+ m_sortType == nsMsgViewSortType::byCustom) {
+ nsAutoString curCustomColumn;
+ GetCurCustomColumn(curCustomColumn);
+ if (curCustomColumn == column) RebuildView(m_viewFlags);
+ }
+
+ return NS_OK;
+}
diff --git a/comm/mailnews/base/src/nsMsgGroupView.h b/comm/mailnews/base/src/nsMsgGroupView.h
new file mode 100644
index 0000000000..3b646f159b
--- /dev/null
+++ b/comm/mailnews/base/src/nsMsgGroupView.h
@@ -0,0 +1,78 @@
+/* -*- 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 _nsMsgGroupView_H_
+#define _nsMsgGroupView_H_
+
+#include "mozilla/Attributes.h"
+#include "nsMsgDBView.h"
+#include "nsInterfaceHashtable.h"
+
+class nsIMsgThread;
+class nsMsgGroupThread;
+
+// Please note that if you override a method of nsMsgDBView,
+// you will most likely want to check the m_viewFlags to see if
+// we're grouping, and if not, call the base class implementation.
+class nsMsgGroupView : public nsMsgDBView {
+ public:
+ nsMsgGroupView();
+ virtual ~nsMsgGroupView();
+
+ NS_IMETHOD Open(nsIMsgFolder* folder, nsMsgViewSortTypeValue sortType,
+ nsMsgViewSortOrderValue sortOrder,
+ nsMsgViewFlagsTypeValue viewFlags, int32_t* pCount) override;
+ NS_IMETHOD OpenWithHdrs(nsIMsgEnumerator* aHeaders,
+ nsMsgViewSortTypeValue aSortType,
+ nsMsgViewSortOrderValue aSortOrder,
+ nsMsgViewFlagsTypeValue aViewFlags,
+ int32_t* aCount) override;
+ NS_IMETHOD GetViewType(nsMsgViewTypeValue* aViewType) override;
+ NS_IMETHOD CopyDBView(nsMsgDBView* aNewMsgDBView,
+ nsIMessenger* aMessengerInstance,
+ nsIMsgWindow* aMsgWindow,
+ nsIMsgDBViewCommandUpdater* aCmdUpdater);
+ NS_IMETHOD Close() override;
+ NS_IMETHOD OnHdrDeleted(nsIMsgDBHdr* aHdrDeleted, nsMsgKey aParentKey,
+ int32_t aFlags,
+ nsIDBChangeListener* aInstigator) override;
+ NS_IMETHOD OnHdrFlagsChanged(nsIMsgDBHdr* aHdrChanged, uint32_t aOldFlags,
+ uint32_t aNewFlags,
+ nsIDBChangeListener* aInstigator) override;
+
+ NS_IMETHOD GetCellProperties(int32_t aRow, nsTreeColumn* aCol,
+ nsAString& aProperties) override;
+ NS_IMETHOD GetRowProperties(int32_t aRow, nsAString& aProperties) override;
+ NS_IMETHOD CellTextForColumn(int32_t aRow, const nsAString& aColumnName,
+ nsAString& aValue) override;
+ NS_IMETHOD GetThreadContainingMsgHdr(nsIMsgDBHdr* msgHdr,
+ nsIMsgThread** pThread) override;
+ NS_IMETHOD AddColumnHandler(const nsAString& column,
+ nsIMsgCustomColumnHandler* handler) override;
+
+ protected:
+ virtual void InternalClose();
+ nsMsgGroupThread* AddHdrToThread(nsIMsgDBHdr* msgHdr, bool* pNewThread);
+ virtual nsresult HashHdr(nsIMsgDBHdr* msgHdr, nsString& aHashKey);
+ // Helper function to get age bucket for a hdr, useful when grouped by date.
+ nsresult GetAgeBucketValue(nsIMsgDBHdr* aMsgHdr, uint32_t* aAgeBucket,
+ bool rcvDate = false);
+ nsresult OnNewHeader(nsIMsgDBHdr* newHdr, nsMsgKey aParentKey,
+ bool /*ensureListed*/) override;
+ virtual int32_t FindLevelInThread(nsIMsgDBHdr* msgHdr,
+ nsMsgViewIndex startOfThread,
+ nsMsgViewIndex viewIndex) override;
+
+ // Returns true if we are grouped by a sort attribute that uses a dummy row.
+ bool GroupViewUsesDummyRow();
+ nsresult RebuildView(nsMsgViewFlagsTypeValue viewFlags);
+ virtual nsMsgGroupThread* CreateGroupThread(nsIMsgDatabase* db);
+
+ nsInterfaceHashtable<nsStringHashKey, nsIMsgThread> m_groupsTable;
+ PRExplodedTime m_lastCurExplodedTime{0};
+ bool m_dayChanged;
+};
+
+#endif
diff --git a/comm/mailnews/base/src/nsMsgI18N.cpp b/comm/mailnews/base/src/nsMsgI18N.cpp
new file mode 100644
index 0000000000..1c81456403
--- /dev/null
+++ b/comm/mailnews/base/src/nsMsgI18N.cpp
@@ -0,0 +1,403 @@
+/* -*- 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 "nsICharsetConverterManager.h"
+#include "mozilla/Utf8.h"
+#include "nsIServiceManager.h"
+
+#include "nsISupports.h"
+#include "nsIPrefBranch.h"
+#include "nsIPrefService.h"
+#include "nsIMimeConverter.h"
+#include "nsMsgUtils.h"
+#include "nsMsgI18N.h"
+#include "nsILineInputStream.h"
+#include "nsMimeTypes.h"
+#include "nsString.h"
+#include "prmem.h"
+#include "plstr.h"
+#include "nsUTF8Utils.h"
+#include "nsNetUtil.h"
+#include "nsCRTGlue.h"
+#include "nsComponentManagerUtils.h"
+#include "nsUnicharUtils.h"
+#include "nsIFileStreams.h"
+#include "../../intl/nsUTF7ToUnicode.h"
+#include "../../intl/nsMUTF7ToUnicode.h"
+#include "../../intl/nsUnicodeToMUTF7.h"
+
+#include <stdlib.h>
+#include <tuple>
+
+//
+// International functions necessary for composition
+//
+
+nsresult nsMsgI18NConvertFromUnicode(const nsACString& aCharset,
+ const nsAString& inString,
+ nsACString& outString,
+ bool aReportUencNoMapping) {
+ if (inString.IsEmpty()) {
+ outString.Truncate();
+ return NS_OK;
+ }
+
+ auto encoding = mozilla::Encoding::ForLabelNoReplacement(aCharset);
+ if (!encoding) {
+ return NS_ERROR_UCONV_NOCONV;
+ } else if (encoding == UTF_16LE_ENCODING || encoding == UTF_16BE_ENCODING) {
+ // We shouldn't ever ship anything in these encodings.
+ return NS_ERROR_UCONV_NOCONV;
+ }
+
+ nsresult rv;
+ std::tie(rv, std::ignore) = encoding->Encode(inString, outString);
+
+ if (rv == NS_OK_HAD_REPLACEMENTS) {
+ rv = aReportUencNoMapping ? NS_ERROR_UENC_NOMAPPING : NS_OK;
+ }
+
+ return rv;
+}
+
+nsresult nsMsgI18NConvertToUnicode(const nsACString& aCharset,
+ const nsACString& inString,
+ nsAString& outString) {
+ if (inString.IsEmpty()) {
+ outString.Truncate();
+ return NS_OK;
+ }
+ if (aCharset.IsEmpty()) {
+ // Despite its name, it also works for Latin-1.
+ CopyASCIItoUTF16(inString, outString);
+ return NS_OK;
+ }
+
+ if (aCharset.Equals("UTF-8", nsCaseInsensitiveCStringComparator)) {
+ return UTF_8_ENCODING->DecodeWithBOMRemoval(inString, outString);
+ }
+
+ // Look up Thunderbird's special aliases from charsetalias.properties.
+ nsresult rv;
+ nsCOMPtr<nsICharsetConverterManager> ccm =
+ do_GetService(NS_CHARSETCONVERTERMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCString newCharset;
+ rv = ccm->GetCharsetAlias(PromiseFlatCString(aCharset).get(), newCharset);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (newCharset.Equals("UTF-7", nsCaseInsensitiveCStringComparator)) {
+ // Special treatment for decoding UTF-7 since it's not handled by
+ // encoding_rs.
+ return CopyUTF7toUTF16(inString, outString);
+ }
+
+ auto encoding = mozilla::Encoding::ForLabelNoReplacement(newCharset);
+ if (!encoding) return NS_ERROR_UCONV_NOCONV;
+ return encoding->DecodeWithoutBOMHandling(inString, outString);
+}
+
+// This is used to decode UTF-7. No support for encoding in UTF-7.
+nsresult CopyUTF7toUTF16(const nsACString& aSrc, nsAString& aDest) {
+ // UTF-7 encoding size cannot be larger than the size in UTF-16.
+ nsUTF7ToUnicode converter;
+ int32_t inLen = aSrc.Length();
+ int32_t outLen = inLen;
+ aDest.SetLength(outLen);
+ converter.ConvertNoBuff(aSrc.BeginReading(), &inLen, aDest.BeginWriting(),
+ &outLen);
+ MOZ_ASSERT(inLen == (int32_t)aSrc.Length(),
+ "UTF-7 should not produce a longer output");
+ aDest.SetLength(outLen);
+ return NS_OK;
+}
+
+nsresult CopyUTF16toMUTF7(const nsAString& aSrc, nsACString& aDest) {
+#define IMAP_UTF7_BUF_LENGTH 100
+ nsUnicodeToMUTF7 converter;
+ static char buffer[IMAP_UTF7_BUF_LENGTH];
+ const char16_t* in = aSrc.BeginReading();
+ int32_t inLen = aSrc.Length();
+ int32_t outLen;
+ aDest.Truncate();
+ while (inLen > 0) {
+ outLen = IMAP_UTF7_BUF_LENGTH;
+ int32_t remaining = inLen;
+ converter.ConvertNoBuffNoErr(in, &remaining, buffer, &outLen);
+ aDest.Append(buffer, outLen);
+ in += remaining;
+ inLen -= remaining;
+ }
+ outLen = IMAP_UTF7_BUF_LENGTH;
+ converter.FinishNoBuff(buffer, &outLen);
+ if (outLen > 0) aDest.Append(buffer, outLen);
+ return NS_OK;
+}
+
+// Hacky function to use for IMAP folders where the name can be in
+// MUTF-7 or UTF-8.
+nsresult CopyFolderNameToUTF16(const nsACString& aSrc, nsAString& aDest) {
+ if (NS_IsAscii(aSrc.BeginReading(), aSrc.Length())) {
+ // An ASCII string may not be valid MUTF-7. For example, it may contain an
+ // ampersand not immediately followed by a dash which is invalid MUTF-7.
+ // Check for validity by converting to UTF-16 and then back to MUTF-7 and
+ // the result should be unchanged. If the MUTF-7 is invalid, treat it as
+ // UTF-8.
+ if (NS_SUCCEEDED(CopyMUTF7toUTF16(aSrc, aDest))) {
+ nsAutoCString tmp;
+ CopyUTF16toMUTF7(aDest, tmp);
+ if (aSrc.Equals(tmp)) return NS_OK;
+ }
+ }
+ // Do if aSrc non-ASCII or if ASCII but invalid MUTF-7.
+ CopyUTF8toUTF16(aSrc, aDest);
+ return NS_OK;
+}
+
+nsresult CopyMUTF7toUTF16(const nsACString& aSrc, nsAString& aDest) {
+ // MUTF-7 encoding size cannot be larger than the size in UTF-16.
+ nsMUTF7ToUnicode converter;
+ int32_t inLen = aSrc.Length();
+ int32_t outLen = inLen;
+ aDest.SetLength(outLen);
+ converter.ConvertNoBuff(aSrc.BeginReading(), &inLen, aDest.BeginWriting(),
+ &outLen);
+ MOZ_ASSERT(inLen == (int32_t)aSrc.Length(),
+ "MUTF-7 should not produce a longer output");
+ aDest.SetLength(outLen);
+ return NS_OK;
+}
+
+// MIME encoder, output string should be freed by PR_FREE
+// XXX : fix callers later to avoid allocation and copy
+char* nsMsgI18NEncodeMimePartIIStr(const char* header, bool structured,
+ const char* charset, int32_t fieldnamelen,
+ bool usemime) {
+ // No MIME, convert to the outgoing mail charset.
+ if (!usemime) {
+ nsAutoCString convertedStr;
+ if (NS_SUCCEEDED(nsMsgI18NConvertFromUnicode(
+ charset ? nsDependentCString(charset) : EmptyCString(),
+ NS_ConvertUTF8toUTF16(header), convertedStr)))
+ return PL_strdup(convertedStr.get());
+ else
+ return PL_strdup(header);
+ }
+
+ nsAutoCString encodedString;
+ nsresult res;
+ nsCOMPtr<nsIMimeConverter> converter =
+ do_GetService("@mozilla.org/messenger/mimeconverter;1", &res);
+ if (NS_SUCCEEDED(res) && nullptr != converter) {
+ res = converter->EncodeMimePartIIStr_UTF8(
+ nsDependentCString(header), structured, fieldnamelen,
+ nsIMimeConverter::MIME_ENCODED_WORD_SIZE, encodedString);
+ }
+
+ return NS_SUCCEEDED(res) ? PL_strdup(encodedString.get()) : nullptr;
+}
+
+// Return True if a charset is stateful (e.g. JIS).
+bool nsMsgI18Nstateful_charset(const char* charset) {
+ // TODO: use charset manager's service
+ return (PL_strcasecmp(charset, "ISO-2022-JP") == 0);
+}
+
+bool nsMsgI18Nmultibyte_charset(const char* charset) {
+ nsresult res;
+ nsCOMPtr<nsICharsetConverterManager> ccm =
+ do_GetService(NS_CHARSETCONVERTERMANAGER_CONTRACTID, &res);
+ bool result = false;
+
+ if (NS_SUCCEEDED(res)) {
+ nsAutoString charsetData;
+ res = ccm->GetCharsetData(charset, u".isMultibyte", charsetData);
+ if (NS_SUCCEEDED(res)) {
+ result = charsetData.LowerCaseEqualsLiteral("true");
+ }
+ }
+
+ return result;
+}
+
+bool nsMsgI18Ncheck_data_in_charset_range(const char* charset,
+ const char16_t* inString) {
+ if (!charset || !*charset || !inString || !*inString) return true;
+
+ bool res = true;
+
+ auto encoding =
+ mozilla::Encoding::ForLabelNoReplacement(nsDependentCString(charset));
+ if (!encoding) return false;
+ auto encoder = encoding->NewEncoder();
+
+ uint8_t buffer[512];
+ auto src = mozilla::MakeStringSpan(inString);
+ auto dst = mozilla::Span(buffer);
+ while (true) {
+ uint32_t result;
+ size_t read;
+ size_t written;
+ std::tie(result, read, written) =
+ 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.
+ res = false;
+ break;
+ }
+ src = src.From(read);
+ // dst = dst.From(written); // Just overwrite output since we don't need it.
+ }
+
+ return res;
+}
+
+// Simple parser to parse META charset.
+// It only supports the case when the description is within one line.
+const char* nsMsgI18NParseMetaCharset(nsIFile* file) {
+ static char charset[nsIMimeConverter::MAX_CHARSET_NAME_LENGTH + 1];
+
+ *charset = '\0';
+
+ bool isDirectory = false;
+ file->IsDirectory(&isDirectory);
+ if (isDirectory) {
+ NS_ERROR("file is a directory");
+ return charset;
+ }
+
+ nsresult rv;
+ nsCOMPtr<nsIFileInputStream> fileStream =
+ do_CreateInstance(NS_LOCALFILEINPUTSTREAM_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, charset);
+
+ rv = fileStream->Init(file, PR_RDONLY, 0664, false);
+ nsCOMPtr<nsILineInputStream> lineStream = do_QueryInterface(fileStream, &rv);
+
+ nsCString curLine;
+ bool more = true;
+ while (NS_SUCCEEDED(rv) && more) {
+ rv = lineStream->ReadLine(curLine, &more);
+ if (curLine.IsEmpty()) continue;
+
+ ToUpperCase(curLine);
+
+ if (curLine.Find("/HEAD") != -1) break;
+
+ if (curLine.Find("META") != -1 && curLine.Find("HTTP-EQUIV") != -1 &&
+ curLine.Find("CONTENT-TYPE") != -1 && curLine.Find("CHARSET") != -1) {
+ char* cp = (char*)PL_strchr(PL_strstr(curLine.get(), "CHARSET"), '=');
+ char* token = nullptr;
+ if (cp) {
+ char* newStr = cp + 1;
+ token = NS_strtok(" \"\'", &newStr);
+ }
+ if (token) {
+ PL_strncpy(charset, token, sizeof(charset));
+ charset[sizeof(charset) - 1] = '\0';
+
+ // this function cannot parse a file if it is really
+ // encoded by one of the following charsets
+ // so we can say that the charset label must be incorrect for
+ // the .html if we actually see those charsets parsed
+ // and we should ignore them
+ if (!PL_strncasecmp("UTF-16", charset, sizeof("UTF-16") - 1) ||
+ !PL_strncasecmp("UTF-32", charset, sizeof("UTF-32") - 1))
+ charset[0] = '\0';
+
+ break;
+ }
+ }
+ }
+
+ return charset;
+}
+
+nsresult nsMsgI18NShrinkUTF8Str(const nsCString& inString, uint32_t aMaxLength,
+ nsACString& outString) {
+ if (inString.IsEmpty()) {
+ outString.Truncate();
+ return NS_OK;
+ }
+ if (inString.Length() < aMaxLength) {
+ outString.Assign(inString);
+ return NS_OK;
+ }
+ NS_ASSERTION(mozilla::IsUtf8(inString), "Invalid UTF-8 string is inputted");
+ const char* start = inString.get();
+ const char* end = start + inString.Length();
+ const char* last = start + aMaxLength;
+ const char* cur = start;
+ const char* prev = nullptr;
+ bool err = false;
+ while (cur < last) {
+ prev = cur;
+ if (!UTF8CharEnumerator::NextChar(&cur, end, &err) || err) break;
+ }
+ if (!prev || err) {
+ outString.Truncate();
+ return NS_OK;
+ }
+ uint32_t len = prev - start;
+ outString.Assign(Substring(inString, 0, len));
+ return NS_OK;
+}
+
+void nsMsgI18NConvertRawBytesToUTF16(const nsCString& inString,
+ const nsACString& charset,
+ nsAString& outString) {
+ if (mozilla::IsUtf8(inString)) {
+ CopyUTF8toUTF16(inString, outString);
+ return;
+ }
+
+ nsresult rv = nsMsgI18NConvertToUnicode(charset, inString, outString);
+ if (NS_SUCCEEDED(rv)) return;
+
+ const char* cur = inString.BeginReading();
+ const char* end = inString.EndReading();
+ outString.Truncate();
+ while (cur < end) {
+ char c = *cur++;
+ if (c & char(0x80))
+ outString.Append(UCS2_REPLACEMENT_CHAR);
+ else
+ outString.Append(c);
+ }
+}
+
+void nsMsgI18NConvertRawBytesToUTF8(const nsCString& inString,
+ const nsACString& charset,
+ nsACString& outString) {
+ if (mozilla::IsUtf8(inString)) {
+ outString.Assign(inString);
+ return;
+ }
+
+ nsAutoString utf16Text;
+ nsresult rv = nsMsgI18NConvertToUnicode(charset, inString, utf16Text);
+ if (NS_SUCCEEDED(rv)) {
+ CopyUTF16toUTF8(utf16Text, outString);
+ return;
+ }
+
+ // EF BF BD (UTF-8 encoding of U+FFFD)
+ constexpr auto utf8ReplacementChar = "\357\277\275"_ns;
+ const char* cur = inString.BeginReading();
+ const char* end = inString.EndReading();
+ outString.Truncate();
+ while (cur < end) {
+ char c = *cur++;
+ if (c & char(0x80))
+ outString.Append(utf8ReplacementChar);
+ else
+ outString.Append(c);
+ }
+}
diff --git a/comm/mailnews/base/src/nsMsgI18N.h b/comm/mailnews/base/src/nsMsgI18N.h
new file mode 100644
index 0000000000..2268b64e26
--- /dev/null
+++ b/comm/mailnews/base/src/nsMsgI18N.h
@@ -0,0 +1,138 @@
+/* -*- 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 _nsMsgI18N_H_
+#define _nsMsgI18N_H_
+
+#include "nscore.h"
+#include "msgCore.h"
+#include "nsString.h"
+class nsIFile;
+
+/**
+ * Encode an input string into RFC 2047 form.
+ *
+ * @param header [IN] A header to encode.
+ * @param structured [IN] Specify the header is structured or non-structured
+ * field (See RFC-822).
+ * @param charset [IN] Charset name to convert.
+ * @param fieldnamelen [IN] Header field name length. (e.g. "From: " -> 6)
+ * @param usemime [IN] If false then apply charset conversion only no MIME
+ * encoding.
+ * @return Encoded buffer (in C string) or NULL in case of error.
+ */
+NS_MSG_BASE char* nsMsgI18NEncodeMimePartIIStr(const char* header,
+ bool structured,
+ const char* charset,
+ int32_t fieldnamelen,
+ bool usemime);
+
+/**
+ * Check if given charset is stateful (e.g. ISO-2022-JP).
+ *
+ * @param charset [IN] Charset name.
+ * @return True if stateful
+ */
+NS_MSG_BASE bool nsMsgI18Nstateful_charset(const char* charset);
+
+/**
+ * Check if given charset is multibyte (e.g. Shift_JIS, Big5).
+ *
+ * @param charset [IN] Charset name.
+ * @return True if multibyte
+ */
+NS_MSG_BASE bool nsMsgI18Nmultibyte_charset(const char* charset);
+
+/**
+ * Check the input (unicode) string is in a range of the given charset after the
+ * conversion. Note, do not use this for large string (e.g. message body) since
+ * this actually applies the conversion to the buffer.
+ *
+ * @param charset [IN] Charset to be converted.
+ * @param inString [IN] Input unicode string to be examined.
+ * @return True if the string can be converted within the charset range.
+ * False if one or more characters cannot be converted to the
+ * target charset.
+ */
+NS_MSG_BASE bool nsMsgI18Ncheck_data_in_charset_range(const char* charset,
+ const char16_t* inString);
+/**
+ * Convert from unicode to target charset.
+ *
+ * @param charset [IN] Charset name.
+ * @param inString [IN] Unicode string to convert.
+ * @param outString [OUT] Converted output string.
+ * @param aReportUencNoMapping [IN] Set encoder to report (instead of using
+ * replacement char on errors). Set to true
+ * to receive NS_ERROR_UENC_NOMAPPING when
+ * that happens. Note that
+ * NS_ERROR_UENC_NOMAPPING is a success code!
+ * @return nsresult.
+ */
+NS_MSG_BASE nsresult nsMsgI18NConvertFromUnicode(
+ const nsACString& aCharset, const nsAString& inString,
+ nsACString& outString, bool reportUencNoMapping = false);
+/**
+ * Convert from charset to unicode.
+ *
+ * @param charset [IN] Charset name.
+ * @param inString [IN] Input string to convert.
+ * @param outString [OUT] Output unicode string.
+ * @return nsresult.
+ */
+NS_MSG_BASE nsresult nsMsgI18NConvertToUnicode(const nsACString& aCharset,
+ const nsACString& inString,
+ nsAString& outString);
+/**
+ * Parse for META charset.
+ *
+ * @param file [IN] A nsIFile.
+ * @return A charset name or empty string if not found.
+ */
+NS_MSG_BASE const char* nsMsgI18NParseMetaCharset(nsIFile* file);
+
+/**
+ * Shrink the aStr to aMaxLength bytes. Note that this doesn't check whether
+ * the aUTF8Str is valid UTF-8 string.
+ *
+ * @param inString [IN] Input UTF-8 string (it must be valid UTF-8 string)
+ * @param aMaxLength [IN] Shrink to this length (it means bytes)
+ * @param outString [OUT] Shrunken UTF-8 string
+ * @return nsresult
+ */
+NS_MSG_BASE nsresult nsMsgI18NShrinkUTF8Str(const nsCString& inString,
+ uint32_t aMaxLength,
+ nsACString& outString);
+
+/*
+ * Convert raw bytes in header to UTF-16
+ *
+ * @param inString [IN] Input raw octets
+ * @param outString [OUT] Output UTF-16 string
+ */
+NS_MSG_BASE void nsMsgI18NConvertRawBytesToUTF16(const nsCString& inString,
+ const nsACString& charset,
+ nsAString& outString);
+
+/*
+ * Convert raw bytes in header to UTF-8
+ *
+ * @param inString [IN] Input raw octets
+ * @param outString [OUT] Output UTF-8 string
+ */
+NS_MSG_BASE void nsMsgI18NConvertRawBytesToUTF8(const nsCString& inString,
+ const nsACString& charset,
+ nsACString& outString);
+
+// Decode UTF-7 to UTF-16. No encoding supported.
+NS_MSG_BASE nsresult CopyUTF7toUTF16(const nsACString& aSrc, nsAString& aDest);
+
+// Convert between UTF-16 and modified UTF-7 used for IMAP.
+NS_MSG_BASE nsresult CopyFolderNameToUTF16(const nsACString& aSrc,
+ nsAString& aDest);
+NS_MSG_BASE nsresult CopyUTF16toMUTF7(const nsAString& aSrc, nsACString& aDest);
+NS_MSG_BASE nsresult CopyMUTF7toUTF16(const nsACString& aSrc, nsAString& aDest);
+
+#endif /* _nsMsgI18N_H_ */
diff --git a/comm/mailnews/base/src/nsMsgIdentity.cpp b/comm/mailnews/base/src/nsMsgIdentity.cpp
new file mode 100644
index 0000000000..a36107203d
--- /dev/null
+++ b/comm/mailnews/base/src/nsMsgIdentity.cpp
@@ -0,0 +1,645 @@
+/* -*- 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 "msgCore.h" // for pre-compiled headers
+#include "nsMsgIdentity.h"
+#include "nsIPrefService.h"
+#include "nsString.h"
+#include "nsMsgFolderFlags.h"
+#include "nsIMsgFolder.h"
+#include "nsIMsgIncomingServer.h"
+#include "nsIMsgAccountManager.h"
+#include "mozilla/mailnews/MimeHeaderParser.h"
+#include "nsIMsgHeaderParser.h"
+#include "prprf.h"
+#include "nsISupportsPrimitives.h"
+#include "nsMsgUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "nsIUUIDGenerator.h"
+#include "mozilla/Components.h"
+
+#define REL_FILE_PREF_SUFFIX "-rel"
+
+NS_IMPL_ISUPPORTS(nsMsgIdentity, nsIMsgIdentity)
+
+/*
+ * accessors for pulling values directly out of preferences
+ * instead of member variables, etc
+ */
+
+NS_IMETHODIMP
+nsMsgIdentity::GetKey(nsACString& aKey) {
+ aKey = mKey;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgIdentity::SetKey(const nsACString& identityKey) {
+ mKey = identityKey;
+ nsresult rv;
+ nsCOMPtr<nsIPrefService> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ if (NS_FAILED(rv)) return rv;
+
+ nsAutoCString branchName;
+ branchName.AssignLiteral("mail.identity.");
+ branchName += mKey;
+ branchName.Append('.');
+ rv = prefs->GetBranch(branchName.get(), getter_AddRefs(mPrefBranch));
+ if (NS_FAILED(rv)) return rv;
+
+ rv = prefs->GetBranch("mail.identity.default.",
+ getter_AddRefs(mDefPrefBranch));
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgIdentity::GetUID(nsACString& uid) {
+ bool hasValue;
+ nsresult rv = mPrefBranch->PrefHasUserValue("uid", &hasValue);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (hasValue) {
+ return mPrefBranch->GetCharPref("uid", uid);
+ }
+
+ nsCOMPtr<nsIUUIDGenerator> uuidgen =
+ mozilla::components::UUIDGenerator::Service();
+ NS_ENSURE_TRUE(uuidgen, NS_ERROR_FAILURE);
+
+ nsID id;
+ rv = uuidgen->GenerateUUIDInPlace(&id);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ char idString[NSID_LENGTH];
+ id.ToProvidedString(idString);
+
+ uid.AppendASCII(idString + 1, NSID_LENGTH - 3);
+ return SetUID(uid);
+}
+
+NS_IMETHODIMP
+nsMsgIdentity::SetUID(const nsACString& uid) {
+ bool hasValue;
+ nsresult rv = mPrefBranch->PrefHasUserValue("uid", &hasValue);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (hasValue) {
+ return NS_ERROR_ABORT;
+ }
+ return SetCharAttribute("uid", uid);
+}
+
+nsresult nsMsgIdentity::GetIdentityName(nsAString& idName) {
+ idName.AssignLiteral("");
+ // Try to use "fullname <email>" as the name.
+ nsresult rv = GetFullAddress(idName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // If a non-empty label exists, append it.
+ nsString label;
+ rv = GetLabel(label);
+ if (NS_SUCCEEDED(rv) &&
+ !label.IsEmpty()) { // TODO: this should be localizable
+ idName.AppendLiteral(" (");
+ idName.Append(label);
+ idName.Append(')');
+ }
+
+ if (!idName.IsEmpty()) return NS_OK;
+
+ // If we still found nothing to use, use our key.
+ return ToString(idName);
+}
+
+nsresult nsMsgIdentity::GetFullAddress(nsAString& fullAddress) {
+ nsAutoString fullName;
+ nsresult rv = GetFullName(fullName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString email;
+ rv = GetEmail(email);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (fullName.IsEmpty() && email.IsEmpty()) {
+ fullAddress.Truncate();
+ } else {
+ nsCOMPtr<msgIAddressObject> mailbox;
+ nsCOMPtr<nsIMsgHeaderParser> headerParser(
+ mozilla::components::HeaderParser::Service());
+ NS_ENSURE_TRUE(headerParser, NS_ERROR_UNEXPECTED);
+ headerParser->MakeMailboxObject(fullName, NS_ConvertUTF8toUTF16(email),
+ getter_AddRefs(mailbox));
+ mailbox->ToString(fullAddress);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgIdentity::ToString(nsAString& aResult) {
+ aResult.AssignLiteral("[nsIMsgIdentity: ");
+ aResult.Append(NS_ConvertASCIItoUTF16(mKey));
+ aResult.Append(']');
+ return NS_OK;
+}
+
+/* Identity attribute accessors */
+
+NS_IMETHODIMP
+nsMsgIdentity::GetSignature(nsIFile** sig) {
+ bool gotRelPref;
+ nsresult rv =
+ NS_GetPersistentFile("sig_file" REL_FILE_PREF_SUFFIX, "sig_file", nullptr,
+ gotRelPref, sig, mPrefBranch);
+ if (NS_SUCCEEDED(rv) && !gotRelPref) {
+ rv = NS_SetPersistentFile("sig_file" REL_FILE_PREF_SUFFIX, "sig_file", *sig,
+ mPrefBranch);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "Failed to write signature file pref.");
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgIdentity::SetSignature(nsIFile* sig) {
+ nsresult rv = NS_OK;
+ if (sig)
+ rv = NS_SetPersistentFile("sig_file" REL_FILE_PREF_SUFFIX, "sig_file", sig,
+ mPrefBranch);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgIdentity::ClearAllValues() {
+ if (!mPrefBranch) return NS_ERROR_NOT_INITIALIZED;
+
+ nsTArray<nsCString> prefNames;
+ nsresult rv = mPrefBranch->GetChildList("", prefNames);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (auto& prefName : prefNames) {
+ mPrefBranch->ClearUserPref(prefName.get());
+ }
+
+ return NS_OK;
+}
+
+NS_IMPL_IDPREF_STR(EscapedVCard, "escapedVCard")
+NS_IMPL_IDPREF_STR(SmtpServerKey, "smtpServer")
+NS_IMPL_IDPREF_WSTR(FullName, "fullName")
+NS_IMPL_IDPREF_STR(Email, "useremail")
+NS_IMPL_IDPREF_BOOL(CatchAll, "catchAll")
+NS_IMPL_IDPREF_STR(CatchAllHint, "catchAllHint")
+NS_IMPL_IDPREF_WSTR(Label, "label")
+NS_IMPL_IDPREF_STR(ReplyTo, "reply_to")
+NS_IMPL_IDPREF_WSTR(Organization, "organization")
+NS_IMPL_IDPREF_BOOL(ComposeHtml, "compose_html")
+NS_IMPL_IDPREF_BOOL(AttachVCard, "attach_vcard")
+NS_IMPL_IDPREF_BOOL(AttachSignature, "attach_signature")
+NS_IMPL_IDPREF_WSTR(HtmlSigText, "htmlSigText")
+NS_IMPL_IDPREF_BOOL(HtmlSigFormat, "htmlSigFormat")
+
+NS_IMPL_IDPREF_BOOL(AutoQuote, "auto_quote")
+NS_IMPL_IDPREF_INT(ReplyOnTop, "reply_on_top")
+NS_IMPL_IDPREF_BOOL(SigBottom, "sig_bottom")
+NS_IMPL_IDPREF_BOOL(SigOnForward, "sig_on_fwd")
+NS_IMPL_IDPREF_BOOL(SigOnReply, "sig_on_reply")
+
+NS_IMPL_IDPREF_INT(SignatureDate, "sig_date")
+
+NS_IMPL_IDPREF_BOOL(DoFcc, "fcc")
+
+NS_IMPL_FOLDERPREF_STR(FccFolder, "fcc_folder", "Sent"_ns,
+ nsMsgFolderFlags::SentMail)
+NS_IMPL_IDPREF_STR(FccFolderPickerMode, "fcc_folder_picker_mode")
+NS_IMPL_IDPREF_BOOL(FccReplyFollowsParent, "fcc_reply_follows_parent")
+NS_IMPL_IDPREF_STR(DraftsFolderPickerMode, "drafts_folder_picker_mode")
+NS_IMPL_IDPREF_STR(ArchivesFolderPickerMode, "archives_folder_picker_mode")
+NS_IMPL_IDPREF_STR(TmplFolderPickerMode, "tmpl_folder_picker_mode")
+
+NS_IMPL_IDPREF_BOOL(BccSelf, "bcc_self")
+NS_IMPL_IDPREF_BOOL(BccOthers, "bcc_other")
+NS_IMPL_IDPREF_STR(BccList, "bcc_other_list")
+
+NS_IMPL_IDPREF_BOOL(SuppressSigSep, "suppress_signature_separator")
+
+NS_IMPL_IDPREF_BOOL(DoCc, "doCc")
+NS_IMPL_IDPREF_STR(DoCcList, "doCcList")
+
+NS_IMPL_IDPREF_BOOL(AttachPgpKey, "attachPgpKey")
+NS_IMPL_IDPREF_BOOL(SendAutocryptHeaders, "sendAutocryptHeaders")
+NS_IMPL_IDPREF_BOOL(AutoEncryptDrafts, "autoEncryptDrafts")
+NS_IMPL_IDPREF_BOOL(ProtectSubject, "protectSubject")
+NS_IMPL_IDPREF_INT(EncryptionPolicy, "encryptionpolicy")
+NS_IMPL_IDPREF_BOOL(SignMail, "sign_mail")
+
+NS_IMETHODIMP
+nsMsgIdentity::GetDoBcc(bool* aValue) {
+ if (!mPrefBranch) return NS_ERROR_NOT_INITIALIZED;
+
+ nsresult rv = mPrefBranch->GetBoolPref("doBcc", aValue);
+ if (NS_SUCCEEDED(rv)) return rv;
+
+ bool bccSelf = false;
+ GetBccSelf(&bccSelf);
+
+ bool bccOthers = false;
+ GetBccOthers(&bccOthers);
+
+ nsCString others;
+ GetBccList(others);
+
+ *aValue = bccSelf || (bccOthers && !others.IsEmpty());
+
+ return SetDoBcc(*aValue);
+}
+
+NS_IMETHODIMP
+nsMsgIdentity::SetDoBcc(bool aValue) {
+ return SetBoolAttribute("doBcc", aValue);
+}
+
+NS_IMETHODIMP
+nsMsgIdentity::GetDoBccList(nsACString& aValue) {
+ if (!mPrefBranch) return NS_ERROR_NOT_INITIALIZED;
+
+ nsCString val;
+ nsresult rv = mPrefBranch->GetCharPref("doBccList", val);
+ aValue = val;
+ if (NS_SUCCEEDED(rv)) return rv;
+
+ bool bccSelf = false;
+ rv = GetBccSelf(&bccSelf);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (bccSelf) GetEmail(aValue);
+
+ bool bccOthers = false;
+ rv = GetBccOthers(&bccOthers);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCString others;
+ rv = GetBccList(others);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (bccOthers && !others.IsEmpty()) {
+ if (bccSelf) aValue.Append(',');
+ aValue.Append(others);
+ }
+
+ return SetDoBccList(aValue);
+}
+
+NS_IMETHODIMP
+nsMsgIdentity::SetDoBccList(const nsACString& aValue) {
+ return SetCharAttribute("doBccList", aValue);
+}
+
+NS_IMPL_FOLDERPREF_STR(DraftFolder, "draft_folder", "Drafts"_ns,
+ nsMsgFolderFlags::Drafts)
+NS_IMPL_FOLDERPREF_STR(ArchiveFolder, "archive_folder", "Archives"_ns,
+ nsMsgFolderFlags::Archive)
+NS_IMPL_FOLDERPREF_STR(StationeryFolder, "stationery_folder", "Templates"_ns,
+ nsMsgFolderFlags::Templates)
+
+NS_IMPL_IDPREF_BOOL(ArchiveEnabled, "archive_enabled")
+NS_IMPL_IDPREF_INT(ArchiveGranularity, "archive_granularity")
+NS_IMPL_IDPREF_BOOL(ArchiveKeepFolderStructure, "archive_keep_folder_structure")
+
+NS_IMPL_IDPREF_BOOL(ShowSaveMsgDlg, "showSaveMsgDlg")
+NS_IMPL_IDPREF_STR(DirectoryServer, "directoryServer")
+NS_IMPL_IDPREF_BOOL(OverrideGlobalPref, "overrideGlobal_Pref")
+NS_IMPL_IDPREF_BOOL(AutocompleteToMyDomain, "autocompleteToMyDomain")
+
+NS_IMPL_IDPREF_BOOL(Valid, "valid")
+
+nsresult nsMsgIdentity::getFolderPref(const char* prefname, nsACString& retval,
+ const nsACString& folderName,
+ uint32_t folderflag) {
+ if (!mPrefBranch) return NS_ERROR_NOT_INITIALIZED;
+
+ nsresult rv = mPrefBranch->GetStringPref(prefname, EmptyCString(), 0, retval);
+ if (NS_SUCCEEDED(rv) && !retval.IsEmpty()) {
+ nsCOMPtr<nsIMsgFolder> folder;
+ rv = GetOrCreateFolder(retval, getter_AddRefs(folder));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ // Make sure that folder hierarchy is built so that legitimate parent-child
+ // relationship is established.
+ folder->GetServer(getter_AddRefs(server));
+ if (server) {
+ nsCOMPtr<nsIMsgFolder> rootFolder;
+ nsCOMPtr<nsIMsgFolder> deferredToRootFolder;
+ server->GetRootFolder(getter_AddRefs(rootFolder));
+ server->GetRootMsgFolder(getter_AddRefs(deferredToRootFolder));
+ // check if we're using a deferred account - if not, use the uri;
+ // otherwise, fall through to code that will fix this pref.
+ if (rootFolder == deferredToRootFolder) {
+ nsCOMPtr<nsIMsgFolder> msgFolder;
+ rv = server->GetMsgFolderFromURI(folder, retval,
+ getter_AddRefs(msgFolder));
+ return NS_SUCCEEDED(rv) ? msgFolder->GetURI(retval) : rv;
+ }
+ }
+ }
+
+ // if the server doesn't exist, fall back to the default pref.
+ rv = mDefPrefBranch->GetStringPref(prefname, EmptyCString(), 0, retval);
+ if (NS_SUCCEEDED(rv) && !retval.IsEmpty())
+ return setFolderPref(prefname, retval, folderflag);
+
+ // here I think we need to create a uri for the folder on the
+ // default server for this identity.
+ nsCOMPtr<nsIMsgAccountManager> accountManager =
+ do_GetService("@mozilla.org/messenger/account-manager;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsTArray<RefPtr<nsIMsgIncomingServer>> servers;
+ rv = accountManager->GetServersForIdentity(this, servers);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (servers.IsEmpty()) {
+ // if there are no servers for this identity, return generic failure.
+ return NS_ERROR_FAILURE;
+ }
+ nsCOMPtr<nsIMsgIncomingServer> server(servers[0]);
+ bool defaultToServer;
+ server->GetDefaultCopiesAndFoldersPrefsToServer(&defaultToServer);
+ // if we should default to special folders on the server,
+ // use the local folders server
+ if (!defaultToServer) {
+ rv = accountManager->GetLocalFoldersServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ nsCOMPtr<nsIMsgFolder> rootFolder;
+ // this will get the deferred to server's root folder, if "server"
+ // is deferred, e.g., using the pop3 global inbox.
+ rv = server->GetRootMsgFolder(getter_AddRefs(rootFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (rootFolder) {
+ rv = rootFolder->GetURI(retval);
+ NS_ENSURE_SUCCESS(rv, rv);
+ retval.Append('/');
+ retval.Append(folderName);
+ return setFolderPref(prefname, retval, folderflag);
+ }
+ return NS_ERROR_FAILURE;
+}
+
+nsresult nsMsgIdentity::setFolderPref(const char* prefname,
+ const nsACString& value,
+ uint32_t folderflag) {
+ if (!mPrefBranch) return NS_ERROR_NOT_INITIALIZED;
+
+ nsCString oldpref;
+ nsresult rv;
+ nsCOMPtr<nsIMsgFolder> folder;
+
+ if (folderflag == nsMsgFolderFlags::SentMail) {
+ // Clear the temporary return receipt filter so that the new filter
+ // rule can be recreated (by ConfigureTemporaryFilters()).
+ nsCOMPtr<nsIMsgAccountManager> accountManager =
+ do_GetService("@mozilla.org/messenger/account-manager;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsTArray<RefPtr<nsIMsgIncomingServer>> servers;
+ rv = accountManager->GetServersForIdentity(this, servers);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!servers.IsEmpty()) {
+ servers[0]->ClearTemporaryReturnReceiptsFilter();
+ // okay to fail; no need to check for return code
+ }
+ }
+
+ // get the old folder, and clear the special folder flag on it
+ rv = mPrefBranch->GetStringPref(prefname, EmptyCString(), 0, oldpref);
+ if (NS_SUCCEEDED(rv) && !oldpref.IsEmpty()) {
+ rv = GetOrCreateFolder(oldpref, getter_AddRefs(folder));
+ if (NS_SUCCEEDED(rv)) {
+ rv = folder->ClearFlag(folderflag);
+ }
+ }
+
+ // set the new folder, and set the special folder flags on it
+ rv = SetUnicharAttribute(prefname, NS_ConvertUTF8toUTF16(value));
+ if (NS_SUCCEEDED(rv) && !value.IsEmpty()) {
+ rv = GetOrCreateFolder(value, getter_AddRefs(folder));
+ if (NS_SUCCEEDED(rv)) rv = folder->SetFlag(folderflag);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgIdentity::SetUnicharAttribute(const char* aName,
+ const nsAString& val) {
+ if (!mPrefBranch) return NS_ERROR_NOT_INITIALIZED;
+
+ if (!val.IsEmpty())
+ return mPrefBranch->SetStringPref(aName, NS_ConvertUTF16toUTF8(val));
+
+ mPrefBranch->ClearUserPref(aName);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgIdentity::GetUnicharAttribute(const char* aName,
+ nsAString& val) {
+ if (!mPrefBranch) return NS_ERROR_NOT_INITIALIZED;
+
+ nsCString valueUtf8;
+ if (NS_FAILED(
+ mPrefBranch->GetStringPref(aName, EmptyCString(), 0, valueUtf8)))
+ mDefPrefBranch->GetStringPref(aName, EmptyCString(), 0, valueUtf8);
+ CopyUTF8toUTF16(valueUtf8, val);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgIdentity::SetCharAttribute(const char* aName,
+ const nsACString& val) {
+ if (!mPrefBranch) return NS_ERROR_NOT_INITIALIZED;
+
+ if (!val.IsEmpty()) return mPrefBranch->SetCharPref(aName, val);
+
+ mPrefBranch->ClearUserPref(aName);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgIdentity::GetCharAttribute(const char* aName,
+ nsACString& val) {
+ if (!mPrefBranch) return NS_ERROR_NOT_INITIALIZED;
+
+ nsCString tmpVal;
+ if (NS_FAILED(mPrefBranch->GetCharPref(aName, tmpVal)))
+ mDefPrefBranch->GetCharPref(aName, tmpVal);
+ val = tmpVal;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgIdentity::SetBoolAttribute(const char* aName, bool val) {
+ if (!mPrefBranch) return NS_ERROR_NOT_INITIALIZED;
+
+ return mPrefBranch->SetBoolPref(aName, val);
+}
+
+NS_IMETHODIMP nsMsgIdentity::GetBoolAttribute(const char* aName, bool* val) {
+ NS_ENSURE_ARG_POINTER(val);
+ if (!mPrefBranch) return NS_ERROR_NOT_INITIALIZED;
+
+ *val = false;
+
+ if (NS_FAILED(mPrefBranch->GetBoolPref(aName, val)))
+ mDefPrefBranch->GetBoolPref(aName, val);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgIdentity::SetIntAttribute(const char* aName, int32_t val) {
+ if (!mPrefBranch) return NS_ERROR_NOT_INITIALIZED;
+
+ return mPrefBranch->SetIntPref(aName, val);
+}
+
+NS_IMETHODIMP nsMsgIdentity::GetIntAttribute(const char* aName, int32_t* val) {
+ NS_ENSURE_ARG_POINTER(val);
+
+ if (!mPrefBranch) return NS_ERROR_NOT_INITIALIZED;
+
+ *val = 0;
+
+ if (NS_FAILED(mPrefBranch->GetIntPref(aName, val)))
+ mDefPrefBranch->GetIntPref(aName, val);
+
+ return NS_OK;
+}
+
+#define COPY_IDENTITY_FILE_VALUE(SRC_ID, MACRO_GETTER, MACRO_SETTER) \
+ { \
+ nsresult macro_rv; \
+ nsCOMPtr<nsIFile> macro_spec; \
+ macro_rv = SRC_ID->MACRO_GETTER(getter_AddRefs(macro_spec)); \
+ if (NS_SUCCEEDED(macro_rv)) this->MACRO_SETTER(macro_spec); \
+ }
+
+#define COPY_IDENTITY_INT_VALUE(SRC_ID, MACRO_GETTER, MACRO_SETTER) \
+ { \
+ nsresult macro_rv; \
+ int32_t macro_oldInt; \
+ macro_rv = SRC_ID->MACRO_GETTER(&macro_oldInt); \
+ if (NS_SUCCEEDED(macro_rv)) this->MACRO_SETTER(macro_oldInt); \
+ }
+
+#define COPY_IDENTITY_BOOL_VALUE(SRC_ID, MACRO_GETTER, MACRO_SETTER) \
+ { \
+ nsresult macro_rv; \
+ bool macro_oldBool; \
+ macro_rv = SRC_ID->MACRO_GETTER(&macro_oldBool); \
+ if (NS_SUCCEEDED(macro_rv)) this->MACRO_SETTER(macro_oldBool); \
+ }
+
+#define COPY_IDENTITY_STR_VALUE(SRC_ID, MACRO_GETTER, MACRO_SETTER) \
+ { \
+ nsCString macro_oldStr; \
+ nsresult macro_rv; \
+ macro_rv = SRC_ID->MACRO_GETTER(macro_oldStr); \
+ if (NS_SUCCEEDED(macro_rv)) { \
+ this->MACRO_SETTER(macro_oldStr); \
+ } \
+ }
+
+#define COPY_IDENTITY_WSTR_VALUE(SRC_ID, MACRO_GETTER, MACRO_SETTER) \
+ { \
+ nsString macro_oldStr; \
+ nsresult macro_rv; \
+ macro_rv = SRC_ID->MACRO_GETTER(macro_oldStr); \
+ if (NS_SUCCEEDED(macro_rv)) { \
+ this->MACRO_SETTER(macro_oldStr); \
+ } \
+ }
+
+NS_IMETHODIMP
+nsMsgIdentity::Copy(nsIMsgIdentity* identity) {
+ NS_ENSURE_ARG_POINTER(identity);
+
+ COPY_IDENTITY_BOOL_VALUE(identity, GetComposeHtml, SetComposeHtml)
+ COPY_IDENTITY_STR_VALUE(identity, GetEmail, SetEmail)
+ COPY_IDENTITY_BOOL_VALUE(identity, GetCatchAll, SetCatchAll)
+ COPY_IDENTITY_WSTR_VALUE(identity, GetLabel, SetLabel)
+ COPY_IDENTITY_STR_VALUE(identity, GetReplyTo, SetReplyTo)
+ COPY_IDENTITY_WSTR_VALUE(identity, GetFullName, SetFullName)
+ COPY_IDENTITY_WSTR_VALUE(identity, GetOrganization, SetOrganization)
+ COPY_IDENTITY_STR_VALUE(identity, GetDraftFolder, SetDraftFolder)
+ COPY_IDENTITY_STR_VALUE(identity, GetArchiveFolder, SetArchiveFolder)
+ COPY_IDENTITY_STR_VALUE(identity, GetFccFolder, SetFccFolder)
+ COPY_IDENTITY_BOOL_VALUE(identity, GetFccReplyFollowsParent,
+ SetFccReplyFollowsParent)
+ COPY_IDENTITY_STR_VALUE(identity, GetStationeryFolder, SetStationeryFolder)
+ COPY_IDENTITY_BOOL_VALUE(identity, GetArchiveEnabled, SetArchiveEnabled)
+ COPY_IDENTITY_INT_VALUE(identity, GetArchiveGranularity,
+ SetArchiveGranularity)
+ COPY_IDENTITY_BOOL_VALUE(identity, GetArchiveKeepFolderStructure,
+ SetArchiveKeepFolderStructure)
+ COPY_IDENTITY_BOOL_VALUE(identity, GetAttachSignature, SetAttachSignature)
+ COPY_IDENTITY_FILE_VALUE(identity, GetSignature, SetSignature)
+ COPY_IDENTITY_WSTR_VALUE(identity, GetHtmlSigText, SetHtmlSigText)
+ COPY_IDENTITY_BOOL_VALUE(identity, GetHtmlSigFormat, SetHtmlSigFormat)
+ COPY_IDENTITY_BOOL_VALUE(identity, GetAutoQuote, SetAutoQuote)
+ COPY_IDENTITY_INT_VALUE(identity, GetReplyOnTop, SetReplyOnTop)
+ COPY_IDENTITY_BOOL_VALUE(identity, GetSigBottom, SetSigBottom)
+ COPY_IDENTITY_BOOL_VALUE(identity, GetSigOnForward, SetSigOnForward)
+ COPY_IDENTITY_BOOL_VALUE(identity, GetSigOnReply, SetSigOnReply)
+ COPY_IDENTITY_INT_VALUE(identity, GetSignatureDate, SetSignatureDate)
+ COPY_IDENTITY_BOOL_VALUE(identity, GetAttachVCard, SetAttachVCard)
+ COPY_IDENTITY_STR_VALUE(identity, GetEscapedVCard, SetEscapedVCard)
+ COPY_IDENTITY_STR_VALUE(identity, GetSmtpServerKey, SetSmtpServerKey)
+ COPY_IDENTITY_BOOL_VALUE(identity, GetSuppressSigSep, SetSuppressSigSep)
+
+ COPY_IDENTITY_BOOL_VALUE(identity, GetAttachPgpKey, SetAttachPgpKey)
+ COPY_IDENTITY_BOOL_VALUE(identity, GetSendAutocryptHeaders,
+ SetSendAutocryptHeaders)
+ COPY_IDENTITY_BOOL_VALUE(identity, GetAutoEncryptDrafts, SetAutoEncryptDrafts)
+ COPY_IDENTITY_BOOL_VALUE(identity, GetProtectSubject, SetProtectSubject)
+ COPY_IDENTITY_INT_VALUE(identity, GetEncryptionPolicy, SetEncryptionPolicy)
+ COPY_IDENTITY_BOOL_VALUE(identity, GetSignMail, SetSignMail)
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgIdentity::GetRequestReturnReceipt(bool* aVal) {
+ NS_ENSURE_ARG_POINTER(aVal);
+
+ bool useCustomPrefs = false;
+ nsresult rv = GetBoolAttribute("use_custom_prefs", &useCustomPrefs);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (useCustomPrefs)
+ return GetBoolAttribute("request_return_receipt_on", aVal);
+
+ nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return prefs->GetBoolPref("mail.receipt.request_return_receipt_on", aVal);
+}
+
+NS_IMETHODIMP
+nsMsgIdentity::GetReceiptHeaderType(int32_t* aType) {
+ NS_ENSURE_ARG_POINTER(aType);
+
+ bool useCustomPrefs = false;
+ nsresult rv = GetBoolAttribute("use_custom_prefs", &useCustomPrefs);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (useCustomPrefs)
+ return GetIntAttribute("request_receipt_header_type", aType);
+
+ nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return prefs->GetIntPref("mail.receipt.request_header_type", aType);
+}
+
+NS_IMETHODIMP
+nsMsgIdentity::GetRequestDSN(bool* aVal) {
+ NS_ENSURE_ARG_POINTER(aVal);
+
+ bool useCustomPrefs = false;
+ nsresult rv = GetBoolAttribute("dsn_use_custom_prefs", &useCustomPrefs);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (useCustomPrefs) return GetBoolAttribute("dsn_always_request_on", aVal);
+
+ nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return prefs->GetBoolPref("mail.dsn.always_request_on", aVal);
+}
diff --git a/comm/mailnews/base/src/nsMsgIdentity.h b/comm/mailnews/base/src/nsMsgIdentity.h
new file mode 100644
index 0000000000..1b9e0f1635
--- /dev/null
+++ b/comm/mailnews/base/src/nsMsgIdentity.h
@@ -0,0 +1,87 @@
+/* -*- 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 nsMsgIdentity_h___
+#define nsMsgIdentity_h___
+
+#include "nsIMsgIdentity.h"
+#include "nsIPrefBranch.h"
+#include "msgCore.h"
+#include "nsCOMPtr.h"
+#include "nsString.h"
+
+class nsMsgIdentity final : public nsIMsgIdentity {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIMSGIDENTITY
+
+ private:
+ ~nsMsgIdentity() {}
+ nsCString mKey;
+ nsCOMPtr<nsIPrefBranch> mPrefBranch;
+ nsCOMPtr<nsIPrefBranch> mDefPrefBranch;
+
+ protected:
+ nsresult getFolderPref(const char* pref, nsACString& retval,
+ const nsACString& folderName, uint32_t folderFlag);
+ nsresult setFolderPref(const char* pref, const nsACString& retval,
+ uint32_t folderFlag);
+};
+
+#define NS_IMPL_IDPREF_STR(_postfix, _prefname) \
+ NS_IMETHODIMP \
+ nsMsgIdentity::Get##_postfix(nsACString& retval) { \
+ return GetCharAttribute(_prefname, retval); \
+ } \
+ NS_IMETHODIMP \
+ nsMsgIdentity::Set##_postfix(const nsACString& value) { \
+ return SetCharAttribute(_prefname, value); \
+ }
+
+#define NS_IMPL_IDPREF_WSTR(_postfix, _prefname) \
+ NS_IMETHODIMP \
+ nsMsgIdentity::Get##_postfix(nsAString& retval) { \
+ return GetUnicharAttribute(_prefname, retval); \
+ } \
+ NS_IMETHODIMP \
+ nsMsgIdentity::Set##_postfix(const nsAString& value) { \
+ return SetUnicharAttribute(_prefname, value); \
+ }
+
+#define NS_IMPL_IDPREF_BOOL(_postfix, _prefname) \
+ NS_IMETHODIMP \
+ nsMsgIdentity::Get##_postfix(bool* retval) { \
+ return GetBoolAttribute(_prefname, retval); \
+ } \
+ NS_IMETHODIMP \
+ nsMsgIdentity::Set##_postfix(bool value) { \
+ return mPrefBranch->SetBoolPref(_prefname, value); \
+ }
+
+#define NS_IMPL_IDPREF_INT(_postfix, _prefname) \
+ NS_IMETHODIMP \
+ nsMsgIdentity::Get##_postfix(int32_t* retval) { \
+ return GetIntAttribute(_prefname, retval); \
+ } \
+ NS_IMETHODIMP \
+ nsMsgIdentity::Set##_postfix(int32_t value) { \
+ return mPrefBranch->SetIntPref(_prefname, value); \
+ }
+
+#define NS_IMPL_FOLDERPREF_STR(_postfix, _prefname, _foldername, _flag) \
+ NS_IMETHODIMP \
+ nsMsgIdentity::Get##_postfix(nsACString& retval) { \
+ nsresult rv; \
+ nsCString folderPref; \
+ rv = getFolderPref(_prefname, folderPref, _foldername, _flag); \
+ retval = folderPref; \
+ return rv; \
+ } \
+ NS_IMETHODIMP \
+ nsMsgIdentity::Set##_postfix(const nsACString& value) { \
+ return setFolderPref(_prefname, value, _flag); \
+ }
+
+#endif /* nsMsgIdentity_h___ */
diff --git a/comm/mailnews/base/src/nsMsgIncomingServer.cpp b/comm/mailnews/base/src/nsMsgIncomingServer.cpp
new file mode 100644
index 0000000000..0d914ffbd9
--- /dev/null
+++ b/comm/mailnews/base/src/nsMsgIncomingServer.cpp
@@ -0,0 +1,2142 @@
+/* -*- 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 "nsMsgIncomingServer.h"
+#include "nscore.h"
+#include "plstr.h"
+#include "prmem.h"
+#include "prprf.h"
+
+#include "nsIServiceManager.h"
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "nsMemory.h"
+#include "nsISupportsPrimitives.h"
+
+#include "nsIMsgBiffManager.h"
+#include "nsIMsgFolder.h"
+#include "nsMsgDBFolder.h"
+#include "nsIMsgFolderCache.h"
+#include "nsIMsgPluggableStore.h"
+#include "nsIMsgFolderCacheElement.h"
+#include "nsIMsgWindow.h"
+#include "nsIMsgFilterService.h"
+#include "nsIMsgProtocolInfo.h"
+#include "nsIPrefService.h"
+#include "nsIRelativeFilePref.h"
+#include "mozilla/nsRelativeFilePref.h"
+#include "nsIDocShell.h"
+#include "nsIAuthPrompt.h"
+#include "nsNetUtil.h"
+#include "nsIWindowWatcher.h"
+#include "nsIStringBundle.h"
+#include "nsIMsgHdr.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsILoginInfo.h"
+#include "nsILoginManager.h"
+#include "nsIMsgAccountManager.h"
+#include "nsIMsgMdnGenerator.h"
+#include "nsMsgUtils.h"
+#include "nsMsgMessageFlags.h"
+#include "nsIMsgSearchTerm.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "mozilla/Components.h"
+#include "mozilla/Services.h"
+#include "nsIMsgFilter.h"
+#include "nsIObserverService.h"
+#include "mozilla/Unused.h"
+#include "nsIUUIDGenerator.h"
+#include "nsArrayUtils.h"
+
+#define PORT_NOT_SET -1
+
+nsMsgIncomingServer::nsMsgIncomingServer()
+ : m_rootFolder(nullptr),
+ m_downloadedHdrs(50),
+ m_numMsgsDownloaded(0),
+ m_biffState(nsIMsgFolder::nsMsgBiffState_Unknown),
+ m_serverBusy(false),
+ m_canHaveFilters(true),
+ m_displayStartupPage(true),
+ mPerformingBiff(false) {}
+
+nsresult nsMsgIncomingServer::Init() {
+ // We need to know when the password manager changes.
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ NS_ENSURE_TRUE(observerService, NS_ERROR_UNEXPECTED);
+
+ observerService->AddObserver(this, "passwordmgr-storage-changed", false);
+ observerService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
+ return NS_OK;
+}
+
+nsMsgIncomingServer::~nsMsgIncomingServer() {}
+
+NS_IMPL_ISUPPORTS(nsMsgIncomingServer, nsIMsgIncomingServer,
+ nsISupportsWeakReference, nsIObserver)
+
+/**
+ * Observe() receives notifications for all accounts, not just this server's
+ * account. So we ignore all notifications not intended for this server.
+ * When the state of the password manager changes we need to clear the
+ * this server's password from the cache in case the user just changed or
+ * removed the password or username.
+ * Oauth2 servers often automatically change the password manager's stored
+ * password (the token).
+ */
+NS_IMETHODIMP
+nsMsgIncomingServer::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) {
+ nsresult rv;
+ if (strcmp(aTopic, "passwordmgr-storage-changed") == 0) {
+ nsAutoString otherFullName;
+ nsAutoString otherUserName;
+ // Check that the notification is for this server.
+ nsCOMPtr<nsILoginInfo> loginInfo = do_QueryInterface(aSubject);
+ if (loginInfo) {
+ // The login info for this server has been removed with aData being
+ // "removeLogin" or "removeAllLogins".
+ loginInfo->GetOrigin(otherFullName);
+ loginInfo->GetUsername(otherUserName);
+ } else {
+ // Probably a 2 element array containing old and new login info due to
+ // aData being "modifyLogin". E.g., a user has modified password or
+ // username in the password manager or an OAuth2 token string has
+ // automatically changed.
+ nsCOMPtr<nsIArray> logins = do_QueryInterface(aSubject);
+ if (logins) {
+ // Only need to look at names in first array element (login info before
+ // any modification) since the user might have changed the username as
+ // found in the 2nd elements. (The hostname can't be modified in the
+ // password manager.)
+ nsCOMPtr<nsILoginInfo> login;
+ logins->QueryElementAt(0, NS_GET_IID(nsILoginInfo),
+ getter_AddRefs(login));
+ if (login) {
+ login->GetOrigin(otherFullName);
+ login->GetUsername(otherUserName);
+ }
+ }
+ }
+ if (!otherFullName.IsEmpty()) {
+ nsAutoCString thisHostname;
+ nsAutoCString thisUsername;
+ GetHostName(thisHostname);
+ GetUsername(thisUsername);
+ nsAutoCString thisFullName;
+ GetType(thisFullName);
+ if (thisFullName.EqualsLiteral("pop3")) {
+ // Note: POP3 now handled by MsgIncomingServer.jsm so does not occur.
+ MOZ_ASSERT_UNREACHABLE("pop3 should not use nsMsgIncomingServer");
+ thisFullName = "mailbox://"_ns + thisHostname;
+ } else {
+ thisFullName += "://"_ns + thisHostname;
+ }
+ if (!thisFullName.Equals(NS_ConvertUTF16toUTF8(otherFullName)) ||
+ !thisUsername.Equals(NS_ConvertUTF16toUTF8(otherUserName))) {
+ // Not for this server; keep this server's cached password.
+ return NS_OK;
+ }
+ } else if (NS_strcmp(aData, u"hostSavingDisabled") != 0) {
+ // "hostSavingDisabled" only occurs during test_smtpServer.js and
+ // expects the password to be removed from memory cache. Otherwise, we
+ // don't have enough information to decide to remove the cached
+ // password, so keep it.
+ return NS_OK;
+ }
+ // When nsMsgImapIncomingServer::ForgetSessionPassword called with
+ // parameter modifyLogin true and if the server uses OAuth2, it causes the
+ // password to not be cleared from cache. This is needed by autosync. When
+ // the aData paremater of Observe() is not "modifyLogin" but is
+ // e.g., "removeLogin" or "removeAllLogins", ForgetSessionPassword(false)
+ // will still clear the cached password regardless of authentication method.
+ rv = ForgetSessionPassword(NS_strcmp(aData, u"modifyLogin") == 0);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else if (strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) {
+ // Now remove ourselves from the observer service as well.
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ NS_ENSURE_TRUE(observerService, NS_ERROR_UNEXPECTED);
+
+ observerService->RemoveObserver(this, "passwordmgr-storage-changed");
+ observerService->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::SetServerBusy(bool aServerBusy) {
+ m_serverBusy = aServerBusy;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::GetServerBusy(bool* aServerBusy) {
+ NS_ENSURE_ARG_POINTER(aServerBusy);
+ *aServerBusy = m_serverBusy;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::GetKey(nsACString& serverKey) {
+ serverKey = m_serverKey;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::SetKey(const nsACString& serverKey) {
+ m_serverKey.Assign(serverKey);
+
+ // in order to actually make use of the key, we need the prefs
+ nsresult rv;
+ nsCOMPtr<nsIPrefService> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString branchName;
+ branchName.AssignLiteral("mail.server.");
+ branchName.Append(m_serverKey);
+ branchName.Append('.');
+ rv = prefs->GetBranch(branchName.get(), getter_AddRefs(mPrefBranch));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return prefs->GetBranch("mail.server.default.",
+ getter_AddRefs(mDefPrefBranch));
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::GetUID(nsACString& uid) {
+ bool hasValue;
+ nsresult rv = mPrefBranch->PrefHasUserValue("uid", &hasValue);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (hasValue) {
+ return GetCharValue("uid", uid);
+ }
+
+ nsCOMPtr<nsIUUIDGenerator> uuidgen =
+ mozilla::components::UUIDGenerator::Service();
+ NS_ENSURE_TRUE(uuidgen, NS_ERROR_FAILURE);
+
+ nsID id;
+ rv = uuidgen->GenerateUUIDInPlace(&id);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ char idString[NSID_LENGTH];
+ id.ToProvidedString(idString);
+
+ uid.AppendASCII(idString + 1, NSID_LENGTH - 3);
+ return SetUID(uid);
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::SetUID(const nsACString& uid) {
+ bool hasValue;
+ nsresult rv = mPrefBranch->PrefHasUserValue("uid", &hasValue);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (hasValue) {
+ return NS_ERROR_ABORT;
+ }
+ return SetCharValue("uid", uid);
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::SetRootFolder(nsIMsgFolder* aRootFolder) {
+ m_rootFolder = aRootFolder;
+ return NS_OK;
+}
+
+// this will return the root folder of this account,
+// even if this server is deferred.
+NS_IMETHODIMP
+nsMsgIncomingServer::GetRootFolder(nsIMsgFolder** aRootFolder) {
+ NS_ENSURE_ARG_POINTER(aRootFolder);
+ if (!m_rootFolder) {
+ nsresult rv = CreateRootFolder();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ NS_IF_ADDREF(*aRootFolder = m_rootFolder);
+ return NS_OK;
+}
+
+// this will return the root folder of the deferred to account,
+// if this server is deferred.
+NS_IMETHODIMP
+nsMsgIncomingServer::GetRootMsgFolder(nsIMsgFolder** aRootMsgFolder) {
+ return GetRootFolder(aRootMsgFolder);
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::PerformExpand(nsIMsgWindow* aMsgWindow) { return NS_OK; }
+
+NS_IMETHODIMP
+nsMsgIncomingServer::VerifyLogon(nsIUrlListener* aUrlListener,
+ nsIMsgWindow* aMsgWindow, nsIURI** aURL) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::PerformBiff(nsIMsgWindow* aMsgWindow) {
+ // This has to be implemented in the derived class, but in case someone
+ // doesn't implement it just return not implemented.
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::GetNewMessages(nsIMsgFolder* aFolder,
+ nsIMsgWindow* aMsgWindow,
+ nsIUrlListener* aUrlListener) {
+ NS_ENSURE_ARG_POINTER(aFolder);
+ return aFolder->GetNewMessages(aMsgWindow, aUrlListener);
+}
+
+NS_IMETHODIMP nsMsgIncomingServer::GetPerformingBiff(bool* aPerformingBiff) {
+ NS_ENSURE_ARG_POINTER(aPerformingBiff);
+ *aPerformingBiff = mPerformingBiff;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgIncomingServer::SetPerformingBiff(bool aPerformingBiff) {
+ mPerformingBiff = aPerformingBiff;
+ return NS_OK;
+}
+
+NS_IMPL_GETSET(nsMsgIncomingServer, BiffState, uint32_t, m_biffState)
+
+NS_IMETHODIMP nsMsgIncomingServer::WriteToFolderCache(
+ nsIMsgFolderCache* folderCache) {
+ nsresult rv = NS_OK;
+ if (m_rootFolder) {
+ rv = m_rootFolder->WriteToFolderCache(folderCache, true /* deep */);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::Shutdown() {
+ nsresult rv = CloseCachedConnections();
+ mFilterPlugin = nullptr;
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (mFilterList) {
+ // close the filter log stream
+ rv = mFilterList->SetLogStream(nullptr);
+ NS_ENSURE_SUCCESS(rv, rv);
+ mFilterList = nullptr;
+ }
+
+ if (mSpamSettings) {
+ // close the spam log stream
+ rv = mSpamSettings->SetLogStream(nullptr);
+ NS_ENSURE_SUCCESS(rv, rv);
+ mSpamSettings = nullptr;
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::CloseCachedConnections() {
+ // derived class should override if they cache connections.
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::GetDownloadMessagesAtStartup(bool* getMessagesAtStartup) {
+ // derived class should override if they need to do this.
+ *getMessagesAtStartup = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::GetCanHaveFilters(bool* canHaveFilters) {
+ NS_ENSURE_ARG_POINTER(canHaveFilters);
+ *canHaveFilters = m_canHaveFilters;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::SetCanHaveFilters(bool aCanHaveFilters) {
+ m_canHaveFilters = aCanHaveFilters;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::GetCanBeDefaultServer(bool* canBeDefaultServer) {
+ // derived class should override if they need to do this.
+ *canBeDefaultServer = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::GetCanSearchMessages(bool* canSearchMessages) {
+ // derived class should override if they need to do this.
+ NS_ENSURE_ARG_POINTER(canSearchMessages);
+ *canSearchMessages = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::GetCanCompactFoldersOnServer(
+ bool* canCompactFoldersOnServer) {
+ // derived class should override if they need to do this.
+ NS_ENSURE_ARG_POINTER(canCompactFoldersOnServer);
+ *canCompactFoldersOnServer = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::GetCanUndoDeleteOnServer(bool* canUndoDeleteOnServer) {
+ // derived class should override if they need to do this.
+ NS_ENSURE_ARG_POINTER(canUndoDeleteOnServer);
+ *canUndoDeleteOnServer = true;
+ return NS_OK;
+}
+
+// construct <localStoreType>://[<username>@]<hostname
+NS_IMETHODIMP
+nsMsgIncomingServer::GetServerURI(nsACString& aResult) {
+ nsresult rv;
+ rv = GetLocalStoreType(aResult);
+ NS_ENSURE_SUCCESS(rv, rv);
+ aResult.AppendLiteral("://");
+
+ nsCString username;
+ rv = GetUsername(username);
+ if (NS_SUCCEEDED(rv) && !username.IsEmpty()) {
+ nsCString escapedUsername;
+ MsgEscapeString(username, nsINetUtil::ESCAPE_XALPHAS, escapedUsername);
+ // not all servers have a username
+ aResult.Append(escapedUsername);
+ aResult.Append('@');
+ }
+
+ nsCString hostname;
+ rv = GetHostName(hostname);
+ if (NS_SUCCEEDED(rv) && !hostname.IsEmpty()) {
+ nsCString escapedHostname;
+ MsgEscapeString(hostname, nsINetUtil::ESCAPE_URL_PATH, escapedHostname);
+ // not all servers have a hostname
+ aResult.Append(escapedHostname);
+ }
+ return NS_OK;
+}
+
+// helper routine to create local folder on disk, if it doesn't exist.
+nsresult nsMsgIncomingServer::CreateLocalFolder(const nsAString& folderName) {
+ nsCOMPtr<nsIMsgFolder> rootFolder;
+ nsresult rv = GetRootFolder(getter_AddRefs(rootFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIMsgFolder> child;
+ rv = rootFolder->GetChildNamed(folderName, getter_AddRefs(child));
+ if (child) return NS_OK;
+ nsCOMPtr<nsIMsgPluggableStore> msgStore;
+ rv = GetMsgStore(getter_AddRefs(msgStore));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return msgStore->CreateFolder(rootFolder, folderName, getter_AddRefs(child));
+}
+
+nsresult nsMsgIncomingServer::CreateRootFolder() {
+ nsresult rv;
+ // get the URI from the incoming server
+ nsCString serverUri;
+ rv = GetServerURI(serverUri);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = GetOrCreateFolder(serverUri, getter_AddRefs(m_rootFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::GetBoolValue(const char* prefname, bool* val) {
+ if (!mPrefBranch) return NS_ERROR_NOT_INITIALIZED;
+
+ NS_ENSURE_ARG_POINTER(val);
+ *val = false;
+
+ if (NS_FAILED(mPrefBranch->GetBoolPref(prefname, val)))
+ mDefPrefBranch->GetBoolPref(prefname, val);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::SetBoolValue(const char* prefname, bool val) {
+ if (!mPrefBranch) return NS_ERROR_NOT_INITIALIZED;
+
+ bool defaultValue;
+ nsresult rv = mDefPrefBranch->GetBoolPref(prefname, &defaultValue);
+
+ if (NS_SUCCEEDED(rv) && val == defaultValue)
+ mPrefBranch->ClearUserPref(prefname);
+ else
+ rv = mPrefBranch->SetBoolPref(prefname, val);
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::GetIntValue(const char* prefname, int32_t* val) {
+ if (!mPrefBranch) return NS_ERROR_NOT_INITIALIZED;
+
+ NS_ENSURE_ARG_POINTER(val);
+ *val = 0;
+
+ if (NS_FAILED(mPrefBranch->GetIntPref(prefname, val)))
+ mDefPrefBranch->GetIntPref(prefname, val);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::GetFileValue(const char* aRelPrefName,
+ const char* aAbsPrefName,
+ nsIFile** aLocalFile) {
+ if (!mPrefBranch) return NS_ERROR_NOT_INITIALIZED;
+
+ // Get the relative first
+ nsCOMPtr<nsIRelativeFilePref> relFilePref;
+ nsresult rv = mPrefBranch->GetComplexValue(aRelPrefName,
+ NS_GET_IID(nsIRelativeFilePref),
+ getter_AddRefs(relFilePref));
+ if (relFilePref) {
+ rv = relFilePref->GetFile(aLocalFile);
+ NS_ASSERTION(*aLocalFile, "An nsIRelativeFilePref has no file.");
+ if (NS_SUCCEEDED(rv)) (*aLocalFile)->Normalize();
+ } else {
+ rv = mPrefBranch->GetComplexValue(aAbsPrefName, NS_GET_IID(nsIFile),
+ reinterpret_cast<void**>(aLocalFile));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIRelativeFilePref> relFilePref =
+ new mozilla::nsRelativeFilePref();
+ mozilla::Unused << relFilePref->SetFile(*aLocalFile);
+ mozilla::Unused << relFilePref->SetRelativeToKey(
+ nsLiteralCString(NS_APP_USER_PROFILE_50_DIR));
+
+ rv = mPrefBranch->SetComplexValue(
+ aRelPrefName, NS_GET_IID(nsIRelativeFilePref), relFilePref);
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::SetFileValue(const char* aRelPrefName,
+ const char* aAbsPrefName,
+ nsIFile* aLocalFile) {
+ if (!mPrefBranch) return NS_ERROR_NOT_INITIALIZED;
+
+ // Write the relative path.
+ nsCOMPtr<nsIRelativeFilePref> relFilePref = new mozilla::nsRelativeFilePref();
+ mozilla::Unused << relFilePref->SetFile(aLocalFile);
+ mozilla::Unused << relFilePref->SetRelativeToKey(
+ nsLiteralCString(NS_APP_USER_PROFILE_50_DIR));
+
+ nsresult rv = mPrefBranch->SetComplexValue(
+ aRelPrefName, NS_GET_IID(nsIRelativeFilePref), relFilePref);
+ if (NS_FAILED(rv)) return rv;
+
+ return mPrefBranch->SetComplexValue(aAbsPrefName, NS_GET_IID(nsIFile),
+ aLocalFile);
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::SetIntValue(const char* prefname, int32_t val) {
+ if (!mPrefBranch) return NS_ERROR_NOT_INITIALIZED;
+
+ int32_t defaultVal;
+ nsresult rv = mDefPrefBranch->GetIntPref(prefname, &defaultVal);
+
+ if (NS_SUCCEEDED(rv) && defaultVal == val)
+ mPrefBranch->ClearUserPref(prefname);
+ else
+ rv = mPrefBranch->SetIntPref(prefname, val);
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::GetCharValue(const char* prefname, nsACString& val) {
+ if (!mPrefBranch) return NS_ERROR_NOT_INITIALIZED;
+
+ nsCString tmpVal;
+ if (NS_FAILED(mPrefBranch->GetCharPref(prefname, tmpVal)))
+ mDefPrefBranch->GetCharPref(prefname, tmpVal);
+ val = tmpVal;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::GetUnicharValue(const char* prefname, nsAString& val) {
+ if (!mPrefBranch) return NS_ERROR_NOT_INITIALIZED;
+
+ nsCString valueUtf8;
+ if (NS_FAILED(
+ mPrefBranch->GetStringPref(prefname, EmptyCString(), 0, valueUtf8)))
+ mDefPrefBranch->GetStringPref(prefname, EmptyCString(), 0, valueUtf8);
+ CopyUTF8toUTF16(valueUtf8, val);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::SetCharValue(const char* prefname, const nsACString& val) {
+ if (!mPrefBranch) return NS_ERROR_NOT_INITIALIZED;
+
+ if (val.IsEmpty()) {
+ mPrefBranch->ClearUserPref(prefname);
+ return NS_OK;
+ }
+
+ nsCString defaultVal;
+ nsresult rv = mDefPrefBranch->GetCharPref(prefname, defaultVal);
+
+ if (NS_SUCCEEDED(rv) && defaultVal.Equals(val))
+ mPrefBranch->ClearUserPref(prefname);
+ else
+ rv = mPrefBranch->SetCharPref(prefname, val);
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::SetUnicharValue(const char* prefname,
+ const nsAString& val) {
+ if (!mPrefBranch) return NS_ERROR_NOT_INITIALIZED;
+
+ if (val.IsEmpty()) {
+ mPrefBranch->ClearUserPref(prefname);
+ return NS_OK;
+ }
+
+ nsCString defaultVal;
+ nsresult rv =
+ mDefPrefBranch->GetStringPref(prefname, EmptyCString(), 0, defaultVal);
+
+ if (NS_SUCCEEDED(rv) && defaultVal.Equals(NS_ConvertUTF16toUTF8(val)))
+ mPrefBranch->ClearUserPref(prefname);
+ else
+ rv = mPrefBranch->SetStringPref(prefname, NS_ConvertUTF16toUTF8(val));
+
+ return rv;
+}
+
+// pretty name is the display name to show to the user
+NS_IMETHODIMP
+nsMsgIncomingServer::GetPrettyName(nsAString& retval) {
+ nsresult rv = GetUnicharValue("name", retval);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // if there's no name, then just return the hostname
+ return retval.IsEmpty() ? GetConstructedPrettyName(retval) : rv;
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::SetPrettyName(const nsAString& value) {
+ SetUnicharValue("name", value);
+ nsCOMPtr<nsIMsgFolder> rootFolder;
+ GetRootFolder(getter_AddRefs(rootFolder));
+ if (rootFolder) rootFolder->SetPrettyName(value);
+ return NS_OK;
+}
+
+// construct the pretty name to show to the user if they haven't
+// specified one. This should be overridden for news and mail.
+NS_IMETHODIMP
+nsMsgIncomingServer::GetConstructedPrettyName(nsAString& retval) {
+ nsCString username;
+ nsresult rv = GetUsername(username);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!username.IsEmpty()) {
+ CopyASCIItoUTF16(username, retval);
+ retval.AppendLiteral(" on ");
+ }
+
+ nsCString hostname;
+ rv = GetHostName(hostname);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ retval.Append(NS_ConvertASCIItoUTF16(hostname));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::ToString(nsAString& aResult) {
+ aResult.AssignLiteral("[nsIMsgIncomingServer: ");
+ aResult.Append(NS_ConvertASCIItoUTF16(m_serverKey));
+ aResult.Append(']');
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgIncomingServer::SetPassword(const nsAString& aPassword) {
+ m_password = aPassword;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgIncomingServer::GetPassword(nsAString& aPassword) {
+ aPassword = m_password;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgIncomingServer::GetServerRequiresPasswordForBiff(
+ bool* aServerRequiresPasswordForBiff) {
+ NS_ENSURE_ARG_POINTER(aServerRequiresPasswordForBiff);
+ *aServerRequiresPasswordForBiff = true;
+ return NS_OK;
+}
+
+// This sets m_password if we find a password in the pw mgr.
+nsresult nsMsgIncomingServer::GetPasswordWithoutUI() {
+ nsresult rv;
+ nsCOMPtr<nsILoginManager> loginMgr(
+ do_GetService(NS_LOGINMANAGER_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Get the current server URI
+ nsCString currServerUri;
+ rv = GetLocalStoreType(currServerUri);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ currServerUri.AppendLiteral("://");
+
+ nsCString temp;
+ rv = GetHostName(temp);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ currServerUri.Append(temp);
+
+ NS_ConvertUTF8toUTF16 currServer(currServerUri);
+
+ nsTArray<RefPtr<nsILoginInfo>> logins;
+ rv = loginMgr->FindLogins(currServer, EmptyString(), currServer, logins);
+
+ // Login manager can produce valid fails, e.g. NS_ERROR_ABORT when a user
+ // cancels the master password dialog. Therefore handle that here, but don't
+ // warn about it.
+ if (NS_FAILED(rv)) return rv;
+ uint32_t numLogins = logins.Length();
+
+ // Don't abort here, if we didn't find any or failed, then we'll just have
+ // to prompt.
+ if (numLogins > 0) {
+ nsCString serverCUsername;
+ rv = GetUsername(serverCUsername);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ConvertUTF8toUTF16 serverUsername(serverCUsername);
+
+ nsString username;
+ for (uint32_t i = 0; i < numLogins; ++i) {
+ rv = logins[i]->GetUsername(username);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (username.Equals(serverUsername)) {
+ nsString password;
+ rv = logins[i]->GetPassword(password);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ m_password = password;
+ break;
+ }
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::GetPasswordWithUI(const nsAString& aPromptMessage,
+ const nsAString& aPromptTitle,
+ nsAString& aPassword) {
+ nsresult rv = NS_OK;
+
+ if (m_password.IsEmpty()) {
+ // let's see if we have the password in the password manager and
+ // can avoid this prompting thing. This makes it easier to get embedders
+ // to get up and running w/o a password prompting UI.
+ rv = GetPasswordWithoutUI();
+ // If GetPasswordWithoutUI returns NS_ERROR_ABORT, the most likely case
+ // is the user canceled getting the master password, so just return
+ // straight away, as they won't want to get prompted again.
+ if (rv == NS_ERROR_ABORT) return NS_MSG_PASSWORD_PROMPT_CANCELLED;
+ }
+ if (m_password.IsEmpty()) {
+ nsCOMPtr<nsIAuthPrompt> authPrompt =
+ do_GetService("@mozilla.org/messenger/msgAuthPrompt;1");
+ if (authPrompt) {
+ // prompt the user for the password
+ nsCString serverUri;
+ rv = GetLocalStoreType(serverUri);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ serverUri.AppendLiteral("://");
+ nsCString temp;
+ rv = GetUsername(temp);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!temp.IsEmpty()) {
+ nsCString escapedUsername;
+ MsgEscapeString(temp, nsINetUtil::ESCAPE_XALPHAS, escapedUsername);
+ serverUri.Append(escapedUsername);
+ serverUri.Append('@');
+ }
+
+ rv = GetHostName(temp);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ serverUri.Append(temp);
+
+ // we pass in the previously used password, if any, into PromptPassword
+ // so that it will appear as ******. This means we can't use an nsString
+ // and getter_Copies.
+ char16_t* uniPassword = nullptr;
+ if (!aPassword.IsEmpty()) uniPassword = ToNewUnicode(aPassword);
+
+ bool okayValue = true;
+ rv = authPrompt->PromptPassword(PromiseFlatString(aPromptTitle).get(),
+ PromiseFlatString(aPromptMessage).get(),
+ NS_ConvertASCIItoUTF16(serverUri).get(),
+ nsIAuthPrompt::SAVE_PASSWORD_PERMANENTLY,
+ &uniPassword, &okayValue);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!okayValue) // if the user pressed cancel, just return an empty
+ // string;
+ {
+ aPassword.Truncate();
+ return NS_MSG_PASSWORD_PROMPT_CANCELLED;
+ }
+
+ // we got a password back...so remember it
+ rv = SetPassword(nsDependentString(uniPassword));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ PR_FREEIF(uniPassword);
+ } // if we got a prompt dialog
+ else
+ return NS_ERROR_FAILURE;
+ } // if the password is empty
+ return GetPassword(aPassword);
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::ForgetPassword() {
+ nsresult rv;
+ nsCOMPtr<nsILoginManager> loginMgr =
+ do_GetService(NS_LOGINMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Get the current server URI
+ nsCString currServerUri;
+ rv = GetLocalStoreType(currServerUri);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ currServerUri.AppendLiteral("://");
+
+ nsCString temp;
+ rv = GetHostName(temp);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ currServerUri.Append(temp);
+
+ NS_ConvertUTF8toUTF16 currServer(currServerUri);
+
+ nsCString serverCUsername;
+ rv = GetUsername(serverCUsername);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ConvertUTF8toUTF16 serverUsername(serverCUsername);
+
+ nsTArray<RefPtr<nsILoginInfo>> logins;
+ rv = loginMgr->FindLogins(currServer, EmptyString(), currServer, logins);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // There should only be one-login stored for this url, however just in case
+ // there isn't.
+ nsString username;
+ for (uint32_t i = 0; i < logins.Length(); ++i) {
+ rv = logins[i]->GetUsername(username);
+ int32_t atPos = serverUsername.FindChar('@');
+ if (NS_SUCCEEDED(rv) &&
+ (username.Equals(serverUsername) ||
+ StringHead(serverUsername, atPos).Equals(username))) {
+ // If this fails, just continue, we'll still want to remove the password
+ // from our local cache.
+ loginMgr->RemoveLogin(logins[i]);
+ }
+ }
+
+ return SetPassword(EmptyString());
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::ForgetSessionPassword(bool modifyLogin) {
+ m_password.Truncate();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::SetDefaultLocalPath(nsIFile* aDefaultLocalPath) {
+ nsresult rv;
+ nsCOMPtr<nsIMsgProtocolInfo> protocolInfo;
+ rv = GetProtocolInfo(getter_AddRefs(protocolInfo));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return protocolInfo->SetDefaultLocalPath(aDefaultLocalPath);
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::GetLocalPath(nsIFile** aLocalPath) {
+ nsresult rv;
+
+ // if the local path has already been set, use it
+ rv = GetFileValue("directory-rel", "directory", aLocalPath);
+ if (NS_SUCCEEDED(rv) && *aLocalPath) return rv;
+
+ // otherwise, create the path using the protocol info.
+ // note we are using the
+ // hostname, unless that directory exists.
+ // this should prevent all collisions.
+ nsCOMPtr<nsIMsgProtocolInfo> protocolInfo;
+ rv = GetProtocolInfo(getter_AddRefs(protocolInfo));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> localPath;
+ rv = protocolInfo->GetDefaultLocalPath(getter_AddRefs(localPath));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = localPath->Create(nsIFile::DIRECTORY_TYPE, 0755);
+ if (rv == NS_ERROR_FILE_ALREADY_EXISTS) rv = NS_OK;
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCString hostname;
+ rv = GetHostName(hostname);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // set the leaf name to "dummy", and then call MakeUnique with a suggested
+ // leaf name
+ rv = localPath->AppendNative(hostname);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = localPath->CreateUnique(nsIFile::DIRECTORY_TYPE, 0755);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = SetLocalPath(localPath);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ localPath.forget(aLocalPath);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::GetMsgStore(nsIMsgPluggableStore** aMsgStore) {
+ NS_ENSURE_ARG_POINTER(aMsgStore);
+ if (!m_msgStore) {
+ nsCString storeContractID;
+ nsresult rv;
+ // We don't want there to be a default pref, I think, since
+ // we can't change the default. We may want no pref to mean
+ // berkeley store, and then set the store pref off of some sort
+ // of default when creating a server. But we need to make sure
+ // that we do always write a store pref.
+ GetCharValue("storeContractID", storeContractID);
+ if (storeContractID.IsEmpty()) {
+ storeContractID.AssignLiteral("@mozilla.org/msgstore/berkeleystore;1");
+ SetCharValue("storeContractID", storeContractID);
+ }
+
+ // After someone starts using the pluggable store, we can no longer
+ // change the value.
+ SetBoolValue("canChangeStoreType", false);
+
+ // Right now, we just have one pluggable store per server. If we want
+ // to support multiple, this pref could be a list of pluggable store
+ // contract id's.
+ m_msgStore = do_CreateInstance(storeContractID.get(), &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ NS_IF_ADDREF(*aMsgStore = m_msgStore);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::SetLocalPath(nsIFile* aLocalPath) {
+ NS_ENSURE_ARG_POINTER(aLocalPath);
+ nsresult rv = aLocalPath->Create(nsIFile::DIRECTORY_TYPE, 0755);
+ if (rv == NS_ERROR_FILE_ALREADY_EXISTS) rv = NS_OK;
+ NS_ENSURE_SUCCESS(rv, rv);
+ return SetFileValue("directory-rel", "directory", aLocalPath);
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::GetLocalStoreType(nsACString& aResult) {
+ MOZ_ASSERT_UNREACHABLE(
+ "nsMsgIncomingServer superclass not implementing GetLocalStoreType!");
+ return NS_ERROR_UNEXPECTED;
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::GetLocalDatabaseType(nsACString& aResult) {
+ MOZ_ASSERT_UNREACHABLE(
+ "nsMsgIncomingServer superclass not implementing GetLocalDatabaseType!");
+ return NS_ERROR_UNEXPECTED;
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::GetAccountManagerChrome(nsAString& aResult) {
+ aResult.AssignLiteral("am-main.xhtml");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::Equals(nsIMsgIncomingServer* server, bool* _retval) {
+ nsresult rv;
+
+ NS_ENSURE_ARG_POINTER(server);
+ NS_ENSURE_ARG_POINTER(_retval);
+
+ nsCString key1;
+ nsCString key2;
+
+ rv = GetKey(key1);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = server->GetKey(key2);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // compare the server keys
+ *_retval = key1.Equals(key2, nsCaseInsensitiveCStringComparator);
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::ClearAllValues() {
+ if (!mPrefBranch) return NS_ERROR_NOT_INITIALIZED;
+
+ nsTArray<nsCString> prefNames;
+ nsresult rv = mPrefBranch->GetChildList("", prefNames);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (auto& prefName : prefNames) {
+ mPrefBranch->ClearUserPref(prefName.get());
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::RemoveFiles() {
+ // IMPORTANT, see bug #77652
+ // TODO: Decide what to do for deferred accounts.
+ nsCString deferredToAccount;
+ GetCharValue("deferred_to_account", deferredToAccount);
+ bool isDeferredTo = true;
+ GetIsDeferredTo(&isDeferredTo);
+ if (!deferredToAccount.IsEmpty() || isDeferredTo) {
+ NS_ASSERTION(false, "shouldn't remove files for a deferred account");
+ return NS_ERROR_FAILURE;
+ }
+ nsCOMPtr<nsIFile> localPath;
+ nsresult rv = GetLocalPath(getter_AddRefs(localPath));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return localPath->Remove(true);
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::SetFilterList(nsIMsgFilterList* aFilterList) {
+ mFilterList = aFilterList;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::GetFilterList(nsIMsgWindow* aMsgWindow,
+ nsIMsgFilterList** aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+ if (!mFilterList) {
+ nsCOMPtr<nsIMsgFolder> msgFolder;
+ // use GetRootFolder so for deferred pop3 accounts, we'll get the filters
+ // file from the deferred account, not the deferred to account,
+ // so that filters will still be per-server.
+ nsresult rv = GetRootFolder(getter_AddRefs(msgFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCString filterType;
+ rv = GetCharValue("filter.type", filterType);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!filterType.IsEmpty() && !filterType.EqualsLiteral("default")) {
+ nsAutoCString contractID("@mozilla.org/filterlist;1?type=");
+ contractID += filterType;
+ ToLowerCase(contractID);
+ mFilterList = do_CreateInstance(contractID.get(), &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mFilterList->SetFolder(msgFolder);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ADDREF(*aResult = mFilterList);
+ return NS_OK;
+ }
+
+ // The default case, a local folder, is a bit special. It requires
+ // more initialization.
+
+ nsCOMPtr<nsIFile> thisFolder;
+ rv = msgFolder->GetFilePath(getter_AddRefs(thisFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mFilterFile = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = mFilterFile->InitWithFile(thisFolder);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mFilterFile->AppendNative("msgFilterRules.dat"_ns);
+
+ bool fileExists;
+ mFilterFile->Exists(&fileExists);
+ if (!fileExists) {
+ nsCOMPtr<nsIFile> oldFilterFile =
+ do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = oldFilterFile->InitWithFile(thisFolder);
+ NS_ENSURE_SUCCESS(rv, rv);
+ oldFilterFile->AppendNative("rules.dat"_ns);
+
+ oldFilterFile->Exists(&fileExists);
+ if (fileExists) // copy rules.dat --> msgFilterRules.dat
+ {
+ rv = oldFilterFile->CopyToNative(thisFolder, "msgFilterRules.dat"_ns);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+ nsCOMPtr<nsIMsgFilterService> filterService =
+ do_GetService("@mozilla.org/messenger/services/filters;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = filterService->OpenFilterList(mFilterFile, msgFolder, aMsgWindow,
+ getter_AddRefs(mFilterList));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ NS_IF_ADDREF(*aResult = mFilterList);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::SetEditableFilterList(
+ nsIMsgFilterList* aEditableFilterList) {
+ mEditableFilterList = aEditableFilterList;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::GetEditableFilterList(nsIMsgWindow* aMsgWindow,
+ nsIMsgFilterList** aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+ if (!mEditableFilterList) {
+ bool editSeparate;
+ nsresult rv = GetBoolValue("filter.editable.separate", &editSeparate);
+ if (NS_FAILED(rv) || !editSeparate)
+ return GetFilterList(aMsgWindow, aResult);
+
+ nsCString filterType;
+ rv = GetCharValue("filter.editable.type", filterType);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString contractID("@mozilla.org/filterlist;1?type=");
+ contractID += filterType;
+ ToLowerCase(contractID);
+ mEditableFilterList = do_CreateInstance(contractID.get(), &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgFolder> msgFolder;
+ // use GetRootFolder so for deferred pop3 accounts, we'll get the filters
+ // file from the deferred account, not the deferred to account,
+ // so that filters will still be per-server.
+ rv = GetRootFolder(getter_AddRefs(msgFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mEditableFilterList->SetFolder(msgFolder);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ADDREF(*aResult = mEditableFilterList);
+ return NS_OK;
+ }
+
+ NS_IF_ADDREF(*aResult = mEditableFilterList);
+ return NS_OK;
+}
+
+// If the hostname contains ':' (like hostname:1431)
+// then parse and set the port number.
+nsresult nsMsgIncomingServer::InternalSetHostName(const nsACString& aHostname,
+ const char* prefName) {
+ nsCString hostname;
+ hostname = aHostname;
+ if (hostname.CountChar(':') == 1) {
+ int32_t colonPos = hostname.FindChar(':');
+ nsAutoCString portString(Substring(hostname, colonPos));
+ hostname.SetLength(colonPos);
+ nsresult err;
+ int32_t port = portString.ToInteger(&err);
+ if (NS_SUCCEEDED(err)) SetPort(port);
+ }
+ return SetCharValue(prefName, hostname);
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::OnUserOrHostNameChanged(const nsACString& oldName,
+ const nsACString& newName,
+ bool hostnameChanged) {
+ nsresult rv;
+
+ // 1. Reset password so that users are prompted for new password for the new
+ // user/host.
+ int32_t atPos = newName.FindChar('@');
+ if (hostnameChanged) {
+ ForgetPassword();
+ }
+
+ // 2. Replace all occurrences of old name in the acct name with the new one.
+ nsString acctName;
+ rv = GetPrettyName(acctName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // 3. Clear the clientid because the user or host have changed.
+ SetClientid(EmptyCString());
+
+ // Will be generated again when used.
+ mPrefBranch->ClearUserPref("spamActionTargetAccount");
+
+ // If new username contains @ then better do not update the account name.
+ if (acctName.IsEmpty() || (!hostnameChanged && (atPos != kNotFound)))
+ return NS_OK;
+
+ atPos = acctName.FindChar('@');
+
+ // get previous username and hostname
+ nsCString userName, hostName;
+ if (hostnameChanged) {
+ rv = GetUsername(userName);
+ NS_ENSURE_SUCCESS(rv, rv);
+ hostName.Assign(oldName);
+ } else {
+ userName.Assign(oldName);
+ rv = GetHostName(hostName);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // switch corresponding part of the account name to the new name...
+ if (!hostnameChanged && (atPos != kNotFound)) {
+ // ...if username changed and the previous username was equal to the part
+ // of the account name before @
+ if (StringHead(acctName, atPos).Equals(NS_ConvertASCIItoUTF16(userName)))
+ acctName.Replace(0, userName.Length(), NS_ConvertASCIItoUTF16(newName));
+ }
+ if (hostnameChanged) {
+ // ...if hostname changed and the previous hostname was equal to the part
+ // of the account name after @, or to the whole account name
+ if (atPos == kNotFound)
+ atPos = 0;
+ else
+ atPos += 1;
+ if (Substring(acctName, atPos).Equals(NS_ConvertASCIItoUTF16(hostName))) {
+ acctName.Replace(atPos, acctName.Length() - atPos,
+ NS_ConvertASCIItoUTF16(newName));
+ }
+ }
+
+ return SetPrettyName(acctName);
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::SetHostName(const nsACString& aHostname) {
+ nsCString oldName;
+ nsresult rv = GetHostName(oldName);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = InternalSetHostName(aHostname, "hostname");
+
+ if (!oldName.IsEmpty() &&
+ !aHostname.Equals(oldName, nsCaseInsensitiveCStringComparator))
+ rv = OnUserOrHostNameChanged(oldName, aHostname, true);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::GetHostName(nsACString& aResult) {
+ nsresult rv = GetCharValue("hostname", aResult);
+ if (aResult.CountChar(':') == 1) {
+ // gack, we need to reformat the hostname - SetHostName will do that
+ SetHostName(aResult);
+ rv = GetCharValue("hostname", aResult);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::SetUsername(const nsACString& aUsername) {
+ nsCString oldName;
+ nsresult rv = GetUsername(oldName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!oldName.IsEmpty() && !oldName.Equals(aUsername)) {
+ // If only username changed and the new name just added a domain we can keep
+ // the password.
+ int32_t atPos = aUsername.FindChar('@');
+ if ((atPos == kNotFound) ||
+ !StringHead(NS_ConvertASCIItoUTF16(aUsername), atPos)
+ .Equals(NS_ConvertASCIItoUTF16(oldName))) {
+ ForgetPassword();
+ }
+ rv = SetCharValue("userName", aUsername);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = OnUserOrHostNameChanged(oldName, aUsername, false);
+ } else {
+ rv = SetCharValue("userName", aUsername);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::GetUsername(nsACString& aResult) {
+ return GetCharValue("userName", aResult);
+}
+
+#define BIFF_PREF_NAME "check_new_mail"
+
+NS_IMETHODIMP
+nsMsgIncomingServer::GetDoBiff(bool* aDoBiff) {
+ NS_ENSURE_ARG_POINTER(aDoBiff);
+
+ if (!mPrefBranch) return NS_ERROR_NOT_INITIALIZED;
+
+ nsresult rv;
+
+ rv = mPrefBranch->GetBoolPref(BIFF_PREF_NAME, aDoBiff);
+ if (NS_SUCCEEDED(rv)) return rv;
+
+ // if the pref isn't set, use the default
+ // value based on the protocol
+ nsCOMPtr<nsIMsgProtocolInfo> protocolInfo;
+ rv = GetProtocolInfo(getter_AddRefs(protocolInfo));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = protocolInfo->GetDefaultDoBiff(aDoBiff);
+ // note, don't call SetDoBiff()
+ // since we keep changing our minds on
+ // if biff should be on or off, let's keep the ability
+ // to change the default in future builds.
+ // if we call SetDoBiff() here, it will be in the users prefs.
+ // and we can't do anything after that.
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::SetDoBiff(bool aDoBiff) {
+ if (!mPrefBranch) return NS_ERROR_NOT_INITIALIZED;
+
+ // Update biffManager immediately, no restart required. Adding/removing
+ // existing/non-existing server is handled without error checking.
+ nsresult rv;
+ nsCOMPtr<nsIMsgBiffManager> biffService =
+ do_GetService("@mozilla.org/messenger/biffManager;1", &rv);
+ if (NS_SUCCEEDED(rv) && biffService) {
+ if (aDoBiff)
+ (void)biffService->AddServerBiff(this);
+ else
+ (void)biffService->RemoveServerBiff(this);
+ }
+
+ return mPrefBranch->SetBoolPref(BIFF_PREF_NAME, aDoBiff);
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::GetPort(int32_t* aPort) {
+ NS_ENSURE_ARG_POINTER(aPort);
+
+ nsresult rv;
+ rv = GetIntValue("port", aPort);
+ // We can't use a port of 0, because the URI parsing code fails.
+ if (*aPort != PORT_NOT_SET && *aPort) return rv;
+
+ // if the port isn't set, use the default
+ // port based on the protocol
+ nsCOMPtr<nsIMsgProtocolInfo> protocolInfo;
+ rv = GetProtocolInfo(getter_AddRefs(protocolInfo));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t socketType;
+ rv = GetSocketType(&socketType);
+ NS_ENSURE_SUCCESS(rv, rv);
+ bool useSSLPort = (socketType == nsMsgSocketType::SSL);
+ return protocolInfo->GetDefaultServerPort(useSSLPort, aPort);
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::SetPort(int32_t aPort) {
+ nsresult rv;
+
+ nsCOMPtr<nsIMsgProtocolInfo> protocolInfo;
+ rv = GetProtocolInfo(getter_AddRefs(protocolInfo));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t socketType;
+ rv = GetSocketType(&socketType);
+ NS_ENSURE_SUCCESS(rv, rv);
+ bool useSSLPort = (socketType == nsMsgSocketType::SSL);
+
+ int32_t defaultPort;
+ protocolInfo->GetDefaultServerPort(useSSLPort, &defaultPort);
+ return SetIntValue("port", aPort == defaultPort ? PORT_NOT_SET : aPort);
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::GetProtocolInfo(nsIMsgProtocolInfo** aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ nsCString type;
+ nsresult rv = GetType(type);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString contractid(NS_MSGPROTOCOLINFO_CONTRACTID_PREFIX);
+ contractid.Append(type);
+
+ nsCOMPtr<nsIMsgProtocolInfo> protocolInfo =
+ do_GetService(contractid.get(), &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ protocolInfo.forget(aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgIncomingServer::GetRetentionSettings(
+ nsIMsgRetentionSettings** settings) {
+ NS_ENSURE_ARG_POINTER(settings);
+ nsMsgRetainByPreference retainByPreference;
+ int32_t daysToKeepHdrs = 0;
+ int32_t numHeadersToKeep = 0;
+ int32_t daysToKeepBodies = 0;
+ bool cleanupBodiesByDays = false;
+ bool applyToFlaggedMessages = false;
+ nsresult rv = NS_OK;
+ // Create an empty retention settings object,
+ // get the settings from the server prefs, and init the object from the prefs.
+ nsCOMPtr<nsIMsgRetentionSettings> retentionSettings =
+ do_CreateInstance("@mozilla.org/msgDatabase/retentionSettings;1");
+ if (retentionSettings) {
+ rv = GetIntValue("retainBy", (int32_t*)&retainByPreference);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = GetIntValue("numHdrsToKeep", &numHeadersToKeep);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = GetIntValue("daysToKeepHdrs", &daysToKeepHdrs);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = GetIntValue("daysToKeepBodies", &daysToKeepBodies);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = GetBoolValue("cleanupBodies", &cleanupBodiesByDays);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = GetBoolValue("applyToFlaggedMessages", &applyToFlaggedMessages);
+ NS_ENSURE_SUCCESS(rv, rv);
+ retentionSettings->SetRetainByPreference(retainByPreference);
+ retentionSettings->SetNumHeadersToKeep((uint32_t)numHeadersToKeep);
+ retentionSettings->SetDaysToKeepBodies(daysToKeepBodies);
+ retentionSettings->SetDaysToKeepHdrs(daysToKeepHdrs);
+ retentionSettings->SetCleanupBodiesByDays(cleanupBodiesByDays);
+ retentionSettings->SetApplyToFlaggedMessages(applyToFlaggedMessages);
+ } else
+ rv = NS_ERROR_OUT_OF_MEMORY;
+ NS_IF_ADDREF(*settings = retentionSettings);
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgIncomingServer::SetRetentionSettings(
+ nsIMsgRetentionSettings* settings) {
+ nsMsgRetainByPreference retainByPreference;
+ uint32_t daysToKeepHdrs = 0;
+ uint32_t numHeadersToKeep = 0;
+ uint32_t daysToKeepBodies = 0;
+ bool cleanupBodiesByDays = false;
+ bool applyToFlaggedMessages = false;
+ settings->GetRetainByPreference(&retainByPreference);
+ settings->GetNumHeadersToKeep(&numHeadersToKeep);
+ settings->GetDaysToKeepBodies(&daysToKeepBodies);
+ settings->GetDaysToKeepHdrs(&daysToKeepHdrs);
+ settings->GetCleanupBodiesByDays(&cleanupBodiesByDays);
+ settings->GetApplyToFlaggedMessages(&applyToFlaggedMessages);
+ nsresult rv = SetIntValue("retainBy", retainByPreference);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = SetIntValue("numHdrsToKeep", numHeadersToKeep);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = SetIntValue("daysToKeepHdrs", daysToKeepHdrs);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = SetIntValue("daysToKeepBodies", daysToKeepBodies);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = SetBoolValue("cleanupBodies", cleanupBodiesByDays);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = SetBoolValue("applyToFlaggedMessages", applyToFlaggedMessages);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::GetDisplayStartupPage(bool* displayStartupPage) {
+ NS_ENSURE_ARG_POINTER(displayStartupPage);
+ *displayStartupPage = m_displayStartupPage;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::SetDisplayStartupPage(bool displayStartupPage) {
+ m_displayStartupPage = displayStartupPage;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgIncomingServer::GetDownloadSettings(
+ nsIMsgDownloadSettings** settings) {
+ NS_ENSURE_ARG_POINTER(settings);
+ bool downloadUnreadOnly = false;
+ bool downloadByDate = false;
+ uint32_t ageLimitOfMsgsToDownload = 0;
+ nsresult rv = NS_OK;
+ if (!m_downloadSettings) {
+ m_downloadSettings =
+ do_CreateInstance("@mozilla.org/msgDatabase/downloadSettings;1");
+ if (m_downloadSettings) {
+ rv = GetBoolValue("downloadUnreadOnly", &downloadUnreadOnly);
+ rv = GetBoolValue("downloadByDate", &downloadByDate);
+ rv = GetIntValue("ageLimit", (int32_t*)&ageLimitOfMsgsToDownload);
+ m_downloadSettings->SetDownloadUnreadOnly(downloadUnreadOnly);
+ m_downloadSettings->SetDownloadByDate(downloadByDate);
+ m_downloadSettings->SetAgeLimitOfMsgsToDownload(ageLimitOfMsgsToDownload);
+ } else
+ rv = NS_ERROR_OUT_OF_MEMORY;
+ // Create an empty download settings object,
+ // get the settings from the server prefs, and init the object from the
+ // prefs.
+ }
+ NS_IF_ADDREF(*settings = m_downloadSettings);
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgIncomingServer::SetDownloadSettings(
+ nsIMsgDownloadSettings* settings) {
+ m_downloadSettings = settings;
+ bool downloadUnreadOnly = false;
+ bool downloadByDate = false;
+ uint32_t ageLimitOfMsgsToDownload = 0;
+ m_downloadSettings->GetDownloadUnreadOnly(&downloadUnreadOnly);
+ m_downloadSettings->GetDownloadByDate(&downloadByDate);
+ m_downloadSettings->GetAgeLimitOfMsgsToDownload(&ageLimitOfMsgsToDownload);
+ nsresult rv = SetBoolValue("downloadUnreadOnly", downloadUnreadOnly);
+ NS_ENSURE_SUCCESS(rv, rv);
+ SetBoolValue("downloadByDate", downloadByDate);
+ return SetIntValue("ageLimit", ageLimitOfMsgsToDownload);
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::GetSupportsDiskSpace(bool* aSupportsDiskSpace) {
+ NS_ENSURE_ARG_POINTER(aSupportsDiskSpace);
+ *aSupportsDiskSpace = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::GetOfflineSupportLevel(int32_t* aSupportLevel) {
+ NS_ENSURE_ARG_POINTER(aSupportLevel);
+
+ nsresult rv = GetIntValue("offline_support_level", aSupportLevel);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (*aSupportLevel == OFFLINE_SUPPORT_LEVEL_UNDEFINED)
+ *aSupportLevel = OFFLINE_SUPPORT_LEVEL_NONE;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::SetOfflineSupportLevel(int32_t aSupportLevel) {
+ SetIntValue("offline_support_level", aSupportLevel);
+ return NS_OK;
+}
+
+// Called only during the migration process. A unique name is generated for the
+// migrated account.
+NS_IMETHODIMP
+nsMsgIncomingServer::GeneratePrettyNameForMigration(nsAString& aPrettyName) {
+ /**
+ * 4.x had provisions for multiple imap servers to be maintained under
+ * single identity. So, when migrated each of those server accounts need
+ * to be represented by unique account name. nsImapIncomingServer will
+ * override the implementation for this to do the right thing.
+ */
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::GetFilterScope(nsMsgSearchScopeValue* filterScope) {
+ NS_ENSURE_ARG_POINTER(filterScope);
+ *filterScope = nsMsgSearchScope::offlineMailFilter;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::GetSearchScope(nsMsgSearchScopeValue* searchScope) {
+ NS_ENSURE_ARG_POINTER(searchScope);
+ *searchScope = nsMsgSearchScope::offlineMail;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::GetIsSecure(bool* aIsSecure) {
+ NS_ENSURE_ARG_POINTER(aIsSecure);
+ int32_t socketType;
+ nsresult rv = GetSocketType(&socketType);
+ NS_ENSURE_SUCCESS(rv, rv);
+ *aIsSecure = (socketType == nsMsgSocketType::alwaysSTARTTLS ||
+ socketType == nsMsgSocketType::SSL);
+ return NS_OK;
+}
+
+// use the convenience macros to implement the accessors
+NS_IMPL_SERVERPREF_INT(nsMsgIncomingServer, AuthMethod, "authMethod")
+NS_IMPL_SERVERPREF_INT(nsMsgIncomingServer, BiffMinutes, "check_time")
+NS_IMPL_SERVERPREF_STR(nsMsgIncomingServer, Type, "type")
+NS_IMPL_SERVERPREF_STR(nsMsgIncomingServer, Clientid, "clientid")
+NS_IMPL_SERVERPREF_BOOL(nsMsgIncomingServer, ClientidEnabled, "clientidEnabled")
+NS_IMPL_SERVERPREF_BOOL(nsMsgIncomingServer, DownloadOnBiff, "download_on_biff")
+NS_IMPL_SERVERPREF_BOOL(nsMsgIncomingServer, Valid, "valid")
+NS_IMPL_SERVERPREF_BOOL(nsMsgIncomingServer, EmptyTrashOnExit,
+ "empty_trash_on_exit")
+NS_IMPL_SERVERPREF_BOOL(nsMsgIncomingServer, CanDelete, "canDelete")
+NS_IMPL_SERVERPREF_BOOL(nsMsgIncomingServer, LoginAtStartUp, "login_at_startup")
+NS_IMPL_SERVERPREF_BOOL(nsMsgIncomingServer,
+ DefaultCopiesAndFoldersPrefsToServer,
+ "allows_specialfolders_usage")
+
+NS_IMPL_SERVERPREF_BOOL(nsMsgIncomingServer, CanCreateFoldersOnServer,
+ "canCreateFolders")
+
+NS_IMPL_SERVERPREF_BOOL(nsMsgIncomingServer, CanFileMessagesOnServer,
+ "canFileMessages")
+
+NS_IMPL_SERVERPREF_BOOL(nsMsgIncomingServer, LimitOfflineMessageSize,
+ "limit_offline_message_size")
+
+NS_IMPL_SERVERPREF_INT(nsMsgIncomingServer, MaxMessageSize, "max_size")
+
+NS_IMPL_SERVERPREF_INT(nsMsgIncomingServer, IncomingDuplicateAction,
+ "dup_action")
+
+NS_IMPL_SERVERPREF_BOOL(nsMsgIncomingServer, Hidden, "hidden")
+
+NS_IMETHODIMP nsMsgIncomingServer::GetSocketType(int32_t* aSocketType) {
+ if (!mPrefBranch) return NS_ERROR_NOT_INITIALIZED;
+
+ nsresult rv = mPrefBranch->GetIntPref("socketType", aSocketType);
+
+ // socketType is set to default value. Look at isSecure setting
+ if (NS_FAILED(rv)) {
+ bool isSecure;
+ rv = mPrefBranch->GetBoolPref("isSecure", &isSecure);
+ if (NS_SUCCEEDED(rv) && isSecure) {
+ *aSocketType = nsMsgSocketType::SSL;
+ // don't call virtual method in case overrides call GetSocketType
+ nsMsgIncomingServer::SetSocketType(*aSocketType);
+ } else {
+ if (!mDefPrefBranch) return NS_ERROR_NOT_INITIALIZED;
+ rv = mDefPrefBranch->GetIntPref("socketType", aSocketType);
+ if (NS_FAILED(rv)) *aSocketType = nsMsgSocketType::plain;
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgIncomingServer::SetSocketType(int32_t aSocketType) {
+ if (!mPrefBranch) return NS_ERROR_NOT_INITIALIZED;
+
+ int32_t socketType = nsMsgSocketType::plain;
+ mPrefBranch->GetIntPref("socketType", &socketType);
+
+ nsresult rv = mPrefBranch->SetIntPref("socketType", aSocketType);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool isSecureOld = (socketType == nsMsgSocketType::alwaysSTARTTLS ||
+ socketType == nsMsgSocketType::SSL);
+ bool isSecureNew = (aSocketType == nsMsgSocketType::alwaysSTARTTLS ||
+ aSocketType == nsMsgSocketType::SSL);
+ if ((isSecureOld != isSecureNew) && m_rootFolder) {
+ m_rootFolder->NotifyBoolPropertyChanged(kIsSecure, isSecureOld,
+ isSecureNew);
+ }
+ return NS_OK;
+}
+
+// Check if the password is available and return a boolean indicating whether
+// it is being authenticated or not.
+NS_IMETHODIMP
+nsMsgIncomingServer::GetPasswordPromptRequired(bool* aPasswordIsRequired) {
+ NS_ENSURE_ARG_POINTER(aPasswordIsRequired);
+ *aPasswordIsRequired = true;
+
+ // If the password is not even required for biff we don't need to check any
+ // further
+ nsresult rv = GetServerRequiresPasswordForBiff(aPasswordIsRequired);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!*aPasswordIsRequired) return NS_OK;
+
+ // If the password is empty, check to see if it is stored and to be retrieved
+ if (m_password.IsEmpty()) (void)GetPasswordWithoutUI();
+
+ *aPasswordIsRequired = m_password.IsEmpty();
+ if (*aPasswordIsRequired) {
+ // Set *aPasswordIsRequired false if authMethod is oauth2.
+ int32_t authMethod = 0;
+ rv = GetAuthMethod(&authMethod);
+ if (NS_SUCCEEDED(rv) && authMethod == nsMsgAuthMethod::OAuth2) {
+ *aPasswordIsRequired = false;
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgIncomingServer::ConfigureTemporaryFilters(
+ nsIMsgFilterList* aFilterList) {
+ nsresult rv = ConfigureTemporaryReturnReceiptsFilter(aFilterList);
+ if (NS_FAILED(rv)) // shut up warnings...
+ return rv;
+ return ConfigureTemporaryServerSpamFilters(aFilterList);
+}
+
+nsresult nsMsgIncomingServer::ConfigureTemporaryServerSpamFilters(
+ nsIMsgFilterList* filterList) {
+ nsCOMPtr<nsISpamSettings> spamSettings;
+ nsresult rv = GetSpamSettings(getter_AddRefs(spamSettings));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool useServerFilter;
+ rv = spamSettings->GetUseServerFilter(&useServerFilter);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // if we aren't configured to use server filters, then return early.
+ if (!useServerFilter) return NS_OK;
+
+ // For performance reasons, we'll handle clearing of filters if the user turns
+ // off the server-side filters from the junk mail controls, in the junk mail
+ // controls.
+ nsAutoCString serverFilterName;
+ spamSettings->GetServerFilterName(serverFilterName);
+ if (serverFilterName.IsEmpty()) return NS_OK;
+ int32_t serverFilterTrustFlags = 0;
+ (void)spamSettings->GetServerFilterTrustFlags(&serverFilterTrustFlags);
+ if (!serverFilterTrustFlags) return NS_OK;
+ // check if filters have been setup already.
+ nsAutoString yesFilterName, noFilterName;
+ CopyASCIItoUTF16(serverFilterName, yesFilterName);
+ yesFilterName.AppendLiteral("Yes");
+
+ CopyASCIItoUTF16(serverFilterName, noFilterName);
+ noFilterName.AppendLiteral("No");
+
+ nsCOMPtr<nsIMsgFilter> newFilter;
+ (void)filterList->GetFilterNamed(yesFilterName, getter_AddRefs(newFilter));
+
+ if (!newFilter)
+ (void)filterList->GetFilterNamed(noFilterName, getter_AddRefs(newFilter));
+ if (newFilter) return NS_OK;
+
+ nsCOMPtr<nsIFile> file;
+ spamSettings->GetServerFilterFile(getter_AddRefs(file));
+
+ // it's possible that we can no longer find the sfd file (i.e. the user
+ // disabled an extnsion that was supplying the .sfd file.
+ if (!file) return NS_OK;
+
+ nsCOMPtr<nsIMsgFilterService> filterService =
+ do_GetService("@mozilla.org/messenger/services/filters;1", &rv);
+ nsCOMPtr<nsIMsgFilterList> serverFilterList;
+
+ rv = filterService->OpenFilterList(file, NULL, NULL,
+ getter_AddRefs(serverFilterList));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = serverFilterList->GetFilterNamed(yesFilterName,
+ getter_AddRefs(newFilter));
+ if (newFilter && serverFilterTrustFlags & nsISpamSettings::TRUST_POSITIVES) {
+ newFilter->SetTemporary(true);
+ // check if we're supposed to move junk mail to junk folder; if so,
+ // add filter action to do so.
+
+ /*
+ * We don't want this filter to activate on messages that have
+ * been marked by the user as not spam. This occurs when messages that
+ * were marked as good are moved back into the inbox. But to
+ * do this with a filter, we have to add a boolean term. That requires
+ * that we rewrite the existing filter search terms to group them.
+ */
+
+ // get the list of search terms from the filter
+ nsTArray<RefPtr<nsIMsgSearchTerm>> searchTerms;
+ rv = newFilter->GetSearchTerms(searchTerms);
+ NS_ENSURE_SUCCESS(rv, rv);
+ uint32_t count = searchTerms.Length();
+ if (count > 1) // don't need to group a single term
+ {
+ // beginGrouping the first term, and endGrouping the last term
+ searchTerms[0]->SetBeginsGrouping(true);
+ searchTerms[count - 1]->SetEndsGrouping(true);
+ }
+
+ // Create a new term, checking if the user set junk status. The term will
+ // search for junkscoreorigin != "user"
+ nsCOMPtr<nsIMsgSearchTerm> searchTerm;
+ rv = newFilter->CreateTerm(getter_AddRefs(searchTerm));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ searchTerm->SetAttrib(nsMsgSearchAttrib::JunkScoreOrigin);
+ searchTerm->SetOp(nsMsgSearchOp::Isnt);
+ searchTerm->SetBooleanAnd(true);
+
+ nsCOMPtr<nsIMsgSearchValue> searchValue;
+ searchTerm->GetValue(getter_AddRefs(searchValue));
+ NS_ENSURE_SUCCESS(rv, rv);
+ searchValue->SetAttrib(nsMsgSearchAttrib::JunkScoreOrigin);
+ searchValue->SetStr(u"user"_ns);
+ searchTerm->SetValue(searchValue);
+
+ newFilter->AppendTerm(searchTerm);
+
+ bool moveOnSpam, markAsReadOnSpam;
+ spamSettings->GetMoveOnSpam(&moveOnSpam);
+ if (moveOnSpam) {
+ nsCString spamFolderURI;
+ rv = spamSettings->GetSpamFolderURI(spamFolderURI);
+ if (NS_SUCCEEDED(rv) && (!spamFolderURI.IsEmpty())) {
+ nsCOMPtr<nsIMsgRuleAction> moveAction;
+ rv = newFilter->CreateAction(getter_AddRefs(moveAction));
+ if (NS_SUCCEEDED(rv)) {
+ moveAction->SetType(nsMsgFilterAction::MoveToFolder);
+ moveAction->SetTargetFolderUri(spamFolderURI);
+ newFilter->AppendAction(moveAction);
+ }
+ }
+ }
+ spamSettings->GetMarkAsReadOnSpam(&markAsReadOnSpam);
+ if (markAsReadOnSpam) {
+ nsCOMPtr<nsIMsgRuleAction> markAsReadAction;
+ rv = newFilter->CreateAction(getter_AddRefs(markAsReadAction));
+ if (NS_SUCCEEDED(rv)) {
+ markAsReadAction->SetType(nsMsgFilterAction::MarkRead);
+ newFilter->AppendAction(markAsReadAction);
+ }
+ }
+ filterList->InsertFilterAt(0, newFilter);
+ }
+
+ rv =
+ serverFilterList->GetFilterNamed(noFilterName, getter_AddRefs(newFilter));
+ if (newFilter && serverFilterTrustFlags & nsISpamSettings::TRUST_NEGATIVES) {
+ newFilter->SetTemporary(true);
+ filterList->InsertFilterAt(0, newFilter);
+ }
+
+ return rv;
+}
+
+nsresult nsMsgIncomingServer::ConfigureTemporaryReturnReceiptsFilter(
+ nsIMsgFilterList* filterList) {
+ nsresult rv;
+
+ nsCOMPtr<nsIMsgAccountManager> accountMgr =
+ do_GetService("@mozilla.org/messenger/account-manager;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgIdentity> identity;
+ rv = accountMgr->GetFirstIdentityForServer(this, getter_AddRefs(identity));
+ NS_ENSURE_SUCCESS(rv, rv);
+ // this can return success and a null identity...
+
+ bool useCustomPrefs = false;
+ int32_t incorp = nsIMsgMdnGenerator::eIncorporateInbox;
+ NS_ENSURE_TRUE(identity, NS_ERROR_NULL_POINTER);
+
+ identity->GetBoolAttribute("use_custom_prefs", &useCustomPrefs);
+ if (useCustomPrefs)
+ rv = GetIntValue("incorporate_return_receipt", &incorp);
+ else {
+ nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID));
+ if (prefs) prefs->GetIntPref("mail.incorporate.return_receipt", &incorp);
+ }
+
+ bool enable = (incorp == nsIMsgMdnGenerator::eIncorporateSent);
+
+ // this is a temporary, internal mozilla filter
+ // it will not show up in the UI, it will not be written to disk
+ constexpr auto internalReturnReceiptFilterName =
+ u"mozilla-temporary-internal-MDN-receipt-filter"_ns;
+
+ nsCOMPtr<nsIMsgFilter> newFilter;
+ rv = filterList->GetFilterNamed(internalReturnReceiptFilterName,
+ getter_AddRefs(newFilter));
+ if (newFilter)
+ newFilter->SetEnabled(enable);
+ else if (enable) {
+ nsCString actionTargetFolderUri;
+ rv = identity->GetFccFolder(actionTargetFolderUri);
+ if (!actionTargetFolderUri.IsEmpty()) {
+ filterList->CreateFilter(internalReturnReceiptFilterName,
+ getter_AddRefs(newFilter));
+ if (newFilter) {
+ newFilter->SetEnabled(true);
+ // this internal filter is temporary
+ // and should not show up in the UI or be written to disk
+ newFilter->SetTemporary(true);
+
+ nsCOMPtr<nsIMsgSearchTerm> term;
+ nsCOMPtr<nsIMsgSearchValue> value;
+
+ rv = newFilter->CreateTerm(getter_AddRefs(term));
+ if (NS_SUCCEEDED(rv)) {
+ rv = term->GetValue(getter_AddRefs(value));
+ if (NS_SUCCEEDED(rv)) {
+ // we need to use OtherHeader + 1 so nsMsgFilter::GetTerm will
+ // return our custom header.
+ value->SetAttrib(nsMsgSearchAttrib::OtherHeader + 1);
+ value->SetStr(u"multipart/report"_ns);
+ term->SetAttrib(nsMsgSearchAttrib::OtherHeader + 1);
+ term->SetOp(nsMsgSearchOp::Contains);
+ term->SetBooleanAnd(true);
+ term->SetArbitraryHeader("Content-Type"_ns);
+ term->SetValue(value);
+ newFilter->AppendTerm(term);
+ }
+ }
+ rv = newFilter->CreateTerm(getter_AddRefs(term));
+ if (NS_SUCCEEDED(rv)) {
+ rv = term->GetValue(getter_AddRefs(value));
+ if (NS_SUCCEEDED(rv)) {
+ // XXX todo
+ // determine if ::OtherHeader is the best way to do this.
+ // see nsMsgSearchOfflineMail::MatchTerms()
+ value->SetAttrib(nsMsgSearchAttrib::OtherHeader + 1);
+ value->SetStr(u"disposition-notification"_ns);
+ term->SetAttrib(nsMsgSearchAttrib::OtherHeader + 1);
+ term->SetOp(nsMsgSearchOp::Contains);
+ term->SetBooleanAnd(true);
+ term->SetArbitraryHeader("Content-Type"_ns);
+ term->SetValue(value);
+ newFilter->AppendTerm(term);
+ }
+ }
+ nsCOMPtr<nsIMsgRuleAction> filterAction;
+ rv = newFilter->CreateAction(getter_AddRefs(filterAction));
+ if (NS_SUCCEEDED(rv)) {
+ filterAction->SetType(nsMsgFilterAction::MoveToFolder);
+ filterAction->SetTargetFolderUri(actionTargetFolderUri);
+ newFilter->AppendAction(filterAction);
+ filterList->InsertFilterAt(0, newFilter);
+ }
+ }
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::ClearTemporaryReturnReceiptsFilter() {
+ if (mFilterList) {
+ nsCOMPtr<nsIMsgFilter> mdnFilter;
+ nsresult rv = mFilterList->GetFilterNamed(
+ u"mozilla-temporary-internal-MDN-receipt-filter"_ns,
+ getter_AddRefs(mdnFilter));
+ if (NS_SUCCEEDED(rv) && mdnFilter)
+ return mFilterList->RemoveFilter(mdnFilter);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::GetMsgFolderFromURI(nsIMsgFolder* aFolderResource,
+ const nsACString& aURI,
+ nsIMsgFolder** aFolder) {
+ nsCOMPtr<nsIMsgFolder> rootMsgFolder;
+ nsresult rv = GetRootMsgFolder(getter_AddRefs(rootMsgFolder));
+ NS_ENSURE_TRUE(rootMsgFolder, NS_ERROR_UNEXPECTED);
+
+ nsCOMPtr<nsIMsgFolder> msgFolder;
+ rv = rootMsgFolder->GetChildWithURI(aURI, true, true /*caseInsensitive*/,
+ getter_AddRefs(msgFolder));
+ if (NS_FAILED(rv) || !msgFolder) msgFolder = aFolderResource;
+ NS_IF_ADDREF(*aFolder = msgFolder);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::GetSpamSettings(nsISpamSettings** aSpamSettings) {
+ NS_ENSURE_ARG_POINTER(aSpamSettings);
+
+ nsAutoCString spamActionTargetAccount;
+ GetCharValue("spamActionTargetAccount", spamActionTargetAccount);
+ if (spamActionTargetAccount.IsEmpty()) {
+ GetServerURI(spamActionTargetAccount);
+ SetCharValue("spamActionTargetAccount", spamActionTargetAccount);
+ }
+
+ if (!mSpamSettings) {
+ nsresult rv;
+ mSpamSettings =
+ do_CreateInstance("@mozilla.org/messenger/spamsettings;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ mSpamSettings->Initialize(this);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ NS_ADDREF(*aSpamSettings = mSpamSettings);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::GetSpamFilterPlugin(nsIMsgFilterPlugin** aFilterPlugin) {
+ NS_ENSURE_ARG_POINTER(aFilterPlugin);
+ if (!mFilterPlugin) {
+ nsresult rv;
+ mFilterPlugin = do_GetService(
+ "@mozilla.org/messenger/filter-plugin;1?name=bayesianfilter", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ NS_IF_ADDREF(*aFilterPlugin = mFilterPlugin);
+ return NS_OK;
+}
+
+// get all the servers that defer to the account for the passed in server. Note
+// that destServer may not be "this"
+nsresult nsMsgIncomingServer::GetDeferredServers(
+ nsIMsgIncomingServer* destServer,
+ nsTArray<RefPtr<nsIPop3IncomingServer>>& aServers) {
+ nsresult rv;
+ nsCOMPtr<nsIMsgAccountManager> accountManager =
+ do_GetService("@mozilla.org/messenger/account-manager;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgAccount> thisAccount;
+ accountManager->FindAccountForServer(destServer, getter_AddRefs(thisAccount));
+ if (thisAccount) {
+ nsCString accountKey;
+ thisAccount->GetKey(accountKey);
+ nsTArray<RefPtr<nsIMsgIncomingServer>> allServers;
+ accountManager->GetAllServers(allServers);
+ for (auto server : allServers) {
+ nsCOMPtr<nsIPop3IncomingServer> popServer(do_QueryInterface(server));
+ if (popServer) {
+ nsCString deferredToAccount;
+ popServer->GetDeferredToAccount(deferredToAccount);
+ if (deferredToAccount.Equals(accountKey))
+ aServers.AppendElement(popServer);
+ }
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgIncomingServer::GetIsDeferredTo(bool* aIsDeferredTo) {
+ NS_ENSURE_ARG_POINTER(aIsDeferredTo);
+ nsCOMPtr<nsIMsgAccountManager> accountManager =
+ do_GetService("@mozilla.org/messenger/account-manager;1");
+ if (accountManager) {
+ nsCOMPtr<nsIMsgAccount> thisAccount;
+ accountManager->FindAccountForServer(this, getter_AddRefs(thisAccount));
+ if (thisAccount) {
+ nsCString accountKey;
+ thisAccount->GetKey(accountKey);
+ nsTArray<RefPtr<nsIMsgIncomingServer>> allServers;
+ accountManager->GetAllServers(allServers);
+ for (auto server : allServers) {
+ if (server) {
+ nsCString deferredToAccount;
+ server->GetCharValue("deferred_to_account", deferredToAccount);
+ if (deferredToAccount.Equals(accountKey)) {
+ *aIsDeferredTo = true;
+ return NS_OK;
+ }
+ }
+ }
+ }
+ }
+ *aIsDeferredTo = false;
+ return NS_OK;
+}
+
+const long kMaxDownloadTableSize = 500;
+
+// hash the concatenation of the message-id and subject as the hash table key,
+// and store the arrival index as the value. To limit the size of the hash
+// table, we just throw out ones with a lower ordinal value than the cut-off
+// point.
+NS_IMETHODIMP nsMsgIncomingServer::IsNewHdrDuplicate(nsIMsgDBHdr* aNewHdr,
+ bool* aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+ NS_ENSURE_ARG_POINTER(aNewHdr);
+ *aResult = false;
+
+ // If the message has been partially downloaded, the message should not
+ // be considered a duplicated message. See bug 714090.
+ uint32_t flags;
+ aNewHdr->GetFlags(&flags);
+ if (flags & nsMsgMessageFlags::Partial) return NS_OK;
+
+ nsAutoCString strHashKey;
+ nsCString messageId, subject;
+ aNewHdr->GetMessageId(getter_Copies(messageId));
+ strHashKey.Append(messageId);
+ aNewHdr->GetSubject(subject);
+ // err on the side of caution and ignore messages w/o subject or messageid.
+ if (subject.IsEmpty() || messageId.IsEmpty()) return NS_OK;
+ strHashKey.Append(subject);
+ int32_t hashValue = m_downloadedHdrs.Get(strHashKey);
+ if (hashValue)
+ *aResult = true;
+ else {
+ // we store the current size of the hash table as the hash
+ // value - this allows us to delete older entries.
+ m_downloadedHdrs.InsertOrUpdate(strHashKey, ++m_numMsgsDownloaded);
+ // Check if hash table is larger than some reasonable size
+ // and if is it, iterate over hash table deleting messages
+ // with an arrival index < number of msgs downloaded - half the reasonable
+ // size.
+ if (m_downloadedHdrs.Count() >= kMaxDownloadTableSize) {
+ for (auto iter = m_downloadedHdrs.Iter(); !iter.Done(); iter.Next()) {
+ if (iter.Data() < m_numMsgsDownloaded - kMaxDownloadTableSize / 2) {
+ iter.Remove();
+ } else if (m_downloadedHdrs.Count() <= kMaxDownloadTableSize / 2) {
+ break;
+ }
+ }
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::GetForcePropertyEmpty(const char* aPropertyName,
+ bool* _retval) {
+ NS_ENSURE_ARG_POINTER(_retval);
+ nsAutoCString nameEmpty(aPropertyName);
+ nameEmpty.AppendLiteral(".empty");
+ nsCString value;
+ GetCharValue(nameEmpty.get(), value);
+ *_retval = value.EqualsLiteral("true");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::SetForcePropertyEmpty(const char* aPropertyName,
+ bool aValue) {
+ nsAutoCString nameEmpty(aPropertyName);
+ nameEmpty.AppendLiteral(".empty");
+ return SetCharValue(nameEmpty.get(), aValue ? "true"_ns : ""_ns);
+}
+
+NS_IMETHODIMP
+nsMsgIncomingServer::GetSortOrder(int32_t* aSortOrder) {
+ NS_ENSURE_ARG_POINTER(aSortOrder);
+ *aSortOrder = 100000000;
+ return NS_OK;
+}
diff --git a/comm/mailnews/base/src/nsMsgIncomingServer.h b/comm/mailnews/base/src/nsMsgIncomingServer.h
new file mode 100644
index 0000000000..c75c1f07c9
--- /dev/null
+++ b/comm/mailnews/base/src/nsMsgIncomingServer.h
@@ -0,0 +1,103 @@
+/* -*- 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 nsMsgIncomingServer_h__
+#define nsMsgIncomingServer_h__
+
+#include "nsIMsgIncomingServer.h"
+#include "nsIPrefBranch.h"
+#include "nsIMsgFilterList.h"
+#include "msgCore.h"
+#include "nsIMsgFolder.h"
+#include "nsIFile.h"
+#include "nsCOMPtr.h"
+#include "nsCOMArray.h"
+#include "nsIPop3IncomingServer.h"
+#include "nsWeakReference.h"
+#include "nsIMsgDatabase.h"
+#include "nsISpamSettings.h"
+#include "nsIMsgFilterPlugin.h"
+#include "nsTHashMap.h"
+#include "nsIMsgPluggableStore.h"
+#include "nsIObserver.h"
+
+class nsIMsgFolderCache;
+class nsIMsgProtocolInfo;
+
+/*
+ * base class for nsIMsgIncomingServer - derive your class from here
+ * if you want to get some free implementation
+ *
+ * this particular implementation is not meant to be used directly.
+ */
+
+class nsMsgIncomingServer : public nsIMsgIncomingServer,
+ public nsSupportsWeakReference,
+ public nsIObserver {
+ public:
+ nsMsgIncomingServer();
+ nsresult Init();
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIMSGINCOMINGSERVER
+ NS_DECL_NSIOBSERVER
+
+ protected:
+ virtual ~nsMsgIncomingServer();
+ nsCString m_serverKey;
+
+ // Sets m_password, if password found. Can return NS_ERROR_ABORT if the
+ // user cancels the master password dialog.
+ nsresult GetPasswordWithoutUI();
+
+ nsresult ConfigureTemporaryReturnReceiptsFilter(nsIMsgFilterList* filterList);
+ nsresult ConfigureTemporaryServerSpamFilters(nsIMsgFilterList* filterList);
+
+ nsCOMPtr<nsIMsgFolder> m_rootFolder;
+ nsCOMPtr<nsIMsgDownloadSettings> m_downloadSettings;
+
+ // For local servers, where we put messages. For imap/pop3, where we store
+ // offline messages.
+ nsCOMPtr<nsIMsgPluggableStore> m_msgStore;
+
+ /// Helper routine to create local folder on disk if it doesn't exist
+ /// under the account's rootFolder.
+ nsresult CreateLocalFolder(const nsAString& folderName);
+
+ static nsresult GetDeferredServers(
+ nsIMsgIncomingServer* destServer,
+ nsTArray<RefPtr<nsIPop3IncomingServer>>& aServers);
+
+ nsresult CreateRootFolder();
+ virtual nsresult CreateRootFolderFromUri(const nsACString& serverUri,
+ nsIMsgFolder** rootFolder) = 0;
+
+ nsresult InternalSetHostName(const nsACString& aHostname,
+ const char* prefName);
+
+ nsCOMPtr<nsIFile> mFilterFile;
+ nsCOMPtr<nsIMsgFilterList> mFilterList;
+ nsCOMPtr<nsIMsgFilterList> mEditableFilterList;
+ nsCOMPtr<nsIPrefBranch> mPrefBranch;
+ nsCOMPtr<nsIPrefBranch> mDefPrefBranch;
+
+ // these allow us to handle duplicate incoming messages, e.g. delete them.
+ nsTHashMap<nsCStringHashKey, int32_t> m_downloadedHdrs;
+ int32_t m_numMsgsDownloaded;
+
+ private:
+ uint32_t m_biffState;
+ bool m_serverBusy;
+ nsCOMPtr<nsISpamSettings> mSpamSettings;
+ nsCOMPtr<nsIMsgFilterPlugin> mFilterPlugin; // XXX should be a list
+
+ protected:
+ nsString m_password;
+ bool m_canHaveFilters;
+ bool m_displayStartupPage;
+ bool mPerformingBiff;
+};
+
+#endif // nsMsgIncomingServer_h__
diff --git a/comm/mailnews/base/src/nsMsgKeySet.cpp b/comm/mailnews/base/src/nsMsgKeySet.cpp
new file mode 100644
index 0000000000..7423e2560a
--- /dev/null
+++ b/comm/mailnews/base/src/nsMsgKeySet.cpp
@@ -0,0 +1,1412 @@
+/* -*- 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 "msgCore.h" // precompiled header...
+#include "prlog.h"
+
+#include "MailNewsTypes.h"
+#include "nsMsgKeySet.h"
+#include "prprf.h"
+#include "prmem.h"
+#include "nsTArray.h"
+#include "nsMemory.h"
+#include <ctype.h>
+
+#if defined(DEBUG_seth_) || defined(DEBUG_sspitzer_)
+# define DEBUG_MSGKEYSET 1
+#endif
+
+/* A compressed encoding for sets of article. This is usually for lines from
+ the newsrc, which have article lists like
+
+ 1-29627,29635,29658,32861-32863
+
+ so the data has these properties:
+
+ - strictly increasing
+ - large subsequences of monotonically increasing ranges
+ - gaps in the set are usually small, but not always
+ - consecutive ranges tend to be large
+
+ The biggest win is to run-length encode the data, storing ranges as two
+ numbers (start+length or start,end). We could also store each number as a
+ delta from the previous number for further compression, but that gets kind
+ of tricky, since there are no guarantees about the sizes of the gaps, and
+ we'd have to store variable-length words.
+
+ Current data format:
+
+ DATA := SIZE [ CHUNK ]*
+ CHUNK := [ RANGE | VALUE ]
+ RANGE := -LENGTH START
+ START := VALUE
+ LENGTH := int32_t
+ VALUE := a literal positive integer, for now
+ it could also be an offset from the previous value.
+ LENGTH could also perhaps be a less-than-32-bit quantity,
+ at least most of the time.
+
+ Lengths of CHUNKs are stored negative to distinguish the beginning of
+ a chunk from a literal: negative means two-word sequence, positive
+ means one-word sequence.
+
+ 0 represents a literal 0, but should not occur, and should never occur
+ except in the first position.
+
+ A length of -1 won't occur either, except temporarily - a sequence of
+ two elements is represented as two literals, since they take up the same
+ space.
+
+ Another optimization we make is to notice that we typically ask the
+ question ``is N a member of the set'' for increasing values of N. So the
+ set holds a cache of the last value asked for, and can simply resume the
+ search from there. */
+
+nsMsgKeySet::nsMsgKeySet(/* MSG_NewsHost* host*/) {
+ m_cached_value = -1;
+ m_cached_value_index = 0;
+ m_length = 0;
+ m_data_size = 10;
+ m_data = (int32_t*)PR_Malloc(sizeof(int32_t) * m_data_size);
+#ifdef NEWSRC_DOES_HOST_STUFF
+ m_host = host;
+#endif
+}
+
+nsMsgKeySet::~nsMsgKeySet() { PR_FREEIF(m_data); }
+
+bool nsMsgKeySet::Grow() {
+ int32_t new_size;
+ int32_t* new_data;
+ new_size = m_data_size * 2;
+ new_data = (int32_t*)PR_REALLOC(m_data, sizeof(int32_t) * new_size);
+ if (!new_data) return false;
+ m_data_size = new_size;
+ m_data = new_data;
+ return true;
+}
+
+nsMsgKeySet::nsMsgKeySet(const char* numbers /* , MSG_NewsHost* host */) {
+ int32_t *head, *tail, *end;
+
+#ifdef NEWSRC_DOES_HOST_STUFF
+ m_host = host;
+#endif
+ m_cached_value = -1;
+ m_cached_value_index = 0;
+ m_length = 0;
+ m_data_size = 10;
+ m_data = (int32_t*)PR_Malloc(sizeof(int32_t) * m_data_size);
+ if (!m_data) return;
+
+ head = m_data;
+ tail = head;
+ end = head + m_data_size;
+
+ if (!numbers) {
+ return;
+ }
+
+ while (isspace(*numbers)) numbers++;
+ while (*numbers) {
+ int32_t from = 0;
+ int32_t to;
+
+ if (tail >= end - 4) {
+ /* out of room! */
+ int32_t tailo = tail - head;
+ if (!Grow()) {
+ PR_FREEIF(m_data);
+ return;
+ }
+ /* data may have been relocated */
+ head = m_data;
+ tail = head + tailo;
+ end = head + m_data_size;
+ }
+
+ while (isspace(*numbers)) numbers++;
+ if (*numbers && !isdigit(*numbers)) {
+ break; /* illegal character */
+ }
+ while (isdigit(*numbers)) {
+ from = (from * 10) + (*numbers++ - '0');
+ }
+ while (isspace(*numbers)) numbers++;
+ if (*numbers != '-') {
+ to = from;
+ } else {
+ to = 0;
+ numbers++;
+ while (*numbers >= '0' && *numbers <= '9')
+ to = (to * 10) + (*numbers++ - '0');
+ while (isspace(*numbers)) numbers++;
+ }
+
+ if (to < from) to = from; /* illegal */
+
+ /* This is a hack - if the newsrc file specifies a range 1-x as
+ being read, we internally pretend that article 0 is read as well.
+ (But if only 2-x are read, then 0 is not read.) This is needed
+ because some servers think that article 0 is an article (I think)
+ but some news readers (including Netscape 1.1) choke if the .newsrc
+ file has lines beginning with 0... ### */
+ if (from == 1) from = 0;
+
+ if (to == from) {
+ /* Write it as a literal */
+ *tail = from;
+ tail++;
+ } else /* Write it as a range. */ {
+ *tail = -(to - from);
+ tail++;
+ *tail = from;
+ tail++;
+ }
+
+ while (*numbers == ',' || isspace(*numbers)) {
+ numbers++;
+ }
+ }
+
+ m_length = tail - head; /* size of data */
+}
+
+nsMsgKeySet* nsMsgKeySet::Create(/*MSG_NewsHost* host*/) {
+ nsMsgKeySet* set = new nsMsgKeySet(/* host */);
+ if (set && set->m_data == NULL) {
+ delete set;
+ set = NULL;
+ }
+ return set;
+}
+
+nsMsgKeySet* nsMsgKeySet::Create(const char* value /* , MSG_NewsHost* host */) {
+#ifdef DEBUG_MSGKEYSET
+ printf("create from %s\n", value);
+#endif
+
+ nsMsgKeySet* set = new nsMsgKeySet(value /* , host */);
+ if (set && set->m_data == NULL) {
+ delete set;
+ set = NULL;
+ }
+ return set;
+}
+
+/* Returns the lowest non-member of the set greater than 0.
+ */
+int32_t nsMsgKeySet::FirstNonMember() {
+ if (m_length <= 0) {
+ return 1;
+ } else if (m_data[0] < 0 && m_data[1] != 1 && m_data[1] != 0) {
+ /* first range not equal to 0 or 1, always return 1 */
+ return 1;
+ } else if (m_data[0] < 0) {
+ /* it's a range */
+ /* If there is a range [N-M] we can presume that M+1 is not in the
+ set. */
+ return (m_data[1] - m_data[0] + 1);
+ } else {
+ /* it's a literal */
+ if (m_data[0] == 1) {
+ /* handle "1,..." */
+ if (m_length > 1 && m_data[1] == 2) {
+ /* This is "1,2,M-N,..." or "1,2,M,..." where M >= 4. Note
+ that M will never be 3, because in that case we would have
+ started with a range: "1-3,..." */
+ return 3;
+ } else {
+ return 2; /* handle "1,M-N,.." or "1,M,..."
+ where M >= 3; */
+ }
+ } else if (m_data[0] == 0) {
+ /* handle "0,..." */
+ if (m_length > 1 && m_data[1] == 1) {
+ /* this is 0,1, (see above) */
+ return 2;
+ } else {
+ return 1;
+ }
+
+ } else {
+ /* handle "M,..." where M >= 2. */
+ return 1;
+ }
+ }
+}
+
+nsresult nsMsgKeySet::Output(char** outputStr) {
+ NS_ENSURE_ARG(outputStr);
+ int32_t size;
+ int32_t* head;
+ int32_t* tail;
+ int32_t* end;
+ int32_t s_size;
+ char* s_head;
+ char *s, *s_end;
+ int32_t last_art = -1;
+
+ *outputStr = nullptr;
+
+ size = m_length;
+ head = m_data;
+ tail = head;
+ end = head + size;
+
+ s_size = (size * 12) +
+ 10; // dmb - try to make this allocation get used at least once.
+ s_head = (char*)moz_xmalloc(s_size);
+ if (!s_head) return NS_ERROR_OUT_OF_MEMORY;
+
+ s_head[0] = '\0'; // otherwise, s_head will contain garbage.
+ s = s_head;
+ s_end = s + s_size;
+
+ while (tail < end) {
+ int32_t from;
+ int32_t to;
+
+ if (s > (s_end - (12 * 2 + 10))) { /* 12 bytes for each number (enough
+ for "2147483647" aka 2^31-1),
+ plus 10 bytes of slop. */
+ int32_t so = s - s_head;
+ s_size += 200;
+ char* tmp = (char*)moz_xmalloc(s_size);
+ if (tmp) PL_strcpy(tmp, s_head);
+ free(s_head);
+ s_head = tmp;
+ if (!s_head) return NS_ERROR_OUT_OF_MEMORY;
+ s = s_head + so;
+ s_end = s_head + s_size;
+ }
+
+ if (*tail < 0) {
+ /* it's a range */
+ from = tail[1];
+ to = from + (-(tail[0]));
+ tail += 2;
+ } else /* it's a literal */
+ {
+ from = *tail;
+ to = from;
+ tail++;
+ }
+ if (from == 0) {
+ from = 1; /* See 'hack' comment above ### */
+ }
+ if (from <= last_art) from = last_art + 1;
+ if (from <= to) {
+ if (from < to) {
+ PR_snprintf(s, s_end - s, "%lu-%lu,", from, to);
+ } else {
+ PR_snprintf(s, s_end - s, "%lu,", from);
+ }
+ s += PL_strlen(s);
+ last_art = to;
+ }
+ }
+ if (last_art >= 0) {
+ s--; /* Strip off the last ',' */
+ }
+
+ *s = 0;
+
+ *outputStr = s_head;
+ return NS_OK;
+}
+
+int32_t nsMsgKeySet::GetLastMember() {
+ if (m_length > 1) {
+ int32_t nextToLast = m_data[m_length - 2];
+ if (nextToLast < 0) // is range at end?
+ {
+ int32_t last = m_data[m_length - 1];
+ return (-nextToLast + last - 1);
+ } else // no, so last number must be last member
+ {
+ return m_data[m_length - 1];
+ }
+ } else if (m_length == 1)
+ return m_data[0]; // must be only 1 read.
+ else
+ return 0;
+}
+
+void nsMsgKeySet::SetLastMember(int32_t newHighWaterMark) {
+ if (newHighWaterMark < GetLastMember()) {
+ while (true) {
+ if (m_length > 1) {
+ int32_t nextToLast = m_data[m_length - 2];
+ int32_t curHighWater;
+ if (nextToLast < 0) // is range at end?
+ {
+ int32_t rangeStart = m_data[m_length - 1];
+ int32_t rangeLength = -nextToLast;
+ curHighWater = (rangeLength + rangeStart - 1);
+ if (curHighWater > newHighWaterMark) {
+ if (rangeStart > newHighWaterMark) {
+ m_length -= 2; // throw away whole range
+ } else if (rangeStart == newHighWaterMark) {
+ // turn range into single element.
+ m_data[m_length - 2] = newHighWaterMark;
+ m_length--;
+ break;
+ } else // just shorten range
+ {
+ m_data[m_length - 2] = -(newHighWaterMark - rangeStart);
+ break;
+ }
+ } else {
+ // prevent the infinite loop
+ // see bug #13062
+ break;
+ }
+ } else if (m_data[m_length - 1] >
+ newHighWaterMark) // no, so last number must be last member
+ {
+ m_length--;
+ } else
+ break;
+ } else
+ break;
+ }
+ // well, the whole range is probably invalid, because the server probably
+ // re-ordered ids, but what can you do?
+#ifdef NEWSRC_DOES_HOST_STUFF
+ if (m_host) m_host->MarkDirty();
+#endif
+ }
+}
+
+int32_t nsMsgKeySet::GetFirstMember() {
+ if (m_length > 1) {
+ int32_t first = m_data[0];
+ if (first < 0) // is range at start?
+ {
+ int32_t second = m_data[1];
+ return (second);
+ } else // no, so first number must be first member
+ {
+ return m_data[0];
+ }
+ } else if (m_length == 1)
+ return m_data[0]; // must be only 1 read.
+ else
+ return 0;
+}
+
+/* Re-compresses a `nsMsgKeySet' object.
+
+ The assumption is made that the `nsMsgKeySet' is syntactically correct
+ (all ranges have a length of at least 1, and all values are non-
+ decreasing) but will optimize the compression, for example, merging
+ consecutive literals or ranges into one range.
+
+ Returns true if successful, false if there wasn't enough memory to
+ allocate scratch space.
+
+ #### This should be changed to modify the buffer in place.
+
+ Also note that we never call Optimize() unless we actually changed
+ something, so it's a great place to tell the MSG_NewsHost* that something
+ changed.
+ */
+bool nsMsgKeySet::Optimize() {
+ int32_t input_size;
+ int32_t output_size;
+ int32_t* input_tail;
+ int32_t* output_data;
+ int32_t* output_tail;
+ int32_t* input_end;
+ int32_t* output_end;
+
+ input_size = m_length;
+ output_size = input_size + 1;
+ input_tail = m_data;
+ output_data = (int32_t*)PR_Malloc(sizeof(int32_t) * output_size);
+ if (!output_data) return false;
+
+ output_tail = output_data;
+ input_end = input_tail + input_size;
+ output_end = output_data + output_size;
+
+ /* We're going to modify the set, so invalidate the cache. */
+ m_cached_value = -1;
+
+ while (input_tail < input_end) {
+ int32_t from, to;
+ bool range_p = (*input_tail < 0);
+
+ if (range_p) {
+ /* it's a range */
+ from = input_tail[1];
+ to = from + (-(input_tail[0]));
+
+ /* Copy it over */
+ *output_tail++ = *input_tail++;
+ *output_tail++ = *input_tail++;
+ } else {
+ /* it's a literal */
+ from = *input_tail;
+ to = from;
+
+ /* Copy it over */
+ *output_tail++ = *input_tail++;
+ }
+ NS_ASSERTION(output_tail < output_end, "invalid end of output string");
+ if (output_tail >= output_end) {
+ PR_Free(output_data);
+ return false;
+ }
+
+ /* As long as this chunk is followed by consecutive chunks,
+ keep extending it. */
+ while (input_tail < input_end &&
+ ((*input_tail > 0 && /* literal... */
+ *input_tail == to + 1) || /* ...and consecutive, or */
+ (*input_tail <= 0 && /* range... */
+ input_tail[1] == to + 1)) /* ...and consecutive. */
+ ) {
+ if (!range_p) {
+ /* convert the literal to a range. */
+ output_tail++;
+ output_tail[-2] = 0;
+ output_tail[-1] = from;
+ range_p = true;
+ }
+
+ if (*input_tail > 0) { /* literal */
+ output_tail[-2]--; /* increase length by 1 */
+ to++;
+ input_tail++;
+ } else {
+ int32_t L2 = (-*input_tail) + 1;
+ output_tail[-2] -= L2; /* increase length by N */
+ to += L2;
+ input_tail += 2;
+ }
+ }
+ }
+
+ PR_Free(m_data);
+ m_data = output_data;
+ m_data_size = output_size;
+ m_length = output_tail - output_data;
+
+ /* One last pass to turn [N - N+1] into [N, N+1]. */
+ output_tail = output_data;
+ output_end = output_tail + m_length;
+ while (output_tail < output_end) {
+ if (*output_tail < 0) {
+ /* it's a range */
+ if (output_tail[0] == -1) {
+ output_tail[0] = output_tail[1];
+ output_tail[1]++;
+ }
+ output_tail += 2;
+ } else {
+ /* it's a literal */
+ output_tail++;
+ }
+ }
+
+#ifdef NEWSRC_DOES_HOST_STUFF
+ if (m_host) m_host->MarkDirty();
+#endif
+ return true;
+}
+
+bool nsMsgKeySet::IsMember(int32_t number) {
+ bool value = false;
+ int32_t size;
+ int32_t* head;
+ int32_t* tail;
+ int32_t* end;
+
+ size = m_length;
+ head = m_data;
+ tail = head;
+ end = head + size;
+
+ /* If there is a value cached, and that value is smaller than the
+ value we're looking for, skip forward that far. */
+ if (m_cached_value > 0 && m_cached_value < number) {
+ tail += m_cached_value_index;
+ }
+
+ while (tail < end) {
+ if (*tail < 0) {
+ /* it's a range */
+ int32_t from = tail[1];
+ int32_t to = from + (-(tail[0]));
+ if (from > number) {
+ /* This range begins after the number - we've passed it. */
+ value = false;
+ goto DONE;
+ } else if (to >= number) {
+ /* In range. */
+ value = true;
+ goto DONE;
+ } else {
+ tail += 2;
+ }
+ } else {
+ /* it's a literal */
+ if (*tail == number) {
+ /* bang */
+ value = true;
+ goto DONE;
+ } else if (*tail > number) {
+ /* This literal is after the number - we've passed it. */
+ value = false;
+ goto DONE;
+ } else {
+ tail++;
+ }
+ }
+ }
+
+DONE:
+ /* Store the position of this chunk for next time. */
+ m_cached_value = number;
+ m_cached_value_index = tail - head;
+
+ return value;
+}
+
+int nsMsgKeySet::Add(int32_t number) {
+ int32_t size;
+ int32_t* head;
+ int32_t* tail;
+ int32_t* end;
+
+#ifdef DEBUG_MSGKEYSET
+ printf("add %d\n", number);
+#endif
+
+ size = m_length;
+ head = m_data;
+ tail = head;
+ end = head + size;
+
+ NS_ASSERTION(number >= 0, "can't have negative items");
+ if (number < 0) return 0;
+
+ /* We're going to modify the set, so invalidate the cache. */
+ m_cached_value = -1;
+
+ while (tail < end) {
+ if (*tail < 0) {
+ /* it's a range */
+ int32_t from = tail[1];
+ int32_t to = from + (-(tail[0]));
+
+ if (from <= number && to >= number) {
+ /* This number is already present - we don't need to do
+ anything. */
+ return 0;
+ }
+
+ if (to > number) {
+ /* We have found the point before which the new number
+ should be inserted. */
+ break;
+ }
+
+ tail += 2;
+ } else {
+ /* it's a literal */
+ if (*tail == number) {
+ /* This number is already present - we don't need to do
+ anything. */
+ return 0;
+ }
+
+ if (*tail > number) {
+ /* We have found the point before which the new number
+ should be inserted. */
+ break;
+ }
+
+ tail++;
+ }
+ }
+
+ /* At this point, `tail' points to a position in the set which represents
+ a value greater than `new'; or it is at `end'. In the interest of
+ avoiding massive duplication of code, simply insert a literal here and
+ then run the optimizer.
+ */
+ int mid = (tail - head);
+
+ if (m_data_size <= m_length + 1) {
+ int endo = end - head;
+ if (!Grow()) {
+ // out of memory
+ return -1;
+ }
+ head = m_data;
+ end = head + endo;
+ }
+
+ if (tail == end) {
+ /* at the end */
+ /* Add a literal to the end. */
+ m_data[m_length++] = number;
+ } else {
+ /* need to insert (or edit) in the middle */
+ int32_t i;
+ for (i = size; i > mid; i--) {
+ m_data[i] = m_data[i - 1];
+ }
+ m_data[i] = number;
+ m_length++;
+ }
+
+ Optimize();
+ return 1;
+}
+
+int nsMsgKeySet::Remove(int32_t number) {
+ int32_t size;
+ int32_t* head;
+ int32_t* tail;
+ int32_t* end;
+#ifdef DEBUG_MSGKEYSET
+ printf("remove %d\n", number);
+#endif
+
+ size = m_length;
+ head = m_data;
+ tail = head;
+ end = head + size;
+
+ // **** I am not sure this is a right thing to comment the following
+ // statements out. The reason for this is due to the implementation of
+ // offline save draft and template. We use faked UIDs (negative ids) for
+ // offline draft and template in order to distinguish them from real
+ // UID. David I need your help here. **** jt
+
+ // PR_ASSERT(number >= 0);
+ // if (number < 0) {
+ // return -1;
+ /// }
+
+ /* We're going to modify the set, so invalidate the cache. */
+ m_cached_value = -1;
+
+ while (tail < end) {
+ int32_t mid = (tail - m_data);
+
+ if (*tail < 0) {
+ /* it's a range */
+ int32_t from = tail[1];
+ int32_t to = from + (-(tail[0]));
+
+ if (number < from || number > to) {
+ /* Not this range */
+ tail += 2;
+ continue;
+ }
+
+ if (to == from + 1) {
+ /* If this is a range [N - N+1] and we are removing M
+ (which must be either N or N+1) replace it with a
+ literal. This reduces the length by 1. */
+ m_data[mid] = (number == from ? to : from);
+ while (++mid < m_length) {
+ m_data[mid] = m_data[mid + 1];
+ }
+ m_length--;
+ Optimize();
+ return 1;
+ } else if (to == from + 2) {
+ /* If this is a range [N - N+2] and we are removing M,
+ replace it with the literals L,M (that is, either
+ (N, N+1), (N, N+2), or (N+1, N+2). The overall
+ length remains the same. */
+ m_data[mid] = from;
+ m_data[mid + 1] = to;
+ if (from == number) {
+ m_data[mid] = from + 1;
+ } else if (to == number) {
+ m_data[mid + 1] = to - 1;
+ }
+ Optimize();
+ return 1;
+ } else if (from == number) {
+ /* This number is at the beginning of a long range (meaning a
+ range which will still be long enough to remain a range.)
+ Increase start and reduce length of the range. */
+ m_data[mid]++;
+ m_data[mid + 1]++;
+ Optimize();
+ return 1;
+ } else if (to == number) {
+ /* This number is at the end of a long range (meaning a range
+ which will still be long enough to remain a range.)
+ Just decrease the length of the range. */
+ m_data[mid]++;
+ Optimize();
+ return 1;
+ } else {
+ /* The number being deleted is in the middle of a range which
+ must be split. This increases overall length by 2.
+ */
+ int32_t i;
+ int endo = end - head;
+ if (m_data_size - m_length <= 2) {
+ if (!Grow())
+ // out of memory
+ return -1;
+ }
+ head = m_data;
+ end = head + endo;
+
+ for (i = m_length + 2; i > mid + 2; i--) {
+ m_data[i] = m_data[i - 2];
+ }
+
+ m_data[mid] = (-(number - from - 1));
+ m_data[mid + 1] = from;
+ m_data[mid + 2] = (-(to - number - 1));
+ m_data[mid + 3] = number + 1;
+ m_length += 2;
+
+ /* Oops, if we've ended up with a range with a 0 length,
+ which is illegal, convert it to a literal, which reduces
+ the overall length by 1. */
+ if (m_data[mid] == 0) {
+ /* first range */
+ m_data[mid] = m_data[mid + 1];
+ for (i = mid + 1; i < m_length; i++) {
+ m_data[i] = m_data[i + 1];
+ }
+ m_length--;
+ }
+ if (m_data[mid + 2] == 0) {
+ /* second range */
+ m_data[mid + 2] = m_data[mid + 3];
+ for (i = mid + 3; i < m_length; i++) {
+ m_data[i] = m_data[i + 1];
+ }
+ m_length--;
+ }
+ Optimize();
+ return 1;
+ }
+ } else {
+ /* it's a literal */
+ if (*tail != number) {
+ /* Not this literal */
+ tail++;
+ continue;
+ }
+
+ /* Excise this literal. */
+ m_length--;
+ while (mid < m_length) {
+ m_data[mid] = m_data[mid + 1];
+ mid++;
+ }
+ Optimize();
+ return 1;
+ }
+ }
+
+ /* It wasn't here at all. */
+ return 0;
+}
+
+static int32_t* msg_emit_range(int32_t* tmp, int32_t a, int32_t b) {
+ if (a == b) {
+ *tmp++ = a;
+ } else {
+ NS_ASSERTION(a < b && a >= 0, "range is out of order");
+ *tmp++ = -(b - a);
+ *tmp++ = a;
+ }
+ return tmp;
+}
+
+int nsMsgKeySet::AddRange(int32_t start, int32_t end) {
+ int32_t tmplength;
+ int32_t* tmp;
+ int32_t* in;
+ int32_t* out;
+ int32_t* tail;
+ int32_t a;
+ int32_t b;
+ bool didit = false;
+
+ /* We're going to modify the set, so invalidate the cache. */
+ m_cached_value = -1;
+
+ NS_ASSERTION(start <= end, "invalid range");
+ if (start > end) return -1;
+
+ if (start == end) {
+ return Add(start);
+ }
+
+ tmplength = m_length + 2;
+ tmp = (int32_t*)PR_Malloc(sizeof(int32_t) * tmplength);
+
+ if (!tmp)
+ // out of memory
+ return -1;
+
+ in = m_data;
+ out = tmp;
+ tail = in + m_length;
+
+#define EMIT(x, y) out = msg_emit_range(out, x, y)
+
+ while (in < tail) {
+ // Set [a,b] to be this range.
+ if (*in < 0) {
+ b = -*in++;
+ a = *in++;
+ b += a;
+ } else {
+ a = b = *in++;
+ }
+
+ if (a <= start && b >= end) {
+ // We already have the entire range marked.
+ PR_Free(tmp);
+ return 0;
+ }
+ if (start > b + 1) {
+ // No overlap yet.
+ EMIT(a, b);
+ } else if (end < a - 1) {
+ // No overlap, and we passed it.
+ EMIT(start, end);
+ EMIT(a, b);
+ didit = true;
+ break;
+ } else {
+ // The ranges overlap. Suck this range into our new range, and
+ // keep looking for other ranges that might overlap.
+ start = start < a ? start : a;
+ end = end > b ? end : b;
+ }
+ }
+ if (!didit) EMIT(start, end);
+ while (in < tail) {
+ *out++ = *in++;
+ }
+
+#undef EMIT
+
+ PR_Free(m_data);
+ m_data = tmp;
+ m_length = out - tmp;
+ m_data_size = tmplength;
+#ifdef NEWSRC_DOES_HOST_STUFF
+ if (m_host) m_host->MarkDirty();
+#endif
+ return 1;
+}
+
+int32_t nsMsgKeySet::CountMissingInRange(int32_t range_start,
+ int32_t range_end) {
+ int32_t count;
+ int32_t* head;
+ int32_t* tail;
+ int32_t* end;
+
+ NS_ASSERTION(range_start >= 0 && range_end >= 0 && range_end >= range_start,
+ "invalid range");
+ if (range_start < 0 || range_end < 0 || range_end < range_start) return -1;
+
+ head = m_data;
+ tail = head;
+ end = head + m_length;
+
+ count = range_end - range_start + 1;
+
+ while (tail < end) {
+ if (*tail < 0) {
+ /* it's a range */
+ int32_t from = tail[1];
+ int32_t to = from + (-(tail[0]));
+ if (from < range_start) from = range_start;
+ if (to > range_end) to = range_end;
+
+ if (to >= from) count -= (to - from + 1);
+
+ tail += 2;
+ } else {
+ /* it's a literal */
+ if (*tail >= range_start && *tail <= range_end) count--;
+ tail++;
+ }
+ NS_ASSERTION(count >= 0, "invalid count");
+ }
+ return count;
+}
+
+int nsMsgKeySet::FirstMissingRange(int32_t min, int32_t max, int32_t* first,
+ int32_t* last) {
+ int32_t size;
+ int32_t* head;
+ int32_t* tail;
+ int32_t* end;
+ int32_t from = 0;
+ int32_t to = 0;
+ int32_t a;
+ int32_t b;
+
+ NS_ASSERTION(first && last, "invalid parameter");
+ if (!first || !last) return -1;
+
+ *first = *last = 0;
+
+ NS_ASSERTION(min <= max && min > 0, "invalid min or max param");
+ if (min > max || min <= 0) return -1;
+
+ size = m_length;
+ head = m_data;
+ tail = head;
+ end = head + size;
+
+ while (tail < end) {
+ a = to + 1;
+ if (*tail < 0) { /* We got a range. */
+ from = tail[1];
+ to = from + (-(tail[0]));
+ tail += 2;
+ } else {
+ from = to = tail[0];
+ tail++;
+ }
+ b = from - 1;
+ /* At this point, [a,b] is the range of unread articles just before
+ the current range of read articles [from,to]. See if this range
+ intersects the [min,max] range we were given. */
+ if (a > max) return 0; /* It's hopeless; there are none. */
+ if (a <= b && b >= min) {
+ /* Ah-hah! We found an intersection. */
+ *first = a > min ? a : min;
+ *last = b < max ? b : max;
+ return 0;
+ }
+ }
+ /* We found no holes in the newsrc that overlaps the range, nor did we hit
+ something read beyond the end of the range. So, the great infinite
+ range of unread articles at the end of any newsrc line intersects the
+ range we want, and we just need to return that. */
+ a = to + 1;
+ *first = a > min ? a : min;
+ *last = max;
+ return 0;
+}
+
+// I'm guessing we didn't include this because we didn't think we're going
+// to need it. I'm not so sure. I'm putting it in for now.
+int nsMsgKeySet::LastMissingRange(int32_t min, int32_t max, int32_t* first,
+ int32_t* last) {
+ int32_t size;
+ int32_t* head;
+ int32_t* tail;
+ int32_t* end;
+ int32_t from = 0;
+ int32_t to = 0;
+ int32_t a;
+ int32_t b;
+
+ NS_ASSERTION(first && last, "invalid null param");
+ if (!first || !last) return -1;
+
+ *first = *last = 0;
+
+ NS_ASSERTION(min <= max && min > 0, "invalid min or max");
+ if (min > max || min <= 0) return -1;
+
+ size = m_length;
+ head = m_data;
+ tail = head;
+ end = head + size;
+
+ while (tail < end) {
+ a = to + 1;
+ if (*tail < 0) { /* We got a range. */
+ from = tail[1];
+ to = from + (-(tail[0]));
+ tail += 2;
+ } else {
+ from = to = tail[0];
+ tail++;
+ }
+ b = from - 1;
+ /* At this point, [a,b] is the range of unread articles just before
+ the current range of read articles [from,to]. See if this range
+ intersects the [min,max] range we were given. */
+ if (a > max)
+ return 0; /* We're done. If we found something, it's already
+ sitting in [*first,*last]. */
+ if (a <= b && b >= min) {
+ /* Ah-hah! We found an intersection. */
+ *first = a > min ? a : min;
+ *last = b < max ? b : max;
+ /* Continue on, looking for a later range. */
+ }
+ }
+ if (to < max) {
+ /* The great infinite range of unread articles at the end of any newsrc
+ line intersects the range we want, and we just need to return that. */
+ a = to + 1;
+ *first = a > min ? a : min;
+ *last = max;
+ }
+ return 0;
+}
+
+/**
+ * Fill the passed in aArray with the keys in the message key set.
+ */
+nsresult nsMsgKeySet::ToMsgKeyArray(nsTArray<nsMsgKey>& aArray) {
+ int32_t size;
+ int32_t* head;
+ int32_t* tail;
+ int32_t* end;
+ int32_t last_art = -1;
+
+ size = m_length;
+ head = m_data;
+ tail = head;
+ end = head + size;
+
+ while (tail < end) {
+ int32_t from;
+ int32_t to;
+
+ if (*tail < 0) {
+ /* it's a range */
+ from = tail[1];
+ to = from + (-(tail[0]));
+ tail += 2;
+ } else /* it's a literal */
+ {
+ from = *tail;
+ to = from;
+ tail++;
+ }
+ // The horrible news-hack used to adjust from to 1 if it was zero right
+ // here, but there is no longer a consumer of this method with that
+ // broken use-case.
+ if (from <= last_art) from = last_art + 1;
+ if (from <= to) {
+ if (from < to) {
+ for (int32_t i = from; i <= to; ++i) {
+ aArray.AppendElement(i);
+ }
+ } else {
+ aArray.AppendElement(from);
+ }
+ last_art = to;
+ }
+ }
+
+ return NS_OK;
+}
+
+#ifdef DEBUG /* A lot of test cases for the above */
+
+# define countof(x) (sizeof(x) / sizeof(*(x)))
+
+void nsMsgKeySet::test_decoder(const char* string) {
+ nsMsgKeySet set(string /* , NULL */);
+ char* tmp;
+ set.Output(&tmp);
+ printf("\t\"%s\"\t--> \"%s\"\n", string, tmp);
+ free(tmp);
+}
+
+# define START(STRING) \
+ string = STRING; \
+ if (!(set = nsMsgKeySet::Create(string))) abort()
+
+# define FROB(N, PUSHP) \
+ i = N; \
+ if (!(NS_SUCCEEDED(set->Output(&s)))) abort(); \
+ printf("%3lu: %-58s %c %3lu =\n", (unsigned long)set->m_length, s, \
+ (PUSHP ? '+' : '-'), (unsigned long)i); \
+ free(s); \
+ if (PUSHP ? set->Add(i) < 0 : set->Remove(i) < 0) abort(); \
+ if (!(NS_SUCCEEDED(set->Output(&s)))) abort(); \
+ printf("%3lu: %-58s optimized =\n", (unsigned long)set->m_length, s); \
+ free(s);
+
+# define END() \
+ if (!(NS_SUCCEEDED(set->Output(&s)))) abort(); \
+ printf("%3lu: %s\n\n", (unsigned long)set->m_length, s); \
+ free(s); \
+ delete set;
+
+void nsMsgKeySet::test_adder(void) {
+ const char* string;
+ nsMsgKeySet* set;
+ char* s;
+ int32_t i;
+
+ START("0-70,72-99,105,107,110-111,117-200");
+
+ FROB(205, true);
+ FROB(206, true);
+ FROB(207, true);
+ FROB(208, true);
+ FROB(208, true);
+ FROB(109, true);
+ FROB(72, true);
+
+ FROB(205, false);
+ FROB(206, false);
+ FROB(207, false);
+ FROB(208, false);
+ FROB(208, false);
+ FROB(109, false);
+ FROB(72, false);
+
+ FROB(72, true);
+ FROB(109, true);
+ FROB(208, true);
+ FROB(208, true);
+ FROB(207, true);
+ FROB(206, true);
+ FROB(205, true);
+
+ FROB(205, false);
+ FROB(206, false);
+ FROB(207, false);
+ FROB(208, false);
+ FROB(208, false);
+ FROB(109, false);
+ FROB(72, false);
+
+ FROB(100, true);
+ FROB(101, true);
+ FROB(102, true);
+ FROB(103, true);
+ FROB(106, true);
+ FROB(104, true);
+ FROB(109, true);
+ FROB(108, true);
+ END();
+
+ // clang-format off
+ START("1-6"); FROB(7, false); END();
+ START("1-6"); FROB(6, false); END();
+ START("1-6"); FROB(5, false); END();
+ START("1-6"); FROB(4, false); END();
+ START("1-6"); FROB(3, false); END();
+ START("1-6"); FROB(2, false); END();
+ START("1-6"); FROB(1, false); END();
+ START("1-6"); FROB(0, false); END();
+
+ START("1-3"); FROB(1, false); END();
+ START("1-3"); FROB(2, false); END();
+ START("1-3"); FROB(3, false); END();
+
+ START("1,3,5-7,9,10"); FROB(5, false); END();
+ START("1,3,5-7,9,10"); FROB(6, false); END();
+ START("1,3,5-7,9,10"); FROB(7, false); FROB(7, true); FROB(8, true);
+ FROB (4, true); FROB (2, false); FROB (2, true);
+
+ FROB (4, false); FROB (5, false); FROB (6, false); FROB (7, false);
+ FROB (8, false); FROB (9, false); FROB (10, false); FROB (3, false);
+ FROB (2, false); FROB (1, false); FROB (1, false); FROB (0, false);
+ END();
+ // clang-format on
+}
+
+# undef START
+# undef FROB
+# undef END
+
+# define START(STRING) \
+ string = STRING; \
+ if (!(set = nsMsgKeySet::Create(string))) abort()
+
+# define FROB(N, M) \
+ i = N; \
+ j = M; \
+ if (!(NS_SUCCEEDED(set->Output(&s)))) abort(); \
+ printf("%3lu: %-58s + %3lu-%3lu =\n", (unsigned long)set->m_length, s, \
+ (unsigned long)i, (unsigned long)j); \
+ free(s); \
+ switch (set->AddRange(i, j)) { \
+ case 0: \
+ printf("(no-op)\n"); \
+ break; \
+ case 1: \
+ break; \
+ default: \
+ abort(); \
+ } \
+ if (!(NS_SUCCEEDED(set->Output(&s)))) abort(); \
+ printf("%3lu: %-58s\n", (unsigned long)set->m_length, s); \
+ free(s);
+
+# define END() \
+ if (!(NS_SUCCEEDED(set->Output(&s)))) abort(); \
+ printf("%3lu: %s\n\n", (unsigned long)set->m_length, s); \
+ free(s); \
+ delete set;
+
+void nsMsgKeySet::test_ranges(void) {
+ const char* string;
+ nsMsgKeySet* set;
+ char* s;
+ int32_t i;
+ int32_t j;
+
+ START("20-40,72-99,105,107,110-111,117-200");
+
+ FROB(205, 208);
+ FROB(50, 70);
+ FROB(0, 10);
+ FROB(112, 113);
+ FROB(101, 101);
+ FROB(5, 75);
+ FROB(103, 109);
+ FROB(2, 20);
+ FROB(1, 9999);
+
+ END();
+
+# undef START
+# undef FROB
+# undef END
+}
+
+# define TEST(N) \
+ if (!with_cache) set->m_cached_value = -1; \
+ if (!(NS_SUCCEEDED(set->Output(&s)))) abort(); \
+ printf(" %3d = %s\n", N, (set->IsMember(N) ? "true" : "false")); \
+ free(s);
+
+void nsMsgKeySet::test_member(bool with_cache) {
+ nsMsgKeySet* set;
+ char* s;
+
+ const char* st1 = "1-70,72-99,105,107,110-111,117-200";
+ printf("\n\nTesting %s (with%s cache)\n", st1, with_cache ? "" : "out");
+ if (!(set = Create(st1))) {
+ abort();
+ }
+
+ TEST(-1);
+ TEST(0);
+ TEST(1);
+ TEST(20);
+
+ delete set;
+ const char* st2 = "0-70,72-99,105,107,110-111,117-200";
+ printf("\n\nTesting %s (with%s cache)\n", st2, with_cache ? "" : "out");
+ if (!(set = Create(st2))) {
+ abort();
+ }
+
+ TEST(-1);
+ TEST(0);
+ TEST(1);
+ TEST(20);
+ TEST(69);
+ TEST(70);
+ TEST(71);
+ TEST(72);
+ TEST(73);
+ TEST(74);
+ TEST(104);
+ TEST(105);
+ TEST(106);
+ TEST(107);
+ TEST(108);
+ TEST(109);
+ TEST(110);
+ TEST(111);
+ TEST(112);
+ TEST(116);
+ TEST(117);
+ TEST(118);
+ TEST(119);
+ TEST(200);
+ TEST(201);
+ TEST(65535);
+
+ delete set;
+}
+
+# undef TEST
+
+// static void
+// test_newsrc (char *file)
+// {
+// FILE *fp = fopen (file, "r");
+// char buf [1024];
+// if (! fp) abort ();
+// while (fgets (buf, sizeof (buf), fp))
+// {
+// if (!strncmp (buf, "options ", 8))
+// fwrite (buf, 1, strlen (buf), stdout);
+// else
+// {
+// char *sep = buf;
+// while (*sep != 0 && *sep != ':' && *sep != '!')
+// sep++;
+// if (*sep) sep++;
+// while (isspace (*sep)) sep++;
+// fwrite (buf, 1, sep - buf, stdout);
+// if (*sep)
+// {
+// char *s;
+// msg_NewsRCSet *set = msg_parse_newsrc_set (sep, &allocinfo);
+// if (! set)
+// abort ();
+// if (! msg_OptimizeNewsRCSet (set))
+// abort ();
+// if (! ((s = msg_format_newsrc_set (set))))
+// abort ();
+// msg_free_newsrc_set (set, &allocinfo);
+// fwrite (s, 1, strlen (s), stdout);
+// free (s);
+// fwrite ("\n", 1, 1, stdout);
+// }
+// }
+// }
+// fclose (fp);
+// }
+
+void nsMsgKeySet::RunTests() {
+ test_decoder("");
+ test_decoder(" ");
+ test_decoder("0");
+ test_decoder("1");
+ test_decoder("123");
+ test_decoder(" 123 ");
+ test_decoder(" 123 4");
+ test_decoder(" 1,2, 3, 4");
+ test_decoder("0-70,72-99,100,101");
+ test_decoder(" 0-70 , 72 - 99 ,100,101 ");
+ test_decoder("0 - 268435455");
+ /* This one overflows - we can't help it.
+ test_decoder ("0 - 4294967295"); */
+
+ test_adder();
+
+ test_ranges();
+
+ test_member(false);
+ test_member(true);
+
+ // test_newsrc ("/u/montulli/.newsrc");
+ /* test_newsrc ("/u/jwz/.newsrc");*/
+}
+
+#endif /* DEBUG */
diff --git a/comm/mailnews/base/src/nsMsgKeySet.h b/comm/mailnews/base/src/nsMsgKeySet.h
new file mode 100644
index 0000000000..7390645870
--- /dev/null
+++ b/comm/mailnews/base/src/nsMsgKeySet.h
@@ -0,0 +1,108 @@
+/* -*- 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 _nsMsgKeySet_H_
+#define _nsMsgKeySet_H_
+
+#include "msgCore.h"
+#include "nsTArray.h"
+
+// nsMsgKeySet represents a set of articles. Typically, it is the set of
+// read articles from a .newsrc file, but it can be used for other purposes
+// too.
+
+#if 0
+// If a MSG_NewsHost* is supplied to the creation routine, then that
+// MSG_NewsHost will be notified whenever a change is made to set.
+class MSG_NewsHost;
+#endif
+
+class nsMsgKeySet {
+ public:
+ NS_INLINE_DECL_REFCOUNTING(nsMsgKeySet);
+ // Creates an empty set.
+ static nsMsgKeySet* Create(/* MSG_NewsHost* host = NULL*/);
+
+ // Creates a set from the list of numbers, as might be found in a
+ // newsrc file.
+ static nsMsgKeySet* Create(const char* str /* , MSG_NewsHost* host = NULL*/);
+
+ // FirstNonMember() returns the lowest non-member of the set that is
+ // greater than 0.
+ int32_t FirstNonMember();
+
+ // Output() converts to a string representation suitable for writing to a
+ // .newsrc file.
+ nsresult Output(char** outputStr);
+
+ // IsMember() returns whether the given article is a member of this set.
+ bool IsMember(int32_t art);
+
+ // Add() adds the given article to the set. (Returns 1 if a change was
+ // made, 0 if it was already there, and negative on error.)
+ int Add(int32_t art);
+
+ // Remove() removes the given article from the set.
+ int Remove(int32_t art);
+
+ // AddRange() adds the (inclusive) given range of articles to the set.
+ int AddRange(int32_t first, int32_t last);
+
+ // CountMissingInRange() takes an inclusive range of articles and returns
+ // the number of articles in that range which are not in the set.
+ int32_t CountMissingInRange(int32_t start, int32_t end);
+
+ // FirstMissingRange() takes an inclusive range and finds the first range
+ // of articles that are not in the set. If none, return zeros.
+ int FirstMissingRange(int32_t min, int32_t max, int32_t* first,
+ int32_t* last);
+
+ // LastMissingRange() takes an inclusive range and finds the last range
+ // of articles that are not in the set. If none, return zeros.
+ int LastMissingRange(int32_t min, int32_t max, int32_t* first, int32_t* last);
+
+ int32_t GetLastMember();
+ int32_t GetFirstMember();
+ void SetLastMember(int32_t highWaterMark);
+ // For debugging only...
+ int32_t getLength() { return m_length; }
+
+ /**
+ * Fill the passed in aArray with the keys in the message key set.
+ */
+ nsresult ToMsgKeyArray(nsTArray<nsMsgKey>& aArray);
+
+#ifdef DEBUG
+ static void RunTests();
+#endif
+
+ protected:
+ nsMsgKeySet(/* MSG_NewsHost* host */);
+ explicit nsMsgKeySet(const char* /* , MSG_NewsHost* host */);
+ bool Grow();
+ bool Optimize();
+
+#ifdef DEBUG
+ static void test_decoder(const char*);
+ static void test_adder();
+ static void test_ranges();
+ static void test_member(bool with_cache);
+#endif
+
+ int32_t* m_data; /* the numbers composing the `chunks' */
+ int32_t m_data_size; /* size of that malloc'ed block */
+ int32_t m_length; /* active area */
+
+ int32_t m_cached_value; /* a potential set member, or -1 if unset*/
+ int32_t m_cached_value_index; /* the index into `data' at which a search
+ to determine whether `cached_value' was
+ a member of the set ended. */
+#ifdef NEWSRC_DOES_HOST_STUFF
+ MSG_NewsHost* m_host;
+#endif
+ ~nsMsgKeySet();
+};
+
+#endif /* _nsMsgKeySet_H_ */
diff --git a/comm/mailnews/base/src/nsMsgLineBuffer.cpp b/comm/mailnews/base/src/nsMsgLineBuffer.cpp
new file mode 100644
index 0000000000..60d38b5b8f
--- /dev/null
+++ b/comm/mailnews/base/src/nsMsgLineBuffer.cpp
@@ -0,0 +1,351 @@
+/* -*- 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 "msgCore.h"
+#include "prlog.h"
+#include "prmem.h"
+#include "nsMsgLineBuffer.h"
+#include "nsMsgUtils.h"
+#include "nsIInputStream.h" // used by nsMsgLineStreamBuffer
+
+nsByteArray::nsByteArray() {
+ MOZ_COUNT_CTOR(nsByteArray);
+ m_buffer = NULL;
+ m_bufferSize = 0;
+ m_bufferPos = 0;
+}
+
+nsByteArray::~nsByteArray() {
+ MOZ_COUNT_DTOR(nsByteArray);
+ PR_FREEIF(m_buffer);
+}
+
+nsresult nsByteArray::GrowBuffer(uint64_t desired_size, uint32_t quantum) {
+ if (desired_size > PR_UINT32_MAX) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ if (m_bufferSize < desired_size) {
+ char* new_buf;
+ uint32_t increment = desired_size - m_bufferSize;
+ if (increment < quantum) /* always grow by a minimum of N bytes */
+ increment = quantum;
+
+ new_buf =
+ (m_buffer ? (char*)PR_REALLOC(m_buffer, (m_bufferSize + increment))
+ : (char*)PR_MALLOC(m_bufferSize + increment));
+ if (!new_buf) return NS_ERROR_OUT_OF_MEMORY;
+ m_buffer = new_buf;
+ m_bufferSize += increment;
+ }
+ return NS_OK;
+}
+
+nsresult nsByteArray::AppendString(const char* string) {
+ uint32_t strLength = (string) ? PL_strlen(string) : 0;
+ return AppendBuffer(string, strLength);
+}
+
+nsresult nsByteArray::AppendBuffer(const char* buffer, uint32_t length) {
+ nsresult ret = NS_OK;
+ if (m_bufferPos + length > m_bufferSize)
+ ret = GrowBuffer(m_bufferPos + length, 1024);
+ if (NS_SUCCEEDED(ret)) {
+ memcpy(m_buffer + m_bufferPos, buffer, length);
+ m_bufferPos += length;
+ }
+ return ret;
+}
+
+nsMsgLineBuffer::nsMsgLineBuffer() { MOZ_COUNT_CTOR(nsMsgLineBuffer); }
+
+nsMsgLineBuffer::~nsMsgLineBuffer() { MOZ_COUNT_DTOR(nsMsgLineBuffer); }
+
+nsresult nsMsgLineBuffer::BufferInput(const char* net_buffer,
+ int32_t net_buffer_size) {
+ if (net_buffer_size < 0) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ nsresult status = NS_OK;
+ if (m_bufferPos > 0 && m_buffer && m_buffer[m_bufferPos - 1] == '\r' &&
+ net_buffer_size > 0 && net_buffer[0] != '\n') {
+ /* The last buffer ended with a CR. The new buffer does not start
+ with a LF. This old buffer should be shipped out and discarded. */
+ PR_ASSERT(m_bufferSize > m_bufferPos);
+ if (m_bufferSize <= m_bufferPos) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ if (NS_FAILED(HandleLine(m_buffer, m_bufferPos))) {
+ return NS_ERROR_FAILURE;
+ }
+ m_bufferPos = 0;
+ }
+ while (net_buffer_size > 0) {
+ const char* net_buffer_end = net_buffer + net_buffer_size;
+ const char* newline = 0;
+ const char* s;
+
+ for (s = net_buffer; s < net_buffer_end; s++) {
+ /* Move forward in the buffer until the first newline.
+ Stop when we see CRLF, CR, or LF, or the end of the buffer.
+ *But*, if we see a lone CR at the *very end* of the buffer,
+ treat this as if we had reached the end of the buffer without
+ seeing a line terminator. This is to catch the case of the
+ buffers splitting a CRLF pair, as in "FOO\r\nBAR\r" "\nBAZ\r\n".
+ */
+ if (*s == '\r' || *s == '\n') {
+ newline = s;
+ if (newline[0] == '\r') {
+ if (s == net_buffer_end - 1) {
+ /* CR at end - wait for the next character. */
+ newline = 0;
+ break;
+ } else if (newline[1] == '\n') {
+ /* CRLF seen; swallow both. */
+ newline++;
+ }
+ }
+ newline++;
+ break;
+ }
+ }
+
+ /* Ensure room in the net_buffer and append some or all of the current
+ chunk of data to it. */
+ {
+ const char* end = (newline ? newline : net_buffer_end);
+ uint64_t desired_size = (end - net_buffer) + (uint64_t)m_bufferPos + 1;
+ if (desired_size >= PR_INT32_MAX) {
+ // We're not willing to buffer more than 2GB data without seeing
+ // a newline, something is wrong with the input.
+ // Using this limit prevents us from overflowing.
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ if (desired_size >= m_bufferSize) {
+ status = GrowBuffer(desired_size, 1024);
+ if (NS_FAILED(status)) return status;
+ }
+ memcpy(m_buffer + m_bufferPos, net_buffer, (end - net_buffer));
+ m_bufferPos += (end - net_buffer);
+ }
+
+ /* Now m_buffer contains either a complete line, or as complete
+ a line as we have read so far.
+
+ If we have a line, process it, and then remove it from `m_buffer'.
+ Then go around the loop again, until we drain the incoming data.
+ */
+ if (!newline) return NS_OK;
+
+ if (NS_FAILED(HandleLine(m_buffer, m_bufferPos))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ net_buffer_size -= (newline - net_buffer);
+ net_buffer = newline;
+ m_bufferPos = 0;
+ }
+ return NS_OK;
+}
+
+// If there's still some data (non CRLF terminated) flush it out
+nsresult nsMsgLineBuffer::Flush() {
+ nsresult rv = NS_OK;
+ if (m_bufferPos > 0) {
+ rv = HandleLine(m_buffer, m_bufferPos);
+ m_bufferPos = 0;
+ }
+ return rv;
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+// This is a utility class used to efficiently extract lines from an input
+// stream by buffering read but unprocessed stream data in a buffer.
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+nsMsgLineStreamBuffer::nsMsgLineStreamBuffer(uint32_t aBufferSize,
+ bool aAllocateNewLines,
+ bool aEatCRLFs, char aLineToken)
+ : m_eatCRLFs(aEatCRLFs),
+ m_allocateNewLines(aAllocateNewLines),
+ m_lineToken(aLineToken) {
+ NS_ASSERTION(aBufferSize > 0, "invalid buffer size!!!");
+ m_dataBuffer = nullptr;
+ m_startPos = 0;
+ m_numBytesInBuffer = 0;
+
+ // used to buffer incoming data by ReadNextLineFromInput
+ if (aBufferSize > 0) {
+ m_dataBuffer = (char*)PR_CALLOC(sizeof(char) * aBufferSize);
+ }
+
+ m_dataBufferSize = aBufferSize;
+}
+
+nsMsgLineStreamBuffer::~nsMsgLineStreamBuffer() {
+ PR_FREEIF(m_dataBuffer); // release our buffer...
+}
+
+nsresult nsMsgLineStreamBuffer::GrowBuffer(uint32_t desiredSize) {
+ char* newBuffer = (char*)PR_REALLOC(m_dataBuffer, desiredSize);
+ NS_ENSURE_TRUE(newBuffer, NS_ERROR_OUT_OF_MEMORY);
+ m_dataBuffer = newBuffer;
+ m_dataBufferSize = desiredSize;
+ return NS_OK;
+}
+
+void nsMsgLineStreamBuffer::ClearBuffer() {
+ m_startPos = 0;
+ m_numBytesInBuffer = 0;
+}
+
+// aInputStream - the input stream we want to read a line from
+// aPauseForMoreData is returned as true if the stream does not yet contain a
+// line and we must wait for more data to come into the stream. Note to people
+// wishing to modify this function: Be *VERY CAREFUL* this is a critical
+// function used by all of our mail protocols including imap, nntp, and pop. If
+// you screw it up, you could break a lot of stuff.....
+
+char* nsMsgLineStreamBuffer::ReadNextLine(nsIInputStream* aInputStream,
+ uint32_t& aNumBytesInLine,
+ bool& aPauseForMoreData,
+ nsresult* prv,
+ bool addLineTerminator) {
+ // try to extract a line from m_inputBuffer. If we don't have an entire line,
+ // then read more bytes out from the stream. If the stream is empty then wait
+ // on the monitor for more data to come in.
+
+ NS_ASSERTION(m_dataBuffer && m_dataBufferSize > 0,
+ "invalid input arguments for read next line from input");
+
+ if (prv) *prv = NS_OK;
+ // initialize out values
+ aPauseForMoreData = false;
+ aNumBytesInLine = 0;
+ char* endOfLine = nullptr;
+ char* startOfLine = m_dataBuffer + m_startPos;
+
+ if (m_numBytesInBuffer > 0) // any data in our internal buffer?
+ endOfLine = PL_strchr(startOfLine, m_lineToken); // see if we already
+ // have a line ending...
+
+ // it's possible that we got here before the first time we receive data from
+ // the server so aInputStream will be nullptr...
+ if (!endOfLine && aInputStream) // get some more data from the server
+ {
+ nsresult rv;
+ uint64_t numBytesInStream = 0;
+ uint32_t numBytesCopied = 0;
+ bool nonBlockingStream;
+ aInputStream->IsNonBlocking(&nonBlockingStream);
+ rv = aInputStream->Available(&numBytesInStream);
+ if (NS_FAILED(rv)) {
+ if (prv) *prv = rv;
+ aNumBytesInLine = 0;
+ return nullptr;
+ }
+ if (!nonBlockingStream && numBytesInStream == 0) // if no data available,
+ numBytesInStream = m_dataBufferSize / 2; // ask for half the data
+ // buffer size.
+
+ // if the number of bytes we want to read from the stream, is greater than
+ // the number of bytes left in our buffer, then we need to shift the start
+ // pos and its contents down to the beginning of m_dataBuffer...
+ uint32_t numFreeBytesInBuffer =
+ m_dataBufferSize - m_startPos - m_numBytesInBuffer;
+ if (numBytesInStream >= numFreeBytesInBuffer) {
+ if (m_startPos) {
+ memmove(m_dataBuffer, startOfLine, m_numBytesInBuffer);
+ // make sure the end of the buffer is terminated
+ m_dataBuffer[m_numBytesInBuffer] = '\0';
+ m_startPos = 0;
+ startOfLine = m_dataBuffer;
+ numFreeBytesInBuffer = m_dataBufferSize - m_numBytesInBuffer;
+ }
+ // If we didn't make enough space (or any), grow the buffer
+ if (numBytesInStream >= numFreeBytesInBuffer) {
+ int64_t growBy = (numBytesInStream - numFreeBytesInBuffer) * 2 + 1;
+ // GrowBuffer cannot handle over 4GB size.
+ if (m_dataBufferSize + growBy > PR_UINT32_MAX) return nullptr;
+ // try growing buffer by twice as much as we need.
+ nsresult rv = GrowBuffer(m_dataBufferSize + growBy);
+ // if we can't grow the buffer, we have to bail.
+ if (NS_FAILED(rv)) return nullptr;
+ startOfLine = m_dataBuffer;
+ numFreeBytesInBuffer += growBy;
+ }
+ NS_ASSERTION(m_startPos == 0, "m_startPos should be 0 .....");
+ }
+
+ uint32_t numBytesToCopy = /* leave one for a null terminator */
+ std::min(uint64_t(numFreeBytesInBuffer - 1), numBytesInStream);
+ if (numBytesToCopy > 0) {
+ // read the data into the end of our data buffer
+ char* startOfNewData = startOfLine + m_numBytesInBuffer;
+ rv = aInputStream->Read(startOfNewData, numBytesToCopy, &numBytesCopied);
+ if (prv) *prv = rv;
+ uint32_t i;
+ for (i = 0; i < numBytesCopied; i++) // replace nulls with spaces
+ {
+ if (!startOfNewData[i]) startOfNewData[i] = ' ';
+ }
+ m_numBytesInBuffer += numBytesCopied;
+ m_dataBuffer[m_startPos + m_numBytesInBuffer] = '\0';
+
+ // okay, now that we've tried to read in more data from the stream,
+ // look for another end of line character in the new data
+ endOfLine = PL_strchr(startOfNewData, m_lineToken);
+ }
+ }
+
+ // okay, now check again for endOfLine.
+ if (endOfLine) {
+ if (!m_eatCRLFs) endOfLine += 1; // count for LF or CR
+
+ aNumBytesInLine = endOfLine - startOfLine;
+
+ if (m_eatCRLFs && aNumBytesInLine > 0 &&
+ startOfLine[aNumBytesInLine - 1] == '\r')
+ aNumBytesInLine--; // Remove the CR in a CRLF sequence.
+
+ // PR_CALLOC zeros out the allocated line
+ char* newLine = (char*)PR_CALLOC(
+ aNumBytesInLine + (addLineTerminator ? MSG_LINEBREAK_LEN : 0) + 1);
+ if (!newLine) {
+ aNumBytesInLine = 0;
+ aPauseForMoreData = true;
+ return nullptr;
+ }
+
+ memcpy(newLine, startOfLine,
+ aNumBytesInLine); // copy the string into the new line buffer
+ if (addLineTerminator) {
+ memcpy(newLine + aNumBytesInLine, MSG_LINEBREAK, MSG_LINEBREAK_LEN);
+ aNumBytesInLine += MSG_LINEBREAK_LEN;
+ }
+
+ if (m_eatCRLFs)
+ endOfLine += 1; // advance past LF or CR if we haven't already done so...
+
+ // now we need to update the data buffer to go past the line we just read
+ // out.
+ m_numBytesInBuffer -= (endOfLine - startOfLine);
+ if (m_numBytesInBuffer)
+ m_startPos = endOfLine - m_dataBuffer;
+ else
+ m_startPos = 0;
+
+ return newLine;
+ }
+
+ aPauseForMoreData = true;
+ return nullptr; // if we somehow got here. we don't have another line in the
+ // buffer yet...need to wait for more data...
+}
+
+bool nsMsgLineStreamBuffer::NextLineAvailable() {
+ return (m_numBytesInBuffer > 0 &&
+ PL_strchr(m_dataBuffer + m_startPos, m_lineToken));
+}
diff --git a/comm/mailnews/base/src/nsMsgLineBuffer.h b/comm/mailnews/base/src/nsMsgLineBuffer.h
new file mode 100644
index 0000000000..2ac579be3b
--- /dev/null
+++ b/comm/mailnews/base/src/nsMsgLineBuffer.h
@@ -0,0 +1,123 @@
+/* -*- 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 _nsMsgLineBuffer_H
+#define _nsMsgLineBuffer_H
+
+#include "msgCore.h" // precompiled header...
+
+// I can't believe I have to have this stupid class, but I can't find
+// anything suitable (nsStrImpl might be, when it's done). nsIByteBuffer
+// would do, if I had a stream for input, which I don't.
+
+class nsByteArray {
+ public:
+ nsByteArray();
+ virtual ~nsByteArray();
+ uint32_t GetSize() { return m_bufferSize; }
+ uint32_t GetBufferPos() { return m_bufferPos; }
+ nsresult GrowBuffer(uint64_t desired_size, uint32_t quantum = 1024);
+ nsresult AppendString(const char* string);
+ nsresult AppendBuffer(const char* buffer, uint32_t length);
+ void ResetWritePos() { m_bufferPos = 0; }
+ char* GetBuffer() { return m_buffer; }
+
+ protected:
+ char* m_buffer;
+ uint32_t m_bufferSize;
+ uint32_t
+ m_bufferPos; // write Pos in m_buffer - where the next byte should go.
+};
+
+/**
+ * nsMsgLineBuffer breaks up incoming data into lines.
+ * It accepts CRLF, CR or LF line endings.
+ *
+ * Data is fed in via BufferInput(). The virtual HandleLine() will be
+ * invoked for each line. The data passed to HandleLine() is verbatim,
+ * and will include whatever line endings were in the source data.
+ *
+ * Flush() should be called when the data is exhausted, to handle any
+ * leftover bytes in the buffer (e.g. if the data doesn't end with an EOL).
+ */
+class nsMsgLineBuffer : private nsByteArray {
+ public:
+ nsMsgLineBuffer();
+ virtual ~nsMsgLineBuffer();
+ nsresult BufferInput(const char* net_buffer, int32_t net_buffer_size);
+
+ /**
+ * HandleLine should be implemented by derived classes, to handle a line.
+ * The line will have whatever end-of-line characters were present in the
+ * source data (potentially none, if the data ends mid-line).
+ */
+ virtual nsresult HandleLine(const char* line, uint32_t line_length) = 0;
+
+ /**
+ * Flush processes any unprocessed data currently in the buffer. Should
+ * be called when the source data is exhausted.
+ */
+ nsresult Flush();
+};
+
+// I'm adding this utility class here for lack of a better place. This utility
+// class is similar to nsMsgLineBuffer except it works from an input stream. It
+// is geared towards efficiently parsing new lines out of a stream by storing
+// read but unprocessed bytes in a buffer. I envision the primary use of this to
+// be our mail protocols such as imap, news and pop which need to process line
+// by line data being returned in the form of a proxied stream from the server.
+
+class nsIInputStream;
+
+class nsMsgLineStreamBuffer {
+ public:
+ NS_INLINE_DECL_REFCOUNTING(nsMsgLineStreamBuffer)
+
+ // aBufferSize -- size of the buffer you want us to use for buffering stream
+ // data
+ // aEndOfLinetoken -- The delimiter string to be used for determining the end
+ // of line. This allows us to parse platform specific end of
+ // line endings by making it a parameter.
+ // aAllocateNewLines -- true if you want calls to ReadNextLine to allocate new
+ // memory for the line.
+ // if false, the char * returned is just a ptr into the buffer.
+ // Subsequent calls to ReadNextLine will alter the data so your
+ // ptr only has a life time of a per call.
+ // aEatCRLFs -- true if you don't want to see the CRLFs on the lines
+ // returned by ReadNextLine.
+ // false if you do want to see them.
+ // aLineToken -- Specify the line token to look for, by default is LF ('\n')
+ // which cover as well CRLF. If lines are terminated with a CR
+ // only, you need to set aLineToken to CR ('\r')
+ nsMsgLineStreamBuffer(
+ uint32_t aBufferSize, bool aAllocateNewLines, bool aEatCRLFs = true,
+ char aLineToken = '\n'); // specify the size of the buffer you want the
+ // class to use....
+
+ // Caller must free the line returned using PR_Free
+ // aEndOfLinetoken -- delimiter used to denote the end of a line.
+ // aNumBytesInLine -- The number of bytes in the line returned
+ // aPauseForMoreData -- There is not enough data in the stream to make a line
+ // at this time...
+ char* ReadNextLine(nsIInputStream* aInputStream, uint32_t& aNumBytesInLine,
+ bool& aPauseForMoreData, nsresult* rv = nullptr,
+ bool addLineTerminator = false);
+ nsresult GrowBuffer(uint32_t desiredSize);
+ void ClearBuffer();
+ bool NextLineAvailable();
+
+ private:
+ virtual ~nsMsgLineStreamBuffer();
+
+ protected:
+ bool m_eatCRLFs;
+ bool m_allocateNewLines;
+ char* m_dataBuffer;
+ uint32_t m_dataBufferSize;
+ uint32_t m_startPos;
+ uint32_t m_numBytesInBuffer;
+ char m_lineToken;
+};
+
+#endif
diff --git a/comm/mailnews/base/src/nsMsgMailNewsUrl.cpp b/comm/mailnews/base/src/nsMsgMailNewsUrl.cpp
new file mode 100644
index 0000000000..3796aeaef2
--- /dev/null
+++ b/comm/mailnews/base/src/nsMsgMailNewsUrl.cpp
@@ -0,0 +1,1070 @@
+/* -*- 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 "msgCore.h"
+#include "nsMsgMailNewsUrl.h"
+#include "nsIMsgAccountManager.h"
+#include "nsString.h"
+#include "nsILoadGroup.h"
+#include "nsIDocShell.h"
+#include "nsIWebProgress.h"
+#include "nsIWebProgressListener.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIIOService.h"
+#include "nsNetCID.h"
+#include "nsIStreamListener.h"
+#include "nsIOutputStream.h"
+#include "nsIInputStream.h"
+#include "nsNetUtil.h"
+#include "nsIFile.h"
+#include "prmem.h"
+#include <time.h>
+#include "nsMsgUtils.h"
+#include "mozilla/Components.h"
+#include "nsProxyRelease.h"
+#include "mozilla/Encoding.h"
+#include "nsDocShellLoadState.h"
+#include "nsContentUtils.h"
+#include "nsIObjectInputStream.h"
+#include "nsIObjectOutputStream.h"
+#include "nsIChannel.h"
+
+nsMsgMailNewsUrl::nsMsgMailNewsUrl() {
+ // nsIURI specific state
+ m_runningUrl = false;
+ m_updatingFolder = false;
+ m_msgIsInLocalCache = false;
+ m_suppressErrorMsgs = false;
+ m_hasNormalizedOrigin = false; // SetSpecInternal() will set this correctly.
+ mMaxProgress = -1;
+}
+
+#define NOTIFY_URL_LISTENERS(propertyfunc_, params_) \
+ PR_BEGIN_MACRO \
+ nsTObserverArray<nsCOMPtr<nsIUrlListener>>::ForwardIterator iter( \
+ mUrlListeners); \
+ while (iter.HasMore()) { \
+ nsCOMPtr<nsIUrlListener> listener = iter.GetNext(); \
+ listener->propertyfunc_ params_; \
+ } \
+ PR_END_MACRO
+
+nsMsgMailNewsUrl::~nsMsgMailNewsUrl() {
+ // In IMAP this URL is created and destroyed on the imap thread,
+ // so we must ensure that releases of XPCOM objects (which might be
+ // implemented by non-threadsafe JS components) are released on the
+ // main thread.
+ NS_ReleaseOnMainThread("nsMsgMailNewsUrl::m_baseURL", m_baseURL.forget());
+ NS_ReleaseOnMainThread("nsMsgMailNewsUrl::mMimeHeaders",
+ mMimeHeaders.forget());
+ NS_ReleaseOnMainThread("nsMsgMailNewsUrl::m_searchSession",
+ m_searchSession.forget());
+
+ nsTObserverArray<nsCOMPtr<nsIUrlListener>>::ForwardIterator iter(
+ mUrlListeners);
+ while (iter.HasMore()) {
+ nsCOMPtr<nsIUrlListener> listener = iter.GetNext();
+ if (listener)
+ NS_ReleaseOnMainThread("nsMsgMailNewsUrl::mUrlListeners",
+ listener.forget());
+ }
+}
+
+NS_IMPL_ADDREF(nsMsgMailNewsUrl)
+NS_IMPL_RELEASE(nsMsgMailNewsUrl)
+
+// We want part URLs to QI to nsIURIWithSpecialOrigin so we can give
+// them a "normalized" origin. URLs that already have a "normalized"
+// origin should not QI to nsIURIWithSpecialOrigin.
+NS_INTERFACE_MAP_BEGIN(nsMsgMailNewsUrl)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIMsgMailNewsUrl)
+ NS_INTERFACE_MAP_ENTRY(nsIMsgMailNewsUrl)
+ NS_INTERFACE_MAP_ENTRY(nsIURL)
+ NS_INTERFACE_MAP_ENTRY(nsIURI)
+ NS_INTERFACE_MAP_ENTRY(nsISerializable)
+ NS_INTERFACE_MAP_ENTRY(nsIClassInfo)
+ NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIURIWithSpecialOrigin,
+ m_hasNormalizedOrigin)
+NS_INTERFACE_MAP_END
+
+//--------------------------
+// Support for serialization
+//--------------------------
+// nsMsgMailNewsUrl is only partly serialized by serializing the "base URL"
+// which is an nsStandardURL, or by only serializing the Spec. This may
+// cause problems in the future. See bug 1512356 and bug 1515337 for details,
+// follow-up in bug 1512698.
+
+NS_IMETHODIMP_(void)
+nsMsgMailNewsUrl::Serialize(mozilla::ipc::URIParams& aParams) {
+ m_baseURL->Serialize(aParams);
+}
+
+//----------------------------
+// Support for nsISerializable
+//----------------------------
+NS_IMETHODIMP nsMsgMailNewsUrl::Read(nsIObjectInputStream* stream) {
+ nsAutoCString urlstr;
+ nsresult rv = NS_ReadOptionalCString(stream, urlstr);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIIOService> ioService = mozilla::components::IO::Service();
+ NS_ENSURE_TRUE(ioService, NS_ERROR_UNEXPECTED);
+ nsCOMPtr<nsIURI> url;
+ rv = ioService->NewURI(urlstr, nullptr, nullptr, getter_AddRefs(url));
+ NS_ENSURE_SUCCESS(rv, rv);
+ m_baseURL = do_QueryInterface(url);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::Write(nsIObjectOutputStream* stream) {
+ nsAutoCString urlstr;
+ nsresult rv = m_baseURL->GetSpec(urlstr);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return NS_WriteOptionalStringZ(stream, urlstr.get());
+}
+
+//-------------------------
+// Support for nsIClassInfo
+//-------------------------
+NS_IMETHODIMP nsMsgMailNewsUrl::GetInterfaces(nsTArray<nsIID>& array) {
+ array.Clear();
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::GetScriptableHelper(
+ nsIXPCScriptable** _retval) {
+ *_retval = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::GetContractID(nsACString& aContractID) {
+ aContractID.SetIsVoid(true);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::GetClassDescription(
+ nsACString& aClassDescription) {
+ aClassDescription.SetIsVoid(true);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::GetClassID(nsCID** aClassID) {
+ *aClassID = (nsCID*)moz_xmalloc(sizeof(nsCID));
+ return GetClassIDNoAlloc(*aClassID);
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::GetFlags(uint32_t* aFlags) {
+ *aFlags = 0;
+ return NS_OK;
+}
+
+#define NS_MSGMAILNEWSURL_CID \
+ { \
+ 0x3fdae3ab, 0x4ac1, 0x4ad4, { \
+ 0xb2, 0x8a, 0x28, 0xd0, 0xfa, 0x36, 0x39, 0x29 \
+ } \
+ }
+static NS_DEFINE_CID(kNS_MSGMAILNEWSURL_CID, NS_MSGMAILNEWSURL_CID);
+NS_IMETHODIMP nsMsgMailNewsUrl::GetClassIDNoAlloc(nsCID* aClassIDNoAlloc) {
+ *aClassIDNoAlloc = kNS_MSGMAILNEWSURL_CID;
+ return NS_OK;
+}
+
+//------------------------------------
+// Support for nsIURIWithSpecialOrigin
+//------------------------------------
+NS_IMETHODIMP nsMsgMailNewsUrl::GetOrigin(nsIURI** aOrigin) {
+ MOZ_ASSERT(m_hasNormalizedOrigin,
+ "nsMsgMailNewsUrl::GetOrigin() can only be called for URLs with "
+ "normalized spec");
+
+ if (!m_normalizedOrigin) {
+ nsCOMPtr<nsIMsgMessageUrl> msgUrl;
+ QueryInterface(NS_GET_IID(nsIMsgMessageUrl), getter_AddRefs(msgUrl));
+
+ nsAutoCString spec;
+ if (!msgUrl || NS_FAILED(msgUrl->GetNormalizedSpec(spec))) {
+ MOZ_ASSERT(false, "Can't get normalized spec");
+ // just use the normal spec.
+ GetSpec(spec);
+ }
+
+ nsresult rv = NS_NewURI(getter_AddRefs(m_normalizedOrigin), spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ NS_IF_ADDREF(*aOrigin = m_normalizedOrigin);
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////////
+// Begin nsIMsgMailNewsUrl specific support
+////////////////////////////////////////////////////////////////////////////////////
+
+nsresult nsMsgMailNewsUrl::GetUrlState(bool* aRunningUrl) {
+ if (aRunningUrl) *aRunningUrl = m_runningUrl;
+
+ return NS_OK;
+}
+
+nsresult nsMsgMailNewsUrl::SetUrlState(bool aRunningUrl, nsresult aExitCode) {
+ // if we already knew this running state, return, unless the url was aborted
+ if (m_runningUrl == aRunningUrl && aExitCode != NS_MSG_ERROR_URL_ABORTED) {
+ return NS_OK;
+ }
+ m_runningUrl = aRunningUrl;
+ nsCOMPtr<nsIMsgStatusFeedback> statusFeedback;
+
+ // put this back - we need it for urls that don't run through the doc loader
+ if (NS_SUCCEEDED(GetStatusFeedback(getter_AddRefs(statusFeedback))) &&
+ statusFeedback) {
+ if (m_runningUrl)
+ statusFeedback->StartMeteors();
+ else {
+ statusFeedback->ShowProgress(0);
+ statusFeedback->StopMeteors();
+ }
+ }
+
+ if (m_runningUrl) {
+ NOTIFY_URL_LISTENERS(OnStartRunningUrl, (this));
+ } else {
+ NOTIFY_URL_LISTENERS(OnStopRunningUrl, (this, aExitCode));
+ mUrlListeners.Clear();
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::RegisterListener(nsIUrlListener* aUrlListener) {
+ NS_ENSURE_ARG_POINTER(aUrlListener);
+ mUrlListeners.AppendElement(aUrlListener);
+ return NS_OK;
+}
+
+nsresult nsMsgMailNewsUrl::UnRegisterListener(nsIUrlListener* aUrlListener) {
+ NS_ENSURE_ARG_POINTER(aUrlListener);
+
+ // Due to the way mailnews is structured, some listeners attempt to remove
+ // themselves twice. This may in fact be an error in the coding, however
+ // if they didn't do it as they do currently, then they could fail to remove
+ // their listeners.
+ mUrlListeners.RemoveElement(aUrlListener);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::GetServer(
+ nsIMsgIncomingServer** aIncomingServer) {
+ // mscott --> we could cache a copy of the server here....but if we did, we
+ // run the risk of leaking the server if any single url gets leaked....of
+ // course that shouldn't happen...but it could. so i'm going to look it up
+ // every time and we can look at caching it later.
+
+ nsresult rv;
+
+ nsAutoCString urlstr;
+ rv = m_baseURL->GetSpec(urlstr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIURL> url;
+ rv = NS_MutateURI(NS_STANDARDURLMUTATOR_CONTRACTID)
+ .SetSpec(urlstr)
+ .Finalize(url);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString scheme;
+ rv = GetScheme(scheme);
+ if (NS_SUCCEEDED(rv)) {
+ if (scheme.EqualsLiteral("pop")) scheme.AssignLiteral("pop3");
+ // we use "nntp" in the server list so translate it here.
+ if (scheme.EqualsLiteral("news")) scheme.AssignLiteral("nntp");
+ rv = NS_MutateURI(url).SetScheme(scheme).Finalize(url);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIMsgAccountManager> accountManager =
+ do_GetService("@mozilla.org/messenger/account-manager;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = accountManager->FindServerByURI(url, aIncomingServer);
+ if (!*aIncomingServer && scheme.EqualsLiteral("imap")) {
+ // look for any imap server with this host name so clicking on
+ // other users folder urls will work. We could override this method
+ // for imap urls, or we could make caching of servers work and
+ // just set the server in the imap code for this case.
+ rv = NS_MutateURI(url).SetUserPass(EmptyCString()).Finalize(url);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = accountManager->FindServerByURI(url, aIncomingServer);
+ }
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::GetMsgWindow(nsIMsgWindow** aMsgWindow) {
+ NS_ENSURE_ARG_POINTER(aMsgWindow);
+ *aMsgWindow = nullptr;
+
+ nsCOMPtr<nsIMsgWindow> msgWindow(do_QueryReferent(m_msgWindowWeak));
+ msgWindow.forget(aMsgWindow);
+ return *aMsgWindow ? NS_OK : NS_ERROR_NULL_POINTER;
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::SetMsgWindow(nsIMsgWindow* aMsgWindow) {
+#ifdef DEBUG_David_Bienvenu
+ NS_ASSERTION(aMsgWindow || !m_msgWindowWeak,
+ "someone crunching non-null msg window");
+#endif
+ m_msgWindowWeak = do_GetWeakReference(aMsgWindow);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::GetStatusFeedback(
+ nsIMsgStatusFeedback** aMsgFeedback) {
+ // note: it is okay to return a null status feedback and not return an error
+ // it's possible the url really doesn't have status feedback
+ *aMsgFeedback = nullptr;
+ if (!m_statusFeedbackWeak) {
+ nsCOMPtr<nsIMsgWindow> msgWindow(do_QueryReferent(m_msgWindowWeak));
+ if (msgWindow) msgWindow->GetStatusFeedback(aMsgFeedback);
+ } else {
+ nsCOMPtr<nsIMsgStatusFeedback> statusFeedback(
+ do_QueryReferent(m_statusFeedbackWeak));
+ statusFeedback.forget(aMsgFeedback);
+ }
+ return *aMsgFeedback ? NS_OK : NS_ERROR_NULL_POINTER;
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::SetStatusFeedback(
+ nsIMsgStatusFeedback* aMsgFeedback) {
+ if (aMsgFeedback) m_statusFeedbackWeak = do_GetWeakReference(aMsgFeedback);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::GetMaxProgress(int64_t* aMaxProgress) {
+ *aMaxProgress = mMaxProgress;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::SetMaxProgress(int64_t aMaxProgress) {
+ mMaxProgress = aMaxProgress;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::GetLoadGroup(nsILoadGroup** aLoadGroup) {
+ *aLoadGroup = nullptr;
+ // note: it is okay to return a null load group and not return an error
+ // it's possible the url really doesn't have load group
+ nsCOMPtr<nsILoadGroup> loadGroup(do_QueryReferent(m_loadGroupWeak));
+ if (!loadGroup) {
+ nsCOMPtr<nsIMsgWindow> msgWindow(do_QueryReferent(m_msgWindowWeak));
+ if (msgWindow) {
+ // XXXbz This is really weird... why are we getting some
+ // random loadgroup we're not really a part of?
+ nsCOMPtr<nsIDocShell> docShell;
+ msgWindow->GetRootDocShell(getter_AddRefs(docShell));
+ loadGroup = do_GetInterface(docShell);
+ m_loadGroupWeak = do_GetWeakReference(loadGroup);
+ }
+ }
+ loadGroup.forget(aLoadGroup);
+ return *aLoadGroup ? NS_OK : NS_ERROR_NULL_POINTER;
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::GetUpdatingFolder(bool* aResult) {
+ NS_ENSURE_ARG(aResult);
+ *aResult = m_updatingFolder;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::SetUpdatingFolder(bool updatingFolder) {
+ m_updatingFolder = updatingFolder;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::GetMsgIsInLocalCache(bool* aMsgIsInLocalCache) {
+ NS_ENSURE_ARG(aMsgIsInLocalCache);
+ *aMsgIsInLocalCache = m_msgIsInLocalCache;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::SetMsgIsInLocalCache(bool aMsgIsInLocalCache) {
+ m_msgIsInLocalCache = aMsgIsInLocalCache;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::GetSuppressErrorMsgs(bool* aSuppressErrorMsgs) {
+ NS_ENSURE_ARG(aSuppressErrorMsgs);
+ *aSuppressErrorMsgs = m_suppressErrorMsgs;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::SetSuppressErrorMsgs(bool aSuppressErrorMsgs) {
+ m_suppressErrorMsgs = aSuppressErrorMsgs;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::GetErrorCode(nsACString& aErrorCode) {
+ aErrorCode = m_errorCode;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::SetErrorCode(const nsACString& aErrorCode) {
+ m_errorCode.Assign(aErrorCode);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::GetErrorMessage(nsAString& aErrorMessage) {
+ aErrorMessage = m_errorMessage;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::SetErrorMessage(
+ const nsAString& aErrorMessage) {
+ m_errorMessage.Assign(aErrorMessage);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::SetSeeOtherURI(const nsACString& aSeeOtherURI) {
+ m_seeOtherURI.Assign(aSeeOtherURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::GetSeeOtherURI(nsACString& aSeeOtherURI) {
+ aSeeOtherURI = m_seeOtherURI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::IsUrlType(uint32_t type, bool* isType) {
+ // base class doesn't know about any specific types
+ NS_ENSURE_ARG(isType);
+ *isType = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::SetSearchSession(
+ nsIMsgSearchSession* aSearchSession) {
+ if (aSearchSession) m_searchSession = aSearchSession;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::GetSearchSession(
+ nsIMsgSearchSession** aSearchSession) {
+ NS_ENSURE_ARG(aSearchSession);
+ NS_IF_ADDREF(*aSearchSession = m_searchSession);
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////////
+// End nsIMsgMailNewsUrl specific support
+////////////////////////////////////////////////////////////////////////////////////
+
+////////////////////////////////////////////////////////////////////////////////////
+// Begin nsIURI support
+////////////////////////////////////////////////////////////////////////////////////
+
+NS_IMETHODIMP nsMsgMailNewsUrl::GetSpec(nsACString& aSpec) {
+ return m_baseURL->GetSpec(aSpec);
+}
+
+nsresult nsMsgMailNewsUrl::CreateURL(const nsACString& aSpec, nsIURL** aURL) {
+ nsCOMPtr<nsIURL> url;
+ nsresult rv = NS_MutateURI(NS_STANDARDURLMUTATOR_CONTRACTID)
+ .SetSpec(aSpec)
+ .Finalize(url);
+ NS_ENSURE_SUCCESS(rv, rv);
+ url.forget(aURL);
+ return NS_OK;
+}
+
+#define FILENAME_PART_LEN 10
+
+nsresult nsMsgMailNewsUrl::SetSpecInternal(const nsACString& aSpec) {
+ nsAutoCString spec(aSpec);
+ // Parse out "filename" attribute if present.
+ char *start, *end;
+ start = PL_strcasestr(spec.BeginWriting(), "?filename=");
+ if (!start) start = PL_strcasestr(spec.BeginWriting(), "&filename=");
+ if (start) { // Make sure we only get our own value.
+ end = PL_strcasestr((char*)(start + FILENAME_PART_LEN), "&");
+ if (end) {
+ *end = 0;
+ mAttachmentFileName = start + FILENAME_PART_LEN;
+ *end = '&';
+ } else
+ mAttachmentFileName = start + FILENAME_PART_LEN;
+ }
+
+ // Now, set the rest.
+ nsresult rv = CreateURL(aSpec, getter_AddRefs(m_baseURL));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Check whether the URL is in normalized form.
+ nsCOMPtr<nsIMsgMessageUrl> msgUrl;
+ QueryInterface(NS_GET_IID(nsIMsgMessageUrl), getter_AddRefs(msgUrl));
+
+ nsAutoCString normalizedSpec;
+ if (!msgUrl || NS_FAILED(msgUrl->GetNormalizedSpec(normalizedSpec))) {
+ // If we can't get the normalized spec, never QI this to
+ // nsIURIWithSpecialOrigin.
+ m_hasNormalizedOrigin = false;
+ } else {
+ m_hasNormalizedOrigin = !spec.Equals(normalizedSpec);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::GetPrePath(nsACString& aPrePath) {
+ return m_baseURL->GetPrePath(aPrePath);
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::GetScheme(nsACString& aScheme) {
+ return m_baseURL->GetScheme(aScheme);
+}
+
+nsresult nsMsgMailNewsUrl::SetScheme(const nsACString& aScheme) {
+ return NS_MutateURI(m_baseURL).SetScheme(aScheme).Finalize(m_baseURL);
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::GetUserPass(nsACString& aUserPass) {
+ return m_baseURL->GetUserPass(aUserPass);
+}
+
+nsresult nsMsgMailNewsUrl::SetUserPass(const nsACString& aUserPass) {
+ return NS_MutateURI(m_baseURL).SetUserPass(aUserPass).Finalize(m_baseURL);
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::GetUsername(nsACString& aUsername) {
+ /* note: this will return an escaped string */
+ return m_baseURL->GetUsername(aUsername);
+}
+
+nsresult nsMsgMailNewsUrl::SetUsername(const nsACString& aUsername) {
+ return NS_MutateURI(m_baseURL).SetUsername(aUsername).Finalize(m_baseURL);
+}
+
+nsresult nsMsgMailNewsUrl::SetUsernameInternal(const nsACString& aUsername) {
+ return NS_MutateURI(m_baseURL).SetUsername(aUsername).Finalize(m_baseURL);
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::GetPassword(nsACString& aPassword) {
+ return m_baseURL->GetPassword(aPassword);
+}
+
+nsresult nsMsgMailNewsUrl::SetPassword(const nsACString& aPassword) {
+ return NS_MutateURI(m_baseURL).SetPassword(aPassword).Finalize(m_baseURL);
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::GetHostPort(nsACString& aHostPort) {
+ return m_baseURL->GetHostPort(aHostPort);
+}
+
+nsresult nsMsgMailNewsUrl::SetHostPort(const nsACString& aHostPort) {
+ return NS_MutateURI(m_baseURL).SetHostPort(aHostPort).Finalize(m_baseURL);
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::GetHost(nsACString& aHost) {
+ return m_baseURL->GetHost(aHost);
+}
+
+nsresult nsMsgMailNewsUrl::SetHost(const nsACString& aHost) {
+ return NS_MutateURI(m_baseURL).SetHost(aHost).Finalize(m_baseURL);
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::GetPort(int32_t* aPort) {
+ return m_baseURL->GetPort(aPort);
+}
+
+nsresult nsMsgMailNewsUrl::SetPort(int32_t aPort) {
+ return NS_MutateURI(m_baseURL).SetPort(aPort).Finalize(m_baseURL);
+}
+
+nsresult nsMsgMailNewsUrl::SetPortInternal(int32_t aPort) {
+ return NS_MutateURI(m_baseURL).SetPort(aPort).Finalize(m_baseURL);
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::GetPathQueryRef(nsACString& aPath) {
+ return m_baseURL->GetPathQueryRef(aPath);
+}
+
+nsresult nsMsgMailNewsUrl::SetPathQueryRef(const nsACString& aPath) {
+ return NS_MutateURI(m_baseURL).SetPathQueryRef(aPath).Finalize(m_baseURL);
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::GetAsciiHost(nsACString& aHostA) {
+ return m_baseURL->GetAsciiHost(aHostA);
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::GetAsciiHostPort(nsACString& aHostPortA) {
+ return m_baseURL->GetAsciiHostPort(aHostPortA);
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::GetAsciiSpec(nsACString& aSpecA) {
+ return m_baseURL->GetAsciiSpec(aSpecA);
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::GetBaseURI(nsIURI** aBaseURI) {
+ NS_ENSURE_ARG_POINTER(aBaseURI);
+ return m_baseURL->QueryInterface(NS_GET_IID(nsIURI), (void**)aBaseURI);
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::Equals(nsIURI* other, bool* _retval) {
+ // The passed-in URI might be a mail news url. Pass our inner URL to its
+ // Equals method. The other mail news url will then pass its inner URL to
+ // to the Equals method of our inner URL. Other URIs will return false.
+ if (other) return other->Equals(m_baseURL, _retval);
+
+ return m_baseURL->Equals(other, _retval);
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::EqualsExceptRef(nsIURI* other, bool* result) {
+ // The passed-in URI might be a mail news url. Pass our inner URL to its
+ // Equals method. The other mail news url will then pass its inner URL to
+ // to the Equals method of our inner URL. Other URIs will return false.
+ if (other) return other->EqualsExceptRef(m_baseURL, result);
+
+ return m_baseURL->EqualsExceptRef(other, result);
+}
+
+NS_IMETHODIMP
+nsMsgMailNewsUrl::GetSpecIgnoringRef(nsACString& result) {
+ return m_baseURL->GetSpecIgnoringRef(result);
+}
+
+NS_IMETHODIMP
+nsMsgMailNewsUrl::GetDisplaySpec(nsACString& aUnicodeSpec) {
+ return m_baseURL->GetDisplaySpec(aUnicodeSpec);
+}
+
+NS_IMETHODIMP
+nsMsgMailNewsUrl::GetDisplayHostPort(nsACString& aHostPort) {
+ return m_baseURL->GetDisplayHostPort(aHostPort);
+}
+
+NS_IMETHODIMP
+nsMsgMailNewsUrl::GetDisplayHost(nsACString& aHost) {
+ return m_baseURL->GetDisplayHost(aHost);
+}
+
+NS_IMETHODIMP
+nsMsgMailNewsUrl::GetDisplayPrePath(nsACString& aPrePath) {
+ return m_baseURL->GetDisplayPrePath(aPrePath);
+}
+
+NS_IMETHODIMP
+nsMsgMailNewsUrl::GetHasRef(bool* result) {
+ return m_baseURL->GetHasRef(result);
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::SchemeIs(const char* aScheme, bool* _retval) {
+ return m_baseURL->SchemeIs(aScheme, _retval);
+}
+
+nsresult nsMsgMailNewsUrl::Clone(nsIURI** _retval) {
+ nsresult rv;
+ nsAutoCString urlSpec;
+ nsCOMPtr<nsIIOService> ioService = mozilla::components::IO::Service();
+ NS_ENSURE_TRUE(ioService, NS_ERROR_UNEXPECTED);
+ rv = GetSpec(urlSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIURI> newUri;
+ rv = ioService->NewURI(urlSpec, nullptr, nullptr, getter_AddRefs(newUri));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // add the msg window to the cloned url
+ nsCOMPtr<nsIMsgWindow> msgWindow(do_QueryReferent(m_msgWindowWeak));
+ if (msgWindow) {
+ nsCOMPtr<nsIMsgMailNewsUrl> msgMailNewsUrl = do_QueryInterface(newUri, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ msgMailNewsUrl->SetMsgWindow(msgWindow);
+ }
+
+ newUri.forget(_retval);
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::Resolve(const nsACString& relativePath,
+ nsACString& result) {
+ // only resolve anchor urls....i.e. urls which start with '#' against the
+ // mailnews url... everything else shouldn't be resolved against mailnews
+ // urls.
+ nsresult rv = NS_OK;
+
+ if (relativePath.IsEmpty()) {
+ // Return base URL.
+ rv = GetSpec(result);
+ } else if (!relativePath.IsEmpty() &&
+ relativePath.First() == '#') // an anchor
+ {
+ rv = m_baseURL->Resolve(relativePath, result);
+ } else {
+ // if relativePath is a complete url with it's own scheme then allow it...
+ nsCOMPtr<nsIIOService> ioService = mozilla::components::IO::Service();
+ NS_ENSURE_TRUE(ioService, NS_ERROR_UNEXPECTED);
+ nsAutoCString scheme;
+
+ rv = ioService->ExtractScheme(relativePath, scheme);
+ // if we have a fully qualified scheme then pass the relative path back as
+ // the result
+ if (NS_SUCCEEDED(rv) && !scheme.IsEmpty()) {
+ result = relativePath;
+ rv = NS_OK;
+ } else {
+ result.Truncate();
+ rv = NS_ERROR_FAILURE;
+ }
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::GetDirectory(nsACString& aDirectory) {
+ return m_baseURL->GetDirectory(aDirectory);
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::GetFileName(nsACString& aFileName) {
+ if (!mAttachmentFileName.IsEmpty()) {
+ aFileName = mAttachmentFileName;
+ return NS_OK;
+ }
+ return m_baseURL->GetFileName(aFileName);
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::GetFileBaseName(nsACString& aFileBaseName) {
+ return m_baseURL->GetFileBaseName(aFileBaseName);
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::GetFileExtension(nsACString& aFileExtension) {
+ if (!mAttachmentFileName.IsEmpty()) {
+ int32_t pos = mAttachmentFileName.RFindChar(char16_t('.'));
+ if (pos > 0)
+ aFileExtension =
+ Substring(mAttachmentFileName, pos + 1 /* skip the '.' */);
+ return NS_OK;
+ }
+ return m_baseURL->GetFileExtension(aFileExtension);
+}
+
+nsresult nsMsgMailNewsUrl::SetFileNameInternal(const nsACString& aFileName) {
+ mAttachmentFileName = aFileName;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::GetQuery(nsACString& aQuery) {
+ return m_baseURL->GetQuery(aQuery);
+}
+
+nsresult nsMsgMailNewsUrl::SetQuery(const nsACString& aQuery) {
+ return NS_MutateURI(m_baseURL).SetQuery(aQuery).Finalize(m_baseURL);
+}
+
+nsresult nsMsgMailNewsUrl::SetQueryInternal(const nsACString& aQuery) {
+ return NS_MutateURI(m_baseURL).SetQuery(aQuery).Finalize(m_baseURL);
+}
+
+nsresult nsMsgMailNewsUrl::SetQueryWithEncoding(
+ const nsACString& aQuery, const mozilla::Encoding* aEncoding) {
+ return NS_MutateURI(m_baseURL)
+ .SetQueryWithEncoding(aQuery, aEncoding)
+ .Finalize(m_baseURL);
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::GetRef(nsACString& aRef) {
+ return m_baseURL->GetRef(aRef);
+}
+
+nsresult nsMsgMailNewsUrl::SetRef(const nsACString& aRef) {
+ return NS_MutateURI(m_baseURL).SetRef(aRef).Finalize(m_baseURL);
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::GetFilePath(nsACString& o_DirFile) {
+ return m_baseURL->GetFilePath(o_DirFile);
+}
+
+nsresult nsMsgMailNewsUrl::SetFilePath(const nsACString& i_DirFile) {
+ return NS_MutateURI(m_baseURL).SetFilePath(i_DirFile).Finalize(m_baseURL);
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::GetCommonBaseSpec(nsIURI* uri2,
+ nsACString& result) {
+ return m_baseURL->GetCommonBaseSpec(uri2, result);
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::GetRelativeSpec(nsIURI* uri2,
+ nsACString& result) {
+ return m_baseURL->GetRelativeSpec(uri2, result);
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::SetMemCacheEntry(nsICacheEntry* memCacheEntry) {
+ m_memCacheEntry = memCacheEntry;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::GetMemCacheEntry(
+ nsICacheEntry** memCacheEntry) {
+ NS_ENSURE_ARG(memCacheEntry);
+ nsresult rv = NS_OK;
+
+ if (m_memCacheEntry) {
+ NS_ADDREF(*memCacheEntry = m_memCacheEntry);
+ } else {
+ *memCacheEntry = nullptr;
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::GetMimeHeaders(nsIMimeHeaders** mimeHeaders) {
+ NS_ENSURE_ARG_POINTER(mimeHeaders);
+ NS_IF_ADDREF(*mimeHeaders = mMimeHeaders);
+ return (mMimeHeaders) ? NS_OK : NS_ERROR_NULL_POINTER;
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::SetMimeHeaders(nsIMimeHeaders* mimeHeaders) {
+ mMimeHeaders = mimeHeaders;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::LoadURI(nsIDocShell* docShell,
+ uint32_t aLoadFlags) {
+ NS_ENSURE_ARG_POINTER(docShell);
+ RefPtr<nsDocShellLoadState> loadState = new nsDocShellLoadState(this);
+ loadState->SetLoadFlags(aLoadFlags);
+ loadState->SetLoadType(MAKE_LOAD_TYPE(LOAD_NORMAL, aLoadFlags));
+ loadState->SetFirstParty(false);
+ loadState->SetTriggeringPrincipal(nsContentUtils::GetSystemPrincipal());
+ return docShell->LoadURI(loadState, false);
+}
+
+#define SAVE_BUF_SIZE FILE_IO_BUFFER_SIZE
+class nsMsgSaveAsListener : public nsIStreamListener {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+
+ nsMsgSaveAsListener(nsIFile* aFile, bool addDummyEnvelope);
+ nsresult SetupMsgWriteStream(nsIFile* aFile, bool addDummyEnvelope);
+
+ protected:
+ virtual ~nsMsgSaveAsListener();
+ nsCOMPtr<nsIOutputStream> m_outputStream;
+ nsCOMPtr<nsIFile> m_outputFile;
+ bool m_addDummyEnvelope;
+ bool m_writtenData;
+ uint32_t m_leftOver;
+ char m_dataBuffer[SAVE_BUF_SIZE +
+ 1]; // temporary buffer for this save operation
+};
+
+NS_IMPL_ISUPPORTS(nsMsgSaveAsListener, nsIStreamListener, nsIRequestObserver)
+
+nsMsgSaveAsListener::nsMsgSaveAsListener(nsIFile* aFile,
+ bool addDummyEnvelope) {
+ m_outputFile = aFile;
+ m_writtenData = false;
+ m_addDummyEnvelope = addDummyEnvelope;
+ m_leftOver = 0;
+}
+
+nsMsgSaveAsListener::~nsMsgSaveAsListener() {}
+
+NS_IMETHODIMP nsMsgSaveAsListener::OnStartRequest(nsIRequest* request) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgSaveAsListener::OnStopRequest(nsIRequest* request, nsresult aStatus) {
+ if (m_outputStream) {
+ m_outputStream->Flush();
+ m_outputStream->Close();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgSaveAsListener::OnDataAvailable(nsIRequest* request,
+ nsIInputStream* inStream,
+ uint64_t srcOffset,
+ uint32_t count) {
+ nsresult rv;
+ uint64_t available;
+ rv = inStream->Available(&available);
+ if (!m_writtenData) {
+ m_writtenData = true;
+ rv = SetupMsgWriteStream(m_outputFile, m_addDummyEnvelope);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ bool useCanonicalEnding = false;
+ // We know the request is an nsIChannel we can get a URI from, but this is
+ // probably bad form. See Bug 1528662.
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(request, &rv);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "error QI nsIRequest to nsIChannel failed");
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIURI> uri;
+ rv = channel->GetURI(getter_AddRefs(uri));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIMsgMessageUrl> msgUrl = do_QueryInterface(uri);
+ if (msgUrl) msgUrl->GetCanonicalLineEnding(&useCanonicalEnding);
+
+ const char* lineEnding = (useCanonicalEnding) ? CRLF : MSG_LINEBREAK;
+ uint32_t lineEndingLength = (useCanonicalEnding) ? 2 : MSG_LINEBREAK_LEN;
+
+ uint32_t readCount, maxReadCount = SAVE_BUF_SIZE - m_leftOver;
+ uint32_t writeCount;
+ char *start, *end, lastCharInPrevBuf = '\0';
+ uint32_t linebreak_len = 0;
+
+ while (count > 0) {
+ if (count < maxReadCount) maxReadCount = count;
+ rv = inStream->Read(m_dataBuffer + m_leftOver, maxReadCount, &readCount);
+ if (NS_FAILED(rv)) return rv;
+
+ m_leftOver += readCount;
+ m_dataBuffer[m_leftOver] = '\0';
+
+ start = m_dataBuffer;
+ // make sure we don't insert another LF, accidentally, by ignoring
+ // second half of CRLF spanning blocks.
+ if (lastCharInPrevBuf == '\r' && *start == '\n') start++;
+
+ end = PL_strpbrk(start, "\r\n");
+ if (end) linebreak_len = (end[0] == '\r' && end[1] == '\n') ? 2 : 1;
+
+ count -= readCount;
+ maxReadCount = SAVE_BUF_SIZE - m_leftOver;
+
+ if (!end && count > maxReadCount)
+ // must be a very very long line; sorry cannot handle it
+ return NS_ERROR_FAILURE;
+
+ while (start && end) {
+ if (m_outputStream && PL_strncasecmp(start, "X-Mozilla-Status:", 17) &&
+ PL_strncasecmp(start, "X-Mozilla-Status2:", 18) &&
+ PL_strncmp(start, "From - ", 7)) {
+ rv = m_outputStream->Write(start, end - start, &writeCount);
+ nsresult tmp =
+ m_outputStream->Write(lineEnding, lineEndingLength, &writeCount);
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ }
+ start = end + linebreak_len;
+ if (start >= m_dataBuffer + m_leftOver) {
+ maxReadCount = SAVE_BUF_SIZE;
+ m_leftOver = 0;
+ break;
+ }
+ end = PL_strpbrk(start, "\r\n");
+ if (end) linebreak_len = (end[0] == '\r' && end[1] == '\n') ? 2 : 1;
+ if (start && !end) {
+ m_leftOver -= (start - m_dataBuffer);
+ memcpy(m_dataBuffer, start,
+ m_leftOver + 1); // including null
+ maxReadCount = SAVE_BUF_SIZE - m_leftOver;
+ }
+ }
+ if (NS_FAILED(rv)) return rv;
+ if (end) lastCharInPrevBuf = *end;
+ }
+ return rv;
+
+ // rv = m_outputStream->WriteFrom(inStream, std::min(available, count),
+ // &bytesWritten);
+}
+
+nsresult nsMsgSaveAsListener::SetupMsgWriteStream(nsIFile* aFile,
+ bool addDummyEnvelope) {
+ // If the file already exists, delete it, but do this before
+ // getting the outputstream.
+ // Due to bug 328027, the nsSaveMsgListener created in
+ // nsMessenger::SaveAs now opens the stream on the nsIFile
+ // object, thus creating an empty file. Actual save operations for
+ // IMAP and NNTP use this nsMsgSaveAsListener here, though, so we
+ // have to close the stream before deleting the file, else data
+ // would still be written happily into a now non-existing file.
+ // (Windows doesn't care, btw, just unixoids do...)
+ aFile->Remove(false);
+
+ nsresult rv = MsgNewBufferedFileOutputStream(getter_AddRefs(m_outputStream),
+ aFile, -1, 0666);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (m_outputStream && addDummyEnvelope) {
+ nsAutoCString result;
+ uint32_t writeCount;
+
+ time_t now = time((time_t*)0);
+ char* ct = ctime(&now);
+ // Remove the ending new-line character.
+ ct[24] = '\0';
+ result = "From - ";
+ result += ct;
+ result += MSG_LINEBREAK;
+ m_outputStream->Write(result.get(), result.Length(), &writeCount);
+
+ result = "X-Mozilla-Status: 0001";
+ result += MSG_LINEBREAK;
+ result += "X-Mozilla-Status2: 00000000";
+ result += MSG_LINEBREAK;
+ m_outputStream->Write(result.get(), result.Length(), &writeCount);
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::GetSaveAsListener(
+ bool addDummyEnvelope, nsIFile* aFile, nsIStreamListener** aSaveListener) {
+ NS_ENSURE_ARG_POINTER(aSaveListener);
+ nsMsgSaveAsListener* saveAsListener =
+ new nsMsgSaveAsListener(aFile, addDummyEnvelope);
+ return saveAsListener->QueryInterface(NS_GET_IID(nsIStreamListener),
+ (void**)aSaveListener);
+}
+
+NS_IMETHODIMP
+nsMsgMailNewsUrl::SetFailedSecInfo(nsITransportSecurityInfo* secInfo) {
+ mFailedSecInfo = secInfo;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::GetFailedSecInfo(
+ nsITransportSecurityInfo** secInfo) {
+ NS_ENSURE_ARG_POINTER(secInfo);
+ NS_IF_ADDREF(*secInfo = mFailedSecInfo);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::SetFolder(nsIMsgFolder* /* aFolder */) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::GetFolder(nsIMsgFolder** /* aFolder */) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsMsgMailNewsUrl::GetIsMessageUri(bool* aIsMessageUri) {
+ NS_ENSURE_ARG(aIsMessageUri);
+ nsAutoCString scheme;
+ m_baseURL->GetScheme(scheme);
+ *aIsMessageUri = StringEndsWith(scheme, "-message"_ns);
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(nsMsgMailNewsUrl::Mutator, nsIURISetters, nsIURIMutator)
+
+NS_IMETHODIMP
+nsMsgMailNewsUrl::Mutate(nsIURIMutator** aMutator) {
+ RefPtr<nsMsgMailNewsUrl::Mutator> mutator = new nsMsgMailNewsUrl::Mutator();
+ nsresult rv = mutator->InitFromURI(this);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ mutator.forget(aMutator);
+ return NS_OK;
+}
diff --git a/comm/mailnews/base/src/nsMsgMailNewsUrl.h b/comm/mailnews/base/src/nsMsgMailNewsUrl.h
new file mode 100644
index 0000000000..21d56e8e65
--- /dev/null
+++ b/comm/mailnews/base/src/nsMsgMailNewsUrl.h
@@ -0,0 +1,142 @@
+/* -*- 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 nsMsgMailNewsUrl_h___
+#define nsMsgMailNewsUrl_h___
+
+#include "msgCore.h"
+#include "nscore.h"
+#include "nsISupports.h"
+#include "nsIUrlListener.h"
+#include "nsTObserverArray.h"
+#include "nsIMsgWindow.h"
+#include "nsIMsgStatusFeedback.h"
+#include "nsCOMPtr.h"
+#include "nsCOMArray.h"
+#include "nsIMimeHeaders.h"
+#include "nsIMsgMailNewsUrl.h"
+#include "nsIURL.h"
+#include "nsIURIWithSpecialOrigin.h"
+#include "nsILoadGroup.h"
+#include "nsIMsgSearchSession.h"
+#include "nsICacheEntry.h"
+#include "nsIWeakReferenceUtils.h"
+#include "nsString.h"
+#include "nsIURIMutator.h"
+#include "nsISerializable.h"
+#include "nsIClassInfo.h"
+#include "nsITransportSecurityInfo.h"
+
+///////////////////////////////////////////////////////////////////////////////////
+// Okay, I found that all of the mail and news url interfaces needed to support
+// several common interfaces (in addition to those provided through nsIURI).
+// So I decided to group them all in this implementation so we don't have to
+// duplicate the code.
+//
+//////////////////////////////////////////////////////////////////////////////////
+
+class NS_MSG_BASE nsMsgMailNewsUrl : public nsIMsgMailNewsUrl,
+ public nsIURIWithSpecialOrigin,
+ public nsISerializable,
+ public nsIClassInfo {
+ public:
+ nsMsgMailNewsUrl();
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIMSGMAILNEWSURL
+ NS_DECL_NSIURI
+ NS_DECL_NSIURL
+ NS_DECL_NSIURIWITHSPECIALORIGIN
+ NS_DECL_NSISERIALIZABLE
+ NS_DECL_NSICLASSINFO
+
+ protected:
+ virtual nsresult Clone(nsIURI** _retval);
+ virtual nsresult SetScheme(const nsACString& aScheme);
+ virtual nsresult SetUserPass(const nsACString& aUserPass);
+ virtual nsresult SetUsername(const nsACString& aUsername);
+ virtual nsresult SetPassword(const nsACString& aPassword);
+ virtual nsresult SetHostPort(const nsACString& aHostPort);
+ virtual nsresult SetHost(const nsACString& aHost);
+ virtual nsresult SetPort(int32_t aPort);
+ virtual nsresult SetPathQueryRef(const nsACString& aPath);
+ virtual nsresult SetRef(const nsACString& aRef);
+ virtual nsresult SetFilePath(const nsACString& aFilePath);
+ virtual nsresult SetQuery(const nsACString& aQuery);
+ virtual nsresult SetQueryWithEncoding(const nsACString& aQuery,
+ const mozilla::Encoding* aEncoding);
+ virtual nsresult CreateURL(const nsACString& aSpec,
+ nsIURL** aURL); // nsMailboxUrl overrides this.
+
+ public:
+ class Mutator : public nsIURIMutator,
+ public BaseURIMutator<nsMsgMailNewsUrl> {
+ NS_DECL_ISUPPORTS
+ NS_FORWARD_SAFE_NSIURISETTERS_RET(mURI)
+
+ NS_IMETHOD Deserialize(const mozilla::ipc::URIParams& aParams) override {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ NS_IMETHOD Finalize(nsIURI** aURI) override {
+ mURI.forget(aURI);
+ return NS_OK;
+ }
+
+ NS_IMETHOD SetSpec(const nsACString& aSpec,
+ nsIURIMutator** aMutator) override {
+ if (aMutator) NS_ADDREF(*aMutator = this);
+ return InitFromSpec(aSpec);
+ }
+
+ explicit Mutator() {}
+
+ private:
+ virtual ~Mutator() {}
+
+ friend class nsMsgMailNewsUrl;
+ };
+ friend BaseURIMutator<nsMsgMailNewsUrl>;
+
+ protected:
+ virtual ~nsMsgMailNewsUrl();
+
+ nsCOMPtr<nsIURL> m_baseURL;
+ nsCOMPtr<nsIURI> m_normalizedOrigin;
+ nsWeakPtr m_statusFeedbackWeak;
+ nsWeakPtr m_msgWindowWeak;
+ nsWeakPtr m_loadGroupWeak;
+ nsCOMPtr<nsIMimeHeaders> mMimeHeaders;
+ nsCOMPtr<nsIMsgSearchSession> m_searchSession;
+ nsCOMPtr<nsICacheEntry> m_memCacheEntry;
+ nsCString m_errorCode;
+ nsCString m_seeOtherURI;
+ nsString m_errorMessage;
+ nsString m_errorParameters;
+ int64_t mMaxProgress;
+ bool m_runningUrl;
+ bool m_updatingFolder;
+ bool m_msgIsInLocalCache;
+ bool m_suppressErrorMsgs;
+ bool m_hasNormalizedOrigin;
+
+ // the following field is really a bit of a hack to make
+ // open attachments work. The external applications code sometimes tries to
+ // figure out the right handler to use by looking at the file extension of the
+ // url we are trying to load. Unfortunately, the attachment file name really
+ // isn't part of the url string....so we'll store it here...and if the url we
+ // are running is an attachment url, we'll set it here. Then when the helper
+ // apps code asks us for it, we'll return the right value.
+ nsCString mAttachmentFileName;
+
+ nsTObserverArray<nsCOMPtr<nsIUrlListener> > mUrlListeners;
+
+ // Security info from the socket transport (if any), after a failed operation.
+ // Here so that urlListeners can access and handle bad certificates in
+ // their OnStopRunningUrl() callback.
+ nsCOMPtr<nsITransportSecurityInfo> mFailedSecInfo;
+};
+
+#endif /* nsMsgMailNewsUrl_h___ */
diff --git a/comm/mailnews/base/src/nsMsgMailSession.cpp b/comm/mailnews/base/src/nsMsgMailSession.cpp
new file mode 100644
index 0000000000..ac34d5d487
--- /dev/null
+++ b/comm/mailnews/base/src/nsMsgMailSession.cpp
@@ -0,0 +1,671 @@
+/* -*- 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 "msgCore.h" // for pre-compiled headers
+#include "nsMsgMailSession.h"
+#include "nsIMsgMessageService.h"
+#include "nsMsgUtils.h"
+#include "nsIMsgAccountManager.h"
+#include "nsIChromeRegistry.h"
+#include "nsIDirectoryService.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsPIDOMWindow.h"
+#include "nsIDocShell.h"
+#include "mozilla/dom/Document.h"
+#include "nsIObserverService.h"
+#include "nsIAppStartup.h"
+#include "nsISupportsPrimitives.h"
+#include "nsIAppShellService.h"
+#include "nsAppShellCID.h"
+#include "nsIWindowMediator.h"
+#include "nsIWindowWatcher.h"
+#include "nsIMsgMailNewsUrl.h"
+#include "prcmon.h"
+#include "nsThreadUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "nsIProperties.h"
+#include "mozilla/Services.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/Components.h"
+#include "nsFocusManager.h"
+#include "nsIPromptService.h"
+#include "nsEmbedCID.h"
+
+NS_IMPL_ISUPPORTS(nsMsgMailSession, nsIMsgMailSession, nsIFolderListener)
+
+nsMsgMailSession::nsMsgMailSession() {}
+
+nsMsgMailSession::~nsMsgMailSession() { Shutdown(); }
+
+nsresult nsMsgMailSession::Init() {
+ // Ensures the shutdown service is initialised
+ nsresult rv;
+ nsCOMPtr<nsIMsgShutdownService> shutdownService =
+ do_GetService("@mozilla.org/messenger/msgshutdownservice;1", &rv);
+ return rv;
+}
+
+nsresult nsMsgMailSession::Shutdown() { return NS_OK; }
+
+NS_IMETHODIMP nsMsgMailSession::AddFolderListener(nsIFolderListener* aListener,
+ uint32_t aNotifyFlags) {
+ NS_ENSURE_ARG_POINTER(aListener);
+
+ // we don't care about the notification flags for equivalence purposes
+ size_t index = mListeners.IndexOf(aListener);
+ NS_ASSERTION(index == size_t(-1), "tried to add duplicate listener");
+ if (index == size_t(-1)) {
+ folderListener newListener(aListener, aNotifyFlags);
+ mListeners.AppendElement(newListener);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgMailSession::RemoveFolderListener(
+ nsIFolderListener* aListener) {
+ NS_ENSURE_ARG_POINTER(aListener);
+
+ mListeners.RemoveElement(aListener);
+ return NS_OK;
+}
+
+#define NOTIFY_FOLDER_LISTENERS(propertyflag_, propertyfunc_, params_) \
+ PR_BEGIN_MACRO \
+ nsTObserverArray<folderListener>::ForwardIterator iter(mListeners); \
+ while (iter.HasMore()) { \
+ const folderListener& fL = iter.GetNext(); \
+ if (fL.mNotifyFlags & nsIFolderListener::propertyflag_) \
+ fL.mListener->propertyfunc_ params_; \
+ } \
+ PR_END_MACRO
+
+NS_IMETHODIMP
+nsMsgMailSession::OnFolderPropertyChanged(nsIMsgFolder* aItem,
+ const nsACString& aProperty,
+ const nsACString& aOldValue,
+ const nsACString& aNewValue) {
+ NOTIFY_FOLDER_LISTENERS(propertyChanged, OnFolderPropertyChanged,
+ (aItem, aProperty, aOldValue, aNewValue));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgMailSession::OnFolderUnicharPropertyChanged(nsIMsgFolder* aItem,
+ const nsACString& aProperty,
+ const nsAString& aOldValue,
+ const nsAString& aNewValue) {
+ NOTIFY_FOLDER_LISTENERS(unicharPropertyChanged,
+ OnFolderUnicharPropertyChanged,
+ (aItem, aProperty, aOldValue, aNewValue));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgMailSession::OnFolderIntPropertyChanged(nsIMsgFolder* aItem,
+ const nsACString& aProperty,
+ int64_t aOldValue,
+ int64_t aNewValue) {
+ NOTIFY_FOLDER_LISTENERS(intPropertyChanged, OnFolderIntPropertyChanged,
+ (aItem, aProperty, aOldValue, aNewValue));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgMailSession::OnFolderBoolPropertyChanged(nsIMsgFolder* aItem,
+ const nsACString& aProperty,
+ bool aOldValue, bool aNewValue) {
+ NOTIFY_FOLDER_LISTENERS(boolPropertyChanged, OnFolderBoolPropertyChanged,
+ (aItem, aProperty, aOldValue, aNewValue));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgMailSession::OnFolderPropertyFlagChanged(nsIMsgDBHdr* aItem,
+ const nsACString& aProperty,
+ uint32_t aOldValue,
+ uint32_t aNewValue) {
+ NOTIFY_FOLDER_LISTENERS(propertyFlagChanged, OnFolderPropertyFlagChanged,
+ (aItem, aProperty, aOldValue, aNewValue));
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgMailSession::OnFolderAdded(nsIMsgFolder* parent,
+ nsIMsgFolder* child) {
+ NOTIFY_FOLDER_LISTENERS(added, OnFolderAdded, (parent, child));
+ return NS_OK;
+}
+NS_IMETHODIMP nsMsgMailSession::OnMessageAdded(nsIMsgFolder* parent,
+ nsIMsgDBHdr* msg) {
+ NOTIFY_FOLDER_LISTENERS(added, OnMessageAdded, (parent, msg));
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgMailSession::OnFolderRemoved(nsIMsgFolder* parent,
+ nsIMsgFolder* child) {
+ NOTIFY_FOLDER_LISTENERS(removed, OnFolderRemoved, (parent, child));
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgMailSession::OnMessageRemoved(nsIMsgFolder* parent,
+ nsIMsgDBHdr* msg) {
+ NOTIFY_FOLDER_LISTENERS(removed, OnMessageRemoved, (parent, msg));
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgMailSession::OnFolderEvent(nsIMsgFolder* aFolder,
+ const nsACString& aEvent) {
+ NOTIFY_FOLDER_LISTENERS(event, OnFolderEvent, (aFolder, aEvent));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgMailSession::AddUserFeedbackListener(
+ nsIMsgUserFeedbackListener* aListener) {
+ NS_ENSURE_ARG_POINTER(aListener);
+
+ size_t index = mFeedbackListeners.IndexOf(aListener);
+ NS_ASSERTION(index == size_t(-1), "tried to add duplicate listener");
+ if (index == size_t(-1)) mFeedbackListeners.AppendElement(aListener);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgMailSession::RemoveUserFeedbackListener(
+ nsIMsgUserFeedbackListener* aListener) {
+ NS_ENSURE_ARG_POINTER(aListener);
+
+ mFeedbackListeners.RemoveElement(aListener);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgMailSession::AlertUser(const nsAString& aMessage,
+ nsIMsgMailNewsUrl* aUrl) {
+ bool listenersNotified = false;
+ nsTObserverArray<nsCOMPtr<nsIMsgUserFeedbackListener>>::ForwardIterator iter(
+ mFeedbackListeners);
+ nsCOMPtr<nsIMsgUserFeedbackListener> listener;
+
+ while (iter.HasMore()) {
+ bool notified = false;
+ listener = iter.GetNext();
+ listener->OnAlert(aMessage, aUrl, &notified);
+ listenersNotified = listenersNotified || notified;
+ }
+
+ // If the listeners notified the user, then we don't need to. Also exit if
+ // aUrl is null because we won't have a nsIMsgWindow in that case.
+ if (listenersNotified || !aUrl) return NS_OK;
+
+ // If the url hasn't got a message window, then the error was a generated as a
+ // result of background activity (e.g. autosync, biff, etc), and hence we
+ // shouldn't prompt either.
+ nsCOMPtr<nsIMsgWindow> msgWindow;
+ aUrl->GetMsgWindow(getter_AddRefs(msgWindow));
+
+ if (!msgWindow) return NS_OK;
+
+ nsCOMPtr<mozIDOMWindowProxy> domWindow;
+ msgWindow->GetDomWindow(getter_AddRefs(domWindow));
+
+ nsresult rv;
+ nsCOMPtr<nsIPromptService> dlgService(
+ do_GetService(NS_PROMPTSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ dlgService->Alert(domWindow, nullptr, PromiseFlatString(aMessage).get());
+
+ return NS_OK;
+}
+
+nsresult nsMsgMailSession::GetTopmostMsgWindow(nsIMsgWindow** aMsgWindow) {
+ NS_ENSURE_ARG_POINTER(aMsgWindow);
+
+ *aMsgWindow = nullptr;
+
+ uint32_t count = mWindows.Count();
+
+ if (count == 1) {
+ NS_ADDREF(*aMsgWindow = mWindows[0]);
+ return (*aMsgWindow) ? NS_OK : NS_ERROR_FAILURE;
+ } else if (count > 1) {
+ // If multiple message windows then we have lots more work.
+ nsresult rv;
+
+ // The msgWindows array does not hold z-order info. Use mediator to get
+ // the top most window then match that with the msgWindows array.
+ nsCOMPtr<nsIWindowMediator> windowMediator =
+ do_GetService(NS_WINDOWMEDIATOR_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsISimpleEnumerator> windowEnum;
+
+ rv = windowMediator->GetEnumerator(nullptr, getter_AddRefs(windowEnum));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsISupports> windowSupports;
+ nsCOMPtr<nsPIDOMWindowOuter> topMostWindow;
+ nsAutoString windowType;
+ bool more;
+
+ // loop to get the top most with attribute "mail:3pane" or
+ // "mail:messageWindow"
+ windowEnum->HasMoreElements(&more);
+ while (more) {
+ rv = windowEnum->GetNext(getter_AddRefs(windowSupports));
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_TRUE(windowSupports, NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsPIDOMWindowOuter> window =
+ do_QueryInterface(windowSupports, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_TRUE(window, NS_ERROR_FAILURE);
+
+ mozilla::dom::Document* domDocument = window->GetDoc();
+ NS_ENSURE_TRUE(domDocument, NS_ERROR_FAILURE);
+
+ mozilla::dom::Element* domElement = domDocument->GetDocumentElement();
+ NS_ENSURE_TRUE(domElement, NS_ERROR_FAILURE);
+
+ domElement->GetAttribute(u"windowtype"_ns, windowType);
+ if (windowType.EqualsLiteral("mail:3pane") ||
+ windowType.EqualsLiteral("mail:messageWindow")) {
+ // topMostWindow is the last 3pane/messageWindow found, not necessarily
+ // the top most.
+ topMostWindow = window;
+ RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager();
+ nsCOMPtr<mozIDOMWindowProxy> currentWindow =
+ do_QueryInterface(windowSupports, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<mozIDOMWindowProxy> activeWindow;
+ rv = fm->GetActiveWindow(getter_AddRefs(activeWindow));
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (currentWindow == activeWindow) {
+ // We are sure topMostWindow is really the top most now.
+ break;
+ }
+ }
+
+ windowEnum->HasMoreElements(&more);
+ }
+
+ // identified the top most window
+ if (topMostWindow) {
+ // use this for the match
+ nsIDocShell* topDocShell = topMostWindow->GetDocShell();
+
+ // loop for the msgWindow array to find the match
+ nsCOMPtr<nsIDocShell> docShell;
+
+ while (count) {
+ nsIMsgWindow* msgWindow = mWindows[--count];
+
+ rv = msgWindow->GetRootDocShell(getter_AddRefs(docShell));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (topDocShell == docShell) {
+ NS_IF_ADDREF(*aMsgWindow = msgWindow);
+ break;
+ }
+ }
+ }
+ }
+
+ return (*aMsgWindow) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP nsMsgMailSession::AddMsgWindow(nsIMsgWindow* msgWindow) {
+ NS_ENSURE_ARG_POINTER(msgWindow);
+
+ mWindows.AppendObject(msgWindow);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgMailSession::RemoveMsgWindow(nsIMsgWindow* msgWindow) {
+ mWindows.RemoveObject(msgWindow);
+ // Mac keeps a hidden window open so the app doesn't shut down when
+ // the last window is closed. So don't shutdown the account manager in that
+ // case. Similarly, for suite, we don't want to disable mailnews when the
+ // last mail window is closed.
+#if !defined(XP_MACOSX) && !defined(MOZ_SUITE)
+ if (!mWindows.Count()) {
+ nsresult rv;
+ nsCOMPtr<nsIMsgAccountManager> accountManager =
+ do_GetService("@mozilla.org/messenger/account-manager;1", &rv);
+ if (NS_FAILED(rv)) return rv;
+ accountManager->CleanupOnExit();
+ }
+#endif
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgMailSession::IsFolderOpenInWindow(nsIMsgFolder* folder,
+ bool* aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ *aResult = false;
+
+ uint32_t count = mWindows.Count();
+
+ for (uint32_t i = 0; i < count; i++) {
+ nsCOMPtr<nsIMsgFolder> openFolder;
+ mWindows[i]->GetOpenFolder(getter_AddRefs(openFolder));
+ if (folder == openFolder.get()) {
+ *aResult = true;
+ break;
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgMailSession::ConvertMsgURIToMsgURL(const nsACString& aURI,
+ nsIMsgWindow* aMsgWindow,
+ nsACString& aURL) {
+ // convert the rdf msg uri into a url that represents the message...
+ nsCOMPtr<nsIMsgMessageService> msgService;
+ nsresult rv = GetMessageServiceFromURI(aURI, getter_AddRefs(msgService));
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_NULL_POINTER);
+
+ nsCOMPtr<nsIURI> tURI;
+ rv = msgService->GetUrlForUri(aURI, aMsgWindow, getter_AddRefs(tURI));
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_NULL_POINTER);
+
+ rv = tURI->GetSpec(aURL);
+ return rv;
+}
+
+//-------------------------------------------------------------------------
+// GetSelectedLocaleDataDir - If a locale is selected, appends the selected
+// locale to the defaults data dir and returns
+// that new defaults data dir
+//-------------------------------------------------------------------------
+nsresult nsMsgMailSession::GetSelectedLocaleDataDir(nsIFile* defaultsDir) {
+ NS_ENSURE_ARG_POINTER(defaultsDir);
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// GetDataFilesDir - Gets the application's default folder and then appends the
+// subdirectory named passed in as param dirName. If there is
+// a selected locale, will append that to the dir path before
+// returning the value
+//-----------------------------------------------------------------------------
+NS_IMETHODIMP
+nsMsgMailSession::GetDataFilesDir(const char* dirName, nsIFile** dataFilesDir) {
+ NS_ENSURE_ARG_POINTER(dirName);
+ NS_ENSURE_ARG_POINTER(dataFilesDir);
+
+ nsresult rv;
+ nsCOMPtr<nsIProperties> directoryService =
+ do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> defaultsDir;
+ rv = directoryService->Get(NS_APP_DEFAULTS_50_DIR, NS_GET_IID(nsIFile),
+ getter_AddRefs(defaultsDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = defaultsDir->AppendNative(nsDependentCString(dirName));
+ if (NS_SUCCEEDED(rv)) rv = GetSelectedLocaleDataDir(defaultsDir);
+
+ defaultsDir.forget(dataFilesDir);
+
+ return rv;
+}
+
+/********************************************************************************/
+
+NS_IMPL_ISUPPORTS(nsMsgShutdownService, nsIMsgShutdownService, nsIUrlListener,
+ nsIObserver)
+
+nsMsgShutdownService::nsMsgShutdownService()
+ : mTaskIndex(0),
+ mQuitMode(nsIAppStartup::eAttemptQuit),
+ mProcessedShutdown(false),
+ mQuitForced(false),
+ mReadyToQuit(false) {
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (observerService) {
+ observerService->AddObserver(this, "quit-application-requested", false);
+ observerService->AddObserver(this, "quit-application-granted", false);
+ observerService->AddObserver(this, "quit-application", false);
+ }
+}
+
+nsMsgShutdownService::~nsMsgShutdownService() {
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (observerService) {
+ observerService->RemoveObserver(this, "quit-application-requested");
+ observerService->RemoveObserver(this, "quit-application-granted");
+ observerService->RemoveObserver(this, "quit-application");
+ }
+}
+
+nsresult nsMsgShutdownService::ProcessNextTask() {
+ bool shutdownTasksDone = true;
+
+ uint32_t count = mShutdownTasks.Length();
+ if (mTaskIndex < count) {
+ shutdownTasksDone = false;
+
+ nsCOMPtr<nsIMsgShutdownTask> curTask = mShutdownTasks[mTaskIndex];
+ nsString taskName;
+ curTask->GetCurrentTaskName(taskName);
+ SetStatusText(taskName);
+
+ nsCOMPtr<nsIMsgMailSession> mailSession =
+ do_GetService("@mozilla.org/messenger/services/session;1");
+ NS_ENSURE_TRUE(mailSession, NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsIMsgWindow> topMsgWindow;
+ mailSession->GetTopmostMsgWindow(getter_AddRefs(topMsgWindow));
+
+ bool taskIsRunning = true;
+ nsresult rv = curTask->DoShutdownTask(this, topMsgWindow, &taskIsRunning);
+ if (NS_FAILED(rv) || !taskIsRunning) {
+ // We have failed, let's go on to the next task.
+ mTaskIndex++;
+ mMsgProgress->OnProgressChange(nullptr, nullptr, 0, 0,
+ (int32_t)mTaskIndex, count);
+ ProcessNextTask();
+ }
+ }
+
+ if (shutdownTasksDone) {
+ if (mMsgProgress)
+ mMsgProgress->OnStateChange(nullptr, nullptr,
+ nsIWebProgressListener::STATE_STOP, NS_OK);
+ AttemptShutdown();
+ }
+
+ return NS_OK;
+}
+
+void nsMsgShutdownService::AttemptShutdown() {
+ if (mQuitForced) {
+ PR_CEnterMonitor(this);
+ mReadyToQuit = true;
+ PR_CNotifyAll(this);
+ PR_CExitMonitor(this);
+ } else {
+ nsCOMPtr<nsIAppStartup> appStartup =
+ mozilla::components::AppStartup::Service();
+ NS_ENSURE_TRUE_VOID(appStartup);
+ bool userAllowedQuit = true;
+ NS_ENSURE_SUCCESS_VOID(appStartup->Quit(mQuitMode, 0, &userAllowedQuit));
+ }
+}
+
+NS_IMETHODIMP nsMsgShutdownService::SetShutdownListener(
+ nsIWebProgressListener* inListener) {
+ NS_ENSURE_TRUE(mMsgProgress, NS_ERROR_FAILURE);
+ mMsgProgress->RegisterListener(inListener);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgShutdownService::Observe(nsISupports* aSubject,
+ const char* aTopic,
+ const char16_t* aData) {
+ // Due to bug 459376 we don't always get quit-application-requested and
+ // quit-application-granted. quit-application-requested is preferred, but if
+ // we don't then we have to hook onto quit-application, but we don't want
+ // to do the checking twice so we set some flags to prevent that.
+ if (!strcmp(aTopic, "quit-application-granted")) {
+ // Quit application has been requested and granted, therefore we will shut
+ // down.
+ mProcessedShutdown = true;
+ return NS_OK;
+ }
+
+ // If we've already processed a shutdown notification, no need to do it again.
+ if (!strcmp(aTopic, "quit-application")) {
+ if (mProcessedShutdown)
+ return NS_OK;
+ else
+ mQuitForced = true;
+ }
+
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ NS_ENSURE_STATE(observerService);
+
+ nsCOMPtr<nsISimpleEnumerator> listenerEnum;
+ nsresult rv = observerService->EnumerateObservers(
+ "msg-shutdown", getter_AddRefs(listenerEnum));
+ if (NS_SUCCEEDED(rv) && listenerEnum) {
+ bool hasMore;
+ listenerEnum->HasMoreElements(&hasMore);
+ if (!hasMore) return NS_OK;
+
+ while (hasMore) {
+ nsCOMPtr<nsISupports> curObject;
+ listenerEnum->GetNext(getter_AddRefs(curObject));
+
+ nsCOMPtr<nsIMsgShutdownTask> curTask = do_QueryInterface(curObject);
+ if (curTask) {
+ bool shouldRunTask;
+ curTask->GetNeedsToRunTask(&shouldRunTask);
+ if (shouldRunTask) mShutdownTasks.AppendObject(curTask);
+ }
+
+ listenerEnum->HasMoreElements(&hasMore);
+ }
+
+ if (mShutdownTasks.Count() < 1) return NS_ERROR_FAILURE;
+
+ mTaskIndex = 0;
+
+ mMsgProgress = do_CreateInstance("@mozilla.org/messenger/progress;1");
+ NS_ENSURE_TRUE(mMsgProgress, NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsIMsgMailSession> mailSession =
+ do_GetService("@mozilla.org/messenger/services/session;1");
+ NS_ENSURE_TRUE(mailSession, NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsIMsgWindow> topMsgWindow;
+ mailSession->GetTopmostMsgWindow(getter_AddRefs(topMsgWindow));
+
+ nsCOMPtr<mozIDOMWindowProxy> internalDomWin;
+ if (topMsgWindow)
+ topMsgWindow->GetDomWindow(getter_AddRefs(internalDomWin));
+
+ if (!internalDomWin) {
+ // First see if there is a window open.
+ nsCOMPtr<nsIWindowMediator> winMed =
+ do_GetService(NS_WINDOWMEDIATOR_CONTRACTID);
+ winMed->GetMostRecentWindow(nullptr, getter_AddRefs(internalDomWin));
+
+ // If not use the hidden window.
+ if (!internalDomWin) {
+ nsCOMPtr<nsIAppShellService> appShell(
+ do_GetService(NS_APPSHELLSERVICE_CONTRACTID));
+ appShell->GetHiddenDOMWindow(getter_AddRefs(internalDomWin));
+ NS_ENSURE_TRUE(internalDomWin,
+ NS_ERROR_FAILURE); // bail if we don't get a window.
+ }
+ }
+
+ if (!mQuitForced) {
+ nsCOMPtr<nsISupportsPRBool> stopShutdown = do_QueryInterface(aSubject);
+ stopShutdown->SetData(true);
+
+ // If the attempted quit was a restart, be sure to restart the app once
+ // the tasks have been run. This is usually the case when addons or
+ // updates are going to be installed.
+ if (aData && nsDependentString(aData).EqualsLiteral("restart"))
+ mQuitMode |= nsIAppStartup::eRestart;
+ }
+
+ mMsgProgress->OpenProgressDialog(
+ internalDomWin, topMsgWindow,
+ "chrome://messenger/content/shutdownWindow.xhtml", false, nullptr);
+
+ if (mQuitForced) {
+ nsCOMPtr<nsIThread> thread(do_GetCurrentThread());
+
+ mReadyToQuit = false;
+ while (!mReadyToQuit) {
+ PR_CEnterMonitor(this);
+ // Waiting for 50 milliseconds
+ PR_CWait(this, PR_MicrosecondsToInterval(50000UL));
+ PR_CExitMonitor(this);
+ NS_ProcessPendingEvents(thread);
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+// nsIUrlListener
+NS_IMETHODIMP nsMsgShutdownService::OnStartRunningUrl(nsIURI* url) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgShutdownService::OnStopRunningUrl(nsIURI* url,
+ nsresult aExitCode) {
+ mTaskIndex++;
+
+ if (mMsgProgress) {
+ int32_t numTasks = mShutdownTasks.Count();
+ mMsgProgress->OnProgressChange(nullptr, nullptr, 0, 0, (int32_t)mTaskIndex,
+ numTasks);
+ }
+
+ ProcessNextTask();
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgShutdownService::GetNumTasks(int32_t* inNumTasks) {
+ *inNumTasks = mShutdownTasks.Count();
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgShutdownService::StartShutdownTasks() {
+ ProcessNextTask();
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgShutdownService::CancelShutdownTasks() {
+ AttemptShutdown();
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgShutdownService::SetStatusText(
+ const nsAString& inStatusString) {
+ nsString statusString(inStatusString);
+ if (mMsgProgress)
+ mMsgProgress->OnStatusChange(nullptr, nullptr, NS_OK,
+ nsString(statusString).get());
+ return NS_OK;
+}
diff --git a/comm/mailnews/base/src/nsMsgMailSession.h b/comm/mailnews/base/src/nsMsgMailSession.h
new file mode 100644
index 0000000000..09cea632e3
--- /dev/null
+++ b/comm/mailnews/base/src/nsMsgMailSession.h
@@ -0,0 +1,110 @@
+/* -*- 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 nsMsgMailSession_h___
+#define nsMsgMailSession_h___
+
+#include "nsIMsgMailSession.h"
+#include "nsISupports.h"
+#include "nsCOMPtr.h"
+#include "nsIMsgStatusFeedback.h"
+#include "nsIMsgWindow.h"
+#include "nsCOMArray.h"
+#include "nsIMsgShutdown.h"
+#include "nsIObserver.h"
+#include "nsIMsgProgress.h"
+#include "nsTObserverArray.h"
+#include "nsIMsgUserFeedbackListener.h"
+#include "nsIUrlListener.h"
+
+///////////////////////////////////////////////////////////////////////////////////
+// The mail session is a replacement for the old 4.x MSG_Master object. It
+// contains mail session generic information such as the user's current mail
+// identity, .... I'm starting this off as an empty interface and as people feel
+// they need to add more information to it, they can. I think this is a better
+// approach than trying to port over the old MSG_Master in its entirety as that
+// had a lot of cruft in it....
+//////////////////////////////////////////////////////////////////////////////////
+
+// nsMsgMailSession also implements nsIFolderListener, in order to relay
+// notifications to its registered listeners.
+// Calling a method on the MailSession causes that method to be invoked upon
+// all the registered listeners (but not listeners directly attached to
+// folders!)
+// In normal operation, most notifications will originate from the
+// nsIMsgFolder.Notify*() functions, which invoke both folder-local
+// listeners, and the global MailSession-registered ones).
+class nsMsgMailSession : public nsIMsgMailSession, public nsIFolderListener {
+ public:
+ nsMsgMailSession();
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIMSGMAILSESSION
+ NS_DECL_NSIFOLDERLISTENER
+
+ nsresult Init();
+ nsresult GetSelectedLocaleDataDir(nsIFile* defaultsDir);
+
+ protected:
+ virtual ~nsMsgMailSession();
+
+ struct folderListener {
+ nsCOMPtr<nsIFolderListener> mListener;
+ uint32_t mNotifyFlags;
+
+ folderListener(nsIFolderListener* aListener, uint32_t aNotifyFlags)
+ : mListener(aListener), mNotifyFlags(aNotifyFlags) {}
+ folderListener(const folderListener& aListener)
+ : mListener(aListener.mListener),
+ mNotifyFlags(aListener.mNotifyFlags) {}
+ ~folderListener() {}
+
+ int operator==(nsIFolderListener* aListener) const {
+ return mListener == aListener;
+ }
+ int operator==(const folderListener& aListener) const {
+ return mListener == aListener.mListener &&
+ mNotifyFlags == aListener.mNotifyFlags;
+ }
+ };
+
+ nsTObserverArray<folderListener> mListeners;
+ nsTObserverArray<nsCOMPtr<nsIMsgUserFeedbackListener> > mFeedbackListeners;
+
+ nsCOMArray<nsIMsgWindow> mWindows;
+ // stick this here temporarily
+ nsCOMPtr<nsIMsgWindow> m_temporaryMsgWindow;
+};
+
+/********************************************************************************/
+
+class nsMsgShutdownService : public nsIMsgShutdownService,
+ public nsIUrlListener,
+ public nsIObserver {
+ public:
+ nsMsgShutdownService();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMSGSHUTDOWNSERVICE
+ NS_DECL_NSIURLLISTENER
+ NS_DECL_NSIOBSERVER
+
+ protected:
+ nsresult ProcessNextTask();
+ void AttemptShutdown();
+
+ private:
+ virtual ~nsMsgShutdownService();
+
+ nsCOMArray<nsIMsgShutdownTask> mShutdownTasks;
+ nsCOMPtr<nsIMsgProgress> mMsgProgress;
+ uint32_t mTaskIndex;
+ uint32_t mQuitMode;
+ bool mProcessedShutdown;
+ bool mQuitForced;
+ bool mReadyToQuit;
+};
+
+#endif /* nsMsgMailSession_h__ */
diff --git a/comm/mailnews/base/src/nsMsgOfflineManager.cpp b/comm/mailnews/base/src/nsMsgOfflineManager.cpp
new file mode 100644
index 0000000000..7d6935fc89
--- /dev/null
+++ b/comm/mailnews/base/src/nsMsgOfflineManager.cpp
@@ -0,0 +1,352 @@
+/* -*- 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/. */
+
+/*
+ * The offline manager service - manages going online and offline, and
+ * synchronization
+ */
+#include "msgCore.h"
+#include "netCore.h"
+#include "nsMsgOfflineManager.h"
+#include "nsIServiceManager.h"
+#include "nsIImapService.h"
+#include "nsIMsgSendLater.h"
+#include "nsIMsgAccountManager.h"
+#include "nsIIOService.h"
+#include "nsNetCID.h"
+#include "nsINntpService.h"
+#include "nsIMsgStatusFeedback.h"
+#include "nsServiceManagerUtils.h"
+#include "mozilla/Components.h"
+
+#define NS_MSGSENDLATER_CID \
+ { /* E15C83F1-1CF4-11d3-8EF0-00A024A7D144 */ \
+ 0xe15c83f1, 0x1cf4, 0x11d3, { \
+ 0x8e, 0xf0, 0x0, 0xa0, 0x24, 0xa7, 0xd1, 0x44 \
+ } \
+ }
+static NS_DEFINE_CID(kMsgSendLaterCID, NS_MSGSENDLATER_CID);
+
+NS_IMPL_ISUPPORTS(nsMsgOfflineManager, nsIMsgOfflineManager,
+ nsIMsgSendLaterListener, nsIObserver,
+ nsISupportsWeakReference, nsIUrlListener)
+
+nsMsgOfflineManager::nsMsgOfflineManager()
+ : m_inProgress(false),
+ m_sendUnsentMessages(false),
+ m_downloadNews(false),
+ m_downloadMail(false),
+ m_playbackOfflineImapOps(false),
+ m_goOfflineWhenDone(false),
+ m_curState(eNoState),
+ m_curOperation(eNoOp) {}
+
+nsMsgOfflineManager::~nsMsgOfflineManager() {}
+
+/* attribute nsIMsgWindow window; */
+NS_IMETHODIMP nsMsgOfflineManager::GetWindow(nsIMsgWindow** aWindow) {
+ NS_ENSURE_ARG(aWindow);
+ NS_IF_ADDREF(*aWindow = m_window);
+ return NS_OK;
+}
+NS_IMETHODIMP nsMsgOfflineManager::SetWindow(nsIMsgWindow* aWindow) {
+ m_window = aWindow;
+ if (m_window)
+ m_window->GetStatusFeedback(getter_AddRefs(m_statusFeedback));
+ else
+ m_statusFeedback = nullptr;
+ return NS_OK;
+}
+
+/* attribute boolean inProgress; */
+NS_IMETHODIMP nsMsgOfflineManager::GetInProgress(bool* aInProgress) {
+ NS_ENSURE_ARG(aInProgress);
+ *aInProgress = m_inProgress;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgOfflineManager::SetInProgress(bool aInProgress) {
+ m_inProgress = aInProgress;
+ return NS_OK;
+}
+
+nsresult nsMsgOfflineManager::StopRunning(nsresult exitStatus) {
+ m_inProgress = false;
+ return exitStatus;
+}
+
+nsresult nsMsgOfflineManager::AdvanceToNextState(nsresult exitStatus) {
+ // NS_BINDING_ABORTED is used for the user pressing stop, which
+ // should cause us to abort the offline process. Other errors
+ // should allow us to continue.
+ if (exitStatus == NS_BINDING_ABORTED) {
+ return StopRunning(exitStatus);
+ }
+ if (m_curOperation == eGoingOnline) {
+ switch (m_curState) {
+ case eNoState:
+
+ m_curState = eSendingUnsent;
+ if (m_sendUnsentMessages) {
+ SendUnsentMessages();
+ } else
+ AdvanceToNextState(NS_OK);
+ break;
+ case eSendingUnsent:
+
+ m_curState = eSynchronizingOfflineImapChanges;
+ if (m_playbackOfflineImapOps)
+ return SynchronizeOfflineImapChanges();
+ else
+ AdvanceToNextState(NS_OK); // recurse to next state.
+ break;
+ case eSynchronizingOfflineImapChanges:
+ m_curState = eDone;
+ return StopRunning(exitStatus);
+ default:
+ NS_ASSERTION(false, "unhandled current state when going online");
+ }
+ } else if (m_curOperation == eDownloadingForOffline) {
+ switch (m_curState) {
+ case eNoState:
+ m_curState = eDownloadingNews;
+ if (m_downloadNews)
+ DownloadOfflineNewsgroups();
+ else
+ AdvanceToNextState(NS_OK);
+ break;
+ case eSendingUnsent:
+ if (m_goOfflineWhenDone) {
+ SetOnlineState(false);
+ }
+ break;
+ case eDownloadingNews:
+ m_curState = eDownloadingMail;
+ if (m_downloadMail)
+ DownloadMail();
+ else
+ AdvanceToNextState(NS_OK);
+ break;
+ case eDownloadingMail:
+ m_curState = eSendingUnsent;
+ if (m_sendUnsentMessages)
+ SendUnsentMessages();
+ else
+ AdvanceToNextState(NS_OK);
+ break;
+ default:
+ NS_ASSERTION(false,
+ "unhandled current state when downloading for offline");
+ }
+ }
+ return NS_OK;
+}
+
+nsresult nsMsgOfflineManager::SynchronizeOfflineImapChanges() {
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIImapService> imapService =
+ do_GetService("@mozilla.org/messenger/imapservice;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return imapService->PlaybackAllOfflineOperations(
+ m_window, this, getter_AddRefs(mOfflineImapSync));
+}
+
+nsresult nsMsgOfflineManager::SendUnsentMessages() {
+ nsresult rv;
+ nsCOMPtr<nsIMsgSendLater> pMsgSendLater(do_GetService(kMsgSendLaterCID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIMsgAccountManager> accountManager =
+ do_GetService("@mozilla.org/messenger/account-manager;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // now we have to iterate over the identities, finding the *unique* unsent
+ // messages folder for each one, determine if they have unsent messages, and
+ // if so, add them to the list of identities to send unsent messages from.
+ // However, I think there's only ever one unsent messages folder at the
+ // moment, so I think we'll go with that for now.
+ nsTArray<RefPtr<nsIMsgIdentity>> identities;
+
+ if (NS_SUCCEEDED(rv) && accountManager) {
+ rv = accountManager->GetAllIdentities(identities);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ nsCOMPtr<nsIMsgIdentity> identityToUse;
+ for (auto thisIdentity : identities) {
+ if (thisIdentity) {
+ nsCOMPtr<nsIMsgFolder> outboxFolder;
+ pMsgSendLater->GetUnsentMessagesFolder(thisIdentity,
+ getter_AddRefs(outboxFolder));
+ if (outboxFolder) {
+ int32_t numMessages;
+ outboxFolder->GetTotalMessages(false, &numMessages);
+ if (numMessages > 0) {
+ identityToUse = thisIdentity;
+ break;
+ }
+ }
+ }
+ }
+ if (identityToUse) {
+#ifdef MOZ_SUITE
+ if (m_statusFeedback) pMsgSendLater->SetStatusFeedback(m_statusFeedback);
+#endif
+
+ pMsgSendLater->AddListener(this);
+ rv = pMsgSendLater->SendUnsentMessages(identityToUse);
+ ShowStatus("sendingUnsent");
+ // if we succeeded, return - we'll run the next operation when the
+ // send finishes. Otherwise, advance to the next state.
+ if (NS_SUCCEEDED(rv)) return rv;
+ }
+ return AdvanceToNextState(rv);
+}
+
+#define MESSENGER_STRING_URL "chrome://messenger/locale/messenger.properties"
+
+nsresult nsMsgOfflineManager::ShowStatus(const char* statusMsgName) {
+ if (!mStringBundle) {
+ nsCOMPtr<nsIStringBundleService> sBundleService =
+ mozilla::components::StringBundle::Service();
+ NS_ENSURE_TRUE(sBundleService, NS_ERROR_UNEXPECTED);
+ sBundleService->CreateBundle(MESSENGER_STRING_URL,
+ getter_AddRefs(mStringBundle));
+ return NS_OK;
+ }
+
+ nsString statusString;
+ nsresult res = mStringBundle->GetStringFromName(statusMsgName, statusString);
+
+ if (NS_SUCCEEDED(res) && m_statusFeedback)
+ m_statusFeedback->ShowStatusString(statusString);
+
+ return res;
+}
+
+nsresult nsMsgOfflineManager::DownloadOfflineNewsgroups() {
+ nsresult rv;
+ ShowStatus("downloadingNewsgroups");
+ nsCOMPtr<nsINntpService> nntpService(
+ do_GetService("@mozilla.org/messenger/nntpservice;1", &rv));
+ if (NS_SUCCEEDED(rv) && nntpService)
+ rv = nntpService->DownloadNewsgroupsForOffline(m_window, this);
+
+ if (NS_FAILED(rv)) return AdvanceToNextState(rv);
+ return rv;
+}
+
+nsresult nsMsgOfflineManager::DownloadMail() {
+ nsresult rv = NS_OK;
+ ShowStatus("downloadingMail");
+ nsCOMPtr<nsIImapService> imapService =
+ do_GetService("@mozilla.org/messenger/imapservice;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return imapService->DownloadAllOffineImapFolders(m_window, this);
+ // ### we should do get new mail on pop servers, and download imap messages
+ // for offline use.
+}
+
+NS_IMETHODIMP nsMsgOfflineManager::GoOnline(bool sendUnsentMessages,
+ bool playbackOfflineImapOperations,
+ nsIMsgWindow* aMsgWindow) {
+ m_sendUnsentMessages = sendUnsentMessages;
+ m_playbackOfflineImapOps = playbackOfflineImapOperations;
+ m_curOperation = eGoingOnline;
+ m_curState = eNoState;
+ SetWindow(aMsgWindow);
+ SetOnlineState(true);
+ if (!m_sendUnsentMessages && !playbackOfflineImapOperations)
+ return NS_OK;
+ else
+ AdvanceToNextState(NS_OK);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgOfflineManager::SynchronizeForOffline(
+ bool downloadNews, bool downloadMail, bool sendUnsentMessages,
+ bool goOfflineWhenDone, nsIMsgWindow* aMsgWindow) {
+ m_curOperation = eDownloadingForOffline;
+ m_downloadNews = downloadNews;
+ m_downloadMail = downloadMail;
+ m_sendUnsentMessages = sendUnsentMessages;
+ SetWindow(aMsgWindow);
+ m_goOfflineWhenDone = goOfflineWhenDone;
+ m_curState = eNoState;
+ if (!downloadNews && !downloadMail && !sendUnsentMessages) {
+ if (goOfflineWhenDone) return SetOnlineState(false);
+ } else
+ return AdvanceToNextState(NS_OK);
+ return NS_OK;
+}
+
+nsresult nsMsgOfflineManager::SetOnlineState(bool online) {
+ nsCOMPtr<nsIIOService> netService = mozilla::components::IO::Service();
+ NS_ENSURE_TRUE(netService, NS_ERROR_UNEXPECTED);
+ return netService->SetOffline(!online);
+}
+
+// nsIUrlListener methods
+
+NS_IMETHODIMP
+nsMsgOfflineManager::OnStartRunningUrl(nsIURI* aUrl) { return NS_OK; }
+
+NS_IMETHODIMP
+nsMsgOfflineManager::OnStopRunningUrl(nsIURI* aUrl, nsresult aExitCode) {
+ mOfflineImapSync = nullptr;
+
+ AdvanceToNextState(aExitCode);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgOfflineManager::Observe(nsISupports* aSubject,
+ const char* aTopic,
+ const char16_t* someData) {
+ return NS_OK;
+}
+
+// nsIMsgSendLaterListener implementation
+NS_IMETHODIMP
+nsMsgOfflineManager::OnStartSending(uint32_t aTotalMessageCount) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgOfflineManager::OnMessageStartSending(uint32_t aCurrentMessage,
+ uint32_t aTotalMessageCount,
+ nsIMsgDBHdr* aMessageHeader,
+ nsIMsgIdentity* aIdentity) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgOfflineManager::OnMessageSendProgress(uint32_t aCurrentMessage,
+ uint32_t aTotalMessageCount,
+ uint32_t aMessageSendPercent,
+ uint32_t aMessageCopyPercent) {
+ if (m_statusFeedback && aTotalMessageCount)
+ return m_statusFeedback->ShowProgress((100 * aCurrentMessage) /
+ aTotalMessageCount);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgOfflineManager::OnMessageSendError(uint32_t aCurrentMessage,
+ nsIMsgDBHdr* aMessageHeader,
+ nsresult aStatus,
+ const char16_t* aMsg) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgOfflineManager::OnStopSending(nsresult aStatus, const char16_t* aMsg,
+ uint32_t aTotalTried, uint32_t aSuccessful) {
+#ifdef NS_DEBUG
+ if (NS_SUCCEEDED(aStatus))
+ printf(
+ "SendLaterListener::OnStopSending: Tried to send %d messages. %d "
+ "successful.\n",
+ aTotalTried, aSuccessful);
+#endif
+ return AdvanceToNextState(aStatus);
+}
diff --git a/comm/mailnews/base/src/nsMsgOfflineManager.h b/comm/mailnews/base/src/nsMsgOfflineManager.h
new file mode 100644
index 0000000000..f340ee0fd1
--- /dev/null
+++ b/comm/mailnews/base/src/nsMsgOfflineManager.h
@@ -0,0 +1,79 @@
+/* -*- 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 nsMsgOfflineManager_h__
+#define nsMsgOfflineManager_h__
+
+#include "nscore.h"
+#include "nsIMsgOfflineManager.h"
+#include "nsCOMPtr.h"
+#include "nsIObserver.h"
+#include "nsWeakReference.h"
+#include "nsIUrlListener.h"
+#include "nsIMsgWindow.h"
+#include "nsIMsgSendLaterListener.h"
+#include "nsIStringBundle.h"
+
+class nsMsgOfflineManager : public nsIMsgOfflineManager,
+ public nsIObserver,
+ public nsSupportsWeakReference,
+ public nsIMsgSendLaterListener,
+ public nsIUrlListener {
+ public:
+ nsMsgOfflineManager();
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ /* nsIMsgOfflineManager methods */
+
+ NS_DECL_NSIMSGOFFLINEMANAGER
+ NS_DECL_NSIOBSERVER
+ NS_DECL_NSIURLLISTENER
+ NS_DECL_NSIMSGSENDLATERLISTENER
+
+ typedef enum {
+ eStarting = 0,
+ eSynchronizingOfflineImapChanges = 1,
+ eDownloadingNews = 2,
+ eDownloadingMail = 3,
+ eSendingUnsent = 4,
+ eDone = 5,
+ eNoState = 6 // we're not doing anything
+ } offlineManagerState;
+
+ typedef enum {
+ eGoingOnline = 0,
+ eDownloadingForOffline = 1,
+ eNoOp = 2 // no operation in progress
+ } offlineManagerOperation;
+
+ private:
+ virtual ~nsMsgOfflineManager();
+
+ nsresult AdvanceToNextState(nsresult exitStatus);
+ nsresult SynchronizeOfflineImapChanges();
+ nsresult StopRunning(nsresult exitStatus);
+ nsresult SendUnsentMessages();
+ nsresult DownloadOfflineNewsgroups();
+ nsresult DownloadMail();
+
+ nsresult SetOnlineState(bool online);
+ nsresult ShowStatus(const char* statusMsgName);
+
+ bool m_inProgress;
+ bool m_sendUnsentMessages;
+ bool m_downloadNews;
+ bool m_downloadMail;
+ bool m_playbackOfflineImapOps;
+ bool m_goOfflineWhenDone;
+ offlineManagerState m_curState;
+ offlineManagerOperation m_curOperation;
+ nsCOMPtr<nsIMsgWindow> m_window;
+ nsCOMPtr<nsIMsgStatusFeedback> m_statusFeedback;
+ nsCOMPtr<nsIStringBundle> mStringBundle;
+ nsCOMPtr<nsISupports> mOfflineImapSync;
+};
+
+#endif
diff --git a/comm/mailnews/base/src/nsMsgProgress.cpp b/comm/mailnews/base/src/nsMsgProgress.cpp
new file mode 100644
index 0000000000..a6349b5add
--- /dev/null
+++ b/comm/mailnews/base/src/nsMsgProgress.cpp
@@ -0,0 +1,250 @@
+/* -*- 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 "nsMsgProgress.h"
+
+#include "nsIBaseWindow.h"
+#include "nsXPCOM.h"
+#include "nsIMutableArray.h"
+#include "nsISupportsPrimitives.h"
+#include "nsIComponentManager.h"
+#include "nsError.h"
+#include "nsIWindowWatcher.h"
+#include "nsPIDOMWindow.h"
+#include "mozIDOMWindow.h"
+#include "nsServiceManagerUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "nsMsgUtils.h"
+#include "mozilla/Components.h"
+#include "mozilla/dom/BrowsingContext.h"
+
+NS_IMPL_ISUPPORTS(nsMsgProgress, nsIMsgStatusFeedback, nsIMsgProgress,
+ nsIWebProgressListener, nsIProgressEventSink,
+ nsISupportsWeakReference)
+
+nsMsgProgress::nsMsgProgress() {
+ m_closeProgress = false;
+ m_processCanceled = false;
+ m_pendingStateFlags = -1;
+ m_pendingStateValue = NS_OK;
+}
+
+nsMsgProgress::~nsMsgProgress() { (void)ReleaseListeners(); }
+
+NS_IMETHODIMP nsMsgProgress::OpenProgressDialog(
+ mozIDOMWindowProxy* parentDOMWindow, nsIMsgWindow* aMsgWindow,
+ const char* dialogURL, bool inDisplayModal, nsISupports* parameters) {
+ nsresult rv;
+
+ if (aMsgWindow) {
+ SetMsgWindow(aMsgWindow);
+ aMsgWindow->SetStatusFeedback(this);
+ }
+
+ NS_ENSURE_ARG_POINTER(dialogURL);
+ NS_ENSURE_ARG_POINTER(parentDOMWindow);
+ nsCOMPtr<nsPIDOMWindowOuter> parent =
+ nsPIDOMWindowOuter::From(parentDOMWindow);
+
+ // Set up window.arguments[0]...
+ nsCOMPtr<nsIMutableArray> array(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsISupportsInterfacePointer> ifptr =
+ do_CreateInstance(NS_SUPPORTS_INTERFACE_POINTER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ ifptr->SetData(static_cast<nsIMsgProgress*>(this));
+ ifptr->SetDataIID(&NS_GET_IID(nsIMsgProgress));
+
+ array->AppendElement(ifptr);
+ array->AppendElement(parameters);
+
+ // Open the dialog.
+ RefPtr<mozilla::dom::BrowsingContext> newWindow;
+
+ nsString chromeOptions(u"chrome,dependent,centerscreen"_ns);
+ if (inDisplayModal) chromeOptions.AppendLiteral(",modal");
+
+ return parent->OpenDialog(NS_ConvertASCIItoUTF16(dialogURL), u"_blank"_ns,
+ chromeOptions, array, getter_AddRefs(newWindow));
+}
+
+NS_IMETHODIMP nsMsgProgress::CloseProgressDialog(bool forceClose) {
+ m_closeProgress = true;
+ return OnStateChange(nullptr, nullptr, nsIWebProgressListener::STATE_STOP,
+ forceClose ? NS_ERROR_FAILURE : NS_OK);
+}
+
+NS_IMETHODIMP nsMsgProgress::GetProcessCanceledByUser(
+ bool* aProcessCanceledByUser) {
+ NS_ENSURE_ARG_POINTER(aProcessCanceledByUser);
+ *aProcessCanceledByUser = m_processCanceled;
+ return NS_OK;
+}
+NS_IMETHODIMP nsMsgProgress::SetProcessCanceledByUser(
+ bool aProcessCanceledByUser) {
+ m_processCanceled = aProcessCanceledByUser;
+ OnStateChange(nullptr, nullptr, nsIWebProgressListener::STATE_STOP,
+ NS_BINDING_ABORTED);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgProgress::RegisterListener(
+ nsIWebProgressListener* listener) {
+ if (!listener) // Nothing to do with a null listener!
+ return NS_OK;
+
+ NS_ENSURE_ARG(this != listener); // Check for self-reference (see bug 271700)
+
+ m_listenerList.AppendObject(listener);
+ if (m_closeProgress || m_processCanceled)
+ listener->OnStateChange(nullptr, nullptr,
+ nsIWebProgressListener::STATE_STOP, NS_OK);
+ else {
+ listener->OnStatusChange(nullptr, nullptr, NS_OK, m_pendingStatus.get());
+ if (m_pendingStateFlags != -1)
+ listener->OnStateChange(nullptr, nullptr, m_pendingStateFlags,
+ m_pendingStateValue);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgProgress::UnregisterListener(
+ nsIWebProgressListener* listener) {
+ if (listener) m_listenerList.RemoveObject(listener);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgProgress::OnStateChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest,
+ uint32_t aStateFlags,
+ nsresult aStatus) {
+ m_pendingStateFlags = aStateFlags;
+ m_pendingStateValue = aStatus;
+
+ nsCOMPtr<nsIMsgWindow> msgWindow(do_QueryReferent(m_msgWindow));
+ if (aStateFlags == nsIWebProgressListener::STATE_STOP && msgWindow &&
+ NS_FAILED(aStatus)) {
+ msgWindow->StopUrls();
+ msgWindow->SetStatusFeedback(nullptr);
+ }
+
+ for (int32_t i = m_listenerList.Count() - 1; i >= 0; i--)
+ m_listenerList[i]->OnStateChange(aWebProgress, aRequest, aStateFlags,
+ aStatus);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgProgress::OnProgressChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest,
+ int32_t aCurSelfProgress,
+ int32_t aMaxSelfProgress,
+ int32_t aCurTotalProgress,
+ int32_t aMaxTotalProgress) {
+ for (int32_t i = m_listenerList.Count() - 1; i >= 0; i--)
+ m_listenerList[i]->OnProgressChange(aWebProgress, aRequest,
+ aCurSelfProgress, aMaxSelfProgress,
+ aCurTotalProgress, aMaxTotalProgress);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgProgress::OnLocationChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest,
+ nsIURI* location,
+ uint32_t aFlags) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgProgress::OnStatusChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest,
+ nsresult aStatus,
+ const char16_t* aMessage) {
+ if (aMessage && *aMessage) m_pendingStatus = aMessage;
+ for (int32_t i = m_listenerList.Count() - 1; i >= 0; i--)
+ m_listenerList[i]->OnStatusChange(aWebProgress, aRequest, aStatus,
+ aMessage);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgProgress::OnSecurityChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest,
+ uint32_t state) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgProgress::OnContentBlockingEvent(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest, uint32_t aEvent) {
+ return NS_OK;
+}
+
+nsresult nsMsgProgress::ReleaseListeners() {
+ m_listenerList.Clear();
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgProgress::ShowStatusString(const nsAString& aStatus) {
+ return OnStatusChange(nullptr, nullptr, NS_OK,
+ PromiseFlatString(aStatus).get());
+}
+
+NS_IMETHODIMP nsMsgProgress::SetStatusString(const nsAString& aStatus) {
+ return OnStatusChange(nullptr, nullptr, NS_OK,
+ PromiseFlatString(aStatus).get());
+}
+
+NS_IMETHODIMP nsMsgProgress::StartMeteors() { return NS_ERROR_NOT_IMPLEMENTED; }
+
+NS_IMETHODIMP nsMsgProgress::StopMeteors() { return NS_ERROR_NOT_IMPLEMENTED; }
+
+NS_IMETHODIMP nsMsgProgress::ShowProgress(int32_t percent) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsMsgProgress::SetWrappedStatusFeedback(
+ nsIMsgStatusFeedback* aJSStatusFeedback) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsMsgProgress::SetMsgWindow(nsIMsgWindow* aMsgWindow) {
+ m_msgWindow = do_GetWeakReference(aMsgWindow);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgProgress::GetMsgWindow(nsIMsgWindow** aMsgWindow) {
+ NS_ENSURE_ARG_POINTER(aMsgWindow);
+
+ if (m_msgWindow)
+ CallQueryReferent(m_msgWindow.get(), aMsgWindow);
+ else
+ *aMsgWindow = nullptr;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgProgress::OnProgress(nsIRequest* request, int64_t aProgress,
+ int64_t aProgressMax) {
+ // XXX: What should the nsIWebProgress be?
+ // XXX: This truncates 64-bit to 32-bit
+ return OnProgressChange(nullptr, request, int32_t(aProgress),
+ int32_t(aProgressMax),
+ int32_t(aProgress) /* current total progress */,
+ int32_t(aProgressMax) /* max total progress */);
+}
+
+NS_IMETHODIMP nsMsgProgress::OnStatus(nsIRequest* request, nsresult aStatus,
+ const char16_t* aStatusArg) {
+ nsresult rv;
+ nsCOMPtr<nsIStringBundleService> sbs =
+ mozilla::components::StringBundle::Service();
+ NS_ENSURE_TRUE(sbs, NS_ERROR_UNEXPECTED);
+ nsString str;
+ rv = sbs->FormatStatusMessage(aStatus, aStatusArg, str);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return ShowStatusString(str);
+}
diff --git a/comm/mailnews/base/src/nsMsgProgress.h b/comm/mailnews/base/src/nsMsgProgress.h
new file mode 100644
index 0000000000..57fae38aa9
--- /dev/null
+++ b/comm/mailnews/base/src/nsMsgProgress.h
@@ -0,0 +1,45 @@
+/* -*- 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 nsMsgProgress_h_
+#define nsMsgProgress_h_
+
+#include "nsIMsgProgress.h"
+#include "nsCOMPtr.h"
+#include "nsCOMArray.h"
+#include "nsIMsgStatusFeedback.h"
+#include "nsString.h"
+#include "nsIMsgWindow.h"
+#include "nsIProgressEventSink.h"
+#include "nsIStringBundle.h"
+#include "nsWeakReference.h"
+
+class nsMsgProgress : public nsIMsgProgress,
+ public nsIMsgStatusFeedback,
+ public nsIProgressEventSink,
+ public nsSupportsWeakReference {
+ public:
+ nsMsgProgress();
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIMSGPROGRESS
+ NS_DECL_NSIWEBPROGRESSLISTENER
+ NS_DECL_NSIMSGSTATUSFEEDBACK
+ NS_DECL_NSIPROGRESSEVENTSINK
+
+ private:
+ virtual ~nsMsgProgress();
+ nsresult ReleaseListeners(void);
+
+ bool m_closeProgress;
+ bool m_processCanceled;
+ nsString m_pendingStatus;
+ int32_t m_pendingStateFlags;
+ nsresult m_pendingStateValue;
+ nsWeakPtr m_msgWindow;
+ nsCOMArray<nsIWebProgressListener> m_listenerList;
+};
+
+#endif // nsMsgProgress_h_
diff --git a/comm/mailnews/base/src/nsMsgProtocol.cpp b/comm/mailnews/base/src/nsMsgProtocol.cpp
new file mode 100644
index 0000000000..7fc832758c
--- /dev/null
+++ b/comm/mailnews/base/src/nsMsgProtocol.cpp
@@ -0,0 +1,1512 @@
+/* -*- 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 "msgCore.h"
+#include "nsString.h"
+#include "nsMemory.h"
+#include "nsMsgProtocol.h"
+#include "nsIMsgMailNewsUrl.h"
+#include "nsIMsgMailSession.h"
+#include "nsIStreamTransportService.h"
+#include "nsISocketTransportService.h"
+#include "nsISocketTransport.h"
+#include "nsITLSSocketControl.h"
+#include "nsITransportSecurityInfo.h"
+#include "nsILoadGroup.h"
+#include "nsILoadInfo.h"
+#include "nsIIOService.h"
+#include "nsNetUtil.h"
+#include "nsIFileURL.h"
+#include "nsIMsgWindow.h"
+#include "nsIMsgStatusFeedback.h"
+#include "nsIWebProgressListener.h"
+#include "nsIPipe.h"
+#include "nsIPrompt.h"
+#include "prprf.h"
+#include "plbase64.h"
+#include "nsIStringBundle.h"
+#include "nsIProxyInfo.h"
+#include "nsThreadUtils.h"
+#include "nsIPrefBranch.h"
+#include "nsIPrefService.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsMsgUtils.h"
+#include "nsILineInputStream.h"
+#include "nsIAsyncInputStream.h"
+#include "nsIMsgIncomingServer.h"
+#include "nsIInputStreamPump.h"
+#include "nsICancelable.h"
+#include "nsMimeTypes.h"
+#include "mozilla/Components.h"
+#include "mozilla/SlicedInputStream.h"
+#include "nsContentSecurityManager.h"
+#include "nsPrintfCString.h"
+
+#undef PostMessage // avoid to collision with WinUser.h
+
+using namespace mozilla;
+
+NS_IMPL_ISUPPORTS_INHERITED(nsMsgProtocol, nsHashPropertyBag, nsIMailChannel,
+ nsIChannel, nsIStreamListener, nsIRequestObserver,
+ nsIRequest, nsITransportEventSink)
+
+static char16_t* FormatStringWithHostNameByName(const char16_t* stringName,
+ nsIMsgMailNewsUrl* msgUri);
+
+nsMsgProtocol::nsMsgProtocol(nsIURI* aURL) {
+ m_flags = 0;
+ m_readCount = 0;
+ mLoadFlags = 0;
+ m_socketIsOpen = false;
+ mContentLength = -1;
+ m_isChannel = false;
+ mContentDisposition = nsIChannel::DISPOSITION_INLINE;
+
+ GetSpecialDirectoryWithFileName(NS_OS_TEMP_DIR, "tempMessage.eml",
+ getter_AddRefs(m_tempMsgFile));
+
+ mSuppressListenerNotifications = false;
+ InitFromURI(aURL);
+}
+
+nsresult nsMsgProtocol::InitFromURI(nsIURI* aUrl) {
+ m_url = aUrl;
+
+ nsCOMPtr<nsIMsgMailNewsUrl> mailUrl = do_QueryInterface(aUrl);
+ if (mailUrl) {
+ mailUrl->GetLoadGroup(getter_AddRefs(m_loadGroup));
+ nsCOMPtr<nsIMsgStatusFeedback> statusFeedback;
+ mailUrl->GetStatusFeedback(getter_AddRefs(statusFeedback));
+ mProgressEventSink = do_QueryInterface(statusFeedback);
+ }
+
+ // Reset channel data in case the object is reused and initialised again.
+ mCharset.Truncate();
+
+ return NS_OK;
+}
+
+nsMsgProtocol::~nsMsgProtocol() {}
+
+static bool gGotTimeoutPref;
+static int32_t gSocketTimeout = 60;
+
+nsresult nsMsgProtocol::GetQoSBits(uint8_t* aQoSBits) {
+ NS_ENSURE_ARG_POINTER(aQoSBits);
+ const char* protocol = GetType();
+
+ if (!protocol) return NS_ERROR_NOT_IMPLEMENTED;
+
+ nsAutoCString prefName("mail.");
+ prefName.Append(protocol);
+ prefName.AppendLiteral(".qos");
+
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> prefBranch =
+ do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t val;
+ rv = prefBranch->GetIntPref(prefName.get(), &val);
+ NS_ENSURE_SUCCESS(rv, rv);
+ *aQoSBits = (uint8_t)clamped(val, 0, 0xff);
+ return NS_OK;
+}
+
+nsresult nsMsgProtocol::OpenNetworkSocketWithInfo(
+ const char* aHostName, int32_t aGetPort, const char* connectionType,
+ nsIProxyInfo* aProxyInfo, nsIInterfaceRequestor* callbacks) {
+ NS_ENSURE_ARG(aHostName);
+
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsISocketTransportService> socketService(
+ do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID));
+ NS_ENSURE_TRUE(socketService, NS_ERROR_FAILURE);
+
+ // with socket connections we want to read as much data as arrives
+ m_readCount = -1;
+
+ nsCOMPtr<nsISocketTransport> strans;
+ AutoTArray<nsCString, 1> connectionTypeArray;
+ if (connectionType) connectionTypeArray.AppendElement(connectionType);
+ rv = socketService->CreateTransport(
+ connectionTypeArray, nsDependentCString(aHostName), aGetPort, aProxyInfo,
+ nullptr, getter_AddRefs(strans));
+ if (NS_FAILED(rv)) return rv;
+
+ strans->SetSecurityCallbacks(callbacks);
+
+ // creates cyclic reference!
+ nsCOMPtr<nsIThread> currentThread(do_GetCurrentThread());
+ strans->SetEventSink(this, currentThread);
+
+ m_socketIsOpen = false;
+ m_transport = strans;
+
+ if (!gGotTimeoutPref) {
+ nsCOMPtr<nsIPrefBranch> prefBranch =
+ do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
+ if (prefBranch) {
+ prefBranch->GetIntPref("mailnews.tcptimeout", &gSocketTimeout);
+ gGotTimeoutPref = true;
+ }
+ }
+ strans->SetTimeout(nsISocketTransport::TIMEOUT_CONNECT, gSocketTimeout + 60);
+ strans->SetTimeout(nsISocketTransport::TIMEOUT_READ_WRITE, gSocketTimeout);
+
+ uint8_t qos;
+ rv = GetQoSBits(&qos);
+ if (NS_SUCCEEDED(rv)) strans->SetQoSBits(qos);
+
+ return SetupTransportState();
+}
+
+nsresult nsMsgProtocol::GetFileFromURL(nsIURI* aURL, nsIFile** aResult) {
+ NS_ENSURE_ARG_POINTER(aURL);
+ NS_ENSURE_ARG_POINTER(aResult);
+ // extract the file path from the uri...
+ nsAutoCString urlSpec;
+ aURL->GetPathQueryRef(urlSpec);
+ urlSpec.InsertLiteral("file://", 0);
+ nsresult rv;
+
+ // dougt - there should be an easier way!
+ nsCOMPtr<nsIURI> uri;
+ if (NS_FAILED(rv = NS_NewURI(getter_AddRefs(uri), urlSpec.get()))) return rv;
+
+ nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(uri);
+ if (!fileURL) return NS_ERROR_FAILURE;
+
+ return fileURL->GetFile(aResult);
+ // dougt
+}
+
+nsresult nsMsgProtocol::OpenFileSocket(nsIURI* aURL, uint64_t aStartPosition,
+ int64_t aReadCount) {
+ // mscott - file needs to be encoded directly into aURL. I should be able to
+ // get rid of this method completely.
+
+ nsresult rv = NS_OK;
+ m_readCount = aReadCount;
+ nsCOMPtr<nsIFile> file;
+
+ rv = GetFileFromURL(aURL, getter_AddRefs(file));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIInputStream> stream;
+ rv = NS_NewLocalFileInputStream(getter_AddRefs(stream), file);
+ if (NS_FAILED(rv)) return rv;
+
+ // create input stream transport
+ nsCOMPtr<nsIStreamTransportService> sts =
+ do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ // This can be called with aReadCount == -1 which means "read as much as we
+ // can". We pass this on as UINT64_MAX, which is in fact uint64_t(-1).
+ RefPtr<SlicedInputStream> slicedStream = new SlicedInputStream(
+ stream.forget(), aStartPosition,
+ aReadCount == -1 ? UINT64_MAX : uint64_t(aReadCount));
+ rv = sts->CreateInputTransport(slicedStream, true,
+ getter_AddRefs(m_transport));
+
+ m_socketIsOpen = false;
+ return rv;
+}
+
+nsresult nsMsgProtocol::GetTopmostMsgWindow(nsIMsgWindow** aWindow) {
+ nsresult rv;
+ nsCOMPtr<nsIMsgMailSession> mailSession(
+ do_GetService("@mozilla.org/messenger/services/session;1", &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return mailSession->GetTopmostMsgWindow(aWindow);
+}
+
+nsresult nsMsgProtocol::SetupTransportState() {
+ if (!m_socketIsOpen && m_transport) {
+ nsresult rv;
+
+ // open buffered, blocking output stream
+ rv = m_transport->OpenOutputStream(nsITransport::OPEN_BLOCKING, 0, 0,
+ getter_AddRefs(m_outputStream));
+ if (NS_FAILED(rv)) return rv;
+ // we want to open the stream
+ } // if m_transport
+
+ return NS_OK;
+}
+
+nsresult nsMsgProtocol::CloseSocket() {
+ nsresult rv = NS_OK;
+ // release all of our socket state
+ m_socketIsOpen = false;
+ m_outputStream = nullptr;
+ if (m_transport) {
+ nsCOMPtr<nsISocketTransport> strans = do_QueryInterface(m_transport);
+ if (strans) {
+ strans->SetEventSink(nullptr, nullptr); // break cyclic reference!
+ }
+ }
+ // we need to call Cancel so that we remove the socket transport from the
+ // mActiveTransportList. see bug #30648
+ if (m_request) {
+ rv = m_request->Cancel(NS_BINDING_ABORTED);
+ }
+ m_request = nullptr;
+ if (m_transport) {
+ m_transport->Close(NS_BINDING_ABORTED);
+ m_transport = nullptr;
+ }
+
+ return rv;
+}
+
+/*
+ * Writes the data contained in dataBuffer into the current output stream. It
+ * also informs the transport layer that this data is now available for
+ * transmission. Returns a positive number for success, 0 for failure (not all
+ * the bytes were written to the stream, etc). We need to make another pass
+ * through this file to install an error system (mscott)
+ *
+ * No logging is done in the base implementation, so aSuppressLogging is
+ * ignored.
+ */
+
+nsresult nsMsgProtocol::SendData(const char* dataBuffer,
+ bool aSuppressLogging) {
+ uint32_t writeCount = 0;
+
+ if (dataBuffer && m_outputStream)
+ return m_outputStream->Write(dataBuffer, PL_strlen(dataBuffer),
+ &writeCount);
+ // TODO make sure all the bytes in PL_strlen(dataBuffer) were written
+ else
+ return NS_ERROR_INVALID_ARG;
+}
+
+// Whenever data arrives from the connection, core netlib notifices the protocol
+// by calling OnDataAvailable. We then read and process the incoming data from
+// the input stream.
+NS_IMETHODIMP nsMsgProtocol::OnDataAvailable(nsIRequest* request,
+ nsIInputStream* inStr,
+ uint64_t sourceOffset,
+ uint32_t count) {
+ // right now, this really just means turn around and churn through the state
+ // machine
+ nsCOMPtr<nsIURI> uri;
+ GetURI(getter_AddRefs(uri));
+
+ return ProcessProtocolState(uri, inStr, sourceOffset, count);
+}
+
+NS_IMETHODIMP nsMsgProtocol::OnStartRequest(nsIRequest* request) {
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIURI> uri;
+ GetURI(getter_AddRefs(uri));
+
+ if (uri) {
+ nsCOMPtr<nsIMsgMailNewsUrl> aMsgUrl = do_QueryInterface(uri);
+ rv = aMsgUrl->SetUrlState(true, NS_OK);
+ if (m_loadGroup)
+ m_loadGroup->AddRequest(static_cast<nsIRequest*>(this),
+ nullptr /* context isupports */);
+ }
+
+ // if we are set up as a channel, we should notify our channel listener that
+ // we are starting... so pass in ourself as the channel and not the underlying
+ // socket or file channel the protocol happens to be using
+ if (!mSuppressListenerNotifications && m_channelListener) {
+ m_isChannel = true;
+ rv = m_channelListener->OnStartRequest(this);
+ }
+
+ nsCOMPtr<nsISocketTransport> strans = do_QueryInterface(m_transport);
+
+ if (strans)
+ strans->SetTimeout(nsISocketTransport::TIMEOUT_READ_WRITE, gSocketTimeout);
+
+ NS_ENSURE_SUCCESS(rv, rv);
+ return rv;
+}
+
+void nsMsgProtocol::ShowAlertMessage(nsIMsgMailNewsUrl* aMsgUrl,
+ nsresult aStatus) {
+ const char16_t* errorString = nullptr;
+ switch (aStatus) {
+ case NS_ERROR_UNKNOWN_HOST:
+ case NS_ERROR_UNKNOWN_PROXY_HOST:
+ errorString = u"unknownHostError";
+ break;
+ case NS_ERROR_CONNECTION_REFUSED:
+ case NS_ERROR_PROXY_CONNECTION_REFUSED:
+ errorString = u"connectionRefusedError";
+ break;
+ case NS_ERROR_NET_TIMEOUT:
+ errorString = u"netTimeoutError";
+ break;
+ case NS_ERROR_NET_RESET:
+ errorString = u"netResetError";
+ break;
+ case NS_ERROR_NET_INTERRUPT:
+ errorString = u"netInterruptError";
+ break;
+ case NS_ERROR_OFFLINE:
+ // Don't alert when offline as that is already displayed in the UI.
+ return;
+ default:
+ nsPrintfCString msg(
+ "Unexpected status passed to ShowAlertMessage: %" PRIx32,
+ static_cast<uint32_t>(aStatus));
+ NS_WARNING(msg.get());
+ return;
+ }
+
+ nsString errorMsg;
+ errorMsg.Adopt(FormatStringWithHostNameByName(errorString, aMsgUrl));
+ if (errorMsg.IsEmpty()) {
+ errorMsg.AssignLiteral(u"[StringID ");
+ errorMsg.Append(errorString);
+ errorMsg.AppendLiteral(u"?]");
+ }
+
+ nsCOMPtr<nsIMsgMailSession> mailSession =
+ do_GetService("@mozilla.org/messenger/services/session;1");
+ if (mailSession) mailSession->AlertUser(errorMsg, aMsgUrl);
+}
+
+// stop binding is a "notification" informing us that the stream associated with
+// aURL is going away.
+NS_IMETHODIMP nsMsgProtocol::OnStopRequest(nsIRequest* request,
+ nsresult aStatus) {
+ nsresult rv = NS_OK;
+
+ // if we are set up as a channel, we should notify our channel listener that
+ // we are starting... so pass in ourself as the channel and not the underlying
+ // socket or file channel the protocol happens to be using
+ if (!mSuppressListenerNotifications && m_channelListener)
+ rv = m_channelListener->OnStopRequest(this, aStatus);
+
+ nsCOMPtr<nsIURI> uri;
+ GetURI(getter_AddRefs(uri));
+
+ if (uri) {
+ nsCOMPtr<nsIMsgMailNewsUrl> msgUrl = do_QueryInterface(uri);
+ rv = msgUrl->SetUrlState(false, aStatus); // Always returns NS_OK.
+ if (m_loadGroup)
+ m_loadGroup->RemoveRequest(static_cast<nsIRequest*>(this), nullptr,
+ aStatus);
+
+ // !m_isChannel because if we're set up as a channel, then the remove
+ // request above will handle alerting the user, so we don't need to.
+ //
+ // !NS_BINDING_ABORTED because we don't want to see an alert if the user
+ // cancelled the operation. also, we'll get here because we call Cancel()
+ // to force removal of the nsSocketTransport. see CloseSocket()
+ // bugs #30775 and #30648 relate to this
+ if (!m_isChannel && NS_FAILED(aStatus) && (aStatus != NS_BINDING_ABORTED))
+ ShowAlertMessage(msgUrl, aStatus);
+ } // if we have a mailnews url.
+
+ // Drop notification callbacks to prevent cycles.
+ mCallbacks = nullptr;
+ mProgressEventSink = nullptr;
+ // Call CloseSocket(), in case we got here because the server dropped the
+ // connection while reading, and we never get a chance to get back into
+ // the protocol state machine via OnDataAvailable.
+ if (m_socketIsOpen) CloseSocket();
+
+ return rv;
+}
+
+nsresult nsMsgProtocol::LoadUrl(nsIURI* aURL, nsISupports* aConsumer) {
+ // nsMsgProtocol implements nsIChannel, and all channels are required to
+ // have non-null loadInfo. So if it's still unset, we've not been correctly
+ // initialised.
+ MOZ_ASSERT(m_loadInfo);
+
+ // okay now kick us off to the next state...
+ // our first state is a process state so drive the state machine...
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIMsgMailNewsUrl> aMsgUrl = do_QueryInterface(aURL, &rv);
+
+ if (NS_SUCCEEDED(rv) && aMsgUrl) {
+ bool msgIsInLocalCache;
+ aMsgUrl->GetMsgIsInLocalCache(&msgIsInLocalCache);
+
+ // Set the url as a url currently being run...
+ rv = aMsgUrl->SetUrlState(true, NS_OK);
+
+ // if the url is given a stream consumer then we should use it to forward
+ // calls to...
+ if (!m_channelListener &&
+ aConsumer) // if we don't have a registered listener already
+ {
+ m_channelListener = do_QueryInterface(aConsumer);
+ m_isChannel = true;
+ }
+
+ if (!m_socketIsOpen) {
+ if (m_transport) {
+ // open buffered, asynchronous input stream
+ nsCOMPtr<nsIInputStream> stream;
+ rv = m_transport->OpenInputStream(0, 0, 0, getter_AddRefs(stream));
+ if (NS_FAILED(rv)) return rv;
+
+ // m_readCount can be -1 which means "read as much as we can".
+ // We pass this on as UINT64_MAX, which is in fact uint64_t(-1).
+ // We don't clone m_inputStream here, we simply give up ownership
+ // since otherwise the original would never be closed.
+ RefPtr<SlicedInputStream> slicedStream = new SlicedInputStream(
+ stream.forget(), 0,
+ m_readCount == -1 ? UINT64_MAX : uint64_t(m_readCount));
+ nsCOMPtr<nsIInputStreamPump> pump;
+ rv = NS_NewInputStreamPump(getter_AddRefs(pump), slicedStream.forget());
+ if (NS_FAILED(rv)) return rv;
+
+ m_request = pump; // keep a reference to the pump so we can cancel it
+
+ // Put us in a state where we are always notified of incoming data.
+ // OnDataAvailable() will be called when that happens, which will
+ // pass that data into ProcessProtocolState().
+ rv = pump->AsyncRead(this);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "AsyncRead failed");
+ m_socketIsOpen = true; // mark the channel as open
+ }
+ } else if (!msgIsInLocalCache) {
+ // The connection is already open so we should begin processing our url.
+ rv = ProcessProtocolState(aURL, nullptr, 0, 0);
+ }
+ }
+
+ return rv;
+}
+
+///////////////////////////////////////////////////////////////////////
+// The rest of this file is mostly nsIChannel mumbo jumbo stuff
+///////////////////////////////////////////////////////////////////////
+
+nsresult nsMsgProtocol::SetUrl(nsIURI* aURL) {
+ m_url = aURL;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgProtocol::SetLoadGroup(nsILoadGroup* aLoadGroup) {
+ m_loadGroup = aLoadGroup;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgProtocol::GetTRRMode(nsIRequest::TRRMode* aTRRMode) {
+ return GetTRRModeImpl(aTRRMode);
+}
+
+NS_IMETHODIMP nsMsgProtocol::SetTRRMode(nsIRequest::TRRMode aTRRMode) {
+ return SetTRRModeImpl(aTRRMode);
+}
+
+NS_IMETHODIMP nsMsgProtocol::GetOriginalURI(nsIURI** aURI) {
+ NS_IF_ADDREF(*aURI = m_originalUrl ? m_originalUrl : m_url);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgProtocol::SetOriginalURI(nsIURI* aURI) {
+ m_originalUrl = aURI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgProtocol::GetURI(nsIURI** aURI) {
+ NS_IF_ADDREF(*aURI = m_url);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgProtocol::Open(nsIInputStream** _retval) {
+ nsCOMPtr<nsIStreamListener> listener;
+ nsresult rv =
+ nsContentSecurityManager::doContentSecurityCheck(this, listener);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return NS_ImplementChannelOpen(this, _retval);
+}
+
+NS_IMETHODIMP nsMsgProtocol::AsyncOpen(nsIStreamListener* aListener) {
+ nsCOMPtr<nsIStreamListener> listener = aListener;
+ nsresult rv =
+ nsContentSecurityManager::doContentSecurityCheck(this, listener);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t port;
+ rv = m_url->GetPort(&port);
+ if (NS_FAILED(rv)) return rv;
+
+ nsAutoCString scheme;
+ rv = m_url->GetScheme(scheme);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = NS_CheckPortSafety(port, scheme.get());
+ if (NS_FAILED(rv)) return rv;
+
+ // set the stream listener and then load the url
+ m_isChannel = true;
+
+ m_channelListener = listener;
+ return LoadUrl(m_url, nullptr);
+}
+
+NS_IMETHODIMP nsMsgProtocol::GetLoadFlags(nsLoadFlags* aLoadFlags) {
+ *aLoadFlags = mLoadFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgProtocol::SetLoadFlags(nsLoadFlags aLoadFlags) {
+ mLoadFlags = aLoadFlags;
+ return NS_OK; // don't fail when trying to set this
+}
+
+NS_IMETHODIMP nsMsgProtocol::GetContentType(nsACString& aContentType) {
+ // as url dispatching matures, we'll be intelligent and actually start
+ // opening the url before specifying the content type. This will allow
+ // us to optimize the case where the message url actual refers to
+ // a part in the message that has a content type that is not message/rfc822
+
+ if (mContentType.IsEmpty())
+ aContentType.AssignLiteral("message/rfc822");
+ else
+ aContentType = mContentType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgProtocol::SetContentType(const nsACString& aContentType) {
+ nsAutoCString charset;
+ nsresult rv =
+ NS_ParseResponseContentType(aContentType, mContentType, charset);
+ if (NS_FAILED(rv) || mContentType.IsEmpty())
+ mContentType.AssignLiteral(UNKNOWN_CONTENT_TYPE);
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgProtocol::GetContentCharset(nsACString& aContentCharset) {
+ aContentCharset.Assign(mCharset);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgProtocol::SetContentCharset(
+ const nsACString& aContentCharset) {
+ mCharset.Assign(aContentCharset);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgProtocol::GetContentDisposition(uint32_t* aContentDisposition) {
+ *aContentDisposition = mContentDisposition;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgProtocol::SetContentDisposition(uint32_t aContentDisposition) {
+ mContentDisposition = aContentDisposition;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgProtocol::GetContentDispositionFilename(
+ nsAString& aContentDispositionFilename) {
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsMsgProtocol::SetContentDispositionFilename(
+ const nsAString& aContentDispositionFilename) {
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsMsgProtocol::GetContentDispositionHeader(
+ nsACString& aContentDispositionHeader) {
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP nsMsgProtocol::GetContentLength(int64_t* aContentLength) {
+ *aContentLength = mContentLength;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgProtocol::SetContentLength(int64_t aContentLength) {
+ mContentLength = aContentLength;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgProtocol::GetSecurityInfo(
+ nsITransportSecurityInfo** secInfo) {
+ *secInfo = nullptr;
+ if (m_transport) {
+ nsCOMPtr<nsISocketTransport> strans = do_QueryInterface(m_transport);
+ if (strans) {
+ nsCOMPtr<nsITLSSocketControl> tlsSocketControl;
+ if (NS_SUCCEEDED(
+ strans->GetTlsSocketControl(getter_AddRefs(tlsSocketControl)))) {
+ nsCOMPtr<nsITransportSecurityInfo> transportSecInfo;
+ if (NS_SUCCEEDED(tlsSocketControl->GetSecurityInfo(
+ getter_AddRefs(transportSecInfo)))) {
+ transportSecInfo.forget(secInfo);
+ }
+ }
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgProtocol::GetName(nsACString& result) {
+ if (m_url) return m_url->GetSpec(result);
+ result.Truncate();
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgProtocol::GetOwner(nsISupports** aPrincipal) {
+ NS_IF_ADDREF(*aPrincipal = mOwner);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgProtocol::SetOwner(nsISupports* aPrincipal) {
+ mOwner = aPrincipal;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgProtocol::GetLoadGroup(nsILoadGroup** aLoadGroup) {
+ NS_IF_ADDREF(*aLoadGroup = m_loadGroup);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgProtocol::GetLoadInfo(nsILoadInfo** aLoadInfo) {
+ NS_IF_ADDREF(*aLoadInfo = m_loadInfo);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgProtocol::SetLoadInfo(nsILoadInfo* aLoadInfo) {
+ m_loadInfo = aLoadInfo;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgProtocol::GetNotificationCallbacks(
+ nsIInterfaceRequestor** aNotificationCallbacks) {
+ NS_IF_ADDREF(*aNotificationCallbacks = mCallbacks.get());
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgProtocol::SetNotificationCallbacks(
+ nsIInterfaceRequestor* aNotificationCallbacks) {
+ mCallbacks = aNotificationCallbacks;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgProtocol::OnTransportStatus(nsITransport* transport, nsresult status,
+ int64_t progress, int64_t progressMax) {
+ if ((mLoadFlags & LOAD_BACKGROUND) || !m_url) return NS_OK;
+
+ // these transport events should not generate any status messages
+ if (status == NS_NET_STATUS_RECEIVING_FROM ||
+ status == NS_NET_STATUS_SENDING_TO)
+ return NS_OK;
+
+ if (!mProgressEventSink) {
+ NS_QueryNotificationCallbacks(mCallbacks, m_loadGroup, mProgressEventSink);
+ if (!mProgressEventSink) return NS_OK;
+ }
+
+ nsAutoCString host;
+ m_url->GetHost(host);
+
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_url);
+ if (mailnewsUrl) {
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ mailnewsUrl->GetServer(getter_AddRefs(server));
+ if (server) server->GetHostName(host);
+ }
+ mProgressEventSink->OnStatus(this, status, NS_ConvertUTF8toUTF16(host).get());
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgProtocol::GetIsDocument(bool* aIsDocument) {
+ return NS_GetIsDocumentChannel(this, aIsDocument);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// From nsIRequest
+////////////////////////////////////////////////////////////////////////////////
+
+NS_IMETHODIMP nsMsgProtocol::IsPending(bool* result) {
+ *result = m_channelListener != nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgProtocol::GetStatus(nsresult* status) {
+ if (m_request) return m_request->GetStatus(status);
+
+ *status = NS_OK;
+ return *status;
+}
+
+NS_IMETHODIMP nsMsgProtocol::SetCanceledReason(const nsACString& aReason) {
+ return SetCanceledReasonImpl(aReason);
+}
+
+NS_IMETHODIMP nsMsgProtocol::GetCanceledReason(nsACString& aReason) {
+ return GetCanceledReasonImpl(aReason);
+}
+
+NS_IMETHODIMP nsMsgProtocol::CancelWithReason(nsresult aStatus,
+ const nsACString& aReason) {
+ return CancelWithReasonImpl(aStatus, aReason);
+}
+
+NS_IMETHODIMP nsMsgProtocol::Cancel(nsresult status) {
+ if (m_proxyRequest) m_proxyRequest->Cancel(status);
+
+ if (m_request) return m_request->Cancel(status);
+
+ NS_WARNING("no request to cancel");
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP nsMsgProtocol::GetCanceled(bool* aCanceled) {
+ nsresult status = NS_ERROR_FAILURE;
+ GetStatus(&status);
+ *aCanceled = NS_FAILED(status);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgProtocol::Suspend() {
+ if (m_request) return m_request->Suspend();
+
+ NS_WARNING("no request to suspend");
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP nsMsgProtocol::Resume() {
+ if (m_request) return m_request->Resume();
+
+ NS_WARNING("no request to resume");
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+nsresult nsMsgProtocol::PostMessage(nsIURI* url, nsIFile* postFile) {
+ if (!url || !postFile) return NS_ERROR_NULL_POINTER;
+
+#define POST_DATA_BUFFER_SIZE 2048
+
+ // mscott -- this function should be re-written to use the file url code
+ // so it can be asynch
+ nsCOMPtr<nsIInputStream> inputStream;
+ nsresult rv =
+ NS_NewLocalFileInputStream(getter_AddRefs(inputStream), postFile);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsILineInputStream> lineInputStream(
+ do_QueryInterface(inputStream, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool more = true;
+ nsCString line;
+ nsCString outputBuffer;
+
+ do {
+ lineInputStream->ReadLine(line, &more);
+
+ /* escape starting periods
+ */
+ if (line.CharAt(0) == '.') line.Insert('.', 0);
+ line.AppendLiteral(CRLF);
+ outputBuffer.Append(line);
+ // test hack by mscott. If our buffer is almost full, then
+ // send it off & reset ourselves
+ // to make more room.
+ if (outputBuffer.Length() > POST_DATA_BUFFER_SIZE || !more) {
+ rv = SendData(outputBuffer.get());
+ NS_ENSURE_SUCCESS(rv, rv);
+ // does this keep the buffer around? That would be best.
+ // Maybe SetLength(0) instead?
+ outputBuffer.Truncate();
+ }
+ } while (more);
+
+ return NS_OK;
+}
+
+nsresult nsMsgProtocol::DoGSSAPIStep1(const nsACString& service,
+ const char* username,
+ nsCString& response) {
+ nsresult rv;
+#ifdef DEBUG_BenB
+ printf("GSSAPI step 1 for service %s, username %s\n", service, username);
+#endif
+
+ // if this fails, then it means that we cannot do GSSAPI SASL.
+ m_authModule = nsIAuthModule::CreateInstance("sasl-gssapi");
+
+ m_authModule->Init(service, nsIAuthModule::REQ_DEFAULT, u""_ns,
+ NS_ConvertUTF8toUTF16(username), u""_ns);
+
+ void* outBuf;
+ uint32_t outBufLen;
+ rv = m_authModule->GetNextToken((void*)nullptr, 0, &outBuf, &outBufLen);
+ if (NS_SUCCEEDED(rv) && outBuf) {
+ char* base64Str = PL_Base64Encode((char*)outBuf, outBufLen, nullptr);
+ if (base64Str)
+ response.Adopt(base64Str);
+ else
+ rv = NS_ERROR_OUT_OF_MEMORY;
+ free(outBuf);
+ }
+
+#ifdef DEBUG_BenB
+ printf("GSSAPI step 1 succeeded\n");
+#endif
+ return rv;
+}
+
+nsresult nsMsgProtocol::DoGSSAPIStep2(nsCString& commandResponse,
+ nsCString& response) {
+#ifdef DEBUG_BenB
+ printf("GSSAPI step 2\n");
+#endif
+ nsresult rv;
+ void *inBuf, *outBuf;
+ uint32_t inBufLen, outBufLen;
+ uint32_t len = commandResponse.Length();
+
+ // Cyrus SASL may send us zero length tokens (grrrr)
+ if (len > 0) {
+ // decode into the input secbuffer
+ inBufLen = (len * 3) / 4; // sufficient size (see plbase64.h)
+ inBuf = moz_xmalloc(inBufLen);
+ if (!inBuf) return NS_ERROR_OUT_OF_MEMORY;
+
+ // strip off any padding (see bug 230351)
+ const char* challenge = commandResponse.get();
+ while (challenge[len - 1] == '=') len--;
+
+ // We need to know the exact length of the decoded string to give to
+ // the GSSAPI libraries. But NSPR's base64 routine doesn't seem capable
+ // of telling us that. So, we figure it out for ourselves.
+
+ // For every 4 characters, add 3 to the destination
+ // If there are 3 remaining, add 2
+ // If there are 2 remaining, add 1
+ // 1 remaining is an error
+ inBufLen =
+ (len / 4) * 3 + ((len % 4 == 3) ? 2 : 0) + ((len % 4 == 2) ? 1 : 0);
+
+ rv = (PL_Base64Decode(challenge, len, (char*)inBuf))
+ ? m_authModule->GetNextToken(inBuf, inBufLen, &outBuf, &outBufLen)
+ : NS_ERROR_FAILURE;
+
+ free(inBuf);
+ } else {
+ rv = m_authModule->GetNextToken(NULL, 0, &outBuf, &outBufLen);
+ }
+ if (NS_SUCCEEDED(rv)) {
+ // And in return, we may need to send Cyrus zero length tokens back
+ if (outBuf) {
+ char* base64Str = PL_Base64Encode((char*)outBuf, outBufLen, nullptr);
+ if (base64Str)
+ response.Adopt(base64Str);
+ else
+ rv = NS_ERROR_OUT_OF_MEMORY;
+ } else
+ response.Adopt((char*)moz_xmemdup("", 1));
+ }
+
+#ifdef DEBUG_BenB
+ printf(NS_SUCCEEDED(rv) ? "GSSAPI step 2 succeeded\n"
+ : "GSSAPI step 2 failed\n");
+#endif
+ return rv;
+}
+
+nsresult nsMsgProtocol::DoNtlmStep1(const nsACString& username,
+ const nsAString& password,
+ nsCString& response) {
+ nsresult rv;
+
+ m_authModule = nsIAuthModule::CreateInstance("ntlm");
+
+ m_authModule->Init(""_ns, 0, u""_ns, NS_ConvertUTF8toUTF16(username),
+ PromiseFlatString(password));
+
+ void* outBuf;
+ uint32_t outBufLen;
+ rv = m_authModule->GetNextToken((void*)nullptr, 0, &outBuf, &outBufLen);
+ if (NS_SUCCEEDED(rv) && outBuf) {
+ char* base64Str = PL_Base64Encode((char*)outBuf, outBufLen, nullptr);
+ if (base64Str)
+ response.Adopt(base64Str);
+ else
+ rv = NS_ERROR_OUT_OF_MEMORY;
+ free(outBuf);
+ }
+
+ return rv;
+}
+
+nsresult nsMsgProtocol::DoNtlmStep2(nsCString& commandResponse,
+ nsCString& response) {
+ nsresult rv;
+ void *inBuf, *outBuf;
+ uint32_t inBufLen, outBufLen;
+ uint32_t len = commandResponse.Length();
+
+ // decode into the input secbuffer
+ inBufLen = (len * 3) / 4; // sufficient size (see plbase64.h)
+ inBuf = moz_xmalloc(inBufLen);
+ if (!inBuf) return NS_ERROR_OUT_OF_MEMORY;
+
+ // strip off any padding (see bug 230351)
+ const char* challenge = commandResponse.get();
+ while (challenge[len - 1] == '=') len--;
+
+ rv = (PL_Base64Decode(challenge, len, (char*)inBuf))
+ ? m_authModule->GetNextToken(inBuf, inBufLen, &outBuf, &outBufLen)
+ : NS_ERROR_FAILURE;
+
+ free(inBuf);
+ if (NS_SUCCEEDED(rv) && outBuf) {
+ char* base64Str = PL_Base64Encode((char*)outBuf, outBufLen, nullptr);
+ if (base64Str)
+ response.Adopt(base64Str);
+ else
+ rv = NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ if (NS_FAILED(rv)) response = "*";
+
+ return rv;
+}
+
+/////////////////////////////////////////////////////////////////////
+// nsMsgAsyncWriteProtocol subclass and related helper classes
+/////////////////////////////////////////////////////////////////////
+
+class nsMsgProtocolStreamProvider : public nsIOutputStreamCallback {
+ public:
+ // XXX this probably doesn't need to be threadsafe
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ nsMsgProtocolStreamProvider() {}
+
+ void Init(nsMsgAsyncWriteProtocol* aProtInstance,
+ nsIInputStream* aInputStream) {
+ mMsgProtocol =
+ do_GetWeakReference(static_cast<nsIStreamListener*>(aProtInstance));
+ mInStream = aInputStream;
+ }
+
+ //
+ // nsIOutputStreamCallback implementation ...
+ //
+ NS_IMETHODIMP OnOutputStreamReady(nsIAsyncOutputStream* aOutStream) override {
+ NS_ASSERTION(mInStream, "not initialized");
+
+ nsresult rv;
+ uint64_t avail;
+
+ // Write whatever is available in the pipe. If the pipe is empty, then
+ // return NS_BASE_STREAM_WOULD_BLOCK; we will resume the write when there
+ // is more data.
+
+ rv = mInStream->Available(&avail);
+ if (NS_FAILED(rv)) return rv;
+
+ nsMsgAsyncWriteProtocol* protInst = nullptr;
+ nsCOMPtr<nsIStreamListener> callback = do_QueryReferent(mMsgProtocol);
+ if (!callback) return NS_ERROR_FAILURE;
+ protInst = static_cast<nsMsgAsyncWriteProtocol*>(callback.get());
+
+ if (avail == 0 && !protInst->mAsyncBuffer.Length()) {
+ // ok, stop writing...
+ protInst->mSuspendedWrite = true;
+ return NS_OK;
+ }
+ protInst->mSuspendedWrite = false;
+
+ uint32_t bytesWritten;
+
+ if (avail) {
+ rv = aOutStream->WriteFrom(mInStream,
+ std::min(avail, uint64_t(FILE_IO_BUFFER_SIZE)),
+ &bytesWritten);
+ // if were full at the time, the input stream may be backed up and we need
+ // to read any remains from the last ODA call before we'll get more ODA
+ // calls
+ if (protInst->mSuspendedRead) protInst->UnblockPostReader();
+ } else {
+ rv = aOutStream->Write(protInst->mAsyncBuffer.get(),
+ protInst->mAsyncBuffer.Length(), &bytesWritten);
+ protInst->mAsyncBuffer.Cut(0, bytesWritten);
+ }
+
+ protInst->UpdateProgress(bytesWritten);
+
+ // try to write again...
+ if (NS_SUCCEEDED(rv))
+ rv = aOutStream->AsyncWait(this, 0, 0, protInst->mProviderThread);
+
+ NS_ASSERTION(NS_SUCCEEDED(rv) || rv == NS_BINDING_ABORTED,
+ "unexpected error writing stream");
+ return NS_OK;
+ }
+
+ protected:
+ virtual ~nsMsgProtocolStreamProvider() {}
+
+ nsWeakPtr mMsgProtocol;
+ nsCOMPtr<nsIInputStream> mInStream;
+};
+
+NS_IMPL_ISUPPORTS(nsMsgProtocolStreamProvider, nsIOutputStreamCallback)
+
+class nsMsgFilePostHelper : public nsIStreamListener {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+
+ nsMsgFilePostHelper() { mSuspendedPostFileRead = false; }
+ nsresult Init(nsIOutputStream* aOutStream,
+ nsMsgAsyncWriteProtocol* aProtInstance, nsIFile* aFileToPost);
+ nsCOMPtr<nsIRequest> mPostFileRequest;
+ bool mSuspendedPostFileRead;
+ void CloseSocket() { mProtInstance = nullptr; }
+
+ protected:
+ virtual ~nsMsgFilePostHelper() {}
+ nsCOMPtr<nsIOutputStream> mOutStream;
+ nsWeakPtr mProtInstance;
+};
+
+NS_IMPL_ISUPPORTS(nsMsgFilePostHelper, nsIStreamListener, nsIRequestObserver)
+
+nsresult nsMsgFilePostHelper::Init(nsIOutputStream* aOutStream,
+ nsMsgAsyncWriteProtocol* aProtInstance,
+ nsIFile* aFileToPost) {
+ nsresult rv = NS_OK;
+ mOutStream = aOutStream;
+ mProtInstance =
+ do_GetWeakReference(static_cast<nsIStreamListener*>(aProtInstance));
+
+ nsCOMPtr<nsIInputStream> stream;
+ rv = NS_NewLocalFileInputStream(getter_AddRefs(stream), aFileToPost);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIInputStreamPump> pump;
+ rv = NS_NewInputStreamPump(getter_AddRefs(pump), stream.forget());
+ if (NS_FAILED(rv)) return rv;
+
+ rv = pump->AsyncRead(this);
+ if (NS_FAILED(rv)) return rv;
+
+ mPostFileRequest = pump;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgFilePostHelper::OnStartRequest(nsIRequest* aChannel) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgFilePostHelper::OnStopRequest(nsIRequest* aChannel,
+ nsresult aStatus) {
+ nsMsgAsyncWriteProtocol* protInst = nullptr;
+ nsCOMPtr<nsIStreamListener> callback = do_QueryReferent(mProtInstance);
+ if (!callback) return NS_OK;
+ protInst = static_cast<nsMsgAsyncWriteProtocol*>(callback.get());
+
+ if (!mSuspendedPostFileRead) protInst->PostDataFinished();
+
+ mSuspendedPostFileRead = false;
+ protInst->mFilePostHelper = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgFilePostHelper::OnDataAvailable(nsIRequest* /* aChannel */,
+ nsIInputStream* inStr,
+ uint64_t sourceOffset,
+ uint32_t count) {
+ nsMsgAsyncWriteProtocol* protInst = nullptr;
+ nsCOMPtr<nsIStreamListener> callback = do_QueryReferent(mProtInstance);
+ if (!callback) return NS_OK;
+
+ protInst = static_cast<nsMsgAsyncWriteProtocol*>(callback.get());
+
+ if (mSuspendedPostFileRead) {
+ protInst->UpdateSuspendedReadBytes(count, protInst->mInsertPeriodRequired);
+ return NS_OK;
+ }
+
+ protInst->ProcessIncomingPostData(inStr, count);
+
+ if (protInst->mSuspendedWrite) {
+ // if we got here then we had suspended the write 'cause we didn't have
+ // anymore data to write (i.e. the pipe went empty). So resume the channel
+ // to kick things off again.
+ protInst->mSuspendedWrite = false;
+ protInst->mAsyncOutStream->AsyncWait(protInst->mProvider, 0, 0,
+ protInst->mProviderThread);
+ }
+
+ return NS_OK;
+}
+
+NS_IMPL_ADDREF_INHERITED(nsMsgAsyncWriteProtocol, nsMsgProtocol)
+NS_IMPL_RELEASE_INHERITED(nsMsgAsyncWriteProtocol, nsMsgProtocol)
+
+NS_INTERFACE_MAP_BEGIN(nsMsgAsyncWriteProtocol)
+ NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+NS_INTERFACE_MAP_END_INHERITING(nsMsgProtocol)
+
+nsMsgAsyncWriteProtocol::nsMsgAsyncWriteProtocol(nsIURI* aURL)
+ : nsMsgProtocol(aURL) {
+ mSuspendedWrite = false;
+ mSuspendedReadBytes = 0;
+ mSuspendedRead = false;
+ mInsertPeriodRequired = false;
+ mGenerateProgressNotifications = false;
+ mSuspendedReadBytesPostPeriod = 0;
+ mFilePostHelper = nullptr;
+ mNumBytesPosted = 0;
+ mFilePostSize = 0;
+}
+
+nsMsgAsyncWriteProtocol::~nsMsgAsyncWriteProtocol() {}
+
+NS_IMETHODIMP nsMsgAsyncWriteProtocol::Cancel(nsresult status) {
+ mGenerateProgressNotifications = false;
+
+ if (m_proxyRequest) {
+ m_proxyRequest->Cancel(status);
+ }
+
+ if (m_request) m_request->Cancel(status);
+
+ if (mAsyncOutStream) mAsyncOutStream->CloseWithStatus(status);
+
+ return NS_OK;
+}
+
+nsresult nsMsgAsyncWriteProtocol::PostMessage(nsIURI* url, nsIFile* file) {
+ nsCOMPtr<nsIStreamListener> listener = new nsMsgFilePostHelper();
+
+ if (!listener) return NS_ERROR_OUT_OF_MEMORY;
+
+ // be sure to initialize some state before posting
+ mSuspendedReadBytes = 0;
+ mNumBytesPosted = 0;
+ file->GetFileSize(&mFilePostSize);
+ mSuspendedRead = false;
+ mInsertPeriodRequired = false;
+ mSuspendedReadBytesPostPeriod = 0;
+ mGenerateProgressNotifications = true;
+
+ mFilePostHelper = static_cast<nsMsgFilePostHelper*>(
+ static_cast<nsIStreamListener*>(listener));
+
+ static_cast<nsMsgFilePostHelper*>(static_cast<nsIStreamListener*>(listener))
+ ->Init(m_outputStream, this, file);
+
+ return NS_OK;
+}
+
+nsresult nsMsgAsyncWriteProtocol::SuspendPostFileRead() {
+ if (mFilePostHelper && !mFilePostHelper->mSuspendedPostFileRead) {
+ // uhoh we need to pause reading in the file until we get unblocked...
+ mFilePostHelper->mPostFileRequest->Suspend();
+ mFilePostHelper->mSuspendedPostFileRead = true;
+ }
+
+ return NS_OK;
+}
+
+nsresult nsMsgAsyncWriteProtocol::ResumePostFileRead() {
+ if (mFilePostHelper) {
+ if (mFilePostHelper->mSuspendedPostFileRead) {
+ mFilePostHelper->mPostFileRequest->Resume();
+ mFilePostHelper->mSuspendedPostFileRead = false;
+ }
+ } else // we must be done with the download so send the '.'
+ {
+ PostDataFinished();
+ }
+
+ return NS_OK;
+}
+
+nsresult nsMsgAsyncWriteProtocol::UpdateSuspendedReadBytes(
+ uint32_t aNewBytes, bool aAddToPostPeriodByteCount) {
+ // depending on our current state, we'll either add aNewBytes to
+ // mSuspendedReadBytes or mSuspendedReadBytesAfterPeriod.
+
+ mSuspendedRead = true;
+ if (aAddToPostPeriodByteCount)
+ mSuspendedReadBytesPostPeriod += aNewBytes;
+ else
+ mSuspendedReadBytes += aNewBytes;
+
+ return NS_OK;
+}
+
+nsresult nsMsgAsyncWriteProtocol::PostDataFinished() {
+ nsresult rv = SendData("." CRLF);
+ if (NS_FAILED(rv)) return rv;
+ mGenerateProgressNotifications = false;
+ mPostDataStream = nullptr;
+ return NS_OK;
+}
+
+nsresult nsMsgAsyncWriteProtocol::ProcessIncomingPostData(nsIInputStream* inStr,
+ uint32_t count) {
+ if (!m_socketIsOpen) return NS_OK; // kick out if the socket was canceled
+
+ // We need to quote any '.' that occur at the beginning of a line.
+ // but I don't want to waste time reading out the data into a buffer and
+ // searching let's try to leverage nsIBufferedInputStream and see if we can
+ // "peek" into the current contents for this particular case.
+
+ nsCOMPtr<nsISearchableInputStream> bufferInputStr = do_QueryInterface(inStr);
+ NS_ASSERTION(
+ bufferInputStr,
+ "i made a wrong assumption about the type of stream we are getting");
+ NS_ASSERTION(mSuspendedReadBytes == 0, "oops, I missed something");
+
+ if (!mPostDataStream) mPostDataStream = inStr;
+
+ if (bufferInputStr) {
+ uint32_t amountWritten;
+
+ while (count > 0) {
+ bool found = false;
+ uint32_t offset = 0;
+ bufferInputStr->Search("\012.", true, &found, &offset); // LF.
+
+ if (!found || offset > count) {
+ // push this data into the output stream
+ m_outputStream->WriteFrom(inStr, count, &amountWritten);
+ // store any remains which need read out at a later date
+ if (count > amountWritten) // stream will block
+ {
+ UpdateSuspendedReadBytes(count - amountWritten, false);
+ SuspendPostFileRead();
+ }
+ break;
+ } else {
+ // count points to the LF in a LF followed by a '.'
+ // go ahead and write up to offset..
+ m_outputStream->WriteFrom(inStr, offset + 1, &amountWritten);
+ count -= amountWritten;
+ if (offset + 1 > amountWritten) {
+ UpdateSuspendedReadBytes(offset + 1 - amountWritten, false);
+ mInsertPeriodRequired = true;
+ UpdateSuspendedReadBytes(count, mInsertPeriodRequired);
+ SuspendPostFileRead();
+ break;
+ }
+
+ // write out the extra '.'
+ m_outputStream->Write(".", 1, &amountWritten);
+ if (amountWritten != 1) {
+ mInsertPeriodRequired = true;
+ // once we do write out the '.', if we are now blocked we need to
+ // remember the remaining count that comes after the '.' so we can
+ // perform processing on that once we become unblocked.
+ UpdateSuspendedReadBytes(count, mInsertPeriodRequired);
+ SuspendPostFileRead();
+ break;
+ }
+ }
+ } // while count > 0
+ }
+
+ return NS_OK;
+}
+nsresult nsMsgAsyncWriteProtocol::UnblockPostReader() {
+ uint32_t amountWritten = 0;
+
+ if (!m_socketIsOpen) return NS_OK; // kick out if the socket was canceled
+
+ if (mSuspendedRead) {
+ // (1) attempt to write out any remaining read bytes we need in order to
+ // unblock the reader
+ if (mSuspendedReadBytes > 0 && mPostDataStream) {
+ uint64_t avail = 0;
+ mPostDataStream->Available(&avail);
+
+ m_outputStream->WriteFrom(mPostDataStream,
+ std::min(avail, uint64_t(mSuspendedReadBytes)),
+ &amountWritten);
+ // hmm sometimes my mSuspendedReadBytes is getting out of whack...so for
+ // now, reset it if necessary.
+ if (mSuspendedReadBytes > avail) mSuspendedReadBytes = avail;
+
+ if (mSuspendedReadBytes > amountWritten)
+ mSuspendedReadBytes -= amountWritten;
+ else
+ mSuspendedReadBytes = 0;
+ }
+
+ // (2) if we are now unblocked, and we need to insert a '.' then do so
+ // now...
+ if (mInsertPeriodRequired && mSuspendedReadBytes == 0) {
+ amountWritten = 0;
+ m_outputStream->Write(".", 1, &amountWritten);
+ if (amountWritten == 1) // if we succeeded then clear pending '.' flag
+ mInsertPeriodRequired = false;
+ }
+
+ // (3) if we inserted a '.' and we still have bytes after the '.' which need
+ // processed before the stream is unblocked then fake an ODA call to handle
+ // this now...
+ if (!mInsertPeriodRequired && mSuspendedReadBytesPostPeriod > 0) {
+ // these bytes actually need processed for extra '.''s.....
+ uint32_t postbytes = mSuspendedReadBytesPostPeriod;
+ mSuspendedReadBytesPostPeriod = 0;
+ ProcessIncomingPostData(mPostDataStream, postbytes);
+ }
+
+ // (4) determine if we are out of the suspended read state...
+ if (mSuspendedReadBytes == 0 && !mInsertPeriodRequired &&
+ mSuspendedReadBytesPostPeriod == 0) {
+ mSuspendedRead = false;
+ ResumePostFileRead();
+ }
+
+ } // if we are in the suspended read state
+
+ return NS_OK;
+}
+
+nsresult nsMsgAsyncWriteProtocol::SetupTransportState() {
+ nsresult rv = NS_OK;
+
+ if (!m_outputStream && m_transport) {
+ // first create a pipe which we'll use to write the data we want to send
+ // into.
+ nsCOMPtr<nsIPipe> pipe = do_CreateInstance("@mozilla.org/pipe;1");
+ rv = pipe->Init(true, true, 1024, 8);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsIAsyncInputStream* inputStream = nullptr;
+ // This always succeeds because the pipe is initialized above.
+ MOZ_ALWAYS_SUCCEEDS(pipe->GetInputStream(&inputStream));
+ mInStream = dont_AddRef(static_cast<nsIInputStream*>(inputStream));
+
+ nsIAsyncOutputStream* outputStream = nullptr;
+ // This always succeeds because the pipe is initialized above.
+ MOZ_ALWAYS_SUCCEEDS(pipe->GetOutputStream(&outputStream));
+ m_outputStream = dont_AddRef(static_cast<nsIOutputStream*>(outputStream));
+
+ mProviderThread = do_GetCurrentThread();
+
+ nsMsgProtocolStreamProvider* provider = new nsMsgProtocolStreamProvider();
+
+ if (!provider) return NS_ERROR_OUT_OF_MEMORY;
+
+ provider->Init(this, mInStream);
+ mProvider = provider; // ADDREF
+
+ nsCOMPtr<nsIOutputStream> stream;
+ rv = m_transport->OpenOutputStream(0, 0, 0, getter_AddRefs(stream));
+ if (NS_FAILED(rv)) return rv;
+
+ mAsyncOutStream = do_QueryInterface(stream, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ // wait for the output stream to become writable
+ rv = mAsyncOutStream->AsyncWait(mProvider, 0, 0, mProviderThread);
+ } // if m_transport
+
+ return rv;
+}
+
+nsresult nsMsgAsyncWriteProtocol::CloseSocket() {
+ nsresult rv = NS_OK;
+ if (mAsyncOutStream) mAsyncOutStream->CloseWithStatus(NS_BINDING_ABORTED);
+
+ nsMsgProtocol::CloseSocket();
+
+ if (mFilePostHelper) {
+ mFilePostHelper->CloseSocket();
+ mFilePostHelper = nullptr;
+ }
+
+ mAsyncOutStream = nullptr;
+ mProvider = nullptr;
+ mProviderThread = nullptr;
+ mAsyncBuffer.Truncate();
+ return rv;
+}
+
+void nsMsgAsyncWriteProtocol::UpdateProgress(uint32_t aNewBytes) {
+ if (!mGenerateProgressNotifications) return;
+
+ mNumBytesPosted += aNewBytes;
+ if (mFilePostSize > 0) {
+ nsCOMPtr<nsIMsgMailNewsUrl> mailUrl = do_QueryInterface(m_url);
+ if (!mailUrl) return;
+
+ nsCOMPtr<nsIMsgStatusFeedback> statusFeedback;
+ mailUrl->GetStatusFeedback(getter_AddRefs(statusFeedback));
+ if (!statusFeedback) return;
+
+ nsCOMPtr<nsIWebProgressListener> webProgressListener(
+ do_QueryInterface(statusFeedback));
+ if (!webProgressListener) return;
+
+ // XXX not sure if m_request is correct here
+ webProgressListener->OnProgressChange(nullptr, m_request, mNumBytesPosted,
+ static_cast<uint32_t>(mFilePostSize),
+ mNumBytesPosted, mFilePostSize);
+ }
+
+ return;
+}
+
+nsresult nsMsgAsyncWriteProtocol::SendData(const char* dataBuffer,
+ bool aSuppressLogging) {
+ this->mAsyncBuffer.Append(dataBuffer);
+ if (!mAsyncOutStream) return NS_ERROR_FAILURE;
+ return mAsyncOutStream->AsyncWait(mProvider, 0, 0, mProviderThread);
+}
+
+char16_t* FormatStringWithHostNameByName(const char16_t* stringName,
+ nsIMsgMailNewsUrl* msgUri) {
+ if (!msgUri) return nullptr;
+
+ nsresult rv;
+
+ nsCOMPtr<nsIStringBundleService> sBundleService =
+ mozilla::components::StringBundle::Service();
+ NS_ENSURE_TRUE(sBundleService, nullptr);
+
+ nsCOMPtr<nsIStringBundle> sBundle;
+ rv = sBundleService->CreateBundle(MSGS_URL, getter_AddRefs(sBundle));
+ NS_ENSURE_SUCCESS(rv, nullptr);
+
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = msgUri->GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, nullptr);
+
+ nsCString hostName;
+ rv = server->GetHostName(hostName);
+ NS_ENSURE_SUCCESS(rv, nullptr);
+
+ AutoTArray<nsString, 1> params;
+ CopyASCIItoUTF16(hostName, *params.AppendElement());
+ nsAutoString str;
+ rv = sBundle->FormatStringFromName(NS_ConvertUTF16toUTF8(stringName).get(),
+ params, str);
+ NS_ENSURE_SUCCESS(rv, nullptr);
+
+ return ToNewUnicode(str);
+}
+
+// vim: ts=2 sw=2
diff --git a/comm/mailnews/base/src/nsMsgProtocol.h b/comm/mailnews/base/src/nsMsgProtocol.h
new file mode 100644
index 0000000000..c4aa707300
--- /dev/null
+++ b/comm/mailnews/base/src/nsMsgProtocol.h
@@ -0,0 +1,263 @@
+/* -*- 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 nsMsgProtocol_h__
+#define nsMsgProtocol_h__
+
+#include "mozilla/Attributes.h"
+#include "nsIStreamListener.h"
+#include "nsIInputStream.h"
+#include "nsIOutputStream.h"
+#include "nsIChannel.h"
+#include "nsIURL.h"
+#include "nsIThread.h"
+#include "nsILoadGroup.h"
+#include "nsIFile.h"
+#include "nsCOMPtr.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIProgressEventSink.h"
+#include "nsITransport.h"
+#include "nsIAsyncOutputStream.h"
+#include "nsIAuthModule.h"
+#include "nsString.h"
+#include "nsWeakReference.h"
+#include "nsHashPropertyBag.h"
+#include "nsMailChannel.h"
+
+class nsIMsgWindow;
+class nsIPrompt;
+class nsIMsgMailNewsUrl;
+class nsMsgFilePostHelper;
+class nsIProxyInfo;
+class nsICancelable;
+
+// This is a helper class used to encapsulate code shared between all of the
+// mailnews protocol objects (imap, news, pop, smtp, etc.) In particular,
+// it unifies the core networking code for the protocols. My hope is that
+// this will make unification with Necko easier as we'll only have to change
+// this class and not all of the mailnews protocols.
+class nsMsgProtocol : public nsIStreamListener,
+ public nsIChannel,
+ public nsITransportEventSink,
+ public nsMailChannel,
+ public nsHashPropertyBag {
+ public:
+ nsMsgProtocol(nsIURI* aURL);
+
+ NS_DECL_ISUPPORTS_INHERITED
+ // nsIChannel support
+ NS_DECL_NSICHANNEL
+ NS_DECL_NSIREQUEST
+
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSITRANSPORTEVENTSINK
+
+ // LoadUrl -- A protocol typically overrides this function, sets up any local
+ // state for the url and then calls the base class which opens the socket if
+ // it needs opened. If the socket is already opened then we just call
+ // ProcessProtocolState to start the churning process. aConsumer is the
+ // consumer for the url. It can be null if this argument is not appropriate
+ virtual nsresult LoadUrl(nsIURI* aURL, nsISupports* aConsumer = nullptr);
+
+ virtual nsresult SetUrl(
+ nsIURI* aURL); // sometimes we want to set the url before we load it
+ void ShowAlertMessage(nsIMsgMailNewsUrl* aMsgUrl, nsresult aStatus);
+
+ // Flag manipulators
+ virtual bool TestFlag(uint32_t flag) { return flag & m_flags; }
+ virtual void SetFlag(uint32_t flag) { m_flags |= flag; }
+ virtual void ClearFlag(uint32_t flag) { m_flags &= ~flag; }
+
+ protected:
+ virtual ~nsMsgProtocol();
+
+ // methods for opening and closing a socket with core netlib....
+ // mscott -okay this is lame. I should break this up into a file protocol and
+ // a socket based protocool class instead of cheating and putting both methods
+ // here...
+
+ // open a connection with a specific host and port
+ // aHostName must be UTF-8 encoded.
+ virtual nsresult OpenNetworkSocketWithInfo(const char* aHostName,
+ int32_t aGetPort,
+ const char* connectionType,
+ nsIProxyInfo* aProxyInfo,
+ nsIInterfaceRequestor* callbacks);
+ // helper routine
+ nsresult GetFileFromURL(nsIURI* aURL, nsIFile** aResult);
+ virtual nsresult OpenFileSocket(
+ nsIURI* aURL, uint64_t aStartPosition,
+ int64_t aReadCount); // used to open a file socket connection
+
+ nsresult GetTopmostMsgWindow(nsIMsgWindow** aWindow);
+
+ virtual const char* GetType() { return nullptr; }
+ nsresult GetQoSBits(uint8_t* aQoSBits);
+
+ // a Protocol typically overrides this method. They free any of their own
+ // connection state and then they call up into the base class to free the
+ // generic connection objects
+ virtual nsresult CloseSocket();
+
+ virtual nsresult
+ SetupTransportState(); // private method used by OpenNetworkSocket and
+ // OpenFileSocket
+
+ // ProcessProtocolState - This is the function that gets churned by calls to
+ // OnDataAvailable. As data arrives on the socket, OnDataAvailable calls
+ // ProcessProtocolState.
+
+ virtual nsresult ProcessProtocolState(nsIURI* url,
+ nsIInputStream* inputStream,
+ uint64_t sourceOffset,
+ uint32_t length) = 0;
+
+ // SendData -- Writes the data contained in dataBuffer into the current output
+ // stream. It also informs the transport layer that this data is now available
+ // for transmission. Returns a positive number for success, 0 for failure (not
+ // all the bytes were written to the stream, etc). aSuppressLogging is a hint
+ // that sensitive data is being sent and should not be logged
+ virtual nsresult SendData(const char* dataBuffer,
+ bool aSuppressLogging = false);
+
+ virtual nsresult PostMessage(nsIURI* url, nsIFile* aPostFile);
+
+ virtual nsresult InitFromURI(nsIURI* aUrl);
+
+ nsresult DoNtlmStep1(const nsACString& username, const nsAString& password,
+ nsCString& response);
+ nsresult DoNtlmStep2(nsCString& commandResponse, nsCString& response);
+
+ nsresult DoGSSAPIStep1(const nsACString& service, const char* username,
+ nsCString& response);
+ nsresult DoGSSAPIStep2(nsCString& commandResponse, nsCString& response);
+ // Output stream for writing commands to the socket
+ nsCOMPtr<nsIOutputStream>
+ m_outputStream; // this will be obtained from the transport interface
+
+ // Output stream for writing commands to the socket
+ nsCOMPtr<nsITransport> m_transport;
+ nsCOMPtr<nsIRequest> m_request;
+ nsCOMPtr<nsICancelable> m_proxyRequest;
+
+ bool m_socketIsOpen; // mscott: we should look into keeping this state in the
+ // nsSocketTransport... I'm using it to make sure I open
+ // the socket the first time a URL is loaded into the
+ // connection
+ uint32_t m_flags; // used to store flag information
+ // uint32_t m_startPosition;
+ int64_t m_readCount;
+
+ nsCOMPtr<nsIFile>
+ m_tempMsgFile; // we currently have a hack where displaying a msg
+ // involves writing it to a temp file first
+
+ // auth module for access to NTLM functions
+ nsCOMPtr<nsIAuthModule> m_authModule;
+
+ // the following is a catch all for nsIChannel related data
+ nsCOMPtr<nsIURI> m_originalUrl; // the original url
+ nsCOMPtr<nsIURI> m_url; // the running url
+ nsCOMPtr<nsISupports> m_consumer;
+ nsCOMPtr<nsIStreamListener> m_channelListener;
+ bool m_isChannel;
+ nsCOMPtr<nsILoadGroup> m_loadGroup;
+ nsLoadFlags mLoadFlags;
+ nsCOMPtr<nsIProgressEventSink> mProgressEventSink;
+ nsCOMPtr<nsIInterfaceRequestor> mCallbacks;
+ nsCOMPtr<nsISupports> mOwner;
+ nsCString mContentType;
+ nsCString mCharset;
+ int64_t mContentLength;
+ nsCOMPtr<nsILoadInfo> m_loadInfo;
+
+ nsString m_lastPasswordSent; // used to prefill the password prompt
+
+ // if a url isn't going to result in any content then we want to suppress
+ // calls to OnStartRequest, OnDataAvailable and OnStopRequest
+ bool mSuppressListenerNotifications;
+
+ uint32_t mContentDisposition;
+};
+
+// This is is a subclass of nsMsgProtocol extends the parent class with
+// AsyncWrite support. Protocols like smtp and news want to leverage async
+// write. We don't want everyone who inherits from nsMsgProtocol to have to pick
+// up the extra overhead.
+class nsMsgAsyncWriteProtocol : public nsMsgProtocol,
+ public nsSupportsWeakReference {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+
+ NS_IMETHOD Cancel(nsresult status) override;
+
+ nsMsgAsyncWriteProtocol(nsIURI* aURL);
+
+ // temporary over ride...
+ virtual nsresult PostMessage(nsIURI* url, nsIFile* postFile) override;
+
+ // over ride the following methods from the base class
+ virtual nsresult SetupTransportState() override;
+ virtual nsresult SendData(const char* dataBuffer,
+ bool aSuppressLogging = false) override;
+ nsCString mAsyncBuffer;
+
+ // if we suspended the asynch write while waiting for more data to write then
+ // this will be TRUE
+ bool mSuspendedWrite;
+ nsCOMPtr<nsIRequest> m_WriteRequest;
+ nsCOMPtr<nsIAsyncOutputStream> mAsyncOutStream;
+ nsCOMPtr<nsIOutputStreamCallback> mProvider;
+ nsCOMPtr<nsIThread> mProviderThread;
+
+ // because we are reading the post data in asynchronously, it's possible that
+ // we aren't sending it out fast enough and the reading gets blocked. The
+ // following set of state variables are used to track this.
+ bool mSuspendedRead;
+ bool mInsertPeriodRequired; // do we need to insert a '.' as part of the
+ // unblocking process
+
+ nsresult ProcessIncomingPostData(nsIInputStream* inStr, uint32_t count);
+ nsresult UnblockPostReader();
+ nsresult UpdateSuspendedReadBytes(uint32_t aNewBytes,
+ bool aAddToPostPeriodByteCount);
+ nsresult PostDataFinished(); // this is so we'll send out a closing '.' and
+ // release any state related to the post
+
+ // these two routines are used to pause and resume our loading of the file
+ // containing the contents we are trying to post. We call these routines when
+ // we aren't sending the bits out fast enough to keep up with the file read.
+ nsresult SuspendPostFileRead();
+ nsresult ResumePostFileRead();
+ nsresult UpdateSuspendedReadBytes(uint32_t aNewBytes);
+ void UpdateProgress(uint32_t aNewBytes);
+ nsMsgFilePostHelper* mFilePostHelper; // needs to be a weak reference
+ protected:
+ virtual ~nsMsgAsyncWriteProtocol();
+
+ // the streams for the pipe used to queue up data for the async write calls to
+ // the server. we actually re-use the same mOutStream variable in our parent
+ // class for the output stream to the socket channel. So no need for a new
+ // variable here.
+ nsCOMPtr<nsIInputStream> mInStream;
+ nsCOMPtr<nsIInputStream> mPostDataStream;
+ uint32_t mSuspendedReadBytes; // remaining # of bytes we need to read before
+ // the input stream becomes unblocked
+ uint32_t mSuspendedReadBytesPostPeriod; // # of bytes which need processed
+ // after we insert a '.' before the
+ // input stream becomes unblocked.
+ int64_t
+ mFilePostSize; // used for file size, we post a single message in a file
+ uint32_t mNumBytesPosted; // used for determining progress on posting files
+ bool
+ mGenerateProgressNotifications; // set during a post operation after
+ // we've started sending the post data...
+
+ virtual nsresult CloseSocket() override;
+};
+
+#endif /* nsMsgProtocol_h__ */
diff --git a/comm/mailnews/base/src/nsMsgPurgeService.cpp b/comm/mailnews/base/src/nsMsgPurgeService.cpp
new file mode 100644
index 0000000000..0f72416c7b
--- /dev/null
+++ b/comm/mailnews/base/src/nsMsgPurgeService.cpp
@@ -0,0 +1,496 @@
+/* -*- 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 "nsMsgPurgeService.h"
+#include "nsIMsgAccountManager.h"
+#include "nsMsgUtils.h"
+#include "nsMsgSearchCore.h"
+#include "msgCore.h"
+#include "nsISpamSettings.h"
+#include "nsIMsgSearchTerm.h"
+#include "nsIMsgHdr.h"
+#include "nsIMsgProtocolInfo.h"
+#include "nsIMsgFilterPlugin.h"
+#include "nsIPrefBranch.h"
+#include "nsIPrefService.h"
+#include "mozilla/Logging.h"
+#include "nsMsgFolderFlags.h"
+#include "nsITimer.h"
+#include <stdlib.h>
+#include "nsComponentManagerUtils.h"
+#include "nsServiceManagerUtils.h"
+
+static mozilla::LazyLogModule MsgPurgeLogModule("MsgPurge");
+
+NS_IMPL_ISUPPORTS(nsMsgPurgeService, nsIMsgPurgeService, nsIMsgSearchNotify)
+
+void OnPurgeTimer(nsITimer* timer, void* aPurgeService) {
+ nsMsgPurgeService* purgeService = (nsMsgPurgeService*)aPurgeService;
+ purgeService->PerformPurge();
+}
+
+nsMsgPurgeService::nsMsgPurgeService() {
+ mHaveShutdown = false;
+ // never purge a folder more than once every 8 hours (60 min/hour * 8 hours.
+ mMinDelayBetweenPurges = 480;
+ // fire the purge timer every 5 minutes, starting 5 minutes after the service
+ // is created (when we load accounts).
+ mPurgeTimerInterval = 5;
+}
+
+nsMsgPurgeService::~nsMsgPurgeService() {
+ if (mPurgeTimer) mPurgeTimer->Cancel();
+
+ if (!mHaveShutdown) Shutdown();
+}
+
+NS_IMETHODIMP nsMsgPurgeService::Init() {
+ nsresult rv;
+
+ // these prefs are here to help QA test this feature
+ nsCOMPtr<nsIPrefBranch> prefBranch(
+ do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ if (NS_SUCCEEDED(rv)) {
+ int32_t min_delay;
+ rv = prefBranch->GetIntPref("mail.purge.min_delay", &min_delay);
+ if (NS_SUCCEEDED(rv) && min_delay) mMinDelayBetweenPurges = min_delay;
+
+ int32_t purge_timer_interval;
+ rv = prefBranch->GetIntPref("mail.purge.timer_interval",
+ &purge_timer_interval);
+ if (NS_SUCCEEDED(rv) && purge_timer_interval)
+ mPurgeTimerInterval = purge_timer_interval;
+ }
+
+ MOZ_LOG(MsgPurgeLogModule, mozilla::LogLevel::Info,
+ ("mail.purge.min_delay=%d minutes", mMinDelayBetweenPurges));
+ MOZ_LOG(MsgPurgeLogModule, mozilla::LogLevel::Info,
+ ("mail.purge.timer_interval=%d minutes", mPurgeTimerInterval));
+
+ // don't start purging right away.
+ // because the accounts aren't loaded and because the user might be trying to
+ // sign in or startup, etc.
+ SetupNextPurge();
+
+ mHaveShutdown = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgPurgeService::Shutdown() {
+ if (mPurgeTimer) {
+ mPurgeTimer->Cancel();
+ mPurgeTimer = nullptr;
+ }
+
+ mHaveShutdown = true;
+ return NS_OK;
+}
+
+nsresult nsMsgPurgeService::SetupNextPurge() {
+ MOZ_LOG(MsgPurgeLogModule, mozilla::LogLevel::Info,
+ ("setting to check again in %d minutes", mPurgeTimerInterval));
+
+ // Convert mPurgeTimerInterval into milliseconds
+ uint32_t timeInMSUint32 = mPurgeTimerInterval * 60000;
+
+ // Can't currently reset a timer when it's in the process of
+ // calling Notify. So, just release the timer here and create a new one.
+ if (mPurgeTimer) mPurgeTimer->Cancel();
+
+ nsresult rv = NS_NewTimerWithFuncCallback(
+ getter_AddRefs(mPurgeTimer), OnPurgeTimer, (void*)this, timeInMSUint32,
+ nsITimer::TYPE_ONE_SHOT, "nsMsgPurgeService::OnPurgeTimer", nullptr);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Could not start mPurgeTimer timer");
+ }
+
+ return NS_OK;
+}
+
+// This is the function that looks for the first folder to purge. It also
+// applies retention settings to any folder that hasn't had retention settings
+// applied in mMinDelayBetweenPurges minutes (default, 8 hours).
+// However, if we've spent more than .5 seconds in this loop, don't
+// apply any more retention settings because it might lock up the UI.
+// This might starve folders later on in the hierarchy, since we always
+// start at the top, but since we also apply retention settings when you
+// open a folder, or when you compact all folders, I think this will do
+// for now, until we have a cleanup on shutdown architecture.
+nsresult nsMsgPurgeService::PerformPurge() {
+ MOZ_LOG(MsgPurgeLogModule, mozilla::LogLevel::Info, ("performing purge"));
+
+ nsresult rv;
+
+ nsCOMPtr<nsIMsgAccountManager> accountManager =
+ do_GetService("@mozilla.org/messenger/account-manager;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ bool keepApplyingRetentionSettings = true;
+
+ nsTArray<RefPtr<nsIMsgIncomingServer>> allServers;
+ rv = accountManager->GetAllServers(allServers);
+ if (NS_SUCCEEDED(rv)) {
+ MOZ_LOG(MsgPurgeLogModule, mozilla::LogLevel::Info,
+ ("%d servers", (int)allServers.Length()));
+ nsCOMPtr<nsIMsgFolder> folderToPurge;
+ PRIntervalTime startTime = PR_IntervalNow();
+ int32_t purgeIntervalToUse = 0;
+ PRTime oldestPurgeTime =
+ 0; // we're going to pick the least-recently purged folder
+
+ // apply retention settings to folders that haven't had retention settings
+ // applied in mMinDelayBetweenPurges minutes (default 8 hours)
+ // Because we get last purge time from the folder cache,
+ // this code won't open db's for folders until it decides it needs
+ // to apply retention settings, and since
+ // nsIMsgFolder::ApplyRetentionSettings will close any db's it opens, this
+ // code won't leave db's open.
+ for (uint32_t serverIndex = 0; serverIndex < allServers.Length();
+ serverIndex++) {
+ nsCOMPtr<nsIMsgIncomingServer> server(allServers[serverIndex]);
+ if (server) {
+ if (keepApplyingRetentionSettings) {
+ nsCOMPtr<nsIMsgFolder> rootFolder;
+ rv = server->GetRootFolder(getter_AddRefs(rootFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsTArray<RefPtr<nsIMsgFolder>> childFolders;
+ rv = rootFolder->GetDescendants(childFolders);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (auto childFolder : childFolders) {
+ uint32_t folderFlags;
+ (void)childFolder->GetFlags(&folderFlags);
+ if (folderFlags & nsMsgFolderFlags::Virtual) continue;
+ PRTime curFolderLastPurgeTime = 0;
+ nsCString curFolderLastPurgeTimeString, curFolderUri;
+ rv = childFolder->GetStringProperty("LastPurgeTime",
+ curFolderLastPurgeTimeString);
+ if (NS_FAILED(rv))
+ continue; // it is ok to fail, go on to next folder
+
+ if (!curFolderLastPurgeTimeString.IsEmpty()) {
+ PRTime theTime;
+ PR_ParseTimeString(curFolderLastPurgeTimeString.get(), false,
+ &theTime);
+ curFolderLastPurgeTime = theTime;
+ }
+
+ childFolder->GetURI(curFolderUri);
+ MOZ_LOG(MsgPurgeLogModule, mozilla::LogLevel::Info,
+ ("%s curFolderLastPurgeTime=%s (if blank, then never)",
+ curFolderUri.get(), curFolderLastPurgeTimeString.get()));
+
+ // check if this folder is due to purge
+ // has to have been purged at least mMinDelayBetweenPurges minutes
+ // ago we don't want to purge the folders all the time - once a
+ // day is good enough
+ int64_t minDelayBetweenPurges(mMinDelayBetweenPurges);
+ int64_t microSecondsPerMinute(60000000);
+ PRTime nextPurgeTime =
+ curFolderLastPurgeTime +
+ (minDelayBetweenPurges * microSecondsPerMinute);
+ if (nextPurgeTime < PR_Now()) {
+ MOZ_LOG(MsgPurgeLogModule, mozilla::LogLevel::Info,
+ ("purging %s", curFolderUri.get()));
+ childFolder->ApplyRetentionSettings();
+ }
+ PRIntervalTime elapsedTime = PR_IntervalNow() - startTime;
+ // check if more than 500 milliseconds have elapsed in this purge
+ // process
+ if (PR_IntervalToMilliseconds(elapsedTime) > 500) {
+ keepApplyingRetentionSettings = false;
+ break;
+ }
+ }
+ }
+ nsCString type;
+ nsresult rv = server->GetType(type);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCString hostName;
+ server->GetHostName(hostName);
+ MOZ_LOG(MsgPurgeLogModule, mozilla::LogLevel::Info,
+ ("[%d] %s (%s)", serverIndex, hostName.get(), type.get()));
+
+ nsCOMPtr<nsISpamSettings> spamSettings;
+ rv = server->GetSpamSettings(getter_AddRefs(spamSettings));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t spamLevel;
+ spamSettings->GetLevel(&spamLevel);
+ // clang-format off
+ MOZ_LOG(MsgPurgeLogModule, mozilla::LogLevel::Info,
+ ("[%d] spamLevel=%d (if 0, don't purge)", serverIndex, spamLevel));
+ // clang-format on
+ if (!spamLevel) continue;
+
+ // check if we are set up to purge for this server
+ // if not, skip it.
+ bool purgeSpam;
+ spamSettings->GetPurge(&purgeSpam);
+
+ MOZ_LOG(MsgPurgeLogModule, mozilla::LogLevel::Info,
+ ("[%d] purgeSpam=%s (if false, don't purge)", serverIndex,
+ purgeSpam ? "true" : "false"));
+ if (!purgeSpam) continue;
+
+ // check if the spam folder uri is set for this server
+ // if not skip it.
+ nsCString junkFolderURI;
+ rv = spamSettings->GetSpamFolderURI(junkFolderURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ MOZ_LOG(MsgPurgeLogModule, mozilla::LogLevel::Info,
+ ("[%d] junkFolderURI=%s (if empty, don't purge)", serverIndex,
+ junkFolderURI.get()));
+ if (junkFolderURI.IsEmpty()) continue;
+
+ // if the junk folder doesn't exist
+ // because the folder pane isn't built yet, for example
+ // skip this account
+ nsCOMPtr<nsIMsgFolder> junkFolder;
+ rv = FindFolder(junkFolderURI, getter_AddRefs(junkFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // clang-format off
+ MOZ_LOG(MsgPurgeLogModule, mozilla::LogLevel::Info,
+ ("[%d] %s exists? %s (if doesn't exist, don't purge)", serverIndex,
+ junkFolderURI.get(), junkFolder ? "true" : "false"));
+ // clang-format on
+ if (!junkFolder) continue;
+
+ PRTime curJunkFolderLastPurgeTime = 0;
+ nsCString curJunkFolderLastPurgeTimeString;
+ rv = junkFolder->GetStringProperty("curJunkFolderLastPurgeTime",
+ curJunkFolderLastPurgeTimeString);
+ if (NS_FAILED(rv))
+ continue; // it is ok to fail, junk folder may not exist
+
+ if (!curJunkFolderLastPurgeTimeString.IsEmpty()) {
+ PRTime theTime;
+ PR_ParseTimeString(curJunkFolderLastPurgeTimeString.get(), false,
+ &theTime);
+ curJunkFolderLastPurgeTime = theTime;
+ }
+
+ MOZ_LOG(MsgPurgeLogModule, mozilla::LogLevel::Info,
+ ("[%d] %s curJunkFolderLastPurgeTime=%s (if blank, then never)",
+ serverIndex, junkFolderURI.get(),
+ curJunkFolderLastPurgeTimeString.get()));
+
+ // check if this account is due to purge
+ // has to have been purged at least mMinDelayBetweenPurges minutes ago
+ // we don't want to purge the folders all the time
+ PRTime nextPurgeTime =
+ curJunkFolderLastPurgeTime +
+ mMinDelayBetweenPurges * 60000000 // convert to microseconds.
+ ;
+ if (nextPurgeTime < PR_Now()) {
+ MOZ_LOG(MsgPurgeLogModule, mozilla::LogLevel::Info,
+ ("[%d] last purge greater than min delay", serverIndex));
+
+ nsCOMPtr<nsIMsgIncomingServer> junkFolderServer;
+ rv = junkFolder->GetServer(getter_AddRefs(junkFolderServer));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool serverBusy = false;
+ bool serverRequiresPassword = true;
+ bool passwordPromptRequired;
+ bool canSearchMessages = false;
+ junkFolderServer->GetPasswordPromptRequired(&passwordPromptRequired);
+ junkFolderServer->GetServerBusy(&serverBusy);
+ junkFolderServer->GetServerRequiresPasswordForBiff(
+ &serverRequiresPassword);
+ junkFolderServer->GetCanSearchMessages(&canSearchMessages);
+ // Make sure we're logged on before doing the search (assuming we need
+ // to be) and make sure the server isn't already in the middle of
+ // downloading new messages and make sure a search isn't already going
+ // on
+ MOZ_LOG(MsgPurgeLogModule, mozilla::LogLevel::Info,
+ ("[%d] (search in progress? %s)", serverIndex,
+ mSearchSession ? "true" : "false"));
+ MOZ_LOG(MsgPurgeLogModule, mozilla::LogLevel::Info,
+ ("[%d] (server busy? %s)", serverIndex,
+ serverBusy ? "true" : "false"));
+ MOZ_LOG(MsgPurgeLogModule, mozilla::LogLevel::Info,
+ ("[%d] (serverRequiresPassword? %s)", serverIndex,
+ serverRequiresPassword ? "true" : "false"));
+ MOZ_LOG(MsgPurgeLogModule, mozilla::LogLevel::Info,
+ ("[%d] (passwordPromptRequired? %s)", serverIndex,
+ passwordPromptRequired ? "true" : "false"));
+ if (canSearchMessages && !mSearchSession && !serverBusy &&
+ (!serverRequiresPassword || !passwordPromptRequired)) {
+ int32_t purgeInterval;
+ spamSettings->GetPurgeInterval(&purgeInterval);
+
+ if ((oldestPurgeTime == 0) ||
+ (curJunkFolderLastPurgeTime < oldestPurgeTime)) {
+ // clang-format off
+ MOZ_LOG(MsgPurgeLogModule, mozilla::LogLevel::Info,
+ ("[%d] purging! searching for messages older than %d days",
+ serverIndex, purgeInterval));
+ // clang-format on
+ oldestPurgeTime = curJunkFolderLastPurgeTime;
+ purgeIntervalToUse = purgeInterval;
+ folderToPurge = junkFolder;
+ // if we've never purged this folder, do it...
+ if (curJunkFolderLastPurgeTime == 0) break;
+ }
+ } else {
+ NS_ASSERTION(canSearchMessages,
+ "unexpected, you should be able to search");
+ MOZ_LOG(MsgPurgeLogModule, mozilla::LogLevel::Info,
+ ("[%d] not a good time for this server, try again later",
+ serverIndex));
+ }
+ } else {
+ MOZ_LOG(MsgPurgeLogModule, mozilla::LogLevel::Info,
+ ("[%d] last purge too recent", serverIndex));
+ }
+ }
+ }
+ if (folderToPurge && purgeIntervalToUse != 0)
+ rv = SearchFolderToPurge(folderToPurge, purgeIntervalToUse);
+ }
+
+ // set up timer to check accounts again
+ SetupNextPurge();
+ return rv;
+}
+
+nsresult nsMsgPurgeService::SearchFolderToPurge(nsIMsgFolder* folder,
+ int32_t purgeInterval) {
+ nsresult rv;
+ mSearchSession =
+ do_CreateInstance("@mozilla.org/messenger/searchSession;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ mSearchSession->RegisterListener(this, nsIMsgSearchSession::allNotifications);
+
+ // update the time we attempted to purge this folder
+ char dateBuf[100];
+ dateBuf[0] = '\0';
+ PRExplodedTime exploded;
+ PR_ExplodeTime(PR_Now(), PR_LocalTimeParameters, &exploded);
+ PR_FormatTimeUSEnglish(dateBuf, sizeof(dateBuf), "%a %b %d %H:%M:%S %Y",
+ &exploded);
+ folder->SetStringProperty("curJunkFolderLastPurgeTime",
+ nsDependentCString(dateBuf));
+ MOZ_LOG(MsgPurgeLogModule, mozilla::LogLevel::Info,
+ ("curJunkFolderLastPurgeTime is now %s", dateBuf));
+
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ // We need to get the folder's server scope because imap can have
+ // local junk folder.
+ rv = folder->GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsMsgSearchScopeValue searchScope;
+ server->GetSearchScope(&searchScope);
+
+ mSearchSession->AddScopeTerm(searchScope, folder);
+
+ // look for messages older than the cutoff
+ // you can't also search by junk status, see
+ // nsMsgPurgeService::OnSearchHit()
+ nsCOMPtr<nsIMsgSearchTerm> searchTerm;
+ mSearchSession->CreateTerm(getter_AddRefs(searchTerm));
+ if (searchTerm) {
+ searchTerm->SetAttrib(nsMsgSearchAttrib::AgeInDays);
+ searchTerm->SetOp(nsMsgSearchOp::IsGreaterThan);
+ nsCOMPtr<nsIMsgSearchValue> searchValue;
+ searchTerm->GetValue(getter_AddRefs(searchValue));
+ if (searchValue) {
+ searchValue->SetAttrib(nsMsgSearchAttrib::AgeInDays);
+ searchValue->SetAge((uint32_t)purgeInterval);
+ searchTerm->SetValue(searchValue);
+ }
+ searchTerm->SetBooleanAnd(false);
+ mSearchSession->AppendTerm(searchTerm);
+ }
+
+ // we are about to search
+ // create mHdrsToDelete array (if not previously created)
+ NS_ASSERTION(mHdrsToDelete.IsEmpty(), "mHdrsToDelete is not empty");
+
+ mSearchFolder = folder;
+ return mSearchSession->Search(nullptr);
+}
+
+NS_IMETHODIMP nsMsgPurgeService::OnNewSearch() {
+ MOZ_LOG(MsgPurgeLogModule, mozilla::LogLevel::Info, ("on new search"));
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgPurgeService::OnSearchHit(nsIMsgDBHdr* aMsgHdr,
+ nsIMsgFolder* aFolder) {
+ NS_ENSURE_ARG_POINTER(aMsgHdr);
+
+ nsCString messageId;
+ nsCString author;
+ nsCString subject;
+
+ aMsgHdr->GetMessageId(getter_Copies(messageId));
+ MOZ_LOG(MsgPurgeLogModule, mozilla::LogLevel::Info,
+ ("messageId=%s", messageId.get()));
+ aMsgHdr->GetSubject(subject);
+ MOZ_LOG(MsgPurgeLogModule, mozilla::LogLevel::Info,
+ ("subject=%s", subject.get()));
+ aMsgHdr->GetAuthor(getter_Copies(author));
+ MOZ_LOG(MsgPurgeLogModule, mozilla::LogLevel::Info,
+ ("author=%s", author.get()));
+
+ // double check that the message is junk before adding to
+ // the list of messages to delete
+ //
+ // note, we can't just search for messages that are junk
+ // because not all imap server support keywords
+ // (which we use for the junk score)
+ // so the junk status would be in the message db.
+ //
+ // see bug #194090
+ nsCString junkScoreStr;
+ nsresult rv = aMsgHdr->GetStringProperty("junkscore", junkScoreStr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ MOZ_LOG(MsgPurgeLogModule, mozilla::LogLevel::Info,
+ ("junkScore=%s (if empty or != nsIJunkMailPlugin::IS_SPAM_SCORE, "
+ "don't add to list delete)",
+ junkScoreStr.get()));
+
+ // if "junkscore" is not set, don't delete the message
+ if (junkScoreStr.IsEmpty()) return NS_OK;
+
+ if (atoi(junkScoreStr.get()) == nsIJunkMailPlugin::IS_SPAM_SCORE) {
+ MOZ_LOG(MsgPurgeLogModule, mozilla::LogLevel::Info,
+ ("added message to delete"));
+ mHdrsToDelete.AppendElement(aMsgHdr);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgPurgeService::OnSearchDone(nsresult status) {
+ if (NS_SUCCEEDED(status)) {
+ uint32_t count = mHdrsToDelete.Length();
+ MOZ_LOG(MsgPurgeLogModule, mozilla::LogLevel::Info,
+ ("%d messages to delete", count));
+
+ if (count > 0) {
+ MOZ_LOG(MsgPurgeLogModule, mozilla::LogLevel::Info, ("delete messages"));
+ if (mSearchFolder)
+ mSearchFolder->DeleteMessages(
+ mHdrsToDelete, nullptr, false /*delete storage*/, false /*isMove*/,
+ nullptr, false /*allowUndo*/);
+ }
+ }
+ mHdrsToDelete.Clear();
+ if (mSearchSession) mSearchSession->UnregisterListener(this);
+ // don't cache the session
+ // just create another search session next time we search, rather than
+ // clearing scopes, terms etc. we also use mSearchSession to determine if the
+ // purge service is "busy"
+ mSearchSession = nullptr;
+ mSearchFolder = nullptr;
+ return NS_OK;
+}
diff --git a/comm/mailnews/base/src/nsMsgPurgeService.h b/comm/mailnews/base/src/nsMsgPurgeService.h
new file mode 100644
index 0000000000..30643624d7
--- /dev/null
+++ b/comm/mailnews/base/src/nsMsgPurgeService.h
@@ -0,0 +1,51 @@
+/* -*- 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 NSMSGPURGESERVICE_H
+#define NSMSGPURGESERVICE_H
+
+#include "msgCore.h"
+#include "nsIMsgPurgeService.h"
+#include "nsIMsgSearchSession.h"
+#include "nsITimer.h"
+#include "nsCOMPtr.h"
+#include "nsIMsgSearchNotify.h"
+#include "nsIMsgFolder.h"
+#include "nsIMsgFolderCache.h"
+#include "nsIMsgFolderCacheElement.h"
+
+class nsMsgPurgeService : public nsIMsgPurgeService, public nsIMsgSearchNotify {
+ public:
+ nsMsgPurgeService();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMSGPURGESERVICE
+ NS_DECL_NSIMSGSEARCHNOTIFY
+
+ nsresult PerformPurge();
+
+ protected:
+ virtual ~nsMsgPurgeService();
+ int32_t FindServer(nsIMsgIncomingServer* server);
+ nsresult SetupNextPurge();
+ nsresult PurgeSurver(nsIMsgIncomingServer* server);
+ nsresult SearchFolderToPurge(nsIMsgFolder* folder, int32_t purgeInterval);
+
+ protected:
+ nsCOMPtr<nsITimer> mPurgeTimer;
+ nsCOMPtr<nsIMsgSearchSession> mSearchSession;
+ nsCOMPtr<nsIMsgFolder> mSearchFolder;
+ nsTArray<RefPtr<nsIMsgDBHdr>> mHdrsToDelete;
+ bool mHaveShutdown;
+
+ private:
+ // in minutes, how long must pass between two consecutive purges on the
+ // same junk folder?
+ int32_t mMinDelayBetweenPurges;
+ // in minutes, how often to check if we need to purge one of the junk folders?
+ int32_t mPurgeTimerInterval;
+};
+
+#endif
diff --git a/comm/mailnews/base/src/nsMsgQuickSearchDBView.cpp b/comm/mailnews/base/src/nsMsgQuickSearchDBView.cpp
new file mode 100644
index 0000000000..97d412acc4
--- /dev/null
+++ b/comm/mailnews/base/src/nsMsgQuickSearchDBView.cpp
@@ -0,0 +1,806 @@
+/* -*- 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 "msgCore.h"
+#include "nsMsgQuickSearchDBView.h"
+#include "nsMsgFolderFlags.h"
+#include "nsIMsgHdr.h"
+#include "nsIMsgImapMailFolder.h"
+#include "nsImapCore.h"
+#include "nsIMsgHdr.h"
+#include "nsIDBFolderInfo.h"
+#include "nsMsgMessageFlags.h"
+#include "nsMsgUtils.h"
+
+nsMsgQuickSearchDBView::nsMsgQuickSearchDBView() {
+ m_usingCachedHits = false;
+ m_cacheEmpty = true;
+}
+
+nsMsgQuickSearchDBView::~nsMsgQuickSearchDBView() {}
+
+NS_IMPL_ISUPPORTS_INHERITED(nsMsgQuickSearchDBView, nsMsgDBView, nsIMsgDBView,
+ nsIMsgSearchNotify)
+
+NS_IMETHODIMP nsMsgQuickSearchDBView::Open(nsIMsgFolder* folder,
+ nsMsgViewSortTypeValue sortType,
+ nsMsgViewSortOrderValue sortOrder,
+ nsMsgViewFlagsTypeValue viewFlags,
+ int32_t* pCount) {
+ nsresult rv =
+ nsMsgDBView::Open(folder, sortType, sortOrder, viewFlags, pCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!m_db) return NS_ERROR_NULL_POINTER;
+ m_viewFolder = nullptr;
+
+ int32_t count;
+ rv = InitThreadedView(count);
+ if (pCount) *pCount = count;
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgQuickSearchDBView::CloneDBView(nsIMessenger* aMessengerInstance,
+ nsIMsgWindow* aMsgWindow,
+ nsIMsgDBViewCommandUpdater* aCmdUpdater,
+ nsIMsgDBView** _retval) {
+ nsMsgQuickSearchDBView* newMsgDBView = new nsMsgQuickSearchDBView();
+ nsresult rv =
+ CopyDBView(newMsgDBView, aMessengerInstance, aMsgWindow, aCmdUpdater);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_IF_ADDREF(*_retval = newMsgDBView);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgQuickSearchDBView::CopyDBView(nsMsgDBView* aNewMsgDBView,
+ nsIMessenger* aMessengerInstance,
+ nsIMsgWindow* aMsgWindow,
+ nsIMsgDBViewCommandUpdater* aCmdUpdater) {
+ nsMsgThreadedDBView::CopyDBView(aNewMsgDBView, aMessengerInstance, aMsgWindow,
+ aCmdUpdater);
+ nsMsgQuickSearchDBView* newMsgDBView = (nsMsgQuickSearchDBView*)aNewMsgDBView;
+
+ // now copy all of our private member data
+ newMsgDBView->m_origKeys = m_origKeys.Clone();
+ return NS_OK;
+}
+
+nsresult nsMsgQuickSearchDBView::DeleteMessages(
+ nsIMsgWindow* window, nsTArray<nsMsgViewIndex> const& selection,
+ bool deleteStorage) {
+ for (nsMsgViewIndex viewIndex : selection) {
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ (void)GetMsgHdrForViewIndex(viewIndex, getter_AddRefs(msgHdr));
+ if (msgHdr) {
+ RememberDeletedMsgHdr(msgHdr);
+ }
+ }
+ return nsMsgDBView::DeleteMessages(window, selection, deleteStorage);
+}
+
+NS_IMETHODIMP nsMsgQuickSearchDBView::DoCommand(
+ nsMsgViewCommandTypeValue aCommand) {
+ if (aCommand == nsMsgViewCommandType::markAllRead) {
+ nsresult rv = NS_OK;
+ m_folder->EnableNotifications(nsIMsgFolder::allMessageCountNotifications,
+ false);
+
+ for (uint32_t i = 0; NS_SUCCEEDED(rv) && i < GetSize(); i++) {
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ m_db->GetMsgHdrForKey(m_keys[i], getter_AddRefs(msgHdr));
+ rv = m_db->MarkHdrRead(msgHdr, true, nullptr);
+ }
+
+ m_folder->EnableNotifications(nsIMsgFolder::allMessageCountNotifications,
+ true);
+
+ nsCOMPtr<nsIMsgImapMailFolder> imapFolder = do_QueryInterface(m_folder);
+ if (NS_SUCCEEDED(rv) && imapFolder)
+ rv = imapFolder->StoreImapFlags(kImapMsgSeenFlag, true, m_keys, nullptr);
+
+ m_db->SetSummaryValid(true);
+ return rv;
+ } else
+ return nsMsgDBView::DoCommand(aCommand);
+}
+
+NS_IMETHODIMP nsMsgQuickSearchDBView::GetViewType(
+ nsMsgViewTypeValue* aViewType) {
+ NS_ENSURE_ARG_POINTER(aViewType);
+ *aViewType = nsMsgViewType::eShowQuickSearchResults;
+ return NS_OK;
+}
+
+nsresult nsMsgQuickSearchDBView::AddHdr(nsIMsgDBHdr* msgHdr,
+ nsMsgViewIndex* resultIndex) {
+ nsMsgKey msgKey;
+ msgHdr->GetMessageKey(&msgKey);
+ // protect against duplication.
+ if (m_origKeys.BinaryIndexOf(msgKey) == m_origKeys.NoIndex) {
+ nsMsgViewIndex insertIndex = GetInsertIndexHelper(
+ msgHdr, m_origKeys, nullptr, nsMsgViewSortOrder::ascending,
+ nsMsgViewSortType::byId);
+ m_origKeys.InsertElementAt(insertIndex, msgKey);
+ }
+ if (m_viewFlags & (nsMsgViewFlagsType::kGroupBySort |
+ nsMsgViewFlagsType::kThreadedDisplay)) {
+ nsMsgKey parentKey;
+ msgHdr->GetThreadParent(&parentKey);
+ return nsMsgThreadedDBView::OnNewHeader(msgHdr, parentKey, true);
+ } else
+ return nsMsgDBView::AddHdr(msgHdr, resultIndex);
+}
+
+nsresult nsMsgQuickSearchDBView::OnNewHeader(nsIMsgDBHdr* newHdr,
+ nsMsgKey aParentKey,
+ bool ensureListed) {
+ if (newHdr) {
+ bool match = false;
+ nsCOMPtr<nsIMsgSearchSession> searchSession =
+ do_QueryReferent(m_searchSession);
+ if (searchSession) searchSession->MatchHdr(newHdr, m_db, &match);
+ if (match) {
+ // put the new header in m_origKeys, so that expanding a thread will
+ // show the newly added header.
+ nsMsgKey newKey;
+ (void)newHdr->GetMessageKey(&newKey);
+ nsMsgViewIndex insertIndex = GetInsertIndexHelper(
+ newHdr, m_origKeys, nullptr, nsMsgViewSortOrder::ascending,
+ nsMsgViewSortType::byId);
+ m_origKeys.InsertElementAt(insertIndex, newKey);
+ nsMsgThreadedDBView::OnNewHeader(
+ newHdr, aParentKey,
+ ensureListed); // do not add a new message if there isn't a match.
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgQuickSearchDBView::OnHdrFlagsChanged(
+ nsIMsgDBHdr* aHdrChanged, uint32_t aOldFlags, uint32_t aNewFlags,
+ nsIDBChangeListener* aInstigator) {
+ nsresult rv = nsMsgGroupView::OnHdrFlagsChanged(aHdrChanged, aOldFlags,
+ aNewFlags, aInstigator);
+
+ if (m_viewFolder && (m_viewFolder != m_folder) &&
+ (aOldFlags & nsMsgMessageFlags::Read) !=
+ (aNewFlags & nsMsgMessageFlags::Read)) {
+ // if we're displaying a single folder virtual folder for an imap folder,
+ // the search criteria might be on message body, and we might not have the
+ // message body offline, in which case we can't tell if the message
+ // matched or not. But if the unread flag changed, we need to update the
+ // unread counts. Normally, VirtualFolderChangeListener::OnHdrFlagsChanged
+ // will handle this, but it won't work for body criteria when we don't have
+ // the body offline.
+ nsCOMPtr<nsIMsgImapMailFolder> imapFolder = do_QueryInterface(m_viewFolder);
+ if (imapFolder) {
+ nsMsgViewIndex hdrIndex = FindHdr(aHdrChanged);
+ if (hdrIndex != nsMsgViewIndex_None) {
+ nsCOMPtr<nsIMsgSearchSession> searchSession =
+ do_QueryReferent(m_searchSession);
+ if (searchSession) {
+ bool oldMatch, newMatch;
+ rv = searchSession->MatchHdr(aHdrChanged, m_db, &newMatch);
+ aHdrChanged->SetFlags(aOldFlags);
+ rv = searchSession->MatchHdr(aHdrChanged, m_db, &oldMatch);
+ aHdrChanged->SetFlags(aNewFlags);
+ // if it doesn't match the criteria,
+ // VirtualFolderChangeListener::OnHdrFlagsChanged won't tweak the
+ // read/unread counts. So do it here:
+ if (!oldMatch && !newMatch) {
+ nsCOMPtr<nsIMsgDatabase> virtDatabase;
+ nsCOMPtr<nsIDBFolderInfo> dbFolderInfo;
+
+ rv = m_viewFolder->GetDBFolderInfoAndDB(
+ getter_AddRefs(dbFolderInfo), getter_AddRefs(virtDatabase));
+ NS_ENSURE_SUCCESS(rv, rv);
+ dbFolderInfo->ChangeNumUnreadMessages(
+ (aOldFlags & nsMsgMessageFlags::Read) ? 1 : -1);
+ m_viewFolder->UpdateSummaryTotals(true); // force update from db.
+ virtDatabase->Commit(nsMsgDBCommitType::kLargeCommit);
+ }
+ }
+ }
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgQuickSearchDBView::OnHdrPropertyChanged(nsIMsgDBHdr* aHdrChanged,
+ const nsACString& property,
+ bool aPreChange, uint32_t* aStatus,
+ nsIDBChangeListener* aInstigator) {
+ // If the junk mail plugin just activated on a message, then
+ // we'll allow filters to remove from view.
+ // Otherwise, just update the view line.
+ //
+ // Note this will not add newly matched headers to the view. This is
+ // probably a bug that needs fixing.
+
+ NS_ENSURE_ARG_POINTER(aStatus);
+ NS_ENSURE_ARG_POINTER(aHdrChanged);
+
+ nsMsgViewIndex index = FindHdr(aHdrChanged);
+ if (index == nsMsgViewIndex_None) // message does not appear in view
+ return NS_OK;
+
+ nsCString originStr;
+ (void)aHdrChanged->GetStringProperty("junkscoreorigin", originStr);
+ // check for "plugin" with only first character for performance
+ bool plugin = (originStr.get()[0] == 'p');
+
+ if (aPreChange) {
+ // first call, done prior to the change
+ *aStatus = plugin;
+ return NS_OK;
+ }
+
+ // second call, done after the change
+ bool wasPlugin = *aStatus;
+
+ bool match = true;
+ nsCOMPtr<nsIMsgSearchSession> searchSession(
+ do_QueryReferent(m_searchSession));
+ if (searchSession) searchSession->MatchHdr(aHdrChanged, m_db, &match);
+
+ if (!match && plugin && !wasPlugin)
+ RemoveByIndex(index); // remove hdr from view
+ else
+ NoteChange(index, 1, nsMsgViewNotificationCode::changed);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgQuickSearchDBView::GetSearchSession(nsIMsgSearchSession** aSession) {
+ NS_ASSERTION(false, "GetSearchSession method is not implemented");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsMsgQuickSearchDBView::SetSearchSession(nsIMsgSearchSession* aSession) {
+ m_searchSession = do_GetWeakReference(aSession);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgQuickSearchDBView::OnSearchHit(nsIMsgDBHdr* aMsgHdr,
+ nsIMsgFolder* folder) {
+ NS_ENSURE_ARG(aMsgHdr);
+ if (!m_db) return NS_ERROR_NULL_POINTER;
+ // remember search hit and when search is done, reconcile cache
+ // with new hits;
+ m_hdrHits.AppendObject(aMsgHdr);
+ nsMsgKey key;
+ aMsgHdr->GetMessageKey(&key);
+ // Is FindKey going to be expensive here? A lot of hits could make
+ // it a little bit slow to search through the view for every hit.
+ if (m_cacheEmpty || FindKey(key, false) == nsMsgViewIndex_None)
+ return AddHdr(aMsgHdr);
+ else
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgQuickSearchDBView::OnSearchDone(nsresult status) {
+ // This batch began in OnNewSearch.
+ if (mJSTree) mJSTree->EndUpdateBatch();
+ // We're a single-folder virtual folder if viewFolder != folder, and that is
+ // the only case in which we want to be messing about with a results cache
+ // or unread counts.
+ if (m_db && m_viewFolder && m_viewFolder != m_folder) {
+ nsTArray<nsMsgKey> keyArray;
+ nsCString searchUri;
+ m_viewFolder->GetURI(searchUri);
+ uint32_t count = m_hdrHits.Count();
+ // Build up message keys. The cache expects them to be sorted.
+ for (uint32_t i = 0; i < count; i++) {
+ nsMsgKey key;
+ m_hdrHits[i]->GetMessageKey(&key);
+ keyArray.AppendElement(key);
+ }
+ keyArray.Sort();
+ nsTArray<nsMsgKey> staleHits;
+ nsresult rv = m_db->RefreshCache(searchUri, keyArray, staleHits);
+ NS_ENSURE_SUCCESS(rv, rv);
+ for (nsMsgKey staleKey : staleHits) {
+ nsCOMPtr<nsIMsgDBHdr> hdrDeleted;
+ m_db->GetMsgHdrForKey(staleKey, getter_AddRefs(hdrDeleted));
+ if (hdrDeleted) OnHdrDeleted(hdrDeleted, nsMsgKey_None, 0, this);
+ }
+
+ nsCOMPtr<nsIMsgDatabase> virtDatabase;
+ nsCOMPtr<nsIDBFolderInfo> dbFolderInfo;
+ rv = m_viewFolder->GetDBFolderInfoAndDB(getter_AddRefs(dbFolderInfo),
+ getter_AddRefs(virtDatabase));
+ NS_ENSURE_SUCCESS(rv, rv);
+ uint32_t numUnread = 0;
+ size_t numTotal = m_origKeys.Length();
+
+ for (size_t i = 0; i < m_origKeys.Length(); i++) {
+ bool isRead;
+ m_db->IsRead(m_origKeys[i], &isRead);
+ if (!isRead) numUnread++;
+ }
+ dbFolderInfo->SetNumUnreadMessages(numUnread);
+ dbFolderInfo->SetNumMessages(numTotal);
+ m_viewFolder->UpdateSummaryTotals(true); // force update from db.
+ virtDatabase->Commit(nsMsgDBCommitType::kLargeCommit);
+ }
+ if (m_sortType !=
+ nsMsgViewSortType::byThread) // we do not find levels for the results.
+ {
+ m_sortValid = false; // sort the results
+ Sort(m_sortType, m_sortOrder);
+ }
+ if (m_viewFolder && (m_viewFolder != m_folder))
+ SetMRUTimeForFolder(m_viewFolder);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgQuickSearchDBView::OnNewSearch() {
+ int32_t oldSize = GetSize();
+
+ m_keys.Clear();
+ m_levels.Clear();
+ m_flags.Clear();
+ m_hdrHits.Clear();
+ // this needs to happen after we remove all the keys, since RowCountChanged()
+ // will call our GetRowCount()
+ if (mTree) mTree->RowCountChanged(0, -oldSize);
+ uint32_t folderFlags = 0;
+ if (m_viewFolder) m_viewFolder->GetFlags(&folderFlags);
+ // check if it's a virtual folder - if so, we should get the cached hits
+ // from the db, and set a flag saying that we're using cached values.
+ if (folderFlags & nsMsgFolderFlags::Virtual) {
+ nsCOMPtr<nsIMsgEnumerator> cachedHits;
+ nsCString searchUri;
+ m_viewFolder->GetURI(searchUri);
+ m_db->GetCachedHits(searchUri, getter_AddRefs(cachedHits));
+ if (cachedHits) {
+ bool hasMore;
+
+ m_usingCachedHits = true;
+ cachedHits->HasMoreElements(&hasMore);
+ m_cacheEmpty = !hasMore;
+ if (mTree) mTree->BeginUpdateBatch();
+ if (mJSTree) mJSTree->BeginUpdateBatch();
+ while (hasMore) {
+ nsCOMPtr<nsIMsgDBHdr> header;
+ nsresult rv = cachedHits->GetNext(getter_AddRefs(header));
+ if (header && NS_SUCCEEDED(rv))
+ AddHdr(header);
+ else
+ break;
+ cachedHits->HasMoreElements(&hasMore);
+ }
+ if (mTree) mTree->EndUpdateBatch();
+ if (mJSTree) mJSTree->EndUpdateBatch();
+ }
+ }
+
+ // Prevent updates for every message found. This batch ends in OnSearchDone.
+ if (mJSTree) mJSTree->BeginUpdateBatch();
+
+ return NS_OK;
+}
+
+nsresult nsMsgQuickSearchDBView::GetFirstMessageHdrToDisplayInThread(
+ nsIMsgThread* threadHdr, nsIMsgDBHdr** result) {
+ uint32_t numChildren;
+ nsresult rv = NS_OK;
+ uint8_t minLevel = 0xff;
+ threadHdr->GetNumChildren(&numChildren);
+ nsMsgKey threadRootKey;
+ nsCOMPtr<nsIMsgDBHdr> rootParent;
+ threadHdr->GetRootHdr(getter_AddRefs(rootParent));
+ if (rootParent)
+ rootParent->GetMessageKey(&threadRootKey);
+ else
+ threadHdr->GetThreadKey(&threadRootKey);
+
+ nsCOMPtr<nsIMsgDBHdr> retHdr;
+
+ // iterate over thread, finding mgsHdr in view with the lowest level.
+ for (uint32_t childIndex = 0; childIndex < numChildren; childIndex++) {
+ nsCOMPtr<nsIMsgDBHdr> child;
+ rv = threadHdr->GetChildHdrAt(childIndex, getter_AddRefs(child));
+ if (NS_SUCCEEDED(rv) && child) {
+ nsMsgKey msgKey;
+ child->GetMessageKey(&msgKey);
+
+ // this works because we've already sorted m_keys by id.
+ nsMsgViewIndex keyIndex = m_origKeys.BinaryIndexOf(msgKey);
+ if (keyIndex != nsMsgViewIndex_None) {
+ // this is the root, so it's the best we're going to do.
+ if (msgKey == threadRootKey) {
+ retHdr = child;
+ break;
+ }
+ uint8_t level = 0;
+ nsMsgKey parentId;
+ child->GetThreadParent(&parentId);
+ nsCOMPtr<nsIMsgDBHdr> parent;
+ // count number of ancestors - that's our level
+ while (parentId != nsMsgKey_None) {
+ m_db->GetMsgHdrForKey(parentId, getter_AddRefs(parent));
+ if (parent) {
+ nsMsgKey saveParentId = parentId;
+ parent->GetThreadParent(&parentId);
+ // message is it's own parent - bad, let's break out of here.
+ // Or we've got some circular ancestry.
+ if (parentId == saveParentId || level > numChildren) break;
+ level++;
+ } else // if we can't find the parent, don't loop forever.
+ break;
+ }
+ if (level < minLevel) {
+ minLevel = level;
+ retHdr = child;
+ }
+ }
+ }
+ }
+ retHdr.forget(result);
+ return NS_OK;
+}
+
+nsresult nsMsgQuickSearchDBView::SortThreads(
+ nsMsgViewSortTypeValue sortType, nsMsgViewSortOrderValue sortOrder) {
+ // don't need to sort by threads for group view.
+ if (m_viewFlags & nsMsgViewFlagsType::kGroupBySort) return NS_OK;
+ // iterate over the messages in the view, getting the thread id's
+ // sort m_keys so we can quickly find if a key is in the view.
+ m_keys.Sort();
+ // array of the threads' root hdr keys.
+ nsTArray<nsMsgKey> threadRootIds;
+ nsCOMPtr<nsIMsgDBHdr> rootHdr;
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ nsCOMPtr<nsIMsgThread> threadHdr;
+ for (uint32_t i = 0; i < m_keys.Length(); i++) {
+ GetMsgHdrForViewIndex(i, getter_AddRefs(msgHdr));
+ m_db->GetThreadContainingMsgHdr(msgHdr, getter_AddRefs(threadHdr));
+ if (threadHdr) {
+ nsMsgKey rootKey;
+ threadHdr->GetChildKeyAt(0, &rootKey);
+ nsMsgViewIndex threadRootIndex = threadRootIds.BinaryIndexOf(rootKey);
+ // if we already have that id in top level threads, ignore this msg.
+ if (threadRootIndex != nsMsgViewIndex_None) continue;
+ // it would be nice if GetInsertIndexHelper always found the hdr, but it
+ // doesn't.
+ threadHdr->GetChildHdrAt(0, getter_AddRefs(rootHdr));
+ if (!rootHdr) continue;
+ threadRootIndex = GetInsertIndexHelper(rootHdr, threadRootIds, nullptr,
+ nsMsgViewSortOrder::ascending,
+ nsMsgViewSortType::byId);
+ threadRootIds.InsertElementAt(threadRootIndex, rootKey);
+ }
+ }
+
+ m_sortType = nsMsgViewSortType::byNone; // sort from scratch
+ // need to sort the top level threads now by sort order, if it's not by id
+ // and ascending (which is the order per above).
+ if (!(sortType == nsMsgViewSortType::byId &&
+ sortOrder == nsMsgViewSortOrder::ascending)) {
+ m_keys.SwapElements(threadRootIds);
+ nsMsgDBView::Sort(sortType, sortOrder);
+ threadRootIds.SwapElements(m_keys);
+ }
+ m_keys.Clear();
+ m_levels.Clear();
+ m_flags.Clear();
+ // now we've build up the list of thread ids - need to build the view
+ // from that. So for each thread id, we need to list the messages in the
+ // thread.
+ uint32_t numThreads = threadRootIds.Length();
+ for (uint32_t threadIndex = 0; threadIndex < numThreads; threadIndex++) {
+ m_db->GetMsgHdrForKey(threadRootIds[threadIndex], getter_AddRefs(rootHdr));
+ if (rootHdr) {
+ nsCOMPtr<nsIMsgDBHdr> displayRootHdr;
+ m_db->GetThreadContainingMsgHdr(rootHdr, getter_AddRefs(threadHdr));
+ if (threadHdr) {
+ nsMsgKey rootKey;
+ uint32_t rootFlags;
+ GetFirstMessageHdrToDisplayInThread(threadHdr,
+ getter_AddRefs(displayRootHdr));
+ if (!displayRootHdr) continue;
+ displayRootHdr->GetMessageKey(&rootKey);
+ displayRootHdr->GetFlags(&rootFlags);
+ rootFlags |= MSG_VIEW_FLAG_ISTHREAD;
+ m_keys.AppendElement(rootKey);
+ m_flags.AppendElement(rootFlags);
+ m_levels.AppendElement(0);
+
+ nsMsgViewIndex startOfThreadViewIndex = m_keys.Length();
+ nsMsgViewIndex rootIndex = startOfThreadViewIndex - 1;
+ uint32_t numListed = 0;
+ ListIdsInThreadOrder(threadHdr, rootKey, 1, &startOfThreadViewIndex,
+ &numListed);
+ if (numListed > 0)
+ m_flags[rootIndex] = rootFlags | MSG_VIEW_FLAG_HASCHILDREN;
+ }
+ }
+ }
+
+ // The thread state is left expanded (despite viewFlags) so at least reflect
+ // the correct state.
+ m_viewFlags |= nsMsgViewFlagsType::kExpandAll;
+
+ return NS_OK;
+}
+
+nsresult nsMsgQuickSearchDBView::ListCollapsedChildren(
+ nsMsgViewIndex viewIndex, nsTArray<RefPtr<nsIMsgDBHdr>>& messageArray) {
+ nsCOMPtr<nsIMsgThread> threadHdr;
+ nsresult rv = GetThreadContainingIndex(viewIndex, getter_AddRefs(threadHdr));
+ NS_ENSURE_SUCCESS(rv, rv);
+ uint32_t numChildren;
+ threadHdr->GetNumChildren(&numChildren);
+ nsCOMPtr<nsIMsgDBHdr> rootHdr;
+ nsMsgKey rootKey;
+ GetMsgHdrForViewIndex(viewIndex, getter_AddRefs(rootHdr));
+ rootHdr->GetMessageKey(&rootKey);
+ // group threads can have the root key twice, one for the dummy row.
+ bool rootKeySkipped = false;
+ for (uint32_t i = 0; i < numChildren; i++) {
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ threadHdr->GetChildHdrAt(i, getter_AddRefs(msgHdr));
+ if (msgHdr) {
+ nsMsgKey msgKey;
+ msgHdr->GetMessageKey(&msgKey);
+ if (msgKey != rootKey || (GroupViewUsesDummyRow() && rootKeySkipped)) {
+ // if this hdr is in the original view, add it to new view.
+ if (m_origKeys.BinaryIndexOf(msgKey) != m_origKeys.NoIndex)
+ messageArray.AppendElement(msgHdr);
+ } else {
+ rootKeySkipped = true;
+ }
+ }
+ }
+ return NS_OK;
+}
+
+nsresult nsMsgQuickSearchDBView::ListIdsInThread(
+ nsIMsgThread* threadHdr, nsMsgViewIndex startOfThreadViewIndex,
+ uint32_t* pNumListed) {
+ if (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay &&
+ !(m_viewFlags & nsMsgViewFlagsType::kGroupBySort)) {
+ nsMsgKey parentKey = m_keys[startOfThreadViewIndex++];
+ return ListIdsInThreadOrder(threadHdr, parentKey, 1,
+ &startOfThreadViewIndex, pNumListed);
+ }
+
+ uint32_t numChildren;
+ threadHdr->GetNumChildren(&numChildren);
+ uint32_t i;
+ uint32_t viewIndex = startOfThreadViewIndex + 1;
+ nsCOMPtr<nsIMsgDBHdr> rootHdr;
+ nsMsgKey rootKey;
+ uint32_t rootFlags = m_flags[startOfThreadViewIndex];
+ *pNumListed = 0;
+ GetMsgHdrForViewIndex(startOfThreadViewIndex, getter_AddRefs(rootHdr));
+ rootHdr->GetMessageKey(&rootKey);
+ // group threads can have the root key twice, one for the dummy row.
+ bool rootKeySkipped = false;
+ for (i = 0; i < numChildren; i++) {
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ threadHdr->GetChildHdrAt(i, getter_AddRefs(msgHdr));
+ if (msgHdr != nullptr) {
+ nsMsgKey msgKey;
+ msgHdr->GetMessageKey(&msgKey);
+ if (msgKey != rootKey || (GroupViewUsesDummyRow() && rootKeySkipped)) {
+ nsMsgViewIndex threadRootIndex = m_origKeys.BinaryIndexOf(msgKey);
+ // if this hdr is in the original view, add it to new view.
+ if (threadRootIndex != nsMsgViewIndex_None) {
+ uint32_t childFlags;
+ msgHdr->GetFlags(&childFlags);
+ InsertMsgHdrAt(
+ viewIndex, msgHdr, msgKey, childFlags,
+ FindLevelInThread(msgHdr, startOfThreadViewIndex, viewIndex));
+ if (!(rootFlags & MSG_VIEW_FLAG_HASCHILDREN))
+ m_flags[startOfThreadViewIndex] =
+ rootFlags | MSG_VIEW_FLAG_HASCHILDREN;
+
+ viewIndex++;
+ (*pNumListed)++;
+ }
+ } else {
+ rootKeySkipped = true;
+ }
+ }
+ }
+ return NS_OK;
+}
+
+nsresult nsMsgQuickSearchDBView::ListIdsInThreadOrder(
+ nsIMsgThread* threadHdr, nsMsgKey parentKey, uint32_t level,
+ uint32_t callLevel, nsMsgKey keyToSkip, nsMsgViewIndex* viewIndex,
+ uint32_t* pNumListed) {
+ nsCOMPtr<nsIMsgEnumerator> msgEnumerator;
+ nsresult rv =
+ threadHdr->EnumerateMessages(parentKey, getter_AddRefs(msgEnumerator));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // We use the numChildren as a sanity check on the thread structure.
+ uint32_t numChildren;
+ (void)threadHdr->GetNumChildren(&numChildren);
+ bool hasMore;
+ while (NS_SUCCEEDED(rv = msgEnumerator->HasMoreElements(&hasMore)) &&
+ hasMore) {
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ rv = msgEnumerator->GetNext(getter_AddRefs(msgHdr));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsMsgKey msgKey;
+ msgHdr->GetMessageKey(&msgKey);
+ if (msgKey == keyToSkip) continue;
+
+ // If we discover depths of more than numChildren, it means we have
+ // some sort of circular thread relationship and we bail out of the
+ // while loop before overflowing the stack with recursive calls.
+ // Technically, this is an error, but forcing a database rebuild
+ // is too destructive so we just return.
+ if (*pNumListed > numChildren || callLevel > numChildren) {
+ NS_ERROR("loop in message threading while listing children");
+ return NS_OK;
+ }
+
+ int32_t childLevel = level;
+ if (m_origKeys.BinaryIndexOf(msgKey) != m_origKeys.NoIndex) {
+ uint32_t msgFlags;
+ msgHdr->GetFlags(&msgFlags);
+ InsertMsgHdrAt(*viewIndex, msgHdr, msgKey, msgFlags & ~MSG_VIEW_FLAGS,
+ level);
+ (*pNumListed)++;
+ (*viewIndex)++;
+ childLevel++;
+ }
+ rv = ListIdsInThreadOrder(threadHdr, msgKey, childLevel, callLevel + 1,
+ keyToSkip, viewIndex, pNumListed);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ return rv;
+}
+
+nsresult nsMsgQuickSearchDBView::ListIdsInThreadOrder(nsIMsgThread* threadHdr,
+ nsMsgKey parentKey,
+ uint32_t level,
+ nsMsgViewIndex* viewIndex,
+ uint32_t* pNumListed) {
+ nsresult rv = ListIdsInThreadOrder(threadHdr, parentKey, level, level,
+ nsMsgKey_None, viewIndex, pNumListed);
+ // Because a quick search view might not have the actual thread root
+ // as its root, and thus might have a message that potentially has siblings
+ // as its root, and the enumerator will miss the siblings, we might need to
+ // make a pass looking for the siblings of the non-root root. We'll put
+ // those after the potential children of the root. So we will list the
+ // children of the faux root's parent, ignoring the faux root.
+ if (level == 1) {
+ nsCOMPtr<nsIMsgDBHdr> root;
+ nsCOMPtr<nsIMsgDBHdr> rootParent;
+ nsMsgKey rootKey;
+ threadHdr->GetRootHdr(getter_AddRefs(rootParent));
+ if (rootParent) {
+ rootParent->GetMessageKey(&rootKey);
+ if (rootKey != parentKey)
+ rv = ListIdsInThreadOrder(threadHdr, rootKey, level, level, parentKey,
+ viewIndex, pNumListed);
+ }
+ }
+ return rv;
+}
+
+nsresult nsMsgQuickSearchDBView::ExpansionDelta(nsMsgViewIndex index,
+ int32_t* expansionDelta) {
+ *expansionDelta = 0;
+ if (index >= ((nsMsgViewIndex)m_keys.Length()))
+ return NS_MSG_MESSAGE_NOT_FOUND;
+
+ char flags = m_flags[index];
+
+ if (!(m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay)) return NS_OK;
+
+ nsCOMPtr<nsIMsgThread> threadHdr;
+ nsresult rv = GetThreadContainingIndex(index, getter_AddRefs(threadHdr));
+ NS_ENSURE_SUCCESS(rv, rv);
+ uint32_t numChildren;
+ threadHdr->GetNumChildren(&numChildren);
+ nsCOMPtr<nsIMsgDBHdr> rootHdr;
+ nsMsgKey rootKey;
+ GetMsgHdrForViewIndex(index, getter_AddRefs(rootHdr));
+ rootHdr->GetMessageKey(&rootKey);
+ // group threads can have the root key twice, one for the dummy row.
+ bool rootKeySkipped = false;
+ for (uint32_t i = 0; i < numChildren; i++) {
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ threadHdr->GetChildHdrAt(i, getter_AddRefs(msgHdr));
+ if (msgHdr) {
+ nsMsgKey msgKey;
+ msgHdr->GetMessageKey(&msgKey);
+ if (msgKey != rootKey || (GroupViewUsesDummyRow() && rootKeySkipped)) {
+ // if this hdr is in the original view, add it to new view.
+ if (m_origKeys.BinaryIndexOf(msgKey) != m_origKeys.NoIndex)
+ (*expansionDelta)++;
+ } else {
+ rootKeySkipped = true;
+ }
+ }
+ }
+ if (!(flags & nsMsgMessageFlags::Elided))
+ *expansionDelta = -(*expansionDelta);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgQuickSearchDBView::OpenWithHdrs(nsIMsgEnumerator* aHeaders,
+ nsMsgViewSortTypeValue aSortType,
+ nsMsgViewSortOrderValue aSortOrder,
+ nsMsgViewFlagsTypeValue aViewFlags,
+ int32_t* aCount) {
+ if (aViewFlags & nsMsgViewFlagsType::kGroupBySort)
+ return nsMsgGroupView::OpenWithHdrs(aHeaders, aSortType, aSortOrder,
+ aViewFlags, aCount);
+
+ m_sortType = aSortType;
+ m_sortOrder = aSortOrder;
+ m_viewFlags = aViewFlags;
+
+ bool hasMore;
+ nsCOMPtr<nsISupports> supports;
+ nsresult rv = NS_OK;
+ while (NS_SUCCEEDED(rv = aHeaders->HasMoreElements(&hasMore)) && hasMore) {
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ rv = aHeaders->GetNext(getter_AddRefs(msgHdr));
+ if (NS_SUCCEEDED(rv) && msgHdr) {
+ AddHdr(msgHdr);
+ } else {
+ break;
+ }
+ }
+ *aCount = m_keys.Length();
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgQuickSearchDBView::SetViewFlags(
+ nsMsgViewFlagsTypeValue aViewFlags) {
+ nsresult rv = NS_OK;
+ // if the grouping has changed, rebuild the view
+ if ((m_viewFlags & nsMsgViewFlagsType::kGroupBySort) ^
+ (aViewFlags & nsMsgViewFlagsType::kGroupBySort))
+ rv = RebuildView(aViewFlags);
+ nsMsgDBView::SetViewFlags(aViewFlags);
+
+ return rv;
+}
+
+nsresult nsMsgQuickSearchDBView::GetMessageEnumerator(
+ nsIMsgEnumerator** enumerator) {
+ return GetViewEnumerator(enumerator);
+}
+
+NS_IMETHODIMP
+nsMsgQuickSearchDBView::OnHdrDeleted(nsIMsgDBHdr* aHdrDeleted,
+ nsMsgKey aParentKey, int32_t aFlags,
+ nsIDBChangeListener* aInstigator) {
+ NS_ENSURE_ARG_POINTER(aHdrDeleted);
+ nsMsgKey msgKey;
+ aHdrDeleted->GetMessageKey(&msgKey);
+ size_t keyIndex = m_origKeys.BinaryIndexOf(msgKey);
+ if (keyIndex != m_origKeys.NoIndex) m_origKeys.RemoveElementAt(keyIndex);
+ return nsMsgThreadedDBView::OnHdrDeleted(aHdrDeleted, aParentKey, aFlags,
+ aInstigator);
+}
+
+NS_IMETHODIMP nsMsgQuickSearchDBView::GetNumMsgsInView(int32_t* aNumMsgs) {
+ NS_ENSURE_ARG_POINTER(aNumMsgs);
+ *aNumMsgs = m_origKeys.Length();
+ return NS_OK;
+}
diff --git a/comm/mailnews/base/src/nsMsgQuickSearchDBView.h b/comm/mailnews/base/src/nsMsgQuickSearchDBView.h
new file mode 100644
index 0000000000..da10a3d9e1
--- /dev/null
+++ b/comm/mailnews/base/src/nsMsgQuickSearchDBView.h
@@ -0,0 +1,99 @@
+/* -*- 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 _nsMsgQuickSearchDBView_H_
+#define _nsMsgQuickSearchDBView_H_
+
+#include "mozilla/Attributes.h"
+#include "nsMsgThreadedDBView.h"
+#include "nsIMsgSearchNotify.h"
+#include "nsIMsgSearchSession.h"
+#include "nsCOMArray.h"
+#include "nsIMsgHdr.h"
+#include "nsIWeakReferenceUtils.h"
+
+class nsMsgQuickSearchDBView : public nsMsgThreadedDBView,
+ public nsIMsgSearchNotify {
+ public:
+ nsMsgQuickSearchDBView();
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIMSGSEARCHNOTIFY
+
+ virtual const char* GetViewName(void) override { return "QuickSearchView"; }
+ NS_IMETHOD Open(nsIMsgFolder* folder, nsMsgViewSortTypeValue sortType,
+ nsMsgViewSortOrderValue sortOrder,
+ nsMsgViewFlagsTypeValue viewFlags, int32_t* pCount) override;
+ NS_IMETHOD OpenWithHdrs(nsIMsgEnumerator* aHeaders,
+ nsMsgViewSortTypeValue aSortType,
+ nsMsgViewSortOrderValue aSortOrder,
+ nsMsgViewFlagsTypeValue aViewFlags,
+ int32_t* aCount) override;
+ NS_IMETHOD CloneDBView(nsIMessenger* aMessengerInstance,
+ nsIMsgWindow* aMsgWindow,
+ nsIMsgDBViewCommandUpdater* aCommandUpdater,
+ nsIMsgDBView** _retval) override;
+ NS_IMETHOD CopyDBView(nsMsgDBView* aNewMsgDBView,
+ nsIMessenger* aMessengerInstance,
+ nsIMsgWindow* aMsgWindow,
+ nsIMsgDBViewCommandUpdater* aCmdUpdater) override;
+ NS_IMETHOD DoCommand(nsMsgViewCommandTypeValue aCommand) override;
+ NS_IMETHOD GetViewType(nsMsgViewTypeValue* aViewType) override;
+ NS_IMETHOD SetViewFlags(nsMsgViewFlagsTypeValue aViewFlags) override;
+ NS_IMETHOD SetSearchSession(nsIMsgSearchSession* aSearchSession) override;
+ NS_IMETHOD GetSearchSession(nsIMsgSearchSession** aSearchSession) override;
+ NS_IMETHOD OnHdrFlagsChanged(nsIMsgDBHdr* aHdrChanged, uint32_t aOldFlags,
+ uint32_t aNewFlags,
+ nsIDBChangeListener* aInstigator) override;
+ NS_IMETHOD OnHdrPropertyChanged(nsIMsgDBHdr* aHdrToChange,
+ const nsACString& property, bool aPreChange,
+ uint32_t* aStatus,
+ nsIDBChangeListener* aInstigator) override;
+ NS_IMETHOD OnHdrDeleted(nsIMsgDBHdr* aHdrDeleted, nsMsgKey aParentKey,
+ int32_t aFlags,
+ nsIDBChangeListener* aInstigator) override;
+ NS_IMETHOD GetNumMsgsInView(int32_t* aNumMsgs) override;
+
+ protected:
+ virtual ~nsMsgQuickSearchDBView();
+ nsWeakPtr m_searchSession;
+ nsTArray<nsMsgKey> m_origKeys;
+ bool m_usingCachedHits;
+ bool m_cacheEmpty;
+ nsCOMArray<nsIMsgDBHdr> m_hdrHits;
+ virtual nsresult AddHdr(nsIMsgDBHdr* msgHdr,
+ nsMsgViewIndex* resultIndex = nullptr) override;
+ virtual nsresult OnNewHeader(nsIMsgDBHdr* newHdr, nsMsgKey aParentKey,
+ bool ensureListed) override;
+ virtual nsresult DeleteMessages(nsIMsgWindow* window,
+ nsTArray<nsMsgViewIndex> const& selection,
+ bool deleteStorage) override;
+ virtual nsresult SortThreads(nsMsgViewSortTypeValue sortType,
+ nsMsgViewSortOrderValue sortOrder) override;
+ virtual nsresult GetFirstMessageHdrToDisplayInThread(
+ nsIMsgThread* threadHdr, nsIMsgDBHdr** result) override;
+ virtual nsresult ExpansionDelta(nsMsgViewIndex index,
+ int32_t* expansionDelta) override;
+ virtual nsresult ListCollapsedChildren(
+ nsMsgViewIndex viewIndex,
+ nsTArray<RefPtr<nsIMsgDBHdr>>& messageArray) override;
+ virtual nsresult ListIdsInThread(nsIMsgThread* threadHdr,
+ nsMsgViewIndex startOfThreadViewIndex,
+ uint32_t* pNumListed) override;
+ virtual nsresult ListIdsInThreadOrder(nsIMsgThread* threadHdr,
+ nsMsgKey parentKey, uint32_t level,
+ nsMsgViewIndex* viewIndex,
+ uint32_t* pNumListed) override;
+ virtual nsresult ListIdsInThreadOrder(nsIMsgThread* threadHdr,
+ nsMsgKey parentKey, uint32_t level,
+ uint32_t callLevel, nsMsgKey keyToSkip,
+ nsMsgViewIndex* viewIndex,
+ uint32_t* pNumListed);
+ virtual nsresult GetMessageEnumerator(nsIMsgEnumerator** enumerator) override;
+ void SavePreSearchInfo();
+ void ClearPreSearchInfo();
+};
+
+#endif
diff --git a/comm/mailnews/base/src/nsMsgReadStateTxn.cpp b/comm/mailnews/base/src/nsMsgReadStateTxn.cpp
new file mode 100644
index 0000000000..80cd0d0bd1
--- /dev/null
+++ b/comm/mailnews/base/src/nsMsgReadStateTxn.cpp
@@ -0,0 +1,43 @@
+/* -*- 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 "nsMsgReadStateTxn.h"
+
+#include "nsIMsgHdr.h"
+#include "nsComponentManagerUtils.h"
+
+nsMsgReadStateTxn::nsMsgReadStateTxn() {}
+
+nsMsgReadStateTxn::~nsMsgReadStateTxn() {}
+
+nsresult nsMsgReadStateTxn::Init(nsIMsgFolder* aParentFolder, uint32_t aNumKeys,
+ nsMsgKey* aMsgKeyArray) {
+ NS_ENSURE_ARG_POINTER(aParentFolder);
+
+ mParentFolder = aParentFolder;
+ mMarkedMessages.AppendElements(aMsgKeyArray, aNumKeys);
+
+ return nsMsgTxn::Init();
+}
+
+NS_IMETHODIMP
+nsMsgReadStateTxn::UndoTransaction() { return MarkMessages(false); }
+
+NS_IMETHODIMP
+nsMsgReadStateTxn::RedoTransaction() { return MarkMessages(true); }
+
+NS_IMETHODIMP
+nsMsgReadStateTxn::MarkMessages(bool aAsRead) {
+ nsTArray<RefPtr<nsIMsgDBHdr>> messages(mMarkedMessages.Length());
+ for (auto msgKey : mMarkedMessages) {
+ nsCOMPtr<nsIMsgDBHdr> curMsgHdr;
+ nsresult rv =
+ mParentFolder->GetMessageHeader(msgKey, getter_AddRefs(curMsgHdr));
+ if (NS_SUCCEEDED(rv) && curMsgHdr) {
+ messages.AppendElement(curMsgHdr);
+ }
+ }
+ return mParentFolder->MarkMessagesRead(messages, aAsRead);
+}
diff --git a/comm/mailnews/base/src/nsMsgReadStateTxn.h b/comm/mailnews/base/src/nsMsgReadStateTxn.h
new file mode 100644
index 0000000000..07bd4c8670
--- /dev/null
+++ b/comm/mailnews/base/src/nsMsgReadStateTxn.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 nsMsgBaseUndoTxn_h_
+#define nsMsgBaseUndoTxn_h_
+
+#include "mozilla/Attributes.h"
+#include "nsMsgTxn.h"
+#include "nsTArray.h"
+#include "nsCOMPtr.h"
+#include "MailNewsTypes.h"
+#include "nsIMsgFolder.h"
+
+#define NS_MSGREADSTATETXN_IID \
+ { /* 121FCE4A-3EA1-455C-8161-839E1557D0CF */ \
+ 0x121FCE4A, 0x3EA1, 0x455C, { \
+ 0x81, 0x61, 0x83, 0x9E, 0x15, 0x57, 0xD0, 0xCF \
+ } \
+ }
+
+//------------------------------------------------------------------------------
+// A mark-all transaction handler. Helper for redo/undo of message read states.
+//------------------------------------------------------------------------------
+class nsMsgReadStateTxn : public nsMsgTxn {
+ public:
+ nsMsgReadStateTxn();
+ virtual ~nsMsgReadStateTxn();
+
+ nsresult Init(nsIMsgFolder* aParentFolder, uint32_t aNumKeys,
+ nsMsgKey* aMsgKeyArray);
+ NS_IMETHOD UndoTransaction() override;
+ NS_IMETHOD RedoTransaction() override;
+
+ protected:
+ NS_IMETHOD MarkMessages(bool aAsRead);
+
+ private:
+ nsCOMPtr<nsIMsgFolder> mParentFolder;
+ nsTArray<nsMsgKey> mMarkedMessages;
+};
+
+#endif // nsMsgBaseUndoTxn_h_
diff --git a/comm/mailnews/base/src/nsMsgSearchDBView.cpp b/comm/mailnews/base/src/nsMsgSearchDBView.cpp
new file mode 100644
index 0000000000..f036d0d304
--- /dev/null
+++ b/comm/mailnews/base/src/nsMsgSearchDBView.cpp
@@ -0,0 +1,1344 @@
+/* -*- 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 "msgCore.h"
+#include "nsMsgSearchDBView.h"
+#include "nsIMsgHdr.h"
+#include "nsIMsgThread.h"
+#include "nsQuickSort.h"
+#include "nsIDBFolderInfo.h"
+#include "nsIMsgCopyService.h"
+#include "nsMsgUtils.h"
+#include "nsTreeColumns.h"
+#include "nsIMsgMessageService.h"
+#include "nsMsgGroupThread.h"
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+#include "nsMsgMessageFlags.h"
+#include "nsIMsgSearchSession.h"
+#include "nsComponentManagerUtils.h"
+#include "nsServiceManagerUtils.h"
+
+static bool gReferenceOnlyThreading;
+
+nsMsgSearchDBView::nsMsgSearchDBView() {
+ // Don't try to display messages for the search pane.
+ mSuppressMsgDisplay = true;
+ m_totalMessagesInView = 0;
+ m_nextThreadId = 1;
+ mCurIndex = 0;
+ mTotalIndices = 0;
+ mCommand = -1;
+}
+
+nsMsgSearchDBView::~nsMsgSearchDBView() {}
+
+NS_IMPL_ISUPPORTS_INHERITED(nsMsgSearchDBView, nsMsgDBView, nsIMsgDBView,
+ nsIMsgCopyServiceListener, nsIMsgSearchNotify)
+
+NS_IMETHODIMP
+nsMsgSearchDBView::Open(nsIMsgFolder* folder, nsMsgViewSortTypeValue sortType,
+ nsMsgViewSortOrderValue sortOrder,
+ nsMsgViewFlagsTypeValue viewFlags, int32_t* pCount) {
+ // DBViewWrapper.jsm likes to create search views with a sort order
+ // of byNone, in order to have the order be the order the search results
+ // are returned. But this doesn't work with threaded view, so make the
+ // sort order be byDate if we're threaded.
+
+ if (viewFlags & nsMsgViewFlagsType::kThreadedDisplay &&
+ sortType == nsMsgViewSortType::byNone)
+ sortType = nsMsgViewSortType::byDate;
+
+ nsresult rv =
+ nsMsgDBView::Open(folder, sortType, sortOrder, viewFlags, pCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIPrefBranch> prefBranch(
+ do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ prefBranch->GetBoolPref("mail.strict_threading", &gReferenceOnlyThreading);
+
+ // Our sort is automatically valid because we have no contents at this point!
+ m_sortValid = true;
+
+ if (pCount) *pCount = 0;
+
+ m_folder = nullptr;
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgSearchDBView::CloneDBView(nsIMessenger* aMessengerInstance,
+ nsIMsgWindow* aMsgWindow,
+ nsIMsgDBViewCommandUpdater* aCmdUpdater,
+ nsIMsgDBView** _retval) {
+ nsMsgSearchDBView* newMsgDBView = new nsMsgSearchDBView();
+ nsresult rv =
+ CopyDBView(newMsgDBView, aMessengerInstance, aMsgWindow, aCmdUpdater);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_IF_ADDREF(*_retval = newMsgDBView);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgSearchDBView::CopyDBView(nsMsgDBView* aNewMsgDBView,
+ nsIMessenger* aMessengerInstance,
+ nsIMsgWindow* aMsgWindow,
+ nsIMsgDBViewCommandUpdater* aCmdUpdater) {
+ nsMsgGroupView::CopyDBView(aNewMsgDBView, aMessengerInstance, aMsgWindow,
+ aCmdUpdater);
+ nsMsgSearchDBView* newMsgDBView = (nsMsgSearchDBView*)aNewMsgDBView;
+
+ // Now copy all of our private member data.
+ newMsgDBView->mDestFolder = mDestFolder;
+ newMsgDBView->mCommand = mCommand;
+ newMsgDBView->mTotalIndices = mTotalIndices;
+ newMsgDBView->mCurIndex = mCurIndex;
+ newMsgDBView->m_folders.InsertObjectsAt(m_folders, 0);
+ newMsgDBView->m_curCustomColumn = m_curCustomColumn;
+ for (auto const& hdrs : m_hdrsForEachFolder) {
+ newMsgDBView->m_hdrsForEachFolder.AppendElement(hdrs.Clone());
+ }
+ newMsgDBView->m_uniqueFoldersSelected.InsertObjectsAt(m_uniqueFoldersSelected,
+ 0);
+
+ int32_t count = m_dbToUseList.Count();
+ for (int32_t i = 0; i < count; i++) {
+ newMsgDBView->m_dbToUseList.AppendObject(m_dbToUseList[i]);
+ // Register the new view with the database so it gets notifications.
+ m_dbToUseList[i]->AddListener(newMsgDBView);
+ }
+ if (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay) {
+ // We need to clone the thread and msg hdr hash tables.
+ for (auto iter = m_threadsTable.Iter(); !iter.Done(); iter.Next()) {
+ newMsgDBView->m_threadsTable.InsertOrUpdate(iter.Key(), iter.UserData());
+ }
+ for (auto iter = m_hdrsTable.Iter(); !iter.Done(); iter.Next()) {
+ newMsgDBView->m_hdrsTable.InsertOrUpdate(iter.Key(), iter.UserData());
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgSearchDBView::Close() {
+ int32_t count = m_dbToUseList.Count();
+
+ for (int32_t i = 0; i < count; i++) m_dbToUseList[i]->RemoveListener(this);
+
+ m_dbToUseList.Clear();
+
+ return nsMsgGroupView::Close();
+}
+
+void nsMsgSearchDBView::InternalClose() {
+ m_threadsTable.Clear();
+ m_hdrsTable.Clear();
+ nsMsgGroupView::InternalClose();
+ m_folders.Clear();
+}
+
+NS_IMETHODIMP
+nsMsgSearchDBView::GetCellText(int32_t aRow, nsTreeColumn* aCol,
+ nsAString& aValue) {
+ NS_ENSURE_TRUE(IsValidIndex(aRow), NS_MSG_INVALID_DBVIEW_INDEX);
+ NS_ENSURE_ARG_POINTER(aCol);
+
+ const nsAString& colID = aCol->GetId();
+ // The only thing we contribute is location; dummy rows have no location, so
+ // bail in that case. Otherwise, check if we are dealing with 'location'.
+ // 'location', need to check for "lo" not just "l" to avoid "label" column.
+ if (!(m_flags[aRow] & MSG_VIEW_FLAG_DUMMY) && colID.Length() >= 2 &&
+ colID.First() == 'l' && colID.CharAt(1) == 'o')
+ return FetchLocation(aRow, aValue);
+ else
+ return nsMsgGroupView::GetCellText(aRow, aCol, aValue);
+}
+
+nsresult nsMsgSearchDBView::HashHdr(nsIMsgDBHdr* msgHdr, nsString& aHashKey) {
+ if (m_sortType == nsMsgViewSortType::byLocation) {
+ aHashKey.Truncate();
+ nsCOMPtr<nsIMsgFolder> folder;
+ msgHdr->GetFolder(getter_AddRefs(folder));
+ return folder->GetPrettyName(aHashKey);
+ }
+
+ return nsMsgGroupView::HashHdr(msgHdr, aHashKey);
+}
+
+nsresult nsMsgSearchDBView::FetchLocation(int32_t aRow,
+ nsAString& aLocationString) {
+ nsCOMPtr<nsIMsgFolder> folder;
+ nsresult rv = GetFolderForViewIndex(aRow, getter_AddRefs(folder));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return folder->GetPrettyName(aLocationString);
+}
+
+nsresult nsMsgSearchDBView::OnNewHeader(nsIMsgDBHdr* newHdr,
+ nsMsgKey aParentKey,
+ bool /*ensureListed*/) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgSearchDBView::OnHdrDeleted(nsIMsgDBHdr* aHdrDeleted, nsMsgKey aParentKey,
+ int32_t aFlags,
+ nsIDBChangeListener* aInstigator) {
+ if (m_viewFlags & nsMsgViewFlagsType::kGroupBySort)
+ return nsMsgGroupView::OnHdrDeleted(aHdrDeleted, aParentKey, aFlags,
+ aInstigator);
+
+ if (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay) {
+ nsMsgViewIndex deletedIndex = FindHdr(aHdrDeleted);
+ uint32_t savedFlags = 0;
+ if (deletedIndex != nsMsgViewIndex_None) {
+ // Check if this message is currently selected. If it is, tell the front
+ // end to be prepared for a delete.
+ nsCOMPtr<nsIMsgDBViewCommandUpdater> commandUpdater(
+ do_QueryReferent(mCommandUpdater));
+ bool isMsgSelected = false;
+ if (mTreeSelection && commandUpdater) {
+ mTreeSelection->IsSelected(deletedIndex, &isMsgSelected);
+ if (isMsgSelected) commandUpdater->UpdateNextMessageAfterDelete();
+ }
+
+ savedFlags = m_flags[deletedIndex];
+ RemoveByIndex(deletedIndex);
+
+ if (isMsgSelected) {
+ // Now tell the front end that the delete happened.
+ commandUpdater->SelectedMessageRemoved();
+ }
+ }
+
+ nsCOMPtr<nsIMsgThread> thread;
+ GetXFThreadFromMsgHdr(aHdrDeleted, getter_AddRefs(thread));
+ if (thread) {
+ nsMsgXFViewThread* viewThread =
+ static_cast<nsMsgXFViewThread*>(thread.get());
+ viewThread->RemoveChildHdr(aHdrDeleted, nullptr);
+ if (deletedIndex == nsMsgViewIndex_None && viewThread->MsgCount() == 1) {
+ // Remove the last child of a collapsed thread. Need to find the root,
+ // and remove the thread flags on it.
+ nsCOMPtr<nsIMsgDBHdr> rootHdr;
+ thread->GetRootHdr(getter_AddRefs(rootHdr));
+ if (rootHdr) {
+ nsMsgViewIndex threadIndex = GetThreadRootIndex(rootHdr);
+ if (IsValidIndex(threadIndex))
+ AndExtraFlag(threadIndex,
+ ~(MSG_VIEW_FLAG_ISTHREAD | nsMsgMessageFlags::Elided |
+ MSG_VIEW_FLAG_HASCHILDREN));
+ }
+ } else if (savedFlags & MSG_VIEW_FLAG_HASCHILDREN) {
+ if (savedFlags & nsMsgMessageFlags::Elided) {
+ nsCOMPtr<nsIMsgDBHdr> rootHdr;
+ nsresult rv = thread->GetRootHdr(getter_AddRefs(rootHdr));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsMsgKey msgKey;
+ uint32_t msgFlags;
+ rootHdr->GetMessageKey(&msgKey);
+ rootHdr->GetFlags(&msgFlags);
+ // Promote the new thread root.
+ if (viewThread->MsgCount() > 1)
+ msgFlags |= MSG_VIEW_FLAG_ISTHREAD | nsMsgMessageFlags::Elided |
+ MSG_VIEW_FLAG_HASCHILDREN;
+ InsertMsgHdrAt(deletedIndex, rootHdr, msgKey, msgFlags, 0);
+ if (!m_deletingRows)
+ NoteChange(deletedIndex, 1,
+ nsMsgViewNotificationCode::insertOrDelete);
+ } else if (viewThread->MsgCount() > 1) {
+ OrExtraFlag(deletedIndex,
+ MSG_VIEW_FLAG_ISTHREAD | MSG_VIEW_FLAG_HASCHILDREN);
+ }
+ }
+ }
+ } else {
+ return nsMsgDBView::OnHdrDeleted(aHdrDeleted, aParentKey, aFlags,
+ aInstigator);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgSearchDBView::OnHdrFlagsChanged(nsIMsgDBHdr* aHdrChanged,
+ uint32_t aOldFlags, uint32_t aNewFlags,
+ nsIDBChangeListener* aInstigator) {
+ // Defer to base class if we're grouped or not threaded at all.
+ if (m_viewFlags & nsMsgViewFlagsType::kGroupBySort ||
+ !(m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay)) {
+ return nsMsgGroupView::OnHdrFlagsChanged(aHdrChanged, aOldFlags, aNewFlags,
+ aInstigator);
+ }
+
+ nsCOMPtr<nsIMsgThread> thread;
+ bool foundMessageId;
+ // Check if the hdr that changed is in a xf thread, and if the read flag
+ // changed, update the thread unread count. GetXFThreadFromMsgHdr returns
+ // the thread the header does or would belong to, so we need to also
+ // check that the header is actually in the thread.
+ GetXFThreadFromMsgHdr(aHdrChanged, getter_AddRefs(thread), &foundMessageId);
+ if (foundMessageId) {
+ nsMsgXFViewThread* viewThread =
+ static_cast<nsMsgXFViewThread*>(thread.get());
+ if (viewThread->HdrIndex(aHdrChanged) != -1) {
+ uint32_t deltaFlags = (aOldFlags ^ aNewFlags);
+ if (deltaFlags & nsMsgMessageFlags::Read)
+ thread->MarkChildRead(aNewFlags & nsMsgMessageFlags::Read);
+ }
+ }
+
+ return nsMsgDBView::OnHdrFlagsChanged(aHdrChanged, aOldFlags, aNewFlags,
+ aInstigator);
+}
+
+void nsMsgSearchDBView::InsertMsgHdrAt(nsMsgViewIndex index, nsIMsgDBHdr* hdr,
+ nsMsgKey msgKey, uint32_t flags,
+ uint32_t level) {
+ if ((int32_t)index < 0) {
+ NS_ERROR("invalid insert index");
+ index = 0;
+ level = 0;
+ } else if (index > m_keys.Length()) {
+ NS_ERROR("inserting past end of array");
+ index = m_keys.Length();
+ }
+
+ m_keys.InsertElementAt(index, msgKey);
+ m_flags.InsertElementAt(index, flags);
+ m_levels.InsertElementAt(index, level);
+ nsCOMPtr<nsIMsgFolder> folder;
+ hdr->GetFolder(getter_AddRefs(folder));
+ m_folders.InsertObjectAt(folder, index);
+}
+
+void nsMsgSearchDBView::SetMsgHdrAt(nsIMsgDBHdr* hdr, nsMsgViewIndex index,
+ nsMsgKey msgKey, uint32_t flags,
+ uint32_t level) {
+ m_keys[index] = msgKey;
+ m_flags[index] = flags;
+ m_levels[index] = level;
+ nsCOMPtr<nsIMsgFolder> folder;
+ hdr->GetFolder(getter_AddRefs(folder));
+ m_folders.ReplaceObjectAt(folder, index);
+}
+
+void nsMsgSearchDBView::InsertEmptyRows(nsMsgViewIndex viewIndex,
+ int32_t numRows) {
+ for (int32_t i = 0; i < numRows; i++) {
+ m_folders.InsertObjectAt(nullptr, viewIndex + i);
+ }
+
+ return nsMsgDBView::InsertEmptyRows(viewIndex, numRows);
+}
+
+void nsMsgSearchDBView::RemoveRows(nsMsgViewIndex viewIndex, int32_t numRows) {
+ nsMsgDBView::RemoveRows(viewIndex, numRows);
+ for (int32_t i = 0; i < numRows; i++) m_folders.RemoveObjectAt(viewIndex);
+}
+
+nsresult nsMsgSearchDBView::GetMsgHdrForViewIndex(nsMsgViewIndex index,
+ nsIMsgDBHdr** msgHdr) {
+ if (index == nsMsgViewIndex_None || index >= (uint32_t)m_folders.Count()) {
+ return NS_MSG_INVALID_DBVIEW_INDEX;
+ }
+
+ nsIMsgFolder* folder = m_folders[index];
+ if (folder) {
+ nsCOMPtr<nsIMsgDatabase> db;
+ nsresult rv = folder->GetMsgDatabase(getter_AddRefs(db));
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (db) {
+ return db->GetMsgHdrForKey(m_keys[index], msgHdr);
+ }
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsMsgSearchDBView::GetFolderForViewIndex(nsMsgViewIndex index,
+ nsIMsgFolder** aFolder) {
+ NS_ENSURE_ARG_POINTER(aFolder);
+
+ if (index == nsMsgViewIndex_None || index >= (uint32_t)m_folders.Count())
+ return NS_MSG_INVALID_DBVIEW_INDEX;
+
+ NS_IF_ADDREF(*aFolder = m_folders[index]);
+ return *aFolder ? NS_OK : NS_ERROR_NULL_POINTER;
+}
+
+nsresult nsMsgSearchDBView::GetDBForViewIndex(nsMsgViewIndex index,
+ nsIMsgDatabase** db) {
+ nsCOMPtr<nsIMsgFolder> aFolder;
+ nsresult rv = GetFolderForViewIndex(index, getter_AddRefs(aFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return aFolder->GetMsgDatabase(db);
+}
+
+nsresult nsMsgSearchDBView::AddHdrFromFolder(nsIMsgDBHdr* msgHdr,
+ nsIMsgFolder* folder) {
+ if (m_viewFlags & nsMsgViewFlagsType::kGroupBySort)
+ return nsMsgGroupView::OnNewHeader(msgHdr, nsMsgKey_None, true);
+
+ nsMsgKey msgKey;
+ uint32_t msgFlags;
+ msgHdr->GetMessageKey(&msgKey);
+ msgHdr->GetFlags(&msgFlags);
+
+ if (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay) {
+ nsCOMPtr<nsIMsgThread> thread;
+ nsCOMPtr<nsIMsgDBHdr> threadRoot;
+ // If we find an xf thread in the hash table corresponding to the new msg's
+ // message id, a previous header must be a reference child of the new
+ // message, which means we need to reparent later.
+ bool msgIsReferredTo;
+ GetXFThreadFromMsgHdr(msgHdr, getter_AddRefs(thread), &msgIsReferredTo);
+ bool newThread = !thread;
+ nsMsgXFViewThread* viewThread;
+ if (!thread) {
+ viewThread = new nsMsgXFViewThread(this, m_nextThreadId++);
+ if (!viewThread) return NS_ERROR_OUT_OF_MEMORY;
+
+ thread = viewThread;
+ } else {
+ viewThread = static_cast<nsMsgXFViewThread*>(thread.get());
+ thread->GetChildHdrAt(0, getter_AddRefs(threadRoot));
+ }
+
+ AddMsgToHashTables(msgHdr, thread);
+ nsCOMPtr<nsIMsgDBHdr> parent;
+ uint32_t posInThread;
+ // We need to move threads in order to keep ourselves sorted
+ // correctly. We want the index of the original thread...we can do this by
+ // getting the root header before we add the new header, and finding that.
+ if (newThread || !viewThread->MsgCount()) {
+ viewThread->AddHdr(msgHdr, false, posInThread, getter_AddRefs(parent));
+ nsMsgViewIndex insertIndex = GetIndexForThread(msgHdr);
+ NS_ASSERTION(insertIndex == m_levels.Length() ||
+ (IsValidIndex(insertIndex) && !m_levels[insertIndex]),
+ "inserting into middle of thread");
+ if (insertIndex == nsMsgViewIndex_None)
+ return NS_MSG_INVALID_DBVIEW_INDEX;
+
+ if (!(m_viewFlags & nsMsgViewFlagsType::kExpandAll))
+ msgFlags |= nsMsgMessageFlags::Elided;
+
+ InsertMsgHdrAt(insertIndex, msgHdr, msgKey, msgFlags, 0);
+ NoteChange(insertIndex, 1, nsMsgViewNotificationCode::insertOrDelete);
+ } else {
+ // Get the thread root index before we add the header, because adding
+ // the header can change the sort position.
+ nsMsgViewIndex threadIndex = GetThreadRootIndex(threadRoot);
+ viewThread->AddHdr(msgHdr, msgIsReferredTo, posInThread,
+ getter_AddRefs(parent));
+ if (!IsValidIndex(threadIndex)) {
+ NS_ERROR("couldn't find thread index for newly inserted header");
+ // Not really OK, but not failure exactly.
+ return NS_OK;
+ }
+
+ NS_ASSERTION(!m_levels[threadIndex],
+ "threadRoot incorrect, or level incorrect");
+
+ bool moveThread = false;
+ if (m_sortType == nsMsgViewSortType::byDate) {
+ uint32_t newestMsgInThread = 0, msgDate = 0;
+ viewThread->GetNewestMsgDate(&newestMsgInThread);
+ msgHdr->GetDateInSeconds(&msgDate);
+ moveThread = (msgDate == newestMsgInThread);
+ }
+
+ OrExtraFlag(threadIndex,
+ MSG_VIEW_FLAG_HASCHILDREN | MSG_VIEW_FLAG_ISTHREAD);
+ if (!(m_flags[threadIndex] & nsMsgMessageFlags::Elided)) {
+ if (parent) {
+ // Since we know posInThread, we just want to insert the new hdr
+ // at threadIndex + posInThread, and then rebuild the view until we
+ // get to a sibling of the new hdr.
+ uint8_t newMsgLevel = viewThread->ChildLevelAt(posInThread);
+ InsertMsgHdrAt(threadIndex + posInThread, msgHdr, msgKey, msgFlags,
+ newMsgLevel);
+
+ NoteChange(threadIndex + posInThread, 1,
+ nsMsgViewNotificationCode::insertOrDelete);
+ for (nsMsgViewIndex viewIndex = threadIndex + ++posInThread;
+ posInThread < viewThread->MsgCount() &&
+ viewThread->ChildLevelAt(posInThread) > newMsgLevel;
+ viewIndex++) {
+ m_levels[viewIndex] = viewThread->ChildLevelAt(posInThread++);
+ }
+
+ } else {
+ // The new header is the root, so we need to adjust all the children.
+ InsertMsgHdrAt(threadIndex, msgHdr, msgKey, msgFlags, 0);
+
+ NoteChange(threadIndex, 1, nsMsgViewNotificationCode::insertOrDelete);
+ nsMsgViewIndex i;
+ for (i = threadIndex + 1;
+ i < m_keys.Length() && (i == threadIndex + 1 || m_levels[i]);
+ i++)
+ m_levels[i] = m_levels[i] + 1;
+ // Turn off thread flags on old root.
+ AndExtraFlag(threadIndex + 1,
+ ~(MSG_VIEW_FLAG_ISTHREAD | nsMsgMessageFlags::Elided |
+ MSG_VIEW_FLAG_HASCHILDREN));
+
+ NoteChange(threadIndex + 1, i - threadIndex + 1,
+ nsMsgViewNotificationCode::changed);
+ }
+ } else if (!parent) {
+ // New parent came into collapsed thread.
+ nsCOMPtr<nsIMsgFolder> msgFolder;
+ msgHdr->GetFolder(getter_AddRefs(msgFolder));
+ m_keys[threadIndex] = msgKey;
+ m_folders.ReplaceObjectAt(msgFolder, threadIndex);
+ m_flags[threadIndex] = msgFlags | MSG_VIEW_FLAG_ISTHREAD |
+ nsMsgMessageFlags::Elided |
+ MSG_VIEW_FLAG_HASCHILDREN;
+ NoteChange(threadIndex, 1, nsMsgViewNotificationCode::changed);
+ }
+
+ if (moveThread) MoveThreadAt(threadIndex);
+ }
+ } else {
+ m_folders.AppendObject(folder);
+ // nsMsgKey_None means it's not a valid hdr.
+ if (msgKey != nsMsgKey_None) {
+ msgHdr->GetFlags(&msgFlags);
+ m_keys.AppendElement(msgKey);
+ m_levels.AppendElement(0);
+ m_flags.AppendElement(msgFlags);
+ NoteChange(GetSize() - 1, 1, nsMsgViewNotificationCode::insertOrDelete);
+ }
+ }
+
+ return NS_OK;
+}
+
+// This method removes the thread at threadIndex from the view
+// and puts it back in its new position, determined by the sort order.
+// And, if the selection is affected, save and restore the selection.
+void nsMsgSearchDBView::MoveThreadAt(nsMsgViewIndex threadIndex) {
+ bool updatesSuppressed = mSuppressChangeNotification;
+ // Turn off tree notifications so that we don't reload the current message.
+ if (!updatesSuppressed) SetSuppressChangeNotifications(true);
+
+ nsCOMPtr<nsIMsgDBHdr> threadHdr;
+ GetMsgHdrForViewIndex(threadIndex, getter_AddRefs(threadHdr));
+
+ uint32_t saveFlags = m_flags[threadIndex];
+ bool threadIsExpanded = !(saveFlags & nsMsgMessageFlags::Elided);
+ int32_t childCount = 0;
+ nsMsgKey preservedKey;
+ AutoTArray<nsMsgKey, 1> preservedSelection;
+ int32_t selectionCount;
+ int32_t currentIndex;
+ bool hasSelection =
+ mTreeSelection &&
+ ((NS_SUCCEEDED(mTreeSelection->GetCurrentIndex(&currentIndex)) &&
+ currentIndex >= 0 && (uint32_t)currentIndex < GetSize()) ||
+ (NS_SUCCEEDED(mTreeSelection->GetRangeCount(&selectionCount)) &&
+ selectionCount > 0));
+ if (hasSelection) SaveAndClearSelection(&preservedKey, preservedSelection);
+
+ if (threadIsExpanded) {
+ ExpansionDelta(threadIndex, &childCount);
+ childCount = -childCount;
+ }
+
+ nsTArray<nsMsgKey> threadKeys;
+ nsTArray<uint32_t> threadFlags;
+ nsTArray<uint8_t> threadLevels;
+ nsCOMArray<nsIMsgFolder> threadFolders;
+
+ if (threadIsExpanded) {
+ threadKeys.SetCapacity(childCount);
+ threadFlags.SetCapacity(childCount);
+ threadLevels.SetCapacity(childCount);
+ threadFolders.SetCapacity(childCount);
+ for (nsMsgViewIndex index = threadIndex + 1;
+ index < (nsMsgViewIndex)GetSize() && m_levels[index]; index++) {
+ threadKeys.AppendElement(m_keys[index]);
+ threadFlags.AppendElement(m_flags[index]);
+ threadLevels.AppendElement(m_levels[index]);
+ threadFolders.AppendObject(m_folders[index]);
+ }
+
+ uint32_t collapseCount;
+ CollapseByIndex(threadIndex, &collapseCount);
+ }
+
+ nsMsgDBView::RemoveByIndex(threadIndex);
+ m_folders.RemoveObjectAt(threadIndex);
+ nsMsgViewIndex newIndex = GetIndexForThread(threadHdr);
+ NS_ASSERTION(newIndex == m_levels.Length() ||
+ (IsValidIndex(newIndex) && !m_levels[newIndex]),
+ "inserting into middle of thread");
+ if (newIndex == nsMsgViewIndex_None) newIndex = 0;
+
+ nsMsgKey msgKey;
+ uint32_t msgFlags;
+ threadHdr->GetMessageKey(&msgKey);
+ threadHdr->GetFlags(&msgFlags);
+ InsertMsgHdrAt(newIndex, threadHdr, msgKey, msgFlags, 0);
+
+ if (threadIsExpanded) {
+ m_keys.InsertElementsAt(newIndex + 1, threadKeys);
+ m_flags.InsertElementsAt(newIndex + 1, threadFlags);
+ m_levels.InsertElementsAt(newIndex + 1, threadLevels);
+ m_folders.InsertObjectsAt(threadFolders, newIndex + 1);
+ }
+
+ m_flags[newIndex] = saveFlags;
+ // Unfreeze selection.
+ if (hasSelection) RestoreSelection(preservedKey, preservedSelection);
+
+ if (!updatesSuppressed) SetSuppressChangeNotifications(false);
+
+ nsMsgViewIndex lowIndex = threadIndex < newIndex ? threadIndex : newIndex;
+ nsMsgViewIndex highIndex = lowIndex == threadIndex ? newIndex : threadIndex;
+ NoteChange(lowIndex, highIndex - lowIndex + childCount + 1,
+ nsMsgViewNotificationCode::changed);
+}
+
+nsresult nsMsgSearchDBView::GetMessageEnumerator(
+ nsIMsgEnumerator** enumerator) {
+ // We do not have an m_db, so the default behavior (in nsMsgDBView) is not
+ // what we want (it will crash). We just want someone to enumerate the
+ // headers that we already have. Conveniently, nsMsgDBView already knows
+ // how to do this with its view enumerator, so we just use that.
+ return nsMsgDBView::GetViewEnumerator(enumerator);
+}
+
+nsresult nsMsgSearchDBView::InsertHdrFromFolder(nsIMsgDBHdr* msgHdr,
+ nsIMsgFolder* folder) {
+ nsMsgViewIndex insertIndex = nsMsgViewIndex_None;
+ // Threaded view always needs to go through AddHdrFromFolder since
+ // it handles the xf view thread object creation.
+ if (!(m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay))
+ insertIndex = GetInsertIndex(msgHdr);
+
+ if (insertIndex == nsMsgViewIndex_None)
+ return AddHdrFromFolder(msgHdr, folder);
+
+ nsMsgKey msgKey;
+ uint32_t msgFlags;
+ msgHdr->GetMessageKey(&msgKey);
+ msgHdr->GetFlags(&msgFlags);
+ InsertMsgHdrAt(insertIndex, msgHdr, msgKey, msgFlags, 0);
+
+ // The call to NoteChange() has to happen after we add the key as
+ // NoteChange() will call RowCountChanged() which will call our GetRowCount().
+ NoteChange(insertIndex, 1, nsMsgViewNotificationCode::insertOrDelete);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgSearchDBView::OnSearchHit(nsIMsgDBHdr* aMsgHdr, nsIMsgFolder* folder) {
+ NS_ENSURE_ARG(aMsgHdr);
+ NS_ENSURE_ARG(folder);
+
+ if (m_folders.IndexOf(folder) < 0)
+ // Do this just for new folder.
+ {
+ nsCOMPtr<nsIMsgDatabase> dbToUse;
+ nsCOMPtr<nsIDBFolderInfo> folderInfo;
+ folder->GetDBFolderInfoAndDB(getter_AddRefs(folderInfo),
+ getter_AddRefs(dbToUse));
+ if (dbToUse) {
+ dbToUse->AddListener(this);
+ m_dbToUseList.AppendObject(dbToUse);
+ }
+ }
+
+ m_totalMessagesInView++;
+ if (m_sortValid)
+ return InsertHdrFromFolder(aMsgHdr, folder);
+ else
+ return AddHdrFromFolder(aMsgHdr, folder);
+}
+
+NS_IMETHODIMP
+nsMsgSearchDBView::OnSearchDone(nsresult status) {
+ // We want to set imap delete model once the search is over because setting
+ // next message after deletion will happen before deleting the message and
+ // search scope can change with every search.
+
+ // Set to default in case it is non-imap folder.
+ mDeleteModel = nsMsgImapDeleteModels::MoveToTrash;
+ nsIMsgFolder* curFolder = m_folders.SafeObjectAt(0);
+ if (curFolder) GetImapDeleteModel(curFolder);
+
+ return NS_OK;
+}
+
+// For now also acts as a way of resetting the search datasource.
+NS_IMETHODIMP
+nsMsgSearchDBView::OnNewSearch() {
+ int32_t oldSize = GetSize();
+
+ int32_t count = m_dbToUseList.Count();
+ for (int32_t j = 0; j < count; j++) m_dbToUseList[j]->RemoveListener(this);
+
+ m_dbToUseList.Clear();
+ m_folders.Clear();
+ m_keys.Clear();
+ m_levels.Clear();
+ m_flags.Clear();
+ m_totalMessagesInView = 0;
+
+ // Needs to happen after we remove the keys, since RowCountChanged() will
+ // call our GetRowCount().
+ if (mTree) mTree->RowCountChanged(0, -oldSize);
+ if (mJSTree) mJSTree->RowCountChanged(0, -oldSize);
+
+ // mSearchResults->Clear();
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgSearchDBView::GetViewType(nsMsgViewTypeValue* aViewType) {
+ NS_ENSURE_ARG_POINTER(aViewType);
+ *aViewType = nsMsgViewType::eShowSearch;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgSearchDBView::SetSearchSession(nsIMsgSearchSession* aSession) {
+ m_searchSession = do_GetWeakReference(aSession);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgSearchDBView::OnAnnouncerGoingAway(
+ nsIDBChangeAnnouncer* instigator) {
+ nsIMsgDatabase* db = static_cast<nsIMsgDatabase*>(instigator);
+ if (db) {
+ db->RemoveListener(this);
+ m_dbToUseList.RemoveObject(db);
+ }
+
+ return NS_OK;
+}
+
+nsCOMArray<nsIMsgFolder>* nsMsgSearchDBView::GetFolders() { return &m_folders; }
+
+NS_IMETHODIMP
+nsMsgSearchDBView::GetCommandStatus(
+ nsMsgViewCommandTypeValue command, bool* selectable_p,
+ nsMsgViewCommandCheckStateValue* selected_p) {
+ if (command != nsMsgViewCommandType::runJunkControls)
+ return nsMsgDBView::GetCommandStatus(command, selectable_p, selected_p);
+
+ *selectable_p = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgSearchDBView::DoCommandWithFolder(nsMsgViewCommandTypeValue command,
+ nsIMsgFolder* destFolder) {
+ mCommand = command;
+ mDestFolder = destFolder;
+ return nsMsgDBView::DoCommandWithFolder(command, destFolder);
+}
+
+NS_IMETHODIMP nsMsgSearchDBView::DoCommand(nsMsgViewCommandTypeValue command) {
+ mCommand = command;
+ if (command == nsMsgViewCommandType::deleteMsg ||
+ command == nsMsgViewCommandType::deleteNoTrash ||
+ command == nsMsgViewCommandType::selectAll ||
+ command == nsMsgViewCommandType::selectThread ||
+ command == nsMsgViewCommandType::selectFlagged ||
+ command == nsMsgViewCommandType::expandAll ||
+ command == nsMsgViewCommandType::collapseAll)
+ return nsMsgDBView::DoCommand(command);
+
+ nsresult rv = NS_OK;
+ nsMsgViewIndexArray selection;
+ GetIndicesForSelection(selection);
+
+ // We need to break apart the selection by folders, and then call
+ // ApplyCommandToIndices with the command and the indices in the
+ // selection that are from that folder.
+
+ mozilla::UniquePtr<nsTArray<nsMsgViewIndex>[]> indexArrays;
+ int32_t numArrays;
+ rv = PartitionSelectionByFolder(selection, indexArrays, &numArrays);
+ NS_ENSURE_SUCCESS(rv, rv);
+ for (int32_t folderIndex = 0; folderIndex < numArrays; folderIndex++) {
+ rv = ApplyCommandToIndices(command, (indexArrays.get())[folderIndex]);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return rv;
+}
+
+// This method removes the specified line from the view, and adjusts the
+// various flags and levels of affected messages.
+nsresult nsMsgSearchDBView::RemoveByIndex(nsMsgViewIndex index) {
+ if (!IsValidIndex(index)) return NS_MSG_INVALID_DBVIEW_INDEX;
+
+ if (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay) {
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ nsCOMPtr<nsIMsgThread> thread;
+ nsresult rv = GetMsgHdrForViewIndex(index, getter_AddRefs(msgHdr));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ GetXFThreadFromMsgHdr(msgHdr, getter_AddRefs(thread));
+ if (thread) {
+ nsMsgXFViewThread* viewThread =
+ static_cast<nsMsgXFViewThread*>(thread.get());
+ if (viewThread->MsgCount() == 2) {
+ // If we removed the next to last message in the thread,
+ // we need to adjust the flags on the first message in the thread.
+ nsMsgViewIndex threadIndex = m_levels[index] ? index - 1 : index;
+ if (threadIndex != nsMsgViewIndex_None) {
+ AndExtraFlag(threadIndex,
+ ~(MSG_VIEW_FLAG_ISTHREAD | nsMsgMessageFlags::Elided |
+ MSG_VIEW_FLAG_HASCHILDREN));
+ m_levels[threadIndex] = 0;
+ NoteChange(threadIndex, 1, nsMsgViewNotificationCode::changed);
+ }
+ }
+
+ // Bump up the level of all the descendents of the message
+ // that was removed, if the thread was expanded.
+ uint8_t removedLevel = m_levels[index];
+ nsMsgViewIndex i = index + 1;
+ if (i < m_levels.Length() && m_levels[i] > removedLevel) {
+ // Promote the child of the removed message.
+ uint8_t promotedLevel = m_levels[i];
+ m_levels[i] = promotedLevel - 1;
+ i++;
+ // Now promote all the children of the promoted message.
+ for (; i < m_levels.Length() && m_levels[i] > promotedLevel; i++)
+ m_levels[i] = m_levels[i] - 1;
+ }
+ }
+ }
+
+ m_folders.RemoveObjectAt(index);
+ return nsMsgDBView::RemoveByIndex(index);
+}
+
+NS_IMETHODIMP nsMsgSearchDBView::ApplyCommandToIndices(
+ nsMsgViewCommandTypeValue command,
+ nsTArray<nsMsgViewIndex> const& selection) {
+ mCommand = command;
+ return nsMsgDBView::ApplyCommandToIndices(command, selection);
+}
+
+nsresult nsMsgSearchDBView::DeleteMessages(
+ nsIMsgWindow* window, nsTArray<nsMsgViewIndex> const& selection,
+ bool deleteStorage) {
+ nsresult rv = GetFoldersAndHdrsForSelection(selection);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (mDeleteModel != nsMsgImapDeleteModels::MoveToTrash) deleteStorage = true;
+
+ if (mDeleteModel != nsMsgImapDeleteModels::IMAPDelete) m_deletingRows = true;
+
+ // Remember the deleted messages in case the user undoes the delete,
+ // and we want to restore the hdr to the view, even if it no
+ // longer matches the search criteria.
+ for (nsMsgViewIndex viewIndex : selection) {
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ (void)GetMsgHdrForViewIndex(viewIndex, getter_AddRefs(msgHdr));
+ if (msgHdr) {
+ RememberDeletedMsgHdr(msgHdr);
+ }
+
+ // If we are deleting rows, save off the view indices.
+ if (m_deletingRows) {
+ mIndicesToNoteChange.AppendElement(viewIndex);
+ }
+ }
+ rv = deleteStorage ? ProcessRequestsInAllFolders(window)
+ : ProcessRequestsInOneFolder(window);
+ if (NS_FAILED(rv)) m_deletingRows = false;
+
+ return rv;
+}
+
+nsresult nsMsgSearchDBView::CopyMessages(
+ nsIMsgWindow* window, nsTArray<nsMsgViewIndex> const& selection,
+ bool isMove, nsIMsgFolder* destFolder) {
+ GetFoldersAndHdrsForSelection(selection);
+ return ProcessRequestsInOneFolder(window);
+}
+
+nsresult nsMsgSearchDBView::PartitionSelectionByFolder(
+ nsTArray<nsMsgViewIndex> const& selection,
+ mozilla::UniquePtr<nsTArray<nsMsgViewIndex>[]>& indexArrays,
+ int32_t* numArrays) {
+ nsCOMArray<nsIMsgFolder> uniqueFoldersSelected;
+ nsTArray<uint32_t> numIndicesSelected;
+ mCurIndex = 0;
+
+ // Build unique folder list based on headers selected by the user.
+ for (nsMsgViewIndex viewIndex : selection) {
+ nsIMsgFolder* curFolder = m_folders[viewIndex];
+ int32_t folderIndex = uniqueFoldersSelected.IndexOf(curFolder);
+ if (folderIndex < 0) {
+ uniqueFoldersSelected.AppendObject(curFolder);
+ numIndicesSelected.AppendElement(1);
+ } else {
+ numIndicesSelected[folderIndex]++;
+ }
+ }
+
+ int32_t numFolders = uniqueFoldersSelected.Count();
+ indexArrays = mozilla::MakeUnique<nsTArray<nsMsgViewIndex>[]>(numFolders);
+ *numArrays = numFolders;
+ NS_ENSURE_TRUE(indexArrays, NS_ERROR_OUT_OF_MEMORY);
+ for (int32_t folderIndex = 0; folderIndex < numFolders; folderIndex++) {
+ (indexArrays.get())[folderIndex].SetCapacity(
+ numIndicesSelected[folderIndex]);
+ }
+ for (nsMsgViewIndex viewIndex : selection) {
+ nsIMsgFolder* curFolder = m_folders[viewIndex];
+ int32_t folderIndex = uniqueFoldersSelected.IndexOf(curFolder);
+ (indexArrays.get())[folderIndex].AppendElement(viewIndex);
+ }
+ return NS_OK;
+}
+
+nsresult nsMsgSearchDBView::GetFoldersAndHdrsForSelection(
+ nsTArray<nsMsgViewIndex> const& selection) {
+ nsresult rv = NS_OK;
+ mCurIndex = 0;
+ m_uniqueFoldersSelected.Clear();
+ m_hdrsForEachFolder.Clear();
+
+ AutoTArray<RefPtr<nsIMsgDBHdr>, 1> messages;
+ rv = GetHeadersFromSelection(selection, messages);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Build unique folder list based on headers selected by the user.
+ for (nsIMsgDBHdr* hdr : messages) {
+ nsCOMPtr<nsIMsgFolder> curFolder;
+ hdr->GetFolder(getter_AddRefs(curFolder));
+ if (m_uniqueFoldersSelected.IndexOf(curFolder) < 0) {
+ m_uniqueFoldersSelected.AppendObject(curFolder);
+ }
+ }
+
+ // Group the headers selected by each folder.
+ uint32_t numFolders = m_uniqueFoldersSelected.Count();
+ for (uint32_t folderIndex = 0; folderIndex < numFolders; folderIndex++) {
+ nsIMsgFolder* curFolder = m_uniqueFoldersSelected[folderIndex];
+ nsTArray<RefPtr<nsIMsgDBHdr>> msgHdrsForOneFolder;
+ for (nsIMsgDBHdr* hdr : messages) {
+ nsCOMPtr<nsIMsgFolder> msgFolder;
+ hdr->GetFolder(getter_AddRefs(msgFolder));
+ if (NS_SUCCEEDED(rv) && msgFolder && msgFolder == curFolder) {
+ msgHdrsForOneFolder.AppendElement(hdr);
+ }
+ }
+
+ m_hdrsForEachFolder.AppendElement(msgHdrsForOneFolder.Clone());
+ }
+
+ return rv;
+}
+
+nsresult nsMsgSearchDBView::ApplyCommandToIndicesWithFolder(
+ nsMsgViewCommandTypeValue command,
+ nsTArray<nsMsgViewIndex> const& selection, nsIMsgFolder* destFolder) {
+ mCommand = command;
+ mDestFolder = destFolder;
+ return nsMsgDBView::ApplyCommandToIndicesWithFolder(command, selection,
+ destFolder);
+}
+
+// nsIMsgCopyServiceListener methods
+
+NS_IMETHODIMP
+nsMsgSearchDBView::OnStartCopy() { return NS_OK; }
+
+NS_IMETHODIMP
+nsMsgSearchDBView::OnProgress(uint32_t aProgress, uint32_t aProgressMax) {
+ return NS_OK;
+}
+
+// Believe it or not, these next two are msgcopyservice listener methods!
+NS_IMETHODIMP
+nsMsgSearchDBView::SetMessageKey(nsMsgKey aMessageKey) { return NS_OK; }
+
+NS_IMETHODIMP
+nsMsgSearchDBView::GetMessageId(nsACString& messageId) { return NS_OK; }
+
+NS_IMETHODIMP
+nsMsgSearchDBView::OnStopCopy(nsresult aStatus) {
+ if (NS_SUCCEEDED(aStatus)) {
+ mCurIndex++;
+ if ((int32_t)mCurIndex < m_uniqueFoldersSelected.Count()) {
+ nsCOMPtr<nsIMsgWindow> msgWindow(do_QueryReferent(mMsgWindowWeak));
+ ProcessRequestsInOneFolder(msgWindow);
+ }
+ }
+
+ return NS_OK;
+}
+
+// End nsIMsgCopyServiceListener methods.
+
+nsresult nsMsgSearchDBView::ProcessRequestsInOneFolder(nsIMsgWindow* window) {
+ nsresult rv = NS_OK;
+
+ // Folder operations like copy/move are not implemented for .eml files.
+ if (m_uniqueFoldersSelected.Count() == 0) return NS_ERROR_NOT_IMPLEMENTED;
+
+ nsIMsgFolder* curFolder = m_uniqueFoldersSelected[mCurIndex];
+ NS_ASSERTION(curFolder, "curFolder is null");
+ nsTArray<RefPtr<nsIMsgDBHdr>> const& msgs = m_hdrsForEachFolder[mCurIndex];
+
+ // called for delete with trash, copy and move
+ if (mCommand == nsMsgViewCommandType::deleteMsg)
+ curFolder->DeleteMessages(msgs, window, false /* delete storage */,
+ false /* is move*/, this, true /*allowUndo*/);
+ else {
+ NS_ASSERTION(!(curFolder == mDestFolder),
+ "The source folder and the destination folder are the same");
+ if (NS_SUCCEEDED(rv) && curFolder != mDestFolder) {
+ nsCOMPtr<nsIMsgCopyService> copyService =
+ do_GetService("@mozilla.org/messenger/messagecopyservice;1", &rv);
+ if (NS_SUCCEEDED(rv)) {
+ if (mCommand == nsMsgViewCommandType::moveMessages)
+ copyService->CopyMessages(curFolder, msgs, mDestFolder,
+ true /* isMove */, this, window,
+ true /*allowUndo*/);
+ else if (mCommand == nsMsgViewCommandType::copyMessages)
+ copyService->CopyMessages(curFolder, msgs, mDestFolder,
+ false /* isMove */, this, window,
+ true /*allowUndo*/);
+ }
+ }
+ }
+
+ return rv;
+}
+
+nsresult nsMsgSearchDBView::ProcessRequestsInAllFolders(nsIMsgWindow* window) {
+ uint32_t numFolders = m_uniqueFoldersSelected.Count();
+ for (uint32_t folderIndex = 0; folderIndex < numFolders; folderIndex++) {
+ nsIMsgFolder* curFolder = m_uniqueFoldersSelected[folderIndex];
+ NS_ASSERTION(curFolder, "curFolder is null");
+ curFolder->DeleteMessages(
+ m_hdrsForEachFolder[folderIndex], window, true /* delete storage */,
+ false /* is move*/, nullptr /*copyServListener*/, false /*allowUndo*/);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgSearchDBView::Sort(nsMsgViewSortTypeValue sortType,
+ nsMsgViewSortOrderValue sortOrder) {
+ if (!m_checkedCustomColumns && CustomColumnsInSortAndNotRegistered())
+ return NS_OK;
+
+ int32_t rowCountBeforeSort = GetSize();
+
+ if (!rowCountBeforeSort) return NS_OK;
+
+ if (m_viewFlags & (nsMsgViewFlagsType::kThreadedDisplay |
+ nsMsgViewFlagsType::kGroupBySort)) {
+ // ### This forgets which threads were expanded, and is sub-optimal
+ // since it rebuilds the thread objects.
+ m_sortType = sortType;
+ m_sortOrder = sortOrder;
+ return RebuildView(m_viewFlags);
+ }
+
+ nsMsgKey preservedKey;
+ AutoTArray<nsMsgKey, 1> preservedSelection;
+ SaveAndClearSelection(&preservedKey, preservedSelection);
+
+ nsresult rv = nsMsgDBView::Sort(sortType, sortOrder);
+ // The sort may have changed the number of rows before we restore the
+ // selection, tell the tree do this before we call restore selection.
+ // This is safe when there is no selection.
+ rv = AdjustRowCount(rowCountBeforeSort, GetSize());
+
+ RestoreSelection(preservedKey, preservedSelection);
+ if (mTree) mTree->Invalidate();
+ if (mJSTree) mJSTree->Invalidate();
+
+ NS_ENSURE_SUCCESS(rv, rv);
+ return rv;
+}
+
+// If nothing selected, return an NS_ERROR.
+NS_IMETHODIMP
+nsMsgSearchDBView::GetHdrForFirstSelectedMessage(nsIMsgDBHdr** hdr) {
+ NS_ENSURE_ARG_POINTER(hdr);
+ nsMsgViewIndex index;
+ nsresult rv = GetViewIndexForFirstSelectedMsg(&index);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return GetMsgHdrForViewIndex(index, hdr);
+}
+
+NS_IMETHODIMP
+nsMsgSearchDBView::OpenWithHdrs(nsIMsgEnumerator* aHeaders,
+ nsMsgViewSortTypeValue aSortType,
+ nsMsgViewSortOrderValue aSortOrder,
+ nsMsgViewFlagsTypeValue aViewFlags,
+ int32_t* aCount) {
+ if (aViewFlags & nsMsgViewFlagsType::kGroupBySort)
+ return nsMsgGroupView::OpenWithHdrs(aHeaders, aSortType, aSortOrder,
+ aViewFlags, aCount);
+
+ m_sortType = aSortType;
+ m_sortOrder = aSortOrder;
+ m_viewFlags = aViewFlags;
+ SaveSortInfo(m_sortType, m_sortOrder);
+
+ bool hasMore;
+ nsresult rv = NS_OK;
+ while (NS_SUCCEEDED(rv) &&
+ NS_SUCCEEDED(rv = aHeaders->HasMoreElements(&hasMore)) && hasMore) {
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ nsCOMPtr<nsIMsgFolder> folder;
+ rv = aHeaders->GetNext(getter_AddRefs(msgHdr));
+ if (NS_SUCCEEDED(rv) && msgHdr) {
+ msgHdr->GetFolder(getter_AddRefs(folder));
+ AddHdrFromFolder(msgHdr, folder);
+ }
+ }
+
+ *aCount = m_keys.Length();
+ return rv;
+}
+
+nsresult nsMsgSearchDBView::GetFolderFromMsgURI(const nsACString& aMsgURI,
+ nsIMsgFolder** aFolder) {
+ nsCOMPtr<nsIMsgMessageService> msgMessageService;
+ nsresult rv =
+ GetMessageServiceFromURI(aMsgURI, getter_AddRefs(msgMessageService));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ rv = msgMessageService->MessageURIToMsgHdr(aMsgURI, getter_AddRefs(msgHdr));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return msgHdr->GetFolder(aFolder);
+}
+
+nsMsgViewIndex nsMsgSearchDBView::FindHdr(nsIMsgDBHdr* msgHdr,
+ nsMsgViewIndex startIndex,
+ bool allowDummy) {
+ nsCOMPtr<nsIMsgDBHdr> curHdr;
+ uint32_t index;
+ // It would be nice to take advantage of sorted views when possible.
+ for (index = startIndex; index < GetSize(); index++) {
+ GetMsgHdrForViewIndex(index, getter_AddRefs(curHdr));
+ if (curHdr == msgHdr &&
+ (allowDummy || !(m_flags[index] & MSG_VIEW_FLAG_DUMMY) ||
+ (m_flags[index] & nsMsgMessageFlags::Elided)))
+ break;
+ }
+
+ return index < GetSize() ? index : nsMsgViewIndex_None;
+}
+
+// This method looks for the XF thread that corresponds to this message hdr,
+// first by looking up the message id, then references, and finally, if subject
+// threading is turned on, the subject.
+nsresult nsMsgSearchDBView::GetXFThreadFromMsgHdr(nsIMsgDBHdr* msgHdr,
+ nsIMsgThread** pThread,
+ bool* foundByMessageId) {
+ NS_ENSURE_ARG_POINTER(msgHdr);
+ NS_ENSURE_ARG_POINTER(pThread);
+
+ nsAutoCString messageId;
+ msgHdr->GetMessageId(getter_Copies(messageId));
+ *pThread = nullptr;
+ m_threadsTable.Get(messageId, pThread);
+ // The caller may want to know if we found the thread by the msgHdr's
+ // messageId.
+ if (foundByMessageId) *foundByMessageId = *pThread != nullptr;
+
+ if (!*pThread) {
+ uint16_t numReferences = 0;
+ msgHdr->GetNumReferences(&numReferences);
+ for (int32_t i = numReferences - 1; i >= 0 && !*pThread; i--) {
+ nsAutoCString reference;
+ msgHdr->GetStringReference(i, reference);
+ if (reference.IsEmpty()) break;
+
+ m_threadsTable.Get(reference, pThread);
+ }
+ }
+
+ // If we're threading by subject, and we couldn't find the thread by ref,
+ // just treat subject as an other ref.
+ if (!*pThread && !gReferenceOnlyThreading) {
+ nsCString subject;
+ msgHdr->GetSubject(subject);
+ // This is the raw rfc822 subject header, so this is OK.
+ m_threadsTable.Get(subject, pThread);
+ }
+
+ return (*pThread) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+bool nsMsgSearchDBView::GetMsgHdrFromHash(nsCString& reference,
+ nsIMsgDBHdr** hdr) {
+ return m_hdrsTable.Get(reference, hdr);
+}
+
+bool nsMsgSearchDBView::GetThreadFromHash(nsCString& reference,
+ nsIMsgThread** thread) {
+ return m_threadsTable.Get(reference, thread);
+}
+
+nsresult nsMsgSearchDBView::AddRefToHash(nsCString& reference,
+ nsIMsgThread* thread) {
+ // Check if this reference is already is associated with a thread;
+ // If so, don't overwrite that association.
+ nsCOMPtr<nsIMsgThread> oldThread;
+ m_threadsTable.Get(reference, getter_AddRefs(oldThread));
+ if (oldThread) return NS_OK;
+
+ m_threadsTable.InsertOrUpdate(reference, thread);
+ return NS_OK;
+}
+
+nsresult nsMsgSearchDBView::AddMsgToHashTables(nsIMsgDBHdr* msgHdr,
+ nsIMsgThread* thread) {
+ NS_ENSURE_ARG_POINTER(msgHdr);
+
+ uint16_t numReferences = 0;
+ nsresult rv;
+
+ msgHdr->GetNumReferences(&numReferences);
+ for (int32_t i = 0; i < numReferences; i++) {
+ nsAutoCString reference;
+ msgHdr->GetStringReference(i, reference);
+ if (reference.IsEmpty()) break;
+
+ rv = AddRefToHash(reference, thread);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ nsCString messageId;
+ msgHdr->GetMessageId(getter_Copies(messageId));
+ m_hdrsTable.InsertOrUpdate(messageId, msgHdr);
+ if (!gReferenceOnlyThreading) {
+ nsCString subject;
+ msgHdr->GetSubject(subject);
+ // if we're threading by subject, just treat subject as an other ref.
+ AddRefToHash(subject, thread);
+ }
+
+ return AddRefToHash(messageId, thread);
+}
+
+nsresult nsMsgSearchDBView::RemoveRefFromHash(nsCString& reference) {
+ m_threadsTable.Remove(reference);
+ return NS_OK;
+}
+
+nsresult nsMsgSearchDBView::RemoveMsgFromHashTables(nsIMsgDBHdr* msgHdr) {
+ NS_ENSURE_ARG_POINTER(msgHdr);
+
+ uint16_t numReferences = 0;
+ nsresult rv = NS_OK;
+
+ msgHdr->GetNumReferences(&numReferences);
+
+ for (int32_t i = 0; i < numReferences; i++) {
+ nsAutoCString reference;
+ msgHdr->GetStringReference(i, reference);
+ if (reference.IsEmpty()) break;
+
+ rv = RemoveRefFromHash(reference);
+ if (NS_FAILED(rv)) break;
+ }
+
+ nsCString messageId;
+ msgHdr->GetMessageId(getter_Copies(messageId));
+ m_hdrsTable.Remove(messageId);
+ RemoveRefFromHash(messageId);
+ if (!gReferenceOnlyThreading) {
+ nsCString subject;
+ msgHdr->GetSubject(subject);
+ // If we're threading by subject, just treat subject as an other ref.
+ RemoveRefFromHash(subject);
+ }
+
+ return rv;
+}
+
+nsMsgGroupThread* nsMsgSearchDBView::CreateGroupThread(
+ nsIMsgDatabase* /* db */) {
+ return new nsMsgXFGroupThread();
+}
+
+NS_IMETHODIMP
+nsMsgSearchDBView::GetThreadContainingMsgHdr(nsIMsgDBHdr* msgHdr,
+ nsIMsgThread** pThread) {
+ if (m_viewFlags & nsMsgViewFlagsType::kGroupBySort)
+ return nsMsgGroupView::GetThreadContainingMsgHdr(msgHdr, pThread);
+ else if (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay)
+ return GetXFThreadFromMsgHdr(msgHdr, pThread);
+
+ // If not threaded, use the real thread.
+ nsCOMPtr<nsIMsgDatabase> msgDB;
+ nsresult rv = GetDBForHeader(msgHdr, getter_AddRefs(msgDB));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return msgDB->GetThreadContainingMsgHdr(msgHdr, pThread);
+}
+
+nsresult nsMsgSearchDBView::ListIdsInThread(
+ nsIMsgThread* threadHdr, nsMsgViewIndex startOfThreadViewIndex,
+ uint32_t* pNumListed) {
+ NS_ENSURE_ARG_POINTER(threadHdr);
+ NS_ENSURE_ARG_POINTER(pNumListed);
+
+ // These children ids should be in thread order.
+ uint32_t i;
+ nsMsgViewIndex viewIndex = startOfThreadViewIndex + 1;
+ *pNumListed = 0;
+
+ uint32_t numChildren;
+ threadHdr->GetNumChildren(&numChildren);
+ NS_ASSERTION(numChildren, "Empty thread in view/db");
+ if (!numChildren) return NS_OK;
+
+ // Account for the existing thread root.
+ numChildren--;
+ InsertEmptyRows(viewIndex, numChildren);
+
+ bool threadedView = m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay &&
+ !(m_viewFlags & nsMsgViewFlagsType::kGroupBySort);
+ nsMsgXFViewThread* viewThread;
+ if (threadedView) viewThread = static_cast<nsMsgXFViewThread*>(threadHdr);
+
+ for (i = 1; i <= numChildren; i++) {
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ threadHdr->GetChildHdrAt(i, getter_AddRefs(msgHdr));
+
+ if (msgHdr) {
+ nsMsgKey msgKey;
+ uint32_t msgFlags;
+ msgHdr->GetMessageKey(&msgKey);
+ msgHdr->GetFlags(&msgFlags);
+ uint8_t level = (threadedView) ? viewThread->ChildLevelAt(i) : 1;
+ SetMsgHdrAt(msgHdr, viewIndex, msgKey, msgFlags & ~MSG_VIEW_FLAGS, level);
+ (*pNumListed)++;
+ viewIndex++;
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgSearchDBView::GetNumMsgsInView(int32_t* aNumMsgs) {
+ NS_ENSURE_ARG_POINTER(aNumMsgs);
+ *aNumMsgs = m_totalMessagesInView;
+ return NS_OK;
+}
diff --git a/comm/mailnews/base/src/nsMsgSearchDBView.h b/comm/mailnews/base/src/nsMsgSearchDBView.h
new file mode 100644
index 0000000000..3dd363b58f
--- /dev/null
+++ b/comm/mailnews/base/src/nsMsgSearchDBView.h
@@ -0,0 +1,182 @@
+/* -*- 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 _nsMsgSearchDBViews_H_
+#define _nsMsgSearchDBViews_H_
+
+#include "mozilla/Attributes.h"
+#include "nsMsgGroupView.h"
+#include "nsIMsgCopyServiceListener.h"
+#include "nsIMsgSearchNotify.h"
+#include "nsMsgXFViewThread.h"
+#include "nsCOMArray.h"
+#include "mozilla/UniquePtr.h"
+#include "nsIWeakReferenceUtils.h"
+
+class nsMsgSearchDBView : public nsMsgGroupView,
+ public nsIMsgCopyServiceListener,
+ public nsIMsgSearchNotify {
+ public:
+ nsMsgSearchDBView();
+
+ // these are tied together pretty intimately
+ friend class nsMsgXFViewThread;
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIMSGSEARCHNOTIFY
+ NS_DECL_NSIMSGCOPYSERVICELISTENER
+
+ NS_IMETHOD SetSearchSession(nsIMsgSearchSession* aSearchSession) override;
+
+ virtual const char* GetViewName(void) override { return "SearchView"; }
+ NS_IMETHOD Open(nsIMsgFolder* folder, nsMsgViewSortTypeValue sortType,
+ nsMsgViewSortOrderValue sortOrder,
+ nsMsgViewFlagsTypeValue viewFlags, int32_t* pCount) override;
+ NS_IMETHOD CloneDBView(nsIMessenger* aMessengerInstance,
+ nsIMsgWindow* aMsgWindow,
+ nsIMsgDBViewCommandUpdater* aCmdUpdater,
+ nsIMsgDBView** _retval) override;
+ NS_IMETHOD CopyDBView(nsMsgDBView* aNewMsgDBView,
+ nsIMessenger* aMessengerInstance,
+ nsIMsgWindow* aMsgWindow,
+ nsIMsgDBViewCommandUpdater* aCmdUpdater) override;
+ NS_IMETHOD Close() override;
+ NS_IMETHOD GetViewType(nsMsgViewTypeValue* aViewType) override;
+ NS_IMETHOD Sort(nsMsgViewSortTypeValue sortType,
+ nsMsgViewSortOrderValue sortOrder) override;
+ NS_IMETHOD GetCommandStatus(
+ nsMsgViewCommandTypeValue command, bool* selectable_p,
+ nsMsgViewCommandCheckStateValue* selected_p) override;
+ NS_IMETHOD DoCommand(nsMsgViewCommandTypeValue command) override;
+ NS_IMETHOD DoCommandWithFolder(nsMsgViewCommandTypeValue command,
+ nsIMsgFolder* destFolder) override;
+ NS_IMETHOD GetHdrForFirstSelectedMessage(nsIMsgDBHdr** hdr) override;
+ NS_IMETHOD OpenWithHdrs(nsIMsgEnumerator* aHeaders,
+ nsMsgViewSortTypeValue aSortType,
+ nsMsgViewSortOrderValue aSortOrder,
+ nsMsgViewFlagsTypeValue aViewFlags,
+ int32_t* aCount) override;
+ NS_IMETHOD OnHdrDeleted(nsIMsgDBHdr* aHdrDeleted, nsMsgKey aParentKey,
+ int32_t aFlags,
+ nsIDBChangeListener* aInstigator) override;
+ NS_IMETHOD OnHdrFlagsChanged(nsIMsgDBHdr* aHdrChanged, uint32_t aOldFlags,
+ uint32_t aNewFlags,
+ nsIDBChangeListener* aInstigator) override;
+ NS_IMETHOD GetNumMsgsInView(int32_t* aNumMsgs) override;
+ // override to get location
+ NS_IMETHOD GetCellText(int32_t aRow, nsTreeColumn* aCol,
+ nsAString& aValue) override;
+ virtual nsresult GetMsgHdrForViewIndex(nsMsgViewIndex index,
+ nsIMsgDBHdr** msgHdr) override;
+ virtual nsresult OnNewHeader(nsIMsgDBHdr* newHdr, nsMsgKey parentKey,
+ bool ensureListed) override;
+ NS_IMETHOD GetFolderForViewIndex(nsMsgViewIndex index,
+ nsIMsgFolder** folder) override;
+
+ NS_IMETHOD OnAnnouncerGoingAway(nsIDBChangeAnnouncer* instigator) override;
+
+ virtual nsCOMArray<nsIMsgFolder>* GetFolders() override;
+ virtual nsresult GetFolderFromMsgURI(const nsACString& aMsgURI,
+ nsIMsgFolder** aFolder) override;
+
+ NS_IMETHOD GetThreadContainingMsgHdr(nsIMsgDBHdr* msgHdr,
+ nsIMsgThread** pThread) override;
+
+ NS_IMETHOD ApplyCommandToIndices(
+ nsMsgViewCommandTypeValue command,
+ nsTArray<nsMsgViewIndex> const& selection) override;
+
+ protected:
+ virtual ~nsMsgSearchDBView();
+ virtual void InternalClose() override;
+ virtual nsresult HashHdr(nsIMsgDBHdr* msgHdr, nsString& aHashKey) override;
+ virtual nsresult ListIdsInThread(nsIMsgThread* threadHdr,
+ nsMsgViewIndex startOfThreadViewIndex,
+ uint32_t* pNumListed) override;
+ nsresult FetchLocation(int32_t aRow, nsAString& aLocationString);
+ virtual nsresult AddHdrFromFolder(nsIMsgDBHdr* msgHdr, nsIMsgFolder* folder);
+ virtual nsresult GetDBForViewIndex(nsMsgViewIndex index,
+ nsIMsgDatabase** db) override;
+ virtual nsresult RemoveByIndex(nsMsgViewIndex index) override;
+ virtual nsresult CopyMessages(nsIMsgWindow* window,
+ nsTArray<nsMsgViewIndex> const& selection,
+ bool isMove, nsIMsgFolder* destFolder) override;
+ virtual nsresult DeleteMessages(nsIMsgWindow* window,
+ nsTArray<nsMsgViewIndex> const& selection,
+ bool deleteStorage) override;
+ virtual void InsertMsgHdrAt(nsMsgViewIndex index, nsIMsgDBHdr* hdr,
+ nsMsgKey msgKey, uint32_t flags,
+ uint32_t level) override;
+ virtual void SetMsgHdrAt(nsIMsgDBHdr* hdr, nsMsgViewIndex index,
+ nsMsgKey msgKey, uint32_t flags,
+ uint32_t level) override;
+ virtual void InsertEmptyRows(nsMsgViewIndex viewIndex,
+ int32_t numRows) override;
+ virtual void RemoveRows(nsMsgViewIndex viewIndex, int32_t numRows) override;
+ virtual nsMsgViewIndex FindHdr(nsIMsgDBHdr* msgHdr,
+ nsMsgViewIndex startIndex = 0,
+ bool allowDummy = false) override;
+ nsresult GetFoldersAndHdrsForSelection(
+ nsTArray<nsMsgViewIndex> const& selection);
+ nsresult GroupSearchResultsByFolder();
+ nsresult PartitionSelectionByFolder(
+ nsTArray<nsMsgViewIndex> const& selection,
+ mozilla::UniquePtr<nsTArray<uint32_t>[]>& indexArrays,
+ int32_t* numArrays);
+
+ virtual nsresult ApplyCommandToIndicesWithFolder(
+ nsMsgViewCommandTypeValue command,
+ nsTArray<nsMsgViewIndex> const& selection,
+ nsIMsgFolder* destFolder) override;
+ void MoveThreadAt(nsMsgViewIndex threadIndex);
+
+ virtual nsresult GetMessageEnumerator(nsIMsgEnumerator** enumerator) override;
+ virtual nsresult InsertHdrFromFolder(nsIMsgDBHdr* msgHdr,
+ nsIMsgFolder* folder);
+
+ // Holds the original folder of each message in this view.
+ // Augments the existing arrays in nsMsgDBView (m_keys, m_flags and m_levels),
+ // and is kept in sync with them.
+ nsCOMArray<nsIMsgFolder> m_folders;
+
+ nsTArray<nsTArray<RefPtr<nsIMsgDBHdr>>> m_hdrsForEachFolder;
+ nsCOMArray<nsIMsgFolder> m_uniqueFoldersSelected;
+ uint32_t mCurIndex;
+
+ int32_t mTotalIndices;
+ nsCOMArray<nsIMsgDatabase> m_dbToUseList;
+ nsMsgViewCommandTypeValue mCommand;
+ nsCOMPtr<nsIMsgFolder> mDestFolder;
+ nsWeakPtr m_searchSession;
+
+ nsresult ProcessRequestsInOneFolder(nsIMsgWindow* window);
+ nsresult ProcessRequestsInAllFolders(nsIMsgWindow* window);
+ // these are for doing threading of the search hits
+
+ // used for assigning thread id's to xfview threads.
+ nsMsgKey m_nextThreadId;
+ // this maps message-ids and reference message ids to
+ // the corresponding nsMsgXFViewThread object. If we're
+ // doing subject threading, we would throw subjects
+ // into the same table.
+ nsInterfaceHashtable<nsCStringHashKey, nsIMsgThread> m_threadsTable;
+
+ // map message-ids to msg hdrs in the view, used for threading.
+ nsInterfaceHashtable<nsCStringHashKey, nsIMsgDBHdr> m_hdrsTable;
+ uint32_t m_totalMessagesInView;
+
+ virtual nsMsgGroupThread* CreateGroupThread(nsIMsgDatabase* db) override;
+ nsresult GetXFThreadFromMsgHdr(nsIMsgDBHdr* msgHdr, nsIMsgThread** pThread,
+ bool* foundByMessageId = nullptr);
+ bool GetThreadFromHash(nsCString& reference, nsIMsgThread** thread);
+ bool GetMsgHdrFromHash(nsCString& reference, nsIMsgDBHdr** hdr);
+ nsresult AddRefToHash(nsCString& reference, nsIMsgThread* thread);
+ nsresult AddMsgToHashTables(nsIMsgDBHdr* msgHdr, nsIMsgThread* thread);
+ nsresult RemoveRefFromHash(nsCString& reference);
+ nsresult RemoveMsgFromHashTables(nsIMsgDBHdr* msgHdr);
+ nsresult InitRefHash();
+};
+
+#endif
diff --git a/comm/mailnews/base/src/nsMsgSpecialViews.cpp b/comm/mailnews/base/src/nsMsgSpecialViews.cpp
new file mode 100644
index 0000000000..6bd42176e0
--- /dev/null
+++ b/comm/mailnews/base/src/nsMsgSpecialViews.cpp
@@ -0,0 +1,163 @@
+/* -*- 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 "msgCore.h"
+#include "nsMsgSpecialViews.h"
+#include "nsIMsgThread.h"
+#include "nsMsgMessageFlags.h"
+
+nsMsgThreadsWithUnreadDBView::nsMsgThreadsWithUnreadDBView()
+ : m_totalUnwantedMessagesInView(0) {}
+
+nsMsgThreadsWithUnreadDBView::~nsMsgThreadsWithUnreadDBView() {}
+
+NS_IMETHODIMP nsMsgThreadsWithUnreadDBView::GetViewType(
+ nsMsgViewTypeValue* aViewType) {
+ NS_ENSURE_ARG_POINTER(aViewType);
+ *aViewType = nsMsgViewType::eShowThreadsWithUnread;
+ return NS_OK;
+}
+
+bool nsMsgThreadsWithUnreadDBView::WantsThisThread(nsIMsgThread* threadHdr) {
+ if (threadHdr) {
+ uint32_t numNewChildren;
+
+ threadHdr->GetNumUnreadChildren(&numNewChildren);
+ if (numNewChildren > 0) return true;
+ uint32_t numChildren;
+ threadHdr->GetNumChildren(&numChildren);
+ m_totalUnwantedMessagesInView += numChildren;
+ }
+ return false;
+}
+
+nsresult nsMsgThreadsWithUnreadDBView::AddMsgToThreadNotInView(
+ nsIMsgThread* threadHdr, nsIMsgDBHdr* msgHdr, bool ensureListed) {
+ nsresult rv = NS_OK;
+
+ nsCOMPtr<nsIMsgDBHdr> parentHdr;
+ uint32_t msgFlags;
+ msgHdr->GetFlags(&msgFlags);
+ GetFirstMessageHdrToDisplayInThread(threadHdr, getter_AddRefs(parentHdr));
+ if (parentHdr && (ensureListed || !(msgFlags & nsMsgMessageFlags::Read))) {
+ nsMsgKey key;
+ uint32_t numMsgsInThread;
+ rv = AddHdr(parentHdr);
+ threadHdr->GetNumChildren(&numMsgsInThread);
+ if (numMsgsInThread > 1) {
+ parentHdr->GetMessageKey(&key);
+ nsMsgViewIndex viewIndex = FindViewIndex(key);
+ if (viewIndex != nsMsgViewIndex_None)
+ OrExtraFlag(viewIndex,
+ nsMsgMessageFlags::Elided | MSG_VIEW_FLAG_HASCHILDREN);
+ }
+ m_totalUnwantedMessagesInView -= numMsgsInThread;
+ } else
+ m_totalUnwantedMessagesInView++;
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgThreadsWithUnreadDBView::CloneDBView(
+ nsIMessenger* aMessengerInstance, nsIMsgWindow* aMsgWindow,
+ nsIMsgDBViewCommandUpdater* aCmdUpdater, nsIMsgDBView** _retval) {
+ nsMsgThreadsWithUnreadDBView* newMsgDBView =
+ new nsMsgThreadsWithUnreadDBView();
+ nsresult rv =
+ CopyDBView(newMsgDBView, aMessengerInstance, aMsgWindow, aCmdUpdater);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_IF_ADDREF(*_retval = newMsgDBView);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgThreadsWithUnreadDBView::GetNumMsgsInView(
+ int32_t* aNumMsgs) {
+ nsresult rv = nsMsgDBView::GetNumMsgsInView(aNumMsgs);
+ NS_ENSURE_SUCCESS(rv, rv);
+ *aNumMsgs = *aNumMsgs - m_totalUnwantedMessagesInView;
+ return rv;
+}
+
+nsMsgWatchedThreadsWithUnreadDBView::nsMsgWatchedThreadsWithUnreadDBView()
+ : m_totalUnwantedMessagesInView(0) {}
+
+NS_IMETHODIMP nsMsgWatchedThreadsWithUnreadDBView::GetViewType(
+ nsMsgViewTypeValue* aViewType) {
+ NS_ENSURE_ARG_POINTER(aViewType);
+ *aViewType = nsMsgViewType::eShowWatchedThreadsWithUnread;
+ return NS_OK;
+}
+
+bool nsMsgWatchedThreadsWithUnreadDBView::WantsThisThread(
+ nsIMsgThread* threadHdr) {
+ if (threadHdr) {
+ uint32_t numNewChildren;
+ uint32_t threadFlags;
+
+ threadHdr->GetNumUnreadChildren(&numNewChildren);
+ threadHdr->GetFlags(&threadFlags);
+ if (numNewChildren > 0 && (threadFlags & nsMsgMessageFlags::Watched) != 0)
+ return true;
+ uint32_t numChildren;
+ threadHdr->GetNumChildren(&numChildren);
+ m_totalUnwantedMessagesInView += numChildren;
+ }
+ return false;
+}
+
+nsresult nsMsgWatchedThreadsWithUnreadDBView::AddMsgToThreadNotInView(
+ nsIMsgThread* threadHdr, nsIMsgDBHdr* msgHdr, bool ensureListed) {
+ nsresult rv = NS_OK;
+ uint32_t threadFlags;
+ uint32_t msgFlags;
+ msgHdr->GetFlags(&msgFlags);
+ threadHdr->GetFlags(&threadFlags);
+ if (threadFlags & nsMsgMessageFlags::Watched) {
+ nsCOMPtr<nsIMsgDBHdr> parentHdr;
+ GetFirstMessageHdrToDisplayInThread(threadHdr, getter_AddRefs(parentHdr));
+ if (parentHdr && (ensureListed || !(msgFlags & nsMsgMessageFlags::Read))) {
+ uint32_t numChildren;
+ threadHdr->GetNumChildren(&numChildren);
+ rv = AddHdr(parentHdr);
+ if (numChildren > 1) {
+ nsMsgKey key;
+ parentHdr->GetMessageKey(&key);
+ nsMsgViewIndex viewIndex = FindViewIndex(key);
+ if (viewIndex != nsMsgViewIndex_None)
+ OrExtraFlag(viewIndex, nsMsgMessageFlags::Elided |
+ MSG_VIEW_FLAG_ISTHREAD |
+ MSG_VIEW_FLAG_HASCHILDREN |
+ nsMsgMessageFlags::Watched);
+ }
+ m_totalUnwantedMessagesInView -= numChildren;
+ return rv;
+ }
+ }
+ m_totalUnwantedMessagesInView++;
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgWatchedThreadsWithUnreadDBView::CloneDBView(
+ nsIMessenger* aMessengerInstance, nsIMsgWindow* aMsgWindow,
+ nsIMsgDBViewCommandUpdater* aCmdUpdater, nsIMsgDBView** _retval) {
+ nsMsgWatchedThreadsWithUnreadDBView* newMsgDBView =
+ new nsMsgWatchedThreadsWithUnreadDBView();
+ nsresult rv =
+ CopyDBView(newMsgDBView, aMessengerInstance, aMsgWindow, aCmdUpdater);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_IF_ADDREF(*_retval = newMsgDBView);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgWatchedThreadsWithUnreadDBView::GetNumMsgsInView(int32_t* aNumMsgs) {
+ nsresult rv = nsMsgDBView::GetNumMsgsInView(aNumMsgs);
+ NS_ENSURE_SUCCESS(rv, rv);
+ *aNumMsgs = *aNumMsgs - m_totalUnwantedMessagesInView;
+ return rv;
+}
diff --git a/comm/mailnews/base/src/nsMsgSpecialViews.h b/comm/mailnews/base/src/nsMsgSpecialViews.h
new file mode 100644
index 0000000000..1503eec123
--- /dev/null
+++ b/comm/mailnews/base/src/nsMsgSpecialViews.h
@@ -0,0 +1,82 @@
+/* -*- 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 _nsMsgSpecialViews_H_
+#define _nsMsgSpecialViews_H_
+
+#include "mozilla/Attributes.h"
+#include "nsMsgThreadedDBView.h"
+
+class nsMsgThreadsWithUnreadDBView : public nsMsgThreadedDBView {
+ public:
+ nsMsgThreadsWithUnreadDBView();
+ virtual ~nsMsgThreadsWithUnreadDBView();
+ virtual const char* GetViewName(void) override {
+ return "ThreadsWithUnreadView";
+ }
+ NS_IMETHOD CloneDBView(nsIMessenger* aMessengerInstance,
+ nsIMsgWindow* aMsgWindow,
+ nsIMsgDBViewCommandUpdater* aCommandUpdater,
+ nsIMsgDBView** _retval) override;
+ NS_IMETHOD GetViewType(nsMsgViewTypeValue* aViewType) override;
+ NS_IMETHOD GetNumMsgsInView(int32_t* aNumMsgs) override;
+ virtual bool WantsThisThread(nsIMsgThread* threadHdr) override;
+
+ protected:
+ virtual nsresult AddMsgToThreadNotInView(nsIMsgThread* threadHdr,
+ nsIMsgDBHdr* msgHdr,
+ bool ensureListed) override;
+ uint32_t m_totalUnwantedMessagesInView;
+};
+
+class nsMsgWatchedThreadsWithUnreadDBView : public nsMsgThreadedDBView {
+ public:
+ nsMsgWatchedThreadsWithUnreadDBView();
+ NS_IMETHOD GetViewType(nsMsgViewTypeValue* aViewType) override;
+ NS_IMETHOD CloneDBView(nsIMessenger* aMessengerInstance,
+ nsIMsgWindow* aMsgWindow,
+ nsIMsgDBViewCommandUpdater* aCommandUpdater,
+ nsIMsgDBView** _retval) override;
+ NS_IMETHOD GetNumMsgsInView(int32_t* aNumMsgs) override;
+ virtual const char* GetViewName(void) override {
+ return "WatchedThreadsWithUnreadView";
+ }
+ virtual bool WantsThisThread(nsIMsgThread* threadHdr) override;
+
+ protected:
+ virtual nsresult AddMsgToThreadNotInView(nsIMsgThread* threadHdr,
+ nsIMsgDBHdr* msgHdr,
+ bool ensureListed) override;
+ uint32_t m_totalUnwantedMessagesInView;
+};
+#ifdef DOING_CACHELESS_VIEW
+// This view will initially be used for cacheless IMAP.
+class nsMsgCachelessView : public nsMsgDBView {
+ public:
+ nsMsgCachelessView();
+ NS_IMETHOD GetViewType(nsMsgViewTypeValue* aViewType);
+ virtual ~nsMsgCachelessView();
+ virtual const char* GetViewName(void) { return "nsMsgCachelessView"; }
+ NS_IMETHOD Open(nsIMsgFolder* folder, nsMsgViewSortTypeValue viewType,
+ int32_t* count);
+ nsresult SetViewSize(int32_t setSize); // Override
+ virtual nsresult AddNewMessages();
+ virtual nsresult AddHdr(nsIMsgDBHdr* msgHdr);
+ // for news, xover line, potentially, for IMAP, imap line...
+ virtual nsresult AddHdrFromServerLine(char* line, nsMsgKey* msgId);
+ virtual void SetInitialSortState(void);
+ virtual nsresult Init(uint32_t* pCount);
+
+ protected:
+ void ClearPendingIds();
+
+ nsIMsgFolder* m_folder;
+ nsMsgViewIndex m_curStartSeq;
+ nsMsgViewIndex m_curEndSeq;
+ bool m_sizeInitialized;
+};
+
+#endif /* DOING_CACHELESS_VIEW */
+#endif
diff --git a/comm/mailnews/base/src/nsMsgStatusFeedback.cpp b/comm/mailnews/base/src/nsMsgStatusFeedback.cpp
new file mode 100644
index 0000000000..3afa14de87
--- /dev/null
+++ b/comm/mailnews/base/src/nsMsgStatusFeedback.cpp
@@ -0,0 +1,261 @@
+/* -*- 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 "msgCore.h"
+
+#include "nsIWebProgress.h"
+#include "nsIXULBrowserWindow.h"
+#include "nsMsgStatusFeedback.h"
+#include "mozilla/dom/Document.h"
+#include "nsIDocShell.h"
+#include "nsIDocShellTreeItem.h"
+#include "nsIChannel.h"
+#include "prinrval.h"
+#include "nsIMsgMailNewsUrl.h"
+#include "nsIMsgWindow.h"
+#include "nsMsgUtils.h"
+#include "nsIMsgHdr.h"
+#include "nsIMsgFolder.h"
+#include "nsMsgDBFolder.h"
+#include "nsServiceManagerUtils.h"
+#include "mozilla/Components.h"
+#include "nsMsgUtils.h"
+
+#define MSGFEEDBACK_TIMER_INTERVAL 500
+
+nsMsgStatusFeedback::nsMsgStatusFeedback()
+ : m_meteorsSpinning(false), m_lastPercent(0), m_lastProgressTime(0) {
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ mozilla::components::StringBundle::Service();
+
+ if (bundleService)
+ bundleService->CreateBundle(
+ "chrome://messenger/locale/messenger.properties",
+ getter_AddRefs(mBundle));
+}
+
+nsMsgStatusFeedback::~nsMsgStatusFeedback() { mBundle = nullptr; }
+
+NS_IMPL_ISUPPORTS(nsMsgStatusFeedback, nsIMsgStatusFeedback,
+ nsIProgressEventSink, nsIWebProgressListener,
+ nsISupportsWeakReference)
+
+//////////////////////////////////////////////////////////////////////////////////
+// nsMsgStatusFeedback::nsIWebProgressListener
+//////////////////////////////////////////////////////////////////////////////////
+
+NS_IMETHODIMP
+nsMsgStatusFeedback::OnProgressChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest,
+ int32_t aCurSelfProgress,
+ int32_t aMaxSelfProgress,
+ int32_t aCurTotalProgress,
+ int32_t aMaxTotalProgress) {
+ int32_t percentage = 0;
+ if (aMaxTotalProgress > 0) {
+ percentage = (aCurTotalProgress * 100) / aMaxTotalProgress;
+ if (percentage) ShowProgress(percentage);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgStatusFeedback::OnStateChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest,
+ uint32_t aProgressStateFlags,
+ nsresult aStatus) {
+ nsresult rv;
+
+ NS_ENSURE_TRUE(mBundle, NS_ERROR_NULL_POINTER);
+ if (aProgressStateFlags & STATE_IS_NETWORK) {
+ if (aProgressStateFlags & STATE_START) {
+ m_lastPercent = 0;
+ StartMeteors();
+ nsString loadingDocument;
+ rv = mBundle->GetStringFromName("documentLoading", loadingDocument);
+ if (NS_SUCCEEDED(rv)) ShowStatusString(loadingDocument);
+ } else if (aProgressStateFlags & STATE_STOP) {
+ // if we are loading message for display purposes, this STATE_STOP
+ // notification is the only notification we get when layout is actually
+ // done rendering the message. We need to fire the appropriate msgHdrSink
+ // notification in this particular case.
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
+ if (channel) {
+ nsCOMPtr<nsIURI> uri;
+ channel->GetURI(getter_AddRefs(uri));
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl(do_QueryInterface(uri));
+ if (mailnewsUrl) {
+ // get the url type
+ bool messageDisplayUrl;
+ mailnewsUrl->IsUrlType(nsIMsgMailNewsUrl::eDisplay,
+ &messageDisplayUrl);
+
+ if (messageDisplayUrl) {
+ // get the folder and notify that the msg has been loaded. We're
+ // using NotifyPropertyFlagChanged. To be completely consistent,
+ // we'd send a similar notification that the old message was
+ // unloaded.
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ nsCOMPtr<nsIMsgFolder> msgFolder;
+ mailnewsUrl->GetFolder(getter_AddRefs(msgFolder));
+ nsCOMPtr<nsIMsgMessageUrl> msgUrl = do_QueryInterface(mailnewsUrl);
+ if (msgUrl) {
+ // not sending this notification is not a fatal error...
+ (void)msgUrl->GetMessageHeader(getter_AddRefs(msgHdr));
+ if (msgFolder && msgHdr)
+ msgFolder->NotifyPropertyFlagChanged(msgHdr, kMsgLoaded, 0, 1);
+ }
+ }
+ }
+ }
+ StopMeteors();
+ nsString documentDone;
+ rv = mBundle->GetStringFromName("documentDone", documentDone);
+ if (NS_SUCCEEDED(rv)) ShowStatusString(documentDone);
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgStatusFeedback::OnLocationChange(
+ nsIWebProgress* aWebProgress, nsIRequest* aRequest, nsIURI* aLocation,
+ uint32_t aFlags) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgStatusFeedback::OnStatusChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest, nsresult aStatus,
+ const char16_t* aMessage) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgStatusFeedback::OnSecurityChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest, uint32_t state) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgStatusFeedback::OnContentBlockingEvent(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest,
+ uint32_t aEvent) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgStatusFeedback::ShowStatusString(const nsAString& aStatus) {
+ nsCOMPtr<nsIMsgStatusFeedback> jsStatusFeedback(
+ do_QueryReferent(mJSStatusFeedbackWeak));
+ if (jsStatusFeedback) jsStatusFeedback->ShowStatusString(aStatus);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgStatusFeedback::SetStatusString(const nsAString& aStatus) {
+ nsCOMPtr<nsIMsgStatusFeedback> jsStatusFeedback(
+ do_QueryReferent(mJSStatusFeedbackWeak));
+ if (jsStatusFeedback) jsStatusFeedback->SetStatusString(aStatus);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgStatusFeedback::ShowProgress(int32_t aPercentage) {
+ // if the percentage hasn't changed...OR if we are going from 0 to 100% in one
+ // step then don't bother....just fall out....
+ if (aPercentage == m_lastPercent ||
+ (m_lastPercent == 0 && aPercentage >= 100))
+ return NS_OK;
+
+ m_lastPercent = aPercentage;
+
+ int64_t nowMS = 0;
+ if (aPercentage < 100) // always need to do 100%
+ {
+ nowMS = PR_IntervalToMilliseconds(PR_IntervalNow());
+ if (nowMS < m_lastProgressTime + 250) return NS_OK;
+ }
+
+ m_lastProgressTime = nowMS;
+ nsCOMPtr<nsIMsgStatusFeedback> jsStatusFeedback(
+ do_QueryReferent(mJSStatusFeedbackWeak));
+ if (jsStatusFeedback) jsStatusFeedback->ShowProgress(aPercentage);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgStatusFeedback::StartMeteors() {
+ nsCOMPtr<nsIMsgStatusFeedback> jsStatusFeedback(
+ do_QueryReferent(mJSStatusFeedbackWeak));
+ if (jsStatusFeedback) jsStatusFeedback->StartMeteors();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgStatusFeedback::StopMeteors() {
+ nsCOMPtr<nsIMsgStatusFeedback> jsStatusFeedback(
+ do_QueryReferent(mJSStatusFeedbackWeak));
+ if (jsStatusFeedback) jsStatusFeedback->StopMeteors();
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgStatusFeedback::SetWrappedStatusFeedback(
+ nsIMsgStatusFeedback* aJSStatusFeedback) {
+ NS_ENSURE_ARG_POINTER(aJSStatusFeedback);
+ mJSStatusFeedbackWeak = do_GetWeakReference(aJSStatusFeedback);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgStatusFeedback::OnProgress(nsIRequest* request,
+ int64_t aProgress,
+ int64_t aProgressMax) {
+ // XXX: What should the nsIWebProgress be?
+ // XXX: this truncates 64-bit to 32-bit
+ return OnProgressChange(nullptr, request, int32_t(aProgress),
+ int32_t(aProgressMax),
+ int32_t(aProgress) /* current total progress */,
+ int32_t(aProgressMax) /* max total progress */);
+}
+
+NS_IMETHODIMP nsMsgStatusFeedback::OnStatus(nsIRequest* request,
+ nsresult aStatus,
+ const char16_t* aStatusArg) {
+ nsresult rv;
+ nsCOMPtr<nsIURI> uri;
+ nsString accountName;
+ // fetching account name from nsIRequest
+ nsCOMPtr<nsIChannel> aChannel = do_QueryInterface(request);
+ rv = aChannel->GetURI(getter_AddRefs(uri));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIMsgMailNewsUrl> url(do_QueryInterface(uri));
+ if (url) {
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ url->GetServer(getter_AddRefs(server));
+ if (server) server->GetPrettyName(accountName);
+ }
+
+ // forming the status message
+ nsCOMPtr<nsIStringBundleService> sbs =
+ mozilla::components::StringBundle::Service();
+ NS_ENSURE_TRUE(sbs, NS_ERROR_UNEXPECTED);
+ nsString str;
+ rv = sbs->FormatStatusMessage(aStatus, aStatusArg, str);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // prefixing the account name to the status message if status message isn't
+ // blank and doesn't already contain the account name.
+ nsString statusMessage;
+ if (!str.IsEmpty() && str.Find(accountName) == kNotFound) {
+ nsCOMPtr<nsIStringBundle> bundle;
+ rv = sbs->CreateBundle(MSGS_URL, getter_AddRefs(bundle));
+ AutoTArray<nsString, 2> params = {accountName, str};
+ rv = bundle->FormatStringFromName("statusMessage", params, statusMessage);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ statusMessage.Assign(str);
+ }
+ return ShowStatusString(statusMessage);
+}
diff --git a/comm/mailnews/base/src/nsMsgStatusFeedback.h b/comm/mailnews/base/src/nsMsgStatusFeedback.h
new file mode 100644
index 0000000000..6713eaea4c
--- /dev/null
+++ b/comm/mailnews/base/src/nsMsgStatusFeedback.h
@@ -0,0 +1,48 @@
+/* -*- 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 _nsMsgStatusFeedback_h
+#define _nsMsgStatusFeedback_h
+
+#include "nsIWebProgressListener.h"
+#include "nsIObserver.h"
+#include "nsITimer.h"
+#include "nsCOMPtr.h"
+#include "nsIMsgStatusFeedback.h"
+#include "nsIProgressEventSink.h"
+#include "nsIStringBundle.h"
+#include "nsWeakReference.h"
+#include "nsIWeakReferenceUtils.h"
+
+class nsMsgStatusFeedback : public nsIMsgStatusFeedback,
+ public nsIProgressEventSink,
+ public nsIWebProgressListener,
+ public nsSupportsWeakReference {
+ public:
+ nsMsgStatusFeedback();
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIMSGSTATUSFEEDBACK
+ NS_DECL_NSIWEBPROGRESSLISTENER
+ NS_DECL_NSIPROGRESSEVENTSINK
+
+ protected:
+ virtual ~nsMsgStatusFeedback();
+
+ bool m_meteorsSpinning;
+ int32_t m_lastPercent;
+ int64_t m_lastProgressTime;
+
+ void BeginObserving();
+ void EndObserving();
+
+ // the JS status feedback implementation object...eventually this object
+ // will replace this very C++ class you are looking at.
+ nsWeakPtr mJSStatusFeedbackWeak;
+
+ nsCOMPtr<nsIStringBundle> mBundle;
+};
+
+#endif // _nsMsgStatusFeedback_h
diff --git a/comm/mailnews/base/src/nsMsgTagService.cpp b/comm/mailnews/base/src/nsMsgTagService.cpp
new file mode 100644
index 0000000000..83ced5fdeb
--- /dev/null
+++ b/comm/mailnews/base/src/nsMsgTagService.cpp
@@ -0,0 +1,458 @@
+/* -*- 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 "msgCore.h"
+#include "nsMsgTagService.h"
+#include "nsIPrefService.h"
+#include "nsISupportsPrimitives.h"
+#include "nsMsgI18N.h"
+#include "nsIPrefLocalizedString.h"
+#include "nsMsgDBView.h" // for labels migration
+#include "nsQuickSort.h"
+#include "nsMsgUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "nsMemory.h"
+
+#define STRLEN(s) (sizeof(s) - 1)
+
+#define TAG_PREF_VERSION "version"
+#define TAG_PREF_SUFFIX_TAG ".tag"
+#define TAG_PREF_SUFFIX_COLOR ".color"
+#define TAG_PREF_SUFFIX_ORDINAL ".ordinal"
+
+static bool gMigratingKeys = false;
+
+// Comparator to set sort order in GetAllTags().
+struct CompareMsgTags {
+ private:
+ int cmp(RefPtr<nsIMsgTag> element1, RefPtr<nsIMsgTag> element2) const {
+ // Sort nsMsgTag objects by ascending order, using their ordinal or key.
+ // The "smallest" value will be first in the sorted array,
+ // thus being the most important element.
+
+ // Only use the key if the ordinal is not defined or empty.
+ nsAutoCString value1, value2;
+ element1->GetOrdinal(value1);
+ if (value1.IsEmpty()) element1->GetKey(value1);
+ element2->GetOrdinal(value2);
+ if (value2.IsEmpty()) element2->GetKey(value2);
+
+ return strcmp(value1.get(), value2.get());
+ }
+
+ public:
+ bool Equals(RefPtr<nsIMsgTag> element1, RefPtr<nsIMsgTag> element2) const {
+ return cmp(element1, element2) == 0;
+ }
+ bool LessThan(RefPtr<nsIMsgTag> element1, RefPtr<nsIMsgTag> element2) const {
+ return cmp(element1, element2) < 0;
+ }
+};
+
+//
+// nsMsgTag
+//
+NS_IMPL_ISUPPORTS(nsMsgTag, nsIMsgTag)
+
+nsMsgTag::nsMsgTag(const nsACString& aKey, const nsAString& aTag,
+ const nsACString& aColor, const nsACString& aOrdinal)
+ : mTag(aTag), mKey(aKey), mColor(aColor), mOrdinal(aOrdinal) {}
+
+nsMsgTag::~nsMsgTag() {}
+
+/* readonly attribute ACString key; */
+NS_IMETHODIMP nsMsgTag::GetKey(nsACString& aKey) {
+ aKey = mKey;
+ return NS_OK;
+}
+
+/* readonly attribute AString tag; */
+NS_IMETHODIMP nsMsgTag::GetTag(nsAString& aTag) {
+ aTag = mTag;
+ return NS_OK;
+}
+
+/* readonly attribute ACString color; */
+NS_IMETHODIMP nsMsgTag::GetColor(nsACString& aColor) {
+ aColor = mColor;
+ return NS_OK;
+}
+
+/* readonly attribute ACString ordinal; */
+NS_IMETHODIMP nsMsgTag::GetOrdinal(nsACString& aOrdinal) {
+ aOrdinal = mOrdinal;
+ return NS_OK;
+}
+
+//
+// nsMsgTagService
+//
+NS_IMPL_ISUPPORTS(nsMsgTagService, nsIMsgTagService)
+
+nsMsgTagService::nsMsgTagService() {
+ m_tagPrefBranch = nullptr;
+ nsCOMPtr<nsIPrefService> prefService(
+ do_GetService(NS_PREFSERVICE_CONTRACTID));
+ if (prefService)
+ prefService->GetBranch("mailnews.tags.", getter_AddRefs(m_tagPrefBranch));
+ SetupLabelTags();
+ RefreshKeyCache();
+}
+
+nsMsgTagService::~nsMsgTagService() {} /* destructor code */
+
+/* wstring getTagForKey (in string key); */
+NS_IMETHODIMP nsMsgTagService::GetTagForKey(const nsACString& key,
+ nsAString& _retval) {
+ nsAutoCString prefName(key);
+ if (!gMigratingKeys) ToLowerCase(prefName);
+ prefName.AppendLiteral(TAG_PREF_SUFFIX_TAG);
+ return GetUnicharPref(prefName.get(), _retval);
+}
+
+/* void setTagForKey (in string key); */
+NS_IMETHODIMP nsMsgTagService::SetTagForKey(const nsACString& key,
+ const nsAString& tag) {
+ nsAutoCString prefName(key);
+ ToLowerCase(prefName);
+ prefName.AppendLiteral(TAG_PREF_SUFFIX_TAG);
+ return SetUnicharPref(prefName.get(), tag);
+}
+
+/* void getKeyForTag (in wstring tag); */
+NS_IMETHODIMP nsMsgTagService::GetKeyForTag(const nsAString& aTag,
+ nsACString& aKey) {
+ nsTArray<nsCString> prefList;
+ nsresult rv = m_tagPrefBranch->GetChildList("", prefList);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // traverse the list, and look for a pref with the desired tag value.
+ // XXXbz is there a good reason to reverse the list here, or did the
+ // old code do it just to be clever and save some characters in the
+ // for loop header?
+ for (auto& prefName : mozilla::Reversed(prefList)) {
+ // We are returned the tag prefs in the form "<key>.<tag_data_type>", but
+ // since we only want the tags, just check that the string ends with "tag".
+ if (StringEndsWith(prefName, nsLiteralCString(TAG_PREF_SUFFIX_TAG))) {
+ nsAutoString curTag;
+ GetUnicharPref(prefName.get(), curTag);
+ if (aTag.Equals(curTag)) {
+ aKey = Substring(prefName, 0,
+ prefName.Length() - STRLEN(TAG_PREF_SUFFIX_TAG));
+ break;
+ }
+ }
+ }
+ ToLowerCase(aKey);
+ return NS_OK;
+}
+
+/* ACString getTopKey (in ACString keylist); */
+NS_IMETHODIMP nsMsgTagService::GetTopKey(const nsACString& keyList,
+ nsACString& _retval) {
+ _retval.Truncate();
+ // find the most important key
+ nsTArray<nsCString> keyArray;
+ ParseString(keyList, ' ', keyArray);
+ uint32_t keyCount = keyArray.Length();
+ nsCString *topKey = nullptr, *key, topOrdinal, ordinal;
+ for (uint32_t i = 0; i < keyCount; ++i) {
+ key = &keyArray[i];
+ if (key->IsEmpty()) continue;
+
+ // ignore unknown keywords
+ nsAutoString tagValue;
+ nsresult rv = GetTagForKey(*key, tagValue);
+ if (NS_FAILED(rv) || tagValue.IsEmpty()) continue;
+
+ // new top key, judged by ordinal order?
+ rv = GetOrdinalForKey(*key, ordinal);
+ if (NS_FAILED(rv) || ordinal.IsEmpty()) ordinal = *key;
+ if ((ordinal < topOrdinal) || topOrdinal.IsEmpty()) {
+ topOrdinal = ordinal;
+ topKey = key; // copy actual result key only once - later
+ }
+ }
+ // return the most important key - if any
+ if (topKey) _retval = *topKey;
+ return NS_OK;
+}
+
+/* void addTagForKey (in string key, in wstring tag, in string color, in string
+ * ordinal); */
+NS_IMETHODIMP nsMsgTagService::AddTagForKey(const nsACString& key,
+ const nsAString& tag,
+ const nsACString& color,
+ const nsACString& ordinal) {
+ nsAutoCString prefName(key);
+ ToLowerCase(prefName);
+ prefName.AppendLiteral(TAG_PREF_SUFFIX_TAG);
+ nsresult rv = SetUnicharPref(prefName.get(), tag);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = SetColorForKey(key, color);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = RefreshKeyCache();
+ NS_ENSURE_SUCCESS(rv, rv);
+ return SetOrdinalForKey(key, ordinal);
+}
+
+/* void addTag (in wstring tag, in long color); */
+NS_IMETHODIMP nsMsgTagService::AddTag(const nsAString& tag,
+ const nsACString& color,
+ const nsACString& ordinal) {
+ // figure out key from tag. Apply transformation stripping out
+ // illegal characters like <SP> and then convert to imap mod utf7.
+ // Then, check if we have a tag with that key yet, and if so,
+ // make it unique by appending A, AA, etc.
+ // Should we use an iterator?
+ nsAutoString transformedTag(tag);
+ transformedTag.ReplaceChar(u" ()/{%*<>\\\"", u'_');
+ nsAutoCString key;
+ CopyUTF16toMUTF7(transformedTag, key);
+ // We have an imap server that converts keys to upper case so we're going
+ // to normalize all keys to lower case (upper case looks ugly in prefs.js)
+ ToLowerCase(key);
+ nsAutoCString prefName(key);
+ while (true) {
+ nsAutoString tagValue;
+ nsresult rv = GetTagForKey(prefName, tagValue);
+ if (NS_FAILED(rv) || tagValue.IsEmpty() || tagValue.Equals(tag))
+ return AddTagForKey(prefName, tag, color, ordinal);
+ prefName.Append('A');
+ }
+ NS_ASSERTION(false, "can't get here");
+ return NS_ERROR_FAILURE;
+}
+
+/* long getColorForKey (in string key); */
+NS_IMETHODIMP nsMsgTagService::GetColorForKey(const nsACString& key,
+ nsACString& _retval) {
+ nsAutoCString prefName(key);
+ if (!gMigratingKeys) ToLowerCase(prefName);
+ prefName.AppendLiteral(TAG_PREF_SUFFIX_COLOR);
+ nsCString color;
+ nsresult rv = m_tagPrefBranch->GetCharPref(prefName.get(), color);
+ if (NS_SUCCEEDED(rv)) _retval = color;
+ return NS_OK;
+}
+
+/* long getSelectorForKey (in ACString key, out AString selector); */
+NS_IMETHODIMP nsMsgTagService::GetSelectorForKey(const nsACString& key,
+ nsAString& _retval) {
+ // Our keys are the result of MUTF-7 encoding. For CSS selectors we need
+ // to reduce this to 0-9A-Za-z_ with a leading alpha character.
+ // We encode non-alphanumeric characters using _ as an escape character
+ // and start with a leading T in all cases. This way users defining tags
+ // "selected" or "focus" don't collide with inbuilt "selected" or "focus".
+
+ // Calculate length of selector string.
+ const char* in = key.BeginReading();
+ size_t outLen = 1;
+ while (*in) {
+ if (('0' <= *in && *in <= '9') || ('A' <= *in && *in <= 'Z') ||
+ ('a' <= *in && *in <= 'z')) {
+ outLen++;
+ } else {
+ outLen += 3;
+ }
+ in++;
+ }
+
+ // Now fill selector string.
+ _retval.SetCapacity(outLen);
+ _retval.Assign('T');
+ in = key.BeginReading();
+ while (*in) {
+ if (('0' <= *in && *in <= '9') || ('A' <= *in && *in <= 'Z') ||
+ ('a' <= *in && *in <= 'z')) {
+ _retval.Append(*in);
+ } else {
+ _retval.AppendPrintf("_%02x", *in);
+ }
+ in++;
+ }
+
+ return NS_OK;
+}
+
+/* void setColorForKey (in ACString key, in ACString color); */
+NS_IMETHODIMP nsMsgTagService::SetColorForKey(const nsACString& key,
+ const nsACString& color) {
+ nsAutoCString prefName(key);
+ ToLowerCase(prefName);
+ prefName.AppendLiteral(TAG_PREF_SUFFIX_COLOR);
+ if (color.IsEmpty()) {
+ m_tagPrefBranch->ClearUserPref(prefName.get());
+ return NS_OK;
+ }
+ return m_tagPrefBranch->SetCharPref(prefName.get(), color);
+}
+
+/* ACString getOrdinalForKey (in ACString key); */
+NS_IMETHODIMP nsMsgTagService::GetOrdinalForKey(const nsACString& key,
+ nsACString& _retval) {
+ nsAutoCString prefName(key);
+ if (!gMigratingKeys) ToLowerCase(prefName);
+ prefName.AppendLiteral(TAG_PREF_SUFFIX_ORDINAL);
+ nsCString ordinal;
+ nsresult rv = m_tagPrefBranch->GetCharPref(prefName.get(), ordinal);
+ _retval = ordinal;
+ return rv;
+}
+
+/* void setOrdinalForKey (in ACString key, in ACString ordinal); */
+NS_IMETHODIMP nsMsgTagService::SetOrdinalForKey(const nsACString& key,
+ const nsACString& ordinal) {
+ nsAutoCString prefName(key);
+ ToLowerCase(prefName);
+ prefName.AppendLiteral(TAG_PREF_SUFFIX_ORDINAL);
+ if (ordinal.IsEmpty()) {
+ m_tagPrefBranch->ClearUserPref(prefName.get());
+ return NS_OK;
+ }
+ return m_tagPrefBranch->SetCharPref(prefName.get(), ordinal);
+}
+
+/* void deleteTag (in wstring tag); */
+NS_IMETHODIMP nsMsgTagService::DeleteKey(const nsACString& key) {
+ // clear the associated prefs
+ nsAutoCString prefName(key);
+ if (!gMigratingKeys) ToLowerCase(prefName);
+ prefName.Append('.');
+
+ nsTArray<nsCString> prefNames;
+ nsresult rv = m_tagPrefBranch->GetChildList(prefName.get(), prefNames);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (auto& prefName : prefNames) {
+ m_tagPrefBranch->ClearUserPref(prefName.get());
+ }
+
+ return RefreshKeyCache();
+}
+
+/* Array<nsIMsgTag> getAllTags(); */
+NS_IMETHODIMP nsMsgTagService::GetAllTags(
+ nsTArray<RefPtr<nsIMsgTag>>& aTagArray) {
+ aTagArray.Clear();
+
+ // get the actual tag definitions
+ nsresult rv;
+ nsTArray<nsCString> prefList;
+ rv = m_tagPrefBranch->GetChildList("", prefList);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // sort them by key for ease of processing
+ prefList.Sort();
+
+ nsString tag;
+ nsCString lastKey, color, ordinal;
+ for (auto& pref : mozilla::Reversed(prefList)) {
+ // extract just the key from <key>.<info=tag|color|ordinal>
+ int32_t dotLoc = pref.RFindChar('.');
+ if (dotLoc != kNotFound) {
+ auto& key = Substring(pref, 0, dotLoc);
+ if (key != lastKey) {
+ if (!key.IsEmpty()) {
+ // .tag MUST exist (but may be empty)
+ rv = GetTagForKey(key, tag);
+ if (NS_SUCCEEDED(rv)) {
+ // .color MAY exist
+ color.Truncate();
+ GetColorForKey(key, color);
+ // .ordinal MAY exist
+ rv = GetOrdinalForKey(key, ordinal);
+ if (NS_FAILED(rv)) ordinal.Truncate();
+ // store the tag info in our array
+ aTagArray.AppendElement(new nsMsgTag(key, tag, color, ordinal));
+ }
+ }
+ lastKey = key;
+ }
+ }
+ }
+
+ // sort the non-null entries by ordinal
+ aTagArray.Sort(CompareMsgTags());
+ return NS_OK;
+}
+
+nsresult nsMsgTagService::SetUnicharPref(const char* prefName,
+ const nsAString& val) {
+ nsresult rv = NS_OK;
+ if (!val.IsEmpty()) {
+ rv = m_tagPrefBranch->SetStringPref(prefName, NS_ConvertUTF16toUTF8(val));
+ } else {
+ m_tagPrefBranch->ClearUserPref(prefName);
+ }
+ return rv;
+}
+
+nsresult nsMsgTagService::GetUnicharPref(const char* prefName,
+ nsAString& prefValue) {
+ nsCString valueUtf8;
+ nsresult rv =
+ m_tagPrefBranch->GetStringPref(prefName, EmptyCString(), 0, valueUtf8);
+ CopyUTF8toUTF16(valueUtf8, prefValue);
+ return rv;
+}
+
+nsresult nsMsgTagService::SetupLabelTags() {
+ nsCString prefString;
+
+ int32_t prefVersion = 0;
+ nsresult rv = m_tagPrefBranch->GetIntPref(TAG_PREF_VERSION, &prefVersion);
+ if (NS_SUCCEEDED(rv) && prefVersion > 1) {
+ return rv;
+ }
+ nsCOMPtr<nsIPrefBranch> prefRoot(do_GetService(NS_PREFSERVICE_CONTRACTID));
+ nsCOMPtr<nsIPrefLocalizedString> pls;
+ nsString ucsval;
+ nsAutoCString labelKey("$label1");
+ for (int32_t i = 0; i < 5;) {
+ prefString.AssignLiteral("mailnews.labels.description.");
+ prefString.AppendInt(i + 1);
+ rv = prefRoot->GetComplexValue(prefString.get(),
+ NS_GET_IID(nsIPrefLocalizedString),
+ getter_AddRefs(pls));
+ NS_ENSURE_SUCCESS(rv, rv);
+ pls->ToString(getter_Copies(ucsval));
+
+ prefString.AssignLiteral("mailnews.labels.color.");
+ prefString.AppendInt(i + 1);
+ nsCString csval;
+ rv = prefRoot->GetCharPref(prefString.get(), csval);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = AddTagForKey(labelKey, ucsval, csval, EmptyCString());
+ NS_ENSURE_SUCCESS(rv, rv);
+ labelKey.SetCharAt(++i + '1', 6);
+ }
+ m_tagPrefBranch->SetIntPref(TAG_PREF_VERSION, 2);
+ return rv;
+}
+
+NS_IMETHODIMP nsMsgTagService::IsValidKey(const nsACString& aKey,
+ bool* aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = m_keys.Contains(aKey);
+ return NS_OK;
+}
+
+// refresh the local tag key array m_keys from preferences
+nsresult nsMsgTagService::RefreshKeyCache() {
+ nsTArray<RefPtr<nsIMsgTag>> tagArray;
+ nsresult rv = GetAllTags(tagArray);
+ NS_ENSURE_SUCCESS(rv, rv);
+ m_keys.Clear();
+
+ uint32_t numTags = tagArray.Length();
+ m_keys.SetCapacity(numTags);
+ for (uint32_t tagIndex = 0; tagIndex < numTags; tagIndex++) {
+ nsAutoCString key;
+ tagArray[tagIndex]->GetKey(key);
+ m_keys.InsertElementAt(tagIndex, key);
+ }
+ return rv;
+}
diff --git a/comm/mailnews/base/src/nsMsgTagService.h b/comm/mailnews/base/src/nsMsgTagService.h
new file mode 100644
index 0000000000..7eee648271
--- /dev/null
+++ b/comm/mailnews/base/src/nsMsgTagService.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 nsMsgTagService_h__
+#define nsMsgTagService_h__
+
+#include "nsIMsgTagService.h"
+#include "nsIPrefBranch.h"
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "nsTArray.h"
+
+class nsMsgTag final : public nsIMsgTag {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMSGTAG
+
+ nsMsgTag(const nsACString& aKey, const nsAString& aTag,
+ const nsACString& aColor, const nsACString& aOrdinal);
+
+ protected:
+ ~nsMsgTag();
+
+ nsString mTag;
+ nsCString mKey, mColor, mOrdinal;
+};
+
+class nsMsgTagService final : public nsIMsgTagService {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMSGTAGSERVICE
+
+ nsMsgTagService();
+
+ private:
+ ~nsMsgTagService();
+
+ protected:
+ nsresult SetUnicharPref(const char* prefName, const nsAString& prefValue);
+ nsresult GetUnicharPref(const char* prefName, nsAString& prefValue);
+ nsresult SetupLabelTags();
+ nsresult RefreshKeyCache();
+
+ nsCOMPtr<nsIPrefBranch> m_tagPrefBranch;
+ nsTArray<nsCString> m_keys;
+};
+
+#endif
diff --git a/comm/mailnews/base/src/nsMsgThreadedDBView.cpp b/comm/mailnews/base/src/nsMsgThreadedDBView.cpp
new file mode 100644
index 0000000000..84105bc4fc
--- /dev/null
+++ b/comm/mailnews/base/src/nsMsgThreadedDBView.cpp
@@ -0,0 +1,899 @@
+/* -*- 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 "msgCore.h"
+#include "nsMsgThreadedDBView.h"
+#include "nsIMsgHdr.h"
+#include "nsIMsgThread.h"
+#include "nsIDBFolderInfo.h"
+#include "nsIMsgSearchSession.h"
+#include "nsMsgMessageFlags.h"
+
+// Allocate this more to avoid reallocation on new mail.
+#define MSGHDR_CACHE_LOOK_AHEAD_SIZE 25
+// Max msghdr cache entries.
+#define MSGHDR_CACHE_MAX_SIZE 8192
+#define MSGHDR_CACHE_DEFAULT_SIZE 100
+
+nsMsgThreadedDBView::nsMsgThreadedDBView() {
+ /* member initializers and constructor code */
+ m_havePrevView = false;
+}
+
+nsMsgThreadedDBView::~nsMsgThreadedDBView() {} /* destructor code */
+
+NS_IMETHODIMP
+nsMsgThreadedDBView::Open(nsIMsgFolder* folder, nsMsgViewSortTypeValue sortType,
+ nsMsgViewSortOrderValue sortOrder,
+ nsMsgViewFlagsTypeValue viewFlags, int32_t* pCount) {
+ nsresult rv =
+ nsMsgDBView::Open(folder, sortType, sortOrder, viewFlags, pCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!m_db) return NS_ERROR_NULL_POINTER;
+
+ // Preset msg hdr cache size for performance reason.
+ int32_t totalMessages, unreadMessages;
+ nsCOMPtr<nsIDBFolderInfo> dbFolderInfo;
+ PersistFolderInfo(getter_AddRefs(dbFolderInfo));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Save off sort type and order, view type and flags.
+ dbFolderInfo->GetNumUnreadMessages(&unreadMessages);
+ dbFolderInfo->GetNumMessages(&totalMessages);
+ if (m_viewFlags & nsMsgViewFlagsType::kUnreadOnly) {
+ // Set unread msg size + extra entries to avoid reallocation on new mail.
+ totalMessages = (uint32_t)unreadMessages + MSGHDR_CACHE_LOOK_AHEAD_SIZE;
+ } else {
+ if (totalMessages > MSGHDR_CACHE_MAX_SIZE)
+ // Use max default.
+ totalMessages = MSGHDR_CACHE_MAX_SIZE;
+ else if (totalMessages > 0)
+ // Allocate extra entries to avoid reallocation on new mail.
+ totalMessages += MSGHDR_CACHE_LOOK_AHEAD_SIZE;
+ }
+
+ // If total messages is 0, then we probably don't have any idea how many
+ // headers are in the db so we have no business setting the cache size.
+ if (totalMessages > 0) m_db->SetMsgHdrCacheSize((uint32_t)totalMessages);
+
+ int32_t count;
+ rv = InitThreadedView(count);
+ if (pCount) *pCount = count;
+
+ // This is a hack, but we're trying to find a way to correct
+ // incorrect total and unread msg counts w/o paying a big
+ // performance penalty. So, if we're not threaded, just add
+ // up the total and unread messages in the view and see if that
+ // matches what the db totals say. Except ignored threads are
+ // going to throw us off...hmm. Unless we just look at the
+ // unread counts which is what mostly tweaks people anyway...
+ int32_t unreadMsgsInView = 0;
+ if (!(m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay)) {
+ for (uint32_t i = m_flags.Length(); i > 0;) {
+ if (!(m_flags[--i] & nsMsgMessageFlags::Read)) ++unreadMsgsInView;
+ }
+
+ if (unreadMessages != unreadMsgsInView) m_db->SyncCounts();
+ }
+
+ m_db->SetMsgHdrCacheSize(MSGHDR_CACHE_DEFAULT_SIZE);
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgThreadedDBView::Close() { return nsMsgDBView::Close(); }
+
+// Populate the view with the ids of the first message in each thread.
+nsresult nsMsgThreadedDBView::InitThreadedView(int32_t& count) {
+ count = 0;
+ m_keys.Clear();
+ m_flags.Clear();
+ m_levels.Clear();
+ m_prevKeys.Clear();
+ m_prevFlags.Clear();
+ m_prevLevels.Clear();
+ m_havePrevView = false;
+
+ bool unreadOnly = (m_viewFlags & nsMsgViewFlagsType::kUnreadOnly);
+
+ nsCOMPtr<nsIMsgThreadEnumerator> threads;
+ nsresult rv = m_db->EnumerateThreads(getter_AddRefs(threads));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool hasMore = false;
+ while (NS_SUCCEEDED(rv = threads->HasMoreElements(&hasMore)) && hasMore) {
+ nsCOMPtr<nsIMsgThread> threadHdr;
+ rv = threads->GetNext(getter_AddRefs(threadHdr));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t numChildren;
+ if (unreadOnly)
+ threadHdr->GetNumUnreadChildren(&numChildren);
+ else
+ threadHdr->GetNumChildren(&numChildren);
+
+ if (numChildren == 0) {
+ continue; // An empty thread.
+ }
+
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ if (unreadOnly) {
+ rv = threadHdr->GetFirstUnreadChild(getter_AddRefs(msgHdr));
+ } else {
+ rv = threadHdr->GetRootHdr(getter_AddRefs(msgHdr));
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Hook to allow derived classes to filter out unwanted threads.
+ if (!WantsThisThread(threadHdr)) {
+ continue;
+ }
+
+ uint32_t msgFlags;
+ msgHdr->GetFlags(&msgFlags);
+ // Turn off high byte of msg flags - used for view flags.
+ msgFlags &= ~MSG_VIEW_FLAGS;
+ // Turn off these flags on msg hdr - they belong in thread.
+ uint32_t newMsgFlagsUnused;
+ msgHdr->AndFlags(~(nsMsgMessageFlags::Watched), &newMsgFlagsUnused);
+ AdjustReadFlag(msgHdr, &msgFlags);
+ // Try adding in MSG_VIEW_FLAG_ISTHREAD flag for unreadonly view.
+ uint32_t threadFlags;
+ threadHdr->GetFlags(&threadFlags);
+ msgFlags |= MSG_VIEW_FLAG_ISTHREAD | threadFlags;
+ if (numChildren > 1) {
+ msgFlags |= MSG_VIEW_FLAG_HASCHILDREN;
+ }
+
+ if (!(m_viewFlags & nsMsgViewFlagsType::kShowIgnored)) {
+ // Skip ignored threads.
+ if (msgFlags & nsMsgMessageFlags::Ignored) {
+ continue;
+ }
+ // Skip ignored subthreads
+ bool killed;
+ msgHdr->GetIsKilled(&killed);
+ if (killed) {
+ continue;
+ }
+ }
+
+ // By default, make threads collapsed unless we're only viewing new msgs.
+ if (msgFlags & MSG_VIEW_FLAG_HASCHILDREN) {
+ msgFlags |= nsMsgMessageFlags::Elided;
+ }
+
+ // OK, now add it to the view!
+ nsMsgKey msgKey;
+ msgHdr->GetMessageKey(&msgKey);
+ m_keys.AppendElement(msgKey);
+ m_flags.AppendElement(msgFlags);
+ m_levels.AppendElement(0);
+
+ // We expand as we build the view, which allows us to insert at the end
+ // of the key array, instead of the middle, and is much faster.
+ if ((!(m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay) ||
+ m_viewFlags & nsMsgViewFlagsType::kExpandAll) &&
+ msgFlags & nsMsgMessageFlags::Elided) {
+ ExpandByIndex(m_keys.Length() - 1, nullptr);
+ }
+
+ count++;
+ }
+
+ rv = InitSort(m_sortType, m_sortOrder);
+ SaveSortInfo(m_sortType, m_sortOrder);
+ return rv;
+}
+
+nsresult nsMsgThreadedDBView::SortThreads(nsMsgViewSortTypeValue sortType,
+ nsMsgViewSortOrderValue sortOrder) {
+ NS_ASSERTION(m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay,
+ "trying to sort unthreaded threads");
+
+ uint32_t numThreads = 0;
+ // The idea here is that copy the current view, then build up an m_keys and
+ // m_flags array of just the top level messages in the view, and then call
+ // nsMsgDBView::Sort(sortType, sortOrder).
+ // Then, we expand the threads in the result array that were expanded in the
+ // original view (perhaps by copying from the original view, but more likely
+ // just be calling expand).
+ for (uint32_t i = 0; i < m_keys.Length(); i++) {
+ if (m_flags[i] & MSG_VIEW_FLAG_ISTHREAD) {
+ if (numThreads < i) {
+ m_keys[numThreads] = m_keys[i];
+ m_flags[numThreads] = m_flags[i];
+ }
+
+ m_levels[numThreads] = 0;
+ numThreads++;
+ }
+ }
+
+ m_keys.SetLength(numThreads);
+ m_flags.SetLength(numThreads);
+ m_levels.SetLength(numThreads);
+ // m_viewFlags &= ~nsMsgViewFlagsType::kThreadedDisplay;
+ m_sortType = nsMsgViewSortType::byNone; // sort from scratch
+ nsMsgDBView::Sort(sortType, sortOrder);
+ m_viewFlags |= nsMsgViewFlagsType::kThreadedDisplay;
+ SetSuppressChangeNotifications(true);
+
+ // Loop through the original array, for each thread that's expanded,
+ // find it in the new array and expand the thread. We have to update
+ // MSG_VIEW_FLAG_HAS_CHILDREN because we may be going from a flat sort,
+ // which doesn't maintain that flag, to a threaded sort, which requires
+ // that flag.
+ for (uint32_t j = 0; j < m_keys.Length(); j++) {
+ uint32_t flags = m_flags[j];
+ if ((flags & (MSG_VIEW_FLAG_HASCHILDREN | nsMsgMessageFlags::Elided)) ==
+ MSG_VIEW_FLAG_HASCHILDREN) {
+ uint32_t numExpanded;
+ m_flags[j] = flags | nsMsgMessageFlags::Elided;
+ ExpandByIndex(j, &numExpanded);
+ j += numExpanded;
+ if (numExpanded > 0)
+ m_flags[j - numExpanded] = flags | MSG_VIEW_FLAG_HASCHILDREN;
+ } else if (flags & MSG_VIEW_FLAG_ISTHREAD &&
+ !(flags & MSG_VIEW_FLAG_HASCHILDREN)) {
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ nsCOMPtr<nsIMsgThread> pThread;
+ m_db->GetMsgHdrForKey(m_keys[j], getter_AddRefs(msgHdr));
+ if (msgHdr) {
+ m_db->GetThreadContainingMsgHdr(msgHdr, getter_AddRefs(pThread));
+ if (pThread) {
+ uint32_t numChildren;
+ pThread->GetNumChildren(&numChildren);
+ if (numChildren > 1)
+ m_flags[j] =
+ flags | MSG_VIEW_FLAG_HASCHILDREN | nsMsgMessageFlags::Elided;
+ }
+ }
+ }
+ }
+
+ SetSuppressChangeNotifications(false);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgThreadedDBView::Sort(nsMsgViewSortTypeValue sortType,
+ nsMsgViewSortOrderValue sortOrder) {
+ nsresult rv;
+
+ int32_t rowCountBeforeSort = GetSize();
+
+ if (!rowCountBeforeSort) {
+ // Still need to setup our flags even when no articles - bug 98183.
+ m_sortType = sortType;
+ m_sortOrder = sortOrder;
+ if (sortType == nsMsgViewSortType::byThread &&
+ !(m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay)) {
+ SetViewFlags(m_viewFlags | nsMsgViewFlagsType::kThreadedDisplay);
+ }
+
+ SaveSortInfo(sortType, sortOrder);
+ return NS_OK;
+ }
+
+ if (!m_checkedCustomColumns && CustomColumnsInSortAndNotRegistered())
+ return NS_OK;
+
+ // Sort threads by sort order.
+ bool sortThreads = m_viewFlags & (nsMsgViewFlagsType::kThreadedDisplay |
+ nsMsgViewFlagsType::kGroupBySort);
+
+ // If sort type is by thread, and we're already threaded, change sort type
+ // to byId.
+ if (sortType == nsMsgViewSortType::byThread &&
+ (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay) != 0) {
+ sortType = nsMsgViewSortType::byId;
+ }
+
+ nsMsgKey preservedKey;
+ AutoTArray<nsMsgKey, 1> preservedSelection;
+ SaveAndClearSelection(&preservedKey, preservedSelection);
+ // If the client wants us to forget our cached id arrays, they
+ // should build a new view. If this isn't good enough, we
+ // need a method to do that.
+ if (sortType != m_sortType || !m_sortValid || sortThreads) {
+ SaveSortInfo(sortType, sortOrder);
+ if (sortType == nsMsgViewSortType::byThread) {
+ m_sortType = sortType;
+ m_viewFlags |= nsMsgViewFlagsType::kThreadedDisplay;
+ m_viewFlags &= ~nsMsgViewFlagsType::kGroupBySort;
+ if (m_havePrevView) {
+ // Restore saved id array and flags array.
+ m_keys = m_prevKeys.Clone();
+ m_flags = m_prevFlags.Clone();
+ m_levels = m_prevLevels.Clone();
+ m_sortValid = true;
+
+ // The sort may have changed the number of rows
+ // before we restore the selection, tell the tree
+ // do this before we call restore selection
+ // this is safe when there is no selection.
+ rv = AdjustRowCount(rowCountBeforeSort, GetSize());
+
+ RestoreSelection(preservedKey, preservedSelection);
+ if (mTree) mTree->Invalidate();
+ if (mJSTree) mJSTree->Invalidate();
+
+ return NS_OK;
+ } else {
+ // Set sort info in anticipation of what Init will do.
+ // Build up thread list.
+ int32_t unused; // count.
+ InitThreadedView(unused);
+ if (sortOrder != nsMsgViewSortOrder::ascending)
+ Sort(sortType, sortOrder);
+
+ // The sort may have changed the number of rows
+ // before we update the selection, tell the tree
+ // do this before we call restore selection
+ // this is safe when there is no selection.
+ rv = AdjustRowCount(rowCountBeforeSort, GetSize());
+
+ RestoreSelection(preservedKey, preservedSelection);
+ if (mTree) mTree->Invalidate();
+ if (mJSTree) mJSTree->Invalidate();
+
+ return NS_OK;
+ }
+ } else if (sortType != nsMsgViewSortType::byThread &&
+ (m_sortType == nsMsgViewSortType::byThread || sortThreads)
+ /* && !m_havePrevView*/) {
+ if (sortThreads) {
+ SortThreads(sortType, sortOrder);
+ // Hack so base class won't do anything.
+ sortType = nsMsgViewSortType::byThread;
+ } else {
+ // Going from SortByThread to non-thread sort - must build new key,
+ // level, and flags arrays.
+ m_prevKeys = m_keys.Clone();
+ m_prevFlags = m_flags.Clone();
+ m_prevLevels = m_levels.Clone();
+ // Do this before we sort, so that we'll use the cheap method
+ // of expanding.
+ m_viewFlags &= ~(nsMsgViewFlagsType::kThreadedDisplay |
+ nsMsgViewFlagsType::kGroupBySort);
+ ExpandAll();
+ // m_idArray.RemoveAll();
+ // m_flags.Clear();
+ m_havePrevView = true;
+ }
+ }
+ } else if (m_sortOrder != sortOrder) {
+ // Check for toggling the sort.
+ nsMsgDBView::Sort(sortType, sortOrder);
+ }
+
+ if (!sortThreads) {
+ // Call the base class in case we're not sorting by thread.
+ rv = nsMsgDBView::Sort(sortType, sortOrder);
+ NS_ENSURE_SUCCESS(rv, rv);
+ SaveSortInfo(sortType, sortOrder);
+ }
+
+ // The sort may have changed the number of rows
+ // before we restore the selection, tell the tree
+ // do this before we call restore selection
+ // this is safe when there is no selection.
+ rv = AdjustRowCount(rowCountBeforeSort, GetSize());
+
+ RestoreSelection(preservedKey, preservedSelection);
+ if (mTree) mTree->Invalidate();
+ if (mJSTree) mJSTree->Invalidate();
+
+ NS_ENSURE_SUCCESS(rv, rv);
+ return NS_OK;
+}
+
+void nsMsgThreadedDBView::OnExtraFlagChanged(nsMsgViewIndex index,
+ uint32_t extraFlag) {
+ if (IsValidIndex(index)) {
+ if (m_havePrevView) {
+ nsMsgKey keyChanged = m_keys[index];
+ nsMsgViewIndex prevViewIndex = m_prevKeys.IndexOf(keyChanged);
+ if (prevViewIndex != nsMsgViewIndex_None) {
+ uint32_t prevFlag = m_prevFlags[prevViewIndex];
+ // Don't want to change the elided bit, or has children or is thread.
+ if (prevFlag & nsMsgMessageFlags::Elided)
+ extraFlag |= nsMsgMessageFlags::Elided;
+ else
+ extraFlag &= ~nsMsgMessageFlags::Elided;
+
+ if (prevFlag & MSG_VIEW_FLAG_ISTHREAD)
+ extraFlag |= MSG_VIEW_FLAG_ISTHREAD;
+ else
+ extraFlag &= ~MSG_VIEW_FLAG_ISTHREAD;
+
+ if (prevFlag & MSG_VIEW_FLAG_HASCHILDREN)
+ extraFlag |= MSG_VIEW_FLAG_HASCHILDREN;
+ else
+ extraFlag &= ~MSG_VIEW_FLAG_HASCHILDREN;
+
+ // Will this be right?
+ m_prevFlags[prevViewIndex] = extraFlag;
+ }
+ }
+ }
+
+ // We don't really know what's changed, but to be on the safe side, set the
+ // sort invalid so that reverse sort will pick it up.
+ if (m_sortType == nsMsgViewSortType::byStatus ||
+ m_sortType == nsMsgViewSortType::byFlagged ||
+ m_sortType == nsMsgViewSortType::byUnread ||
+ m_sortType == nsMsgViewSortType::byPriority) {
+ m_sortValid = false;
+ }
+}
+
+void nsMsgThreadedDBView::OnHeaderAddedOrDeleted() { ClearPrevIdArray(); }
+
+void nsMsgThreadedDBView::ClearPrevIdArray() {
+ m_prevKeys.Clear();
+ m_prevLevels.Clear();
+ m_prevFlags.Clear();
+ m_havePrevView = false;
+}
+
+nsresult nsMsgThreadedDBView::InitSort(nsMsgViewSortTypeValue sortType,
+ nsMsgViewSortOrderValue sortOrder) {
+ // Nothing to do.
+ if (m_viewFlags & nsMsgViewFlagsType::kGroupBySort) return NS_OK;
+
+ if (sortType == nsMsgViewSortType::byThread) {
+ // Sort top level threads by id.
+ nsMsgDBView::Sort(nsMsgViewSortType::byId, sortOrder);
+ m_sortType = nsMsgViewSortType::byThread;
+ m_viewFlags |= nsMsgViewFlagsType::kThreadedDisplay;
+ m_viewFlags &= ~nsMsgViewFlagsType::kGroupBySort;
+ // Persist the view flags.
+ SetViewFlags(m_viewFlags);
+ // m_db->SetSortInfo(m_sortType, sortOrder);
+ }
+ // else
+ // m_viewFlags &= ~nsMsgViewFlagsType::kThreadedDisplay;
+
+ // By default, the unread only view should have all threads expanded.
+ if ((m_viewFlags &
+ (nsMsgViewFlagsType::kUnreadOnly | nsMsgViewFlagsType::kExpandAll)) &&
+ (m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay)) {
+ ExpandAll();
+ }
+
+ if (!(m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay)) {
+ // For now, expand all and do a flat sort.
+ ExpandAll();
+ }
+
+ Sort(sortType, sortOrder);
+ if (sortType != nsMsgViewSortType::byThread) {
+ // Forget prev view, since it has everything expanded.
+ ClearPrevIdArray();
+ }
+
+ return NS_OK;
+}
+
+nsresult nsMsgThreadedDBView::OnNewHeader(nsIMsgDBHdr* newHdr,
+ nsMsgKey aParentKey,
+ bool ensureListed) {
+ if (m_viewFlags & nsMsgViewFlagsType::kGroupBySort)
+ return nsMsgGroupView::OnNewHeader(newHdr, aParentKey, ensureListed);
+
+ NS_ENSURE_TRUE(newHdr, NS_MSG_MESSAGE_NOT_FOUND);
+
+ nsMsgKey newKey;
+ newHdr->GetMessageKey(&newKey);
+
+ // Views can override this behaviour, which is to append to view.
+ // This is the mail behaviour, but threaded views want
+ // to insert in order...
+ uint32_t msgFlags;
+ newHdr->GetFlags(&msgFlags);
+ if (m_viewFlags & nsMsgViewFlagsType::kUnreadOnly && !ensureListed &&
+ msgFlags & nsMsgMessageFlags::Read) {
+ return NS_OK;
+ }
+
+ // Currently, we only add the header in a threaded view if it's a thread.
+ // We used to check if this was the first header in the thread, but that's
+ // a bit harder in the unreadOnly view. But we'll catch it below.
+
+ // If not threaded display just add it to the view.
+ if (!(m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay))
+ return AddHdr(newHdr);
+
+ // Need to find the thread we added this to so we can change the hasnew flag
+ // added message to existing thread, but not to view.
+ // Fix flags on thread header.
+ int32_t threadCount;
+ uint32_t threadFlags;
+ bool moveThread = false;
+ nsMsgViewIndex threadIndex =
+ ThreadIndexOfMsg(newKey, nsMsgViewIndex_None, &threadCount, &threadFlags);
+ bool threadRootIsDisplayed = false;
+
+ nsCOMPtr<nsIMsgThread> threadHdr;
+ m_db->GetThreadContainingMsgHdr(newHdr, getter_AddRefs(threadHdr));
+ if (threadHdr && m_sortType == nsMsgViewSortType::byDate) {
+ uint32_t newestMsgInThread = 0, msgDate = 0;
+ threadHdr->GetNewestMsgDate(&newestMsgInThread);
+ newHdr->GetDateInSeconds(&msgDate);
+ moveThread = (msgDate == newestMsgInThread);
+ }
+
+ if (threadIndex != nsMsgViewIndex_None) {
+ threadRootIsDisplayed = (m_currentlyDisplayedViewIndex == threadIndex);
+ uint32_t flags = m_flags[threadIndex];
+ if (!(flags & MSG_VIEW_FLAG_HASCHILDREN)) {
+ flags |= MSG_VIEW_FLAG_HASCHILDREN | MSG_VIEW_FLAG_ISTHREAD;
+ if (!(m_viewFlags & nsMsgViewFlagsType::kUnreadOnly))
+ flags |= nsMsgMessageFlags::Elided;
+
+ m_flags[threadIndex] = flags;
+ }
+
+ if (!(flags & nsMsgMessageFlags::Elided)) {
+ // Thread is expanded.
+ // Insert child into thread.
+ // Levels of other hdrs may have changed!
+ uint32_t newFlags = msgFlags;
+ int32_t level = 0;
+ nsMsgViewIndex insertIndex = threadIndex;
+ if (aParentKey == nsMsgKey_None) {
+ newFlags |= MSG_VIEW_FLAG_ISTHREAD | MSG_VIEW_FLAG_HASCHILDREN;
+ } else {
+ nsMsgViewIndex parentIndex =
+ FindParentInThread(aParentKey, threadIndex);
+ level = m_levels[parentIndex] + 1;
+ insertIndex = GetInsertInfoForNewHdr(newHdr, parentIndex, level);
+ }
+
+ InsertMsgHdrAt(insertIndex, newHdr, newKey, newFlags, level);
+ // The call to NoteChange() has to happen after we add the key as
+ // NoteChange() will call RowCountChanged() which will call our
+ // GetRowCount().
+ NoteChange(insertIndex, 1, nsMsgViewNotificationCode::insertOrDelete);
+
+ if (aParentKey == nsMsgKey_None) {
+ // this header is the new king! try collapsing the existing thread,
+ // removing it, installing this header as king, and expanding it.
+ CollapseByIndex(threadIndex, nullptr);
+ // call base class, so child won't get promoted.
+ // nsMsgDBView::RemoveByIndex(threadIndex);
+ ExpandByIndex(threadIndex, nullptr);
+ }
+ } else if (aParentKey == nsMsgKey_None) {
+ // if we have a collapsed thread which just got a new
+ // top of thread, change the keys array.
+ m_keys[threadIndex] = newKey;
+ }
+
+ // If this message is new, the thread is collapsed, it is the
+ // root and it was displayed, expand it so that the user does
+ // not find that their message has magically turned into a summary.
+ if (msgFlags & nsMsgMessageFlags::New &&
+ m_flags[threadIndex] & nsMsgMessageFlags::Elided &&
+ threadRootIsDisplayed)
+ ExpandByIndex(threadIndex, nullptr);
+
+ if (moveThread)
+ MoveThreadAt(threadIndex);
+ else
+ // note change, to update the parent thread's unread and total counts
+ NoteChange(threadIndex, 1, nsMsgViewNotificationCode::changed);
+ } else if (threadHdr) {
+ // Adding msg to thread that's not in view.
+ AddMsgToThreadNotInView(threadHdr, newHdr, ensureListed);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgThreadedDBView::OnParentChanged(nsMsgKey aKeyChanged, nsMsgKey oldParent,
+ nsMsgKey newParent,
+ nsIDBChangeListener* aInstigator) {
+ // We need to adjust the level of the hdr whose parent changed, and
+ // invalidate that row, iff we're in threaded mode.
+#if 0
+ // This code never runs due to the if (false) and Clang complains about it
+ // so it is ifdefed out for now.
+ if (false && m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay)
+ {
+ nsMsgViewIndex childIndex = FindViewIndex(aKeyChanged);
+ if (childIndex != nsMsgViewIndex_None)
+ {
+ nsMsgViewIndex parentIndex = FindViewIndex(newParent);
+ int32_t newParentLevel =
+ (parentIndex == nsMsgViewIndex_None) ? -1 : m_levels[parentIndex];
+
+ nsMsgViewIndex oldParentIndex = FindViewIndex(oldParent);
+
+ int32_t oldParentLevel =
+ (oldParentIndex != nsMsgViewIndex_None ||
+ newParent == nsMsgKey_None) ? m_levels[oldParentIndex] : -1 ;
+
+ int32_t levelChanged = m_levels[childIndex];
+ int32_t parentDelta = oldParentLevel - newParentLevel;
+ m_levels[childIndex] = (newParent == nsMsgKey_None) ? 0 : newParentLevel + 1;
+ if (parentDelta > 0)
+ {
+ for (nsMsgViewIndex viewIndex = childIndex + 1;
+ viewIndex < GetSize() && m_levels[viewIndex] > levelChanged;
+ viewIndex++)
+ {
+ m_levels[viewIndex] = m_levels[viewIndex] - parentDelta;
+ NoteChange(viewIndex, 1, nsMsgViewNotificationCode::changed);
+ }
+ }
+
+ NoteChange(childIndex, 1, nsMsgViewNotificationCode::changed);
+ }
+ }
+#endif
+ return NS_OK;
+}
+
+nsMsgViewIndex nsMsgThreadedDBView::GetInsertInfoForNewHdr(
+ nsIMsgDBHdr* newHdr, nsMsgViewIndex parentIndex, int32_t targetLevel) {
+ uint32_t viewSize = GetSize();
+ while (++parentIndex < viewSize) {
+ // Loop until we find a message at a level less than or equal to the
+ // parent level
+ if (m_levels[parentIndex] < targetLevel) break;
+ }
+
+ return parentIndex;
+}
+
+// This method removes the thread at threadIndex from the view
+// and puts it back in its new position, determined by the sort order.
+// And, if the selection is affected, save and restore the selection.
+void nsMsgThreadedDBView::MoveThreadAt(nsMsgViewIndex threadIndex) {
+ // We need to check if the thread is collapsed or not...
+ // We want to turn off tree notifications so that we don't
+ // reload the current message.
+ // We also need to invalidate the range between where the thread was
+ // and where it ended up.
+ bool changesDisabled = mSuppressChangeNotification;
+ if (!changesDisabled) SetSuppressChangeNotifications(true);
+
+ nsCOMPtr<nsIMsgDBHdr> threadHdr;
+
+ GetMsgHdrForViewIndex(threadIndex, getter_AddRefs(threadHdr));
+ int32_t childCount = 0;
+
+ nsMsgKey preservedKey;
+ AutoTArray<nsMsgKey, 1> preservedSelection;
+ int32_t selectionCount;
+ int32_t currentIndex;
+ bool hasSelection =
+ mTreeSelection &&
+ ((NS_SUCCEEDED(mTreeSelection->GetCurrentIndex(&currentIndex)) &&
+ currentIndex >= 0 && (uint32_t)currentIndex < GetSize()) ||
+ (NS_SUCCEEDED(mTreeSelection->GetRangeCount(&selectionCount)) &&
+ selectionCount > 0));
+ if (hasSelection) SaveAndClearSelection(&preservedKey, preservedSelection);
+
+ uint32_t saveFlags = m_flags[threadIndex];
+ bool threadIsExpanded = !(saveFlags & nsMsgMessageFlags::Elided);
+
+ if (threadIsExpanded) {
+ ExpansionDelta(threadIndex, &childCount);
+ childCount = -childCount;
+ }
+
+ nsTArray<nsMsgKey> threadKeys;
+ nsTArray<uint32_t> threadFlags;
+ nsTArray<uint8_t> threadLevels;
+
+ if (threadIsExpanded) {
+ threadKeys.SetCapacity(childCount);
+ threadFlags.SetCapacity(childCount);
+ threadLevels.SetCapacity(childCount);
+ for (nsMsgViewIndex index = threadIndex + 1;
+ index < GetSize() && m_levels[index]; index++) {
+ threadKeys.AppendElement(m_keys[index]);
+ threadFlags.AppendElement(m_flags[index]);
+ threadLevels.AppendElement(m_levels[index]);
+ }
+
+ uint32_t collapseCount;
+ CollapseByIndex(threadIndex, &collapseCount);
+ }
+
+ nsMsgDBView::RemoveByIndex(threadIndex);
+ nsMsgViewIndex newIndex = nsMsgViewIndex_None;
+ AddHdr(threadHdr, &newIndex);
+
+ // AddHdr doesn't always set newIndex, and getting it to do so
+ // is going to require some refactoring.
+ if (newIndex == nsMsgViewIndex_None) newIndex = FindHdr(threadHdr);
+
+ if (threadIsExpanded) {
+ m_keys.InsertElementsAt(newIndex + 1, threadKeys);
+ m_flags.InsertElementsAt(newIndex + 1, threadFlags);
+ m_levels.InsertElementsAt(newIndex + 1, threadLevels);
+ }
+
+ if (newIndex == nsMsgViewIndex_None) {
+ NS_WARNING("newIndex=-1 in MoveThreadAt");
+ newIndex = 0;
+ }
+
+ m_flags[newIndex] = saveFlags;
+ // Unfreeze selection.
+ if (hasSelection) RestoreSelection(preservedKey, preservedSelection);
+
+ if (!changesDisabled) SetSuppressChangeNotifications(false);
+
+ nsMsgViewIndex lowIndex = threadIndex < newIndex ? threadIndex : newIndex;
+ nsMsgViewIndex highIndex = lowIndex == threadIndex ? newIndex : threadIndex;
+
+ NoteChange(lowIndex, highIndex - lowIndex + childCount + 1,
+ nsMsgViewNotificationCode::changed);
+}
+
+nsresult nsMsgThreadedDBView::AddMsgToThreadNotInView(nsIMsgThread* threadHdr,
+ nsIMsgDBHdr* msgHdr,
+ bool ensureListed) {
+ nsresult rv = NS_OK;
+ uint32_t threadFlags;
+ threadHdr->GetFlags(&threadFlags);
+ if (!(threadFlags & nsMsgMessageFlags::Ignored)) {
+ bool msgKilled;
+ msgHdr->GetIsKilled(&msgKilled);
+ if (!msgKilled) rv = nsMsgDBView::AddHdr(msgHdr);
+ }
+
+ return rv;
+}
+
+// This method just removes the specified line from the view. It does
+// NOT delete it from the database.
+nsresult nsMsgThreadedDBView::RemoveByIndex(nsMsgViewIndex index) {
+ nsresult rv = NS_OK;
+ int32_t flags;
+
+ if (!IsValidIndex(index)) return NS_MSG_INVALID_DBVIEW_INDEX;
+
+ OnHeaderAddedOrDeleted();
+
+ flags = m_flags[index];
+
+ if (!(m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay))
+ return nsMsgDBView::RemoveByIndex(index);
+
+ nsCOMPtr<nsIMsgThread> threadHdr;
+ GetThreadContainingIndex(index, getter_AddRefs(threadHdr));
+ NS_ENSURE_SUCCESS(rv, rv);
+ uint32_t numThreadChildren = 0;
+ // If we can't get a thread, it's already deleted and thus has 0 children.
+ if (threadHdr) threadHdr->GetNumChildren(&numThreadChildren);
+
+ // Check if we're the top level msg in the thread, and we're not collapsed.
+ if ((flags & MSG_VIEW_FLAG_ISTHREAD) &&
+ !(flags & nsMsgMessageFlags::Elided) &&
+ (flags & MSG_VIEW_FLAG_HASCHILDREN)) {
+ // Fix flags on thread header - newly promoted message should have
+ // flags set correctly.
+ if (threadHdr) {
+ nsMsgDBView::RemoveByIndex(index);
+ nsCOMPtr<nsIMsgThread> nextThreadHdr;
+ // Above RemoveByIndex may now make index out of bounds.
+ if (IsValidIndex(index) && numThreadChildren > 0) {
+ // unreadOnly
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ rv = threadHdr->GetChildHdrAt(0, getter_AddRefs(msgHdr));
+ if (msgHdr != nullptr) {
+ uint32_t flag = 0;
+ msgHdr->GetFlags(&flag);
+ if (numThreadChildren > 1)
+ flag |= MSG_VIEW_FLAG_ISTHREAD | MSG_VIEW_FLAG_HASCHILDREN;
+
+ m_flags[index] = flag;
+ m_levels[index] = 0;
+ }
+ }
+ }
+
+ return rv;
+ } else if (!(flags & MSG_VIEW_FLAG_ISTHREAD)) {
+ // We're not deleting the top level msg, but top level msg might be the
+ // only msg in thread now.
+ if (threadHdr && numThreadChildren == 1) {
+ nsMsgKey msgKey;
+ rv = threadHdr->GetChildKeyAt(0, &msgKey);
+ if (NS_SUCCEEDED(rv)) {
+ nsMsgViewIndex threadIndex = FindViewIndex(msgKey);
+ if (IsValidIndex(threadIndex)) {
+ uint32_t flags = m_flags[threadIndex];
+ flags &= ~(MSG_VIEW_FLAG_ISTHREAD | nsMsgMessageFlags::Elided |
+ MSG_VIEW_FLAG_HASCHILDREN);
+ m_flags[threadIndex] = flags;
+ NoteChange(threadIndex, 1, nsMsgViewNotificationCode::changed);
+ }
+ }
+ }
+
+ return nsMsgDBView::RemoveByIndex(index);
+ }
+
+ // Deleting collapsed thread header is special case. Child will be promoted,
+ // so just tell FE that line changed, not that it was deleted.
+ // Header has already been deleted from thread.
+ if (threadHdr && numThreadChildren > 0) {
+ // Change the id array and flags array to reflect the child header.
+ // If we're not deleting the header, we want the second header,
+ // Otherwise, the first one (which just got promoted).
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ rv = threadHdr->GetChildHdrAt(0, getter_AddRefs(msgHdr));
+ if (msgHdr != nullptr) {
+ msgHdr->GetMessageKey(&m_keys[index]);
+ uint32_t flag = 0;
+ msgHdr->GetFlags(&flag);
+ flag |= MSG_VIEW_FLAG_ISTHREAD;
+
+ // If only hdr in thread (with one about to be deleted).
+ if (numThreadChildren == 1) {
+ // Adjust flags.
+ flag &= ~MSG_VIEW_FLAG_HASCHILDREN;
+ flag &= ~nsMsgMessageFlags::Elided;
+ // Tell FE that thread header needs to be repainted.
+ NoteChange(index, 1, nsMsgViewNotificationCode::changed);
+ } else {
+ flag |= MSG_VIEW_FLAG_HASCHILDREN;
+ flag |= nsMsgMessageFlags::Elided;
+ }
+
+ m_flags[index] = flag;
+ mIndicesToNoteChange.RemoveElement(index);
+ } else {
+ NS_ASSERTION(false, "couldn't find thread child");
+ }
+
+ NoteChange(index, 1, nsMsgViewNotificationCode::changed);
+ } else {
+ // We may have deleted a whole, collapsed thread - if so,
+ // ensure that the current index will be noted as changed.
+ if (!mIndicesToNoteChange.Contains(index))
+ mIndicesToNoteChange.AppendElement(index);
+
+ rv = nsMsgDBView::RemoveByIndex(index);
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgThreadedDBView::GetViewType(nsMsgViewTypeValue* aViewType) {
+ NS_ENSURE_ARG_POINTER(aViewType);
+ *aViewType = nsMsgViewType::eShowAllThreads;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgThreadedDBView::CloneDBView(nsIMessenger* aMessengerInstance,
+ nsIMsgWindow* aMsgWindow,
+ nsIMsgDBViewCommandUpdater* aCmdUpdater,
+ nsIMsgDBView** _retval) {
+ nsMsgThreadedDBView* newMsgDBView = new nsMsgThreadedDBView();
+
+ if (!newMsgDBView) return NS_ERROR_OUT_OF_MEMORY;
+
+ nsresult rv =
+ CopyDBView(newMsgDBView, aMessengerInstance, aMsgWindow, aCmdUpdater);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_IF_ADDREF(*_retval = newMsgDBView);
+ return NS_OK;
+}
diff --git a/comm/mailnews/base/src/nsMsgThreadedDBView.h b/comm/mailnews/base/src/nsMsgThreadedDBView.h
new file mode 100644
index 0000000000..a33c98a3bf
--- /dev/null
+++ b/comm/mailnews/base/src/nsMsgThreadedDBView.h
@@ -0,0 +1,62 @@
+/* -*- 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 _nsMsgThreadedDBView_H_
+#define _nsMsgThreadedDBView_H_
+
+#include "mozilla/Attributes.h"
+#include "nsMsgGroupView.h"
+
+class nsMsgThreadedDBView : public nsMsgGroupView {
+ public:
+ nsMsgThreadedDBView();
+ virtual ~nsMsgThreadedDBView();
+
+ NS_IMETHOD Open(nsIMsgFolder* folder, nsMsgViewSortTypeValue sortType,
+ nsMsgViewSortOrderValue sortOrder,
+ nsMsgViewFlagsTypeValue viewFlags, int32_t* pCount) override;
+ NS_IMETHOD CloneDBView(nsIMessenger* aMessengerInstance,
+ nsIMsgWindow* aMsgWindow,
+ nsIMsgDBViewCommandUpdater* aCommandUpdater,
+ nsIMsgDBView** _retval) override;
+ NS_IMETHOD Close() override;
+ NS_IMETHOD Sort(nsMsgViewSortTypeValue sortType,
+ nsMsgViewSortOrderValue sortOrder) override;
+ NS_IMETHOD GetViewType(nsMsgViewTypeValue* aViewType) override;
+ NS_IMETHOD OnParentChanged(nsMsgKey aKeyChanged, nsMsgKey oldParent,
+ nsMsgKey newParent,
+ nsIDBChangeListener* aInstigator) override;
+
+ protected:
+ virtual const char* GetViewName(void) override { return "ThreadedDBView"; }
+ nsresult InitThreadedView(int32_t& count);
+ virtual nsresult OnNewHeader(nsIMsgDBHdr* newHdr, nsMsgKey aParentKey,
+ bool ensureListed) override;
+ virtual nsresult AddMsgToThreadNotInView(nsIMsgThread* threadHdr,
+ nsIMsgDBHdr* msgHdr,
+ bool ensureListed);
+ nsresult InitSort(nsMsgViewSortTypeValue sortType,
+ nsMsgViewSortOrderValue sortOrder);
+ virtual nsresult SortThreads(nsMsgViewSortTypeValue sortType,
+ nsMsgViewSortOrderValue sortOrder);
+ virtual void OnExtraFlagChanged(nsMsgViewIndex index,
+ uint32_t extraFlag) override;
+ virtual void OnHeaderAddedOrDeleted() override;
+ void ClearPrevIdArray();
+ virtual nsresult RemoveByIndex(nsMsgViewIndex index) override;
+ nsMsgViewIndex GetInsertInfoForNewHdr(nsIMsgDBHdr* newHdr,
+ nsMsgViewIndex threadIndex,
+ int32_t targetLevel);
+ void MoveThreadAt(nsMsgViewIndex threadIndex);
+
+ // these are used to save off the previous view so that bopping back and forth
+ // between two views is quick (e.g., threaded and flat sorted by date).
+ bool m_havePrevView;
+ nsTArray<nsMsgKey> m_prevKeys; // this is used for caching non-threaded view.
+ nsTArray<uint32_t> m_prevFlags;
+ nsTArray<uint8_t> m_prevLevels;
+};
+
+#endif
diff --git a/comm/mailnews/base/src/nsMsgTxn.cpp b/comm/mailnews/base/src/nsMsgTxn.cpp
new file mode 100644
index 0000000000..0074fc1960
--- /dev/null
+++ b/comm/mailnews/base/src/nsMsgTxn.cpp
@@ -0,0 +1,247 @@
+/* -*- 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 "nsMsgTxn.h"
+#include "nsIMsgHdr.h"
+#include "nsIMsgDatabase.h"
+#include "nsCOMArray.h"
+#include "nsArrayEnumerator.h"
+#include "nsComponentManagerUtils.h"
+#include "nsVariant.h"
+#include "nsIProperty.h"
+#include "nsMsgMessageFlags.h"
+#include "nsIMsgFolder.h"
+
+NS_IMPL_ADDREF(nsMsgTxn)
+NS_IMPL_RELEASE(nsMsgTxn)
+NS_INTERFACE_MAP_BEGIN(nsMsgTxn)
+ NS_INTERFACE_MAP_ENTRY(nsIWritablePropertyBag)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsIPropertyBag, nsIWritablePropertyBag)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIWritablePropertyBag)
+ NS_INTERFACE_MAP_ENTRY(nsITransaction)
+ NS_INTERFACE_MAP_ENTRY(nsIPropertyBag2)
+ NS_INTERFACE_MAP_ENTRY(nsIWritablePropertyBag2)
+NS_INTERFACE_MAP_END
+
+nsMsgTxn::nsMsgTxn() { m_txnType = 0; }
+
+nsMsgTxn::~nsMsgTxn() {}
+
+nsresult nsMsgTxn::Init() { return NS_OK; }
+
+NS_IMETHODIMP nsMsgTxn::HasKey(const nsAString& name, bool* aResult) {
+ *aResult = mPropertyHash.Get(name, nullptr);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgTxn::Get(const nsAString& name, nsIVariant** _retval) {
+ mPropertyHash.Get(name, _retval);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgTxn::GetProperty(const nsAString& name,
+ nsIVariant** _retval) {
+ return mPropertyHash.Get(name, _retval) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP nsMsgTxn::SetProperty(const nsAString& name, nsIVariant* value) {
+ NS_ENSURE_ARG_POINTER(value);
+ mPropertyHash.InsertOrUpdate(name, value);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgTxn::DeleteProperty(const nsAString& name) {
+ if (!mPropertyHash.Get(name, nullptr)) return NS_ERROR_FAILURE;
+
+ mPropertyHash.Remove(name);
+ return mPropertyHash.Get(name, nullptr) ? NS_ERROR_FAILURE : NS_OK;
+}
+
+//
+// nsMailSimpleProperty class and impl; used for GetEnumerator
+// This is same as nsSimpleProperty but for external API use.
+//
+
+class nsMailSimpleProperty final : public nsIProperty {
+ public:
+ nsMailSimpleProperty(const nsAString& aName, nsIVariant* aValue)
+ : mName(aName), mValue(aValue) {}
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIPROPERTY
+ protected:
+ ~nsMailSimpleProperty() {}
+
+ nsString mName;
+ nsCOMPtr<nsIVariant> mValue;
+};
+
+NS_IMPL_ISUPPORTS(nsMailSimpleProperty, nsIProperty)
+
+NS_IMETHODIMP nsMailSimpleProperty::GetName(nsAString& aName) {
+ aName.Assign(mName);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMailSimpleProperty::GetValue(nsIVariant** aValue) {
+ NS_IF_ADDREF(*aValue = mValue);
+ return NS_OK;
+}
+
+// end nsMailSimpleProperty
+
+NS_IMETHODIMP nsMsgTxn::GetEnumerator(nsISimpleEnumerator** _retval) {
+ nsCOMArray<nsIProperty> propertyArray;
+ for (auto iter = mPropertyHash.Iter(); !iter.Done(); iter.Next()) {
+ nsMailSimpleProperty* sprop =
+ new nsMailSimpleProperty(iter.Key(), iter.Data());
+ propertyArray.AppendObject(sprop);
+ }
+ return NS_NewArrayEnumerator(_retval, propertyArray, NS_GET_IID(nsIProperty));
+}
+
+#define IMPL_GETSETPROPERTY_AS(Name, Type) \
+ NS_IMETHODIMP \
+ nsMsgTxn::GetPropertyAs##Name(const nsAString& prop, Type* _retval) { \
+ nsIVariant* v = mPropertyHash.GetWeak(prop); \
+ if (!v) return NS_ERROR_NOT_AVAILABLE; \
+ return v->GetAs##Name(_retval); \
+ } \
+ \
+ NS_IMETHODIMP \
+ nsMsgTxn::SetPropertyAs##Name(const nsAString& prop, Type value) { \
+ nsCOMPtr<nsIWritableVariant> var = new nsVariant(); \
+ var->SetAs##Name(value); \
+ return SetProperty(prop, var); \
+ }
+
+IMPL_GETSETPROPERTY_AS(Int32, int32_t)
+IMPL_GETSETPROPERTY_AS(Uint32, uint32_t)
+IMPL_GETSETPROPERTY_AS(Int64, int64_t)
+IMPL_GETSETPROPERTY_AS(Uint64, uint64_t)
+IMPL_GETSETPROPERTY_AS(Double, double)
+IMPL_GETSETPROPERTY_AS(Bool, bool)
+
+NS_IMETHODIMP nsMsgTxn::GetPropertyAsAString(const nsAString& prop,
+ nsAString& _retval) {
+ nsIVariant* v = mPropertyHash.GetWeak(prop);
+ if (!v) return NS_ERROR_NOT_AVAILABLE;
+ return v->GetAsAString(_retval);
+}
+
+NS_IMETHODIMP nsMsgTxn::GetPropertyAsACString(const nsAString& prop,
+ nsACString& _retval) {
+ nsIVariant* v = mPropertyHash.GetWeak(prop);
+ if (!v) return NS_ERROR_NOT_AVAILABLE;
+ return v->GetAsACString(_retval);
+}
+
+NS_IMETHODIMP nsMsgTxn::GetPropertyAsAUTF8String(const nsAString& prop,
+ nsACString& _retval) {
+ nsIVariant* v = mPropertyHash.GetWeak(prop);
+ if (!v) return NS_ERROR_NOT_AVAILABLE;
+ return v->GetAsAUTF8String(_retval);
+}
+
+NS_IMETHODIMP nsMsgTxn::GetPropertyAsInterface(const nsAString& prop,
+ const nsIID& aIID,
+ void** _retval) {
+ nsIVariant* v = mPropertyHash.GetWeak(prop);
+ if (!v) return NS_ERROR_NOT_AVAILABLE;
+ nsCOMPtr<nsISupports> val;
+ nsresult rv = v->GetAsISupports(getter_AddRefs(val));
+ if (NS_FAILED(rv)) return rv;
+ if (!val) {
+ // We have a value, but it's null
+ *_retval = nullptr;
+ return NS_OK;
+ }
+ return val->QueryInterface(aIID, _retval);
+}
+
+NS_IMETHODIMP nsMsgTxn::SetPropertyAsAString(const nsAString& prop,
+ const nsAString& value) {
+ nsCOMPtr<nsIWritableVariant> var = new nsVariant();
+ var->SetAsAString(value);
+ return SetProperty(prop, var);
+}
+
+NS_IMETHODIMP nsMsgTxn::SetPropertyAsACString(const nsAString& prop,
+ const nsACString& value) {
+ nsCOMPtr<nsIWritableVariant> var = new nsVariant();
+ var->SetAsACString(value);
+ return SetProperty(prop, var);
+}
+
+NS_IMETHODIMP nsMsgTxn::SetPropertyAsAUTF8String(const nsAString& prop,
+ const nsACString& value) {
+ nsCOMPtr<nsIWritableVariant> var = new nsVariant();
+ var->SetAsAUTF8String(value);
+ return SetProperty(prop, var);
+}
+
+NS_IMETHODIMP nsMsgTxn::SetPropertyAsInterface(const nsAString& prop,
+ nsISupports* value) {
+ nsCOMPtr<nsIWritableVariant> var = new nsVariant();
+ var->SetAsISupports(value);
+ return SetProperty(prop, var);
+}
+
+/////////////////////// Transaction Stuff //////////////////
+NS_IMETHODIMP nsMsgTxn::DoTransaction(void) { return NS_OK; }
+
+NS_IMETHODIMP nsMsgTxn::GetIsTransient(bool* aIsTransient) {
+ if (nullptr != aIsTransient)
+ *aIsTransient = false;
+ else
+ return NS_ERROR_NULL_POINTER;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgTxn::Merge(nsITransaction* aTransaction, bool* aDidMerge) {
+ *aDidMerge = false;
+ return NS_OK;
+}
+
+nsresult nsMsgTxn::GetMsgWindow(nsIMsgWindow** msgWindow) {
+ if (!msgWindow || !m_msgWindow) return NS_ERROR_NULL_POINTER;
+ NS_ADDREF(*msgWindow = m_msgWindow);
+ return NS_OK;
+}
+
+nsresult nsMsgTxn::SetMsgWindow(nsIMsgWindow* msgWindow) {
+ m_msgWindow = msgWindow;
+ return NS_OK;
+}
+
+nsresult nsMsgTxn::SetTransactionType(uint32_t txnType) {
+ return SetPropertyAsUint32(u"type"_ns, txnType);
+}
+
+/*none of the callers pass null aFolder,
+ we always initialize aResult (before we pass in) for the case where the key is
+ not in the db*/
+nsresult nsMsgTxn::CheckForToggleDelete(nsIMsgFolder* aFolder,
+ const nsMsgKey& aMsgKey,
+ bool* aResult) {
+ NS_ENSURE_ARG(aResult);
+ nsCOMPtr<nsIMsgDBHdr> message;
+ nsCOMPtr<nsIMsgDatabase> db;
+ nsresult rv = aFolder->GetMsgDatabase(getter_AddRefs(db));
+ if (db) {
+ bool containsKey;
+ rv = db->ContainsKey(aMsgKey, &containsKey);
+ if (NS_FAILED(rv) || !containsKey) // the message has been deleted from db,
+ // so we cannot do toggle here
+ return NS_OK;
+ rv = db->GetMsgHdrForKey(aMsgKey, getter_AddRefs(message));
+ uint32_t flags;
+ if (NS_SUCCEEDED(rv) && message) {
+ message->GetFlags(&flags);
+ *aResult = (flags & nsMsgMessageFlags::IMAPDeleted) != 0;
+ }
+ }
+ return rv;
+}
diff --git a/comm/mailnews/base/src/nsMsgTxn.h b/comm/mailnews/base/src/nsMsgTxn.h
new file mode 100644
index 0000000000..824b26993b
--- /dev/null
+++ b/comm/mailnews/base/src/nsMsgTxn.h
@@ -0,0 +1,77 @@
+/* -*- 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 nsMsgTxn_h__
+#define nsMsgTxn_h__
+
+#include "mozilla/Attributes.h"
+#include "nsITransaction.h"
+#include "msgCore.h"
+#include "nsCOMPtr.h"
+#include "nsIMsgWindow.h"
+#include "nsInterfaceHashtable.h"
+#include "MailNewsTypes2.h"
+#include "nsIVariant.h"
+#include "nsIWritablePropertyBag.h"
+#include "nsIWritablePropertyBag2.h"
+
+#include "mozilla/EditTransactionBase.h"
+
+using mozilla::EditTransactionBase;
+
+#define NS_MESSAGETRANSACTION_IID \
+ { /* da621b30-1efc-11d3-abe4-00805f8ac968 */ \
+ 0xda621b30, 0x1efc, 0x11d3, { \
+ 0xab, 0xe4, 0x00, 0x80, 0x5f, 0x8a, 0xc9, 0x68 \
+ } \
+ }
+/**
+ * base class for all message undo/redo transactions.
+ */
+
+class nsMsgTxn : public nsITransaction,
+ public nsIWritablePropertyBag,
+ public nsIWritablePropertyBag2 {
+ public:
+ nsMsgTxn();
+
+ nsresult Init();
+
+ NS_IMETHOD DoTransaction(void) override;
+
+ NS_IMETHOD UndoTransaction(void) override = 0;
+
+ NS_IMETHOD RedoTransaction(void) override = 0;
+
+ NS_IMETHOD GetIsTransient(bool* aIsTransient) override;
+
+ NS_IMETHOD Merge(nsITransaction* aTransaction, bool* aDidMerge) override;
+
+ nsresult GetMsgWindow(nsIMsgWindow** msgWindow);
+ nsresult SetMsgWindow(nsIMsgWindow* msgWindow);
+ nsresult SetTransactionType(uint32_t txnType);
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIPROPERTYBAG
+ NS_DECL_NSIPROPERTYBAG2
+ NS_DECL_NSIWRITABLEPROPERTYBAG
+ NS_DECL_NSIWRITABLEPROPERTYBAG2
+
+ NS_IMETHOD GetAsEditTransactionBase(EditTransactionBase**) final {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ protected:
+ virtual ~nsMsgTxn();
+
+ // a hash table of string -> nsIVariant
+ nsInterfaceHashtable<nsStringHashKey, nsIVariant> mPropertyHash;
+ nsCOMPtr<nsIMsgWindow> m_msgWindow;
+ uint32_t m_txnType;
+ nsresult CheckForToggleDelete(nsIMsgFolder* aFolder, const nsMsgKey& aMsgKey,
+ bool* aResult);
+};
+
+#endif
diff --git a/comm/mailnews/base/src/nsMsgUtils.cpp b/comm/mailnews/base/src/nsMsgUtils.cpp
new file mode 100644
index 0000000000..661b4fb219
--- /dev/null
+++ b/comm/mailnews/base/src/nsMsgUtils.cpp
@@ -0,0 +1,1926 @@
+/* -*- 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 "msgCore.h"
+#include "nsIMsgHdr.h"
+#include "nsMsgUtils.h"
+#include "nsMsgFolderFlags.h"
+#include "nsMsgMessageFlags.h"
+#include "nsString.h"
+#include "nsIServiceManager.h"
+#include "nsCOMPtr.h"
+#include "nsIFolderLookupService.h"
+#include "nsIImapUrl.h"
+#include "nsIMailboxUrl.h"
+#include "nsINntpUrl.h"
+#include "nsMsgI18N.h"
+#include "nsNativeCharsetUtils.h"
+#include "nsCharTraits.h"
+#include "prprf.h"
+#include "prmem.h"
+#include "nsNetCID.h"
+#include "nsIIOService.h"
+#include "nsIMimeConverter.h"
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+#include "nsISupportsPrimitives.h"
+#include "nsIPrefLocalizedString.h"
+#include "nsIRelativeFilePref.h"
+#include "mozilla/nsRelativeFilePref.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsISpamSettings.h"
+#include "nsICryptoHash.h"
+#include "nsNativeCharsetUtils.h"
+#include "nsDirectoryServiceUtils.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsIRssIncomingServer.h"
+#include "nsIMsgFolder.h"
+#include "nsIMsgProtocolInfo.h"
+#include "nsIMsgMessageService.h"
+#include "nsIMsgAccountManager.h"
+#include "nsIOutputStream.h"
+#include "nsMsgFileStream.h"
+#include "nsIFileURL.h"
+#include "nsNetUtil.h"
+#include "nsProtocolProxyService.h"
+#include "nsIProtocolProxyCallback.h"
+#include "nsICancelable.h"
+#include "nsIMsgDatabase.h"
+#include "nsIMsgMailNewsUrl.h"
+#include "nsIStringBundle.h"
+#include "nsIMsgWindow.h"
+#include "nsIWindowWatcher.h"
+#include "nsIPrompt.h"
+#include "nsIMsgSearchTerm.h"
+#include "nsTextFormatter.h"
+#include "nsIStreamListener.h"
+#include "nsReadLine.h"
+#include "nsILineInputStream.h"
+#include "nsIParserUtils.h"
+#include "nsICharsetConverterManager.h"
+#include "nsIDocumentEncoder.h"
+#include "mozilla/Components.h"
+#include "locale.h"
+#include "nsStringStream.h"
+#include "nsIInputStreamPump.h"
+#include "nsIInputStream.h"
+#include "nsIChannel.h"
+#include "nsIURIMutator.h"
+#include "mozilla/Unused.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Encoding.h"
+#include "mozilla/EncodingDetector.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/Utf8.h"
+#include "mozilla/Buffer.h"
+#include "nsIPromptService.h"
+#include "nsEmbedCID.h"
+
+/* for logging to Error Console */
+#include "nsIScriptError.h"
+#include "nsIConsoleService.h"
+
+// Log an error string to the error console
+// (adapted from nsContentUtils::LogSimpleConsoleError).
+// Flag can indicate error, warning or info.
+NS_MSG_BASE void MsgLogToConsole4(const nsAString& aErrorText,
+ const nsAString& aFilename,
+ uint32_t aLinenumber, uint32_t aFlag) {
+ nsCOMPtr<nsIScriptError> scriptError =
+ do_CreateInstance(NS_SCRIPTERROR_CONTRACTID);
+ if (NS_WARN_IF(!scriptError)) return;
+ nsCOMPtr<nsIConsoleService> console =
+ do_GetService(NS_CONSOLESERVICE_CONTRACTID);
+ if (NS_WARN_IF(!console)) return;
+ if (NS_FAILED(scriptError->Init(aErrorText, aFilename, EmptyString(),
+ aLinenumber, 0, aFlag, "mailnews"_ns, false,
+ false)))
+ return;
+ console->LogMessage(scriptError);
+ return;
+}
+
+using namespace mozilla;
+using namespace mozilla::net;
+
+#define ILLEGAL_FOLDER_CHARS ";#"
+#define ILLEGAL_FOLDER_CHARS_AS_FIRST_LETTER "."
+#define ILLEGAL_FOLDER_CHARS_AS_LAST_LETTER ".~ "
+
+nsresult GetMessageServiceContractIDForURI(const char* uri,
+ nsCString& contractID) {
+ nsresult rv = NS_OK;
+ // Find protocol
+ nsAutoCString uriStr(uri);
+ int32_t pos = uriStr.FindChar(':');
+ if (pos == -1) return NS_ERROR_FAILURE;
+
+ nsAutoCString protocol(StringHead(uriStr, pos));
+
+ if (protocol.EqualsLiteral("file")) {
+ protocol.AssignLiteral("mailbox");
+ }
+ // Build message service contractid
+ contractID = "@mozilla.org/messenger/messageservice;1?type=";
+ contractID += protocol.get();
+
+ return rv;
+}
+
+// Note: This function is also implemented in JS, see MailServices.jsm.
+nsresult GetMessageServiceFromURI(const nsACString& uri,
+ nsIMsgMessageService** aMessageService) {
+ nsresult rv;
+
+ nsAutoCString contractID;
+ rv = GetMessageServiceContractIDForURI(PromiseFlatCString(uri).get(),
+ contractID);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgMessageService> msgService =
+ do_GetService(contractID.get(), &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ msgService.forget(aMessageService);
+ return rv;
+}
+
+nsresult GetMsgDBHdrFromURI(const nsACString& uri, nsIMsgDBHdr** msgHdr) {
+ nsCOMPtr<nsIMsgMessageService> msgMessageService;
+ nsresult rv =
+ GetMessageServiceFromURI(uri, getter_AddRefs(msgMessageService));
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!msgMessageService) return NS_ERROR_FAILURE;
+
+ return msgMessageService->MessageURIToMsgHdr(uri, msgHdr);
+}
+
+// Where should this live? It's a utility used to convert a string priority,
+// e.g., "High, Low, Normal" to an enum.
+// Perhaps we should have an interface that groups together all these
+// utilities...
+nsresult NS_MsgGetPriorityFromString(const char* const priority,
+ nsMsgPriorityValue& outPriority) {
+ if (!priority) return NS_ERROR_NULL_POINTER;
+
+ // Note: Checking the values separately and _before_ the names,
+ // hoping for a much faster match;
+ // Only _drawback_, as "priority" handling is not truly specified:
+ // some software may have the number meanings reversed (1=Lowest) !?
+ if (PL_strchr(priority, '1'))
+ outPriority = nsMsgPriority::highest;
+ else if (PL_strchr(priority, '2'))
+ outPriority = nsMsgPriority::high;
+ else if (PL_strchr(priority, '3'))
+ outPriority = nsMsgPriority::normal;
+ else if (PL_strchr(priority, '4'))
+ outPriority = nsMsgPriority::low;
+ else if (PL_strchr(priority, '5'))
+ outPriority = nsMsgPriority::lowest;
+ else if (PL_strcasestr(priority, "Highest"))
+ outPriority = nsMsgPriority::highest;
+ // Important: "High" must be tested after "Highest" !
+ else if (PL_strcasestr(priority, "High") || PL_strcasestr(priority, "Urgent"))
+ outPriority = nsMsgPriority::high;
+ else if (PL_strcasestr(priority, "Normal"))
+ outPriority = nsMsgPriority::normal;
+ else if (PL_strcasestr(priority, "Lowest"))
+ outPriority = nsMsgPriority::lowest;
+ // Important: "Low" must be tested after "Lowest" !
+ else if (PL_strcasestr(priority, "Low") ||
+ PL_strcasestr(priority, "Non-urgent"))
+ outPriority = nsMsgPriority::low;
+ else
+ // "Default" case gets default value.
+ outPriority = nsMsgPriority::Default;
+
+ return NS_OK;
+}
+
+nsresult NS_MsgGetPriorityValueString(const nsMsgPriorityValue p,
+ nsACString& outValueString) {
+ switch (p) {
+ case nsMsgPriority::highest:
+ outValueString.Assign('1');
+ break;
+ case nsMsgPriority::high:
+ outValueString.Assign('2');
+ break;
+ case nsMsgPriority::normal:
+ outValueString.Assign('3');
+ break;
+ case nsMsgPriority::low:
+ outValueString.Assign('4');
+ break;
+ case nsMsgPriority::lowest:
+ outValueString.Assign('5');
+ break;
+ case nsMsgPriority::none:
+ case nsMsgPriority::notSet:
+ // Note: '0' is a "fake" value; we expect to never be in this case.
+ outValueString.Assign('0');
+ break;
+ default:
+ NS_ASSERTION(false, "invalid priority value");
+ }
+
+ return NS_OK;
+}
+
+nsresult NS_MsgGetUntranslatedPriorityName(const nsMsgPriorityValue p,
+ nsACString& outName) {
+ switch (p) {
+ case nsMsgPriority::highest:
+ outName.AssignLiteral("Highest");
+ break;
+ case nsMsgPriority::high:
+ outName.AssignLiteral("High");
+ break;
+ case nsMsgPriority::normal:
+ outName.AssignLiteral("Normal");
+ break;
+ case nsMsgPriority::low:
+ outName.AssignLiteral("Low");
+ break;
+ case nsMsgPriority::lowest:
+ outName.AssignLiteral("Lowest");
+ break;
+ case nsMsgPriority::none:
+ case nsMsgPriority::notSet:
+ // Note: 'None' is a "fake" value; we expect to never be in this case.
+ outName.AssignLiteral("None");
+ break;
+ default:
+ NS_ASSERTION(false, "invalid priority value");
+ }
+
+ return NS_OK;
+}
+
+/* this used to be XP_StringHash2 from xp_hash.c */
+/* phong's linear congruential hash */
+static uint32_t StringHash(const char* ubuf, int32_t len = -1) {
+ unsigned char* buf = (unsigned char*)ubuf;
+ uint32_t h = 1;
+ unsigned char* end = buf + (len == -1 ? strlen(ubuf) : len);
+ while (buf < end) {
+ h = 0x63c63cd9 * h + 0x9c39c33d + (int32_t)*buf;
+ buf++;
+ }
+ return h;
+}
+
+inline uint32_t StringHash(const nsAutoString& str) {
+ const char16_t* strbuf = str.get();
+ return StringHash(reinterpret_cast<const char*>(strbuf), str.Length() * 2);
+}
+
+/* Utility functions used in a few places in mailnews */
+int32_t MsgFindCharInSet(const nsCString& aString, const char* aChars,
+ uint32_t aOffset) {
+ return aString.FindCharInSet(aChars, aOffset);
+}
+
+int32_t MsgFindCharInSet(const nsString& aString, const char16_t* aChars,
+ uint32_t aOffset) {
+ return aString.FindCharInSet(aChars, aOffset);
+}
+
+static bool ConvertibleToNative(const nsAutoString& str) {
+ nsAutoCString native;
+ nsAutoString roundTripped;
+ NS_CopyUnicodeToNative(str, native);
+ NS_CopyNativeToUnicode(native, roundTripped);
+ return str.Equals(roundTripped);
+}
+
+#if defined(XP_UNIX)
+const static uint32_t MAX_LEN = 55;
+#elif defined(XP_WIN)
+const static uint32_t MAX_LEN = 55;
+#else
+# error need_to_define_your_max_filename_length
+#endif
+
+nsresult NS_MsgHashIfNecessary(nsAutoCString& name) {
+ if (name.IsEmpty()) return NS_OK; // Nothing to do.
+ nsAutoCString str(name);
+
+ // Given a filename, make it safe for filesystem
+ // certain filenames require hashing because they
+ // are too long or contain illegal characters
+ int32_t illegalCharacterIndex = MsgFindCharInSet(
+ str, FILE_PATH_SEPARATOR FILE_ILLEGAL_CHARACTERS ILLEGAL_FOLDER_CHARS, 0);
+
+ // Need to check the first ('.') and last ('.', '~' and ' ') char
+ if (illegalCharacterIndex == -1) {
+ int32_t lastIndex = str.Length() - 1;
+ if (nsLiteralCString(ILLEGAL_FOLDER_CHARS_AS_FIRST_LETTER)
+ .FindChar(str[0]) != -1)
+ illegalCharacterIndex = 0;
+ else if (nsLiteralCString(ILLEGAL_FOLDER_CHARS_AS_LAST_LETTER)
+ .FindChar(str[lastIndex]) != -1)
+ illegalCharacterIndex = lastIndex;
+ else
+ illegalCharacterIndex = -1;
+ }
+
+ char hashedname[MAX_LEN + 1];
+ if (illegalCharacterIndex == -1) {
+ // no illegal chars, it's just too long
+ // keep the initial part of the string, but hash to make it fit
+ if (str.Length() > MAX_LEN) {
+ PL_strncpy(hashedname, str.get(), MAX_LEN + 1);
+ PR_snprintf(hashedname + MAX_LEN - 8, 9, "%08lx",
+ (unsigned long)StringHash(str.get()));
+ name = hashedname;
+ }
+ } else {
+ // found illegal chars, hash the whole thing
+ // if we do substitution, then hash, two strings
+ // could hash to the same value.
+ // for example, on mac: "foo__bar", "foo:_bar", "foo::bar"
+ // would map to "foo_bar". this way, all three will map to
+ // different values
+ PR_snprintf(hashedname, 9, "%08lx", (unsigned long)StringHash(str.get()));
+ name = hashedname;
+ }
+
+ return NS_OK;
+}
+
+// XXX : The number of UTF-16 2byte code units are half the number of
+// bytes in legacy encodings for CJK strings and non-Latin1 in UTF-8.
+// The ratio can be 1/3 for CJK strings in UTF-8. However, we can
+// get away with using the same MAX_LEN for nsCString and nsString
+// because MAX_LEN is defined rather conservatively in the first place.
+nsresult NS_MsgHashIfNecessary(nsAutoString& name) {
+ if (name.IsEmpty()) return NS_OK; // Nothing to do.
+ int32_t illegalCharacterIndex = MsgFindCharInSet(
+ name,
+ u"" FILE_PATH_SEPARATOR FILE_ILLEGAL_CHARACTERS ILLEGAL_FOLDER_CHARS, 0);
+
+ // Need to check the first ('.') and last ('.', '~' and ' ') char
+ if (illegalCharacterIndex == -1) {
+ int32_t lastIndex = name.Length() - 1;
+ if (NS_LITERAL_STRING_FROM_CSTRING(ILLEGAL_FOLDER_CHARS_AS_FIRST_LETTER)
+ .FindChar(name[0]) != -1)
+ illegalCharacterIndex = 0;
+ else if (NS_LITERAL_STRING_FROM_CSTRING(ILLEGAL_FOLDER_CHARS_AS_LAST_LETTER)
+ .FindChar(name[lastIndex]) != -1)
+ illegalCharacterIndex = lastIndex;
+ else
+ illegalCharacterIndex = -1;
+ }
+
+ char hashedname[9];
+ int32_t keptLength = -1;
+ if (illegalCharacterIndex != -1)
+ keptLength = illegalCharacterIndex;
+ else if (!ConvertibleToNative(name))
+ keptLength = 0;
+ else if (name.Length() > MAX_LEN) {
+ keptLength = MAX_LEN - 8;
+ // To avoid keeping only the high surrogate of a surrogate pair
+ if (NS_IS_HIGH_SURROGATE(name.CharAt(keptLength - 1))) --keptLength;
+ }
+
+ if (keptLength >= 0) {
+ PR_snprintf(hashedname, 9, "%08lx", (unsigned long)StringHash(name));
+ name.SetLength(keptLength);
+ name.Append(NS_ConvertASCIItoUTF16(hashedname));
+ }
+
+ return NS_OK;
+}
+
+nsresult FormatFileSize(int64_t size, bool useKB, nsAString& formattedSize) {
+ const char* sizeAbbrNames[] = {
+ "byteAbbreviation2", "kiloByteAbbreviation2", "megaByteAbbreviation2",
+ "gigaByteAbbreviation2", "teraByteAbbreviation2", "petaByteAbbreviation2",
+ };
+
+ nsresult rv;
+
+ nsCOMPtr<nsIStringBundleService> bundleSvc =
+ mozilla::components::StringBundle::Service();
+ NS_ENSURE_TRUE(bundleSvc, NS_ERROR_UNEXPECTED);
+
+ nsCOMPtr<nsIStringBundle> bundle;
+ rv = bundleSvc->CreateBundle("chrome://messenger/locale/messenger.properties",
+ getter_AddRefs(bundle));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ double unitSize = size < 0 ? 0.0 : size;
+ uint32_t unitIndex = 0;
+
+ if (useKB) {
+ // Start by formatting in kilobytes
+ unitSize /= 1024;
+ if (unitSize < 0.1 && unitSize != 0) unitSize = 0.1;
+ unitIndex++;
+ }
+
+ // Convert to next unit if it needs 4 digits (after rounding), but only if
+ // we know the name of the next unit
+ while ((unitSize >= 999.5) && (unitIndex < ArrayLength(sizeAbbrNames) - 1)) {
+ unitSize /= 1024;
+ unitIndex++;
+ }
+
+ // Grab the string for the appropriate unit
+ nsString sizeAbbr;
+ rv = bundle->GetStringFromName(sizeAbbrNames[unitIndex], sizeAbbr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Get rid of insignificant bits by truncating to 1 or 0 decimal points
+ // 0.1 -> 0.1; 1.2 -> 1.2; 12.3 -> 12.3; 123.4 -> 123; 234.5 -> 235
+ nsTextFormatter::ssprintf(
+ formattedSize, sizeAbbr.get(),
+ (unitIndex != 0) && (unitSize < 99.95 && unitSize != 0) ? 1 : 0,
+ unitSize);
+
+ int32_t separatorPos = formattedSize.FindChar('.');
+ if (separatorPos != kNotFound) {
+ // The ssprintf returned a decimal number using a dot (.) as the decimal
+ // separator. Now we try to localize the separator.
+ // Try to get the decimal separator from the system's locale.
+ char* decimalPoint;
+#ifdef HAVE_LOCALECONV
+ struct lconv* locale = localeconv();
+ decimalPoint = locale->decimal_point;
+#else
+ decimalPoint = getenv("LOCALE_DECIMAL_POINT");
+#endif
+ NS_ConvertUTF8toUTF16 decimalSeparator(decimalPoint);
+ if (decimalSeparator.IsEmpty()) decimalSeparator.Assign('.');
+
+ formattedSize.Replace(separatorPos, 1, decimalSeparator);
+ }
+
+ return NS_OK;
+}
+
+nsresult NS_MsgCreatePathStringFromFolderURI(const char* aFolderURI,
+ nsCString& aPathCString,
+ const nsCString& aScheme,
+ bool aIsNewsFolder) {
+ // A file name has to be in native charset. Here we convert
+ // to UTF-16 and check for 'unsafe' characters before converting
+ // to native charset.
+ NS_ENSURE_TRUE(mozilla::IsUtf8(nsDependentCString(aFolderURI)),
+ NS_ERROR_UNEXPECTED);
+ NS_ConvertUTF8toUTF16 oldPath(aFolderURI);
+
+ nsAutoString pathPiece, path;
+
+ int32_t startSlashPos = oldPath.FindChar('/');
+ int32_t endSlashPos = (startSlashPos >= 0)
+ ? oldPath.FindChar('/', startSlashPos + 1) - 1
+ : oldPath.Length() - 1;
+ if (endSlashPos < 0) endSlashPos = oldPath.Length();
+#if defined(XP_UNIX) || defined(XP_MACOSX)
+ bool isLocalUri = aScheme.EqualsLiteral("none") ||
+ aScheme.EqualsLiteral("pop3") ||
+ aScheme.EqualsLiteral("rss");
+#endif
+ // trick to make sure we only add the path to the first n-1 folders
+ bool haveFirst = false;
+ while (startSlashPos != -1) {
+ pathPiece.Assign(
+ Substring(oldPath, startSlashPos + 1, endSlashPos - startSlashPos));
+ // skip leading '/' (and other // style things)
+ if (!pathPiece.IsEmpty()) {
+ // add .sbd onto the previous path
+ if (haveFirst) {
+ path.AppendLiteral(FOLDER_SUFFIX "/");
+ }
+
+ if (aIsNewsFolder) {
+ nsAutoCString tmp;
+ CopyUTF16toMUTF7(pathPiece, tmp);
+ CopyASCIItoUTF16(tmp, pathPiece);
+ }
+#if defined(XP_UNIX) || defined(XP_MACOSX)
+ // Don't hash path pieces because local mail folder uri's have already
+ // been hashed. We're only doing this on the mac to limit potential
+ // regressions.
+ if (!isLocalUri)
+#endif
+ NS_MsgHashIfNecessary(pathPiece);
+ path += pathPiece;
+ haveFirst = true;
+ }
+ // look for the next slash
+ startSlashPos = endSlashPos + 1;
+
+ endSlashPos = (startSlashPos >= 0)
+ ? oldPath.FindChar('/', startSlashPos + 1) - 1
+ : oldPath.Length() - 1;
+ if (endSlashPos < 0) endSlashPos = oldPath.Length();
+
+ if (startSlashPos >= endSlashPos) break;
+ }
+ return NS_CopyUnicodeToNative(path, aPathCString);
+}
+
+bool NS_MsgStripRE(const nsCString& subject, nsCString& modifiedSubject) {
+ bool result = false;
+
+ // Get localizedRe pref.
+ nsresult rv;
+ nsString utf16LocalizedRe;
+ NS_GetLocalizedUnicharPreferenceWithDefault(nullptr, "mailnews.localizedRe",
+ EmptyString(), utf16LocalizedRe);
+ NS_ConvertUTF16toUTF8 localizedRe(utf16LocalizedRe);
+
+ // Hardcoded "Re" so that no one can configure Mozilla standards incompatible.
+ nsAutoCString checkString("Re,RE,re,rE");
+ if (!localizedRe.IsEmpty()) {
+ checkString.Append(',');
+ checkString.Append(localizedRe);
+ }
+
+ // Decode the string.
+ nsCString decodedString;
+ nsCOMPtr<nsIMimeConverter> mimeConverter;
+ // We cannot strip "Re:" for RFC2047-encoded subject without modifying the
+ // original.
+ if (subject.Find("=?") != kNotFound) {
+ mimeConverter =
+ do_GetService("@mozilla.org/messenger/mimeconverter;1", &rv);
+ if (NS_SUCCEEDED(rv))
+ rv = mimeConverter->DecodeMimeHeaderToUTF8(subject, nullptr, false, true,
+ decodedString);
+ }
+
+ const char *s, *s_end;
+ if (decodedString.IsEmpty()) {
+ s = subject.BeginReading();
+ s_end = s + subject.Length();
+ } else {
+ s = decodedString.BeginReading();
+ s_end = s + decodedString.Length();
+ }
+
+AGAIN:
+ while (s < s_end && IS_SPACE(*s)) s++;
+
+ const char* tokPtr = checkString.get();
+ while (*tokPtr) {
+ // Tokenize the comma separated list.
+ size_t tokenLength = 0;
+ while (*tokPtr && *tokPtr != ',') {
+ tokenLength++;
+ tokPtr++;
+ }
+ // Check if the beginning of s is the actual token.
+ if (tokenLength && !strncmp(s, tokPtr - tokenLength, tokenLength)) {
+ if (s[tokenLength] == ':') {
+ s = s + tokenLength + 1; /* Skip over "Re:" */
+ result = true; /* Yes, we stripped it. */
+ goto AGAIN; /* Skip whitespace and try again. */
+ } else if (s[tokenLength] == '[' || s[tokenLength] == '(') {
+ const char* s2 = s + tokenLength + 1; /* Skip over "Re[" */
+
+ // Skip forward over digits after the "[".
+ while (s2 < (s_end - 2) && isdigit((unsigned char)*s2)) s2++;
+
+ // Now ensure that the following thing is "]:".
+ // Only if it is do we alter `s`.
+ if ((s2[0] == ']' || s2[0] == ')') && s2[1] == ':') {
+ s = s2 + 2; /* Skip over "]:" */
+ result = true; /* Yes, we stripped it. */
+ goto AGAIN; /* Skip whitespace and try again. */
+ }
+ }
+ }
+ if (*tokPtr) tokPtr++;
+ }
+
+ // If we didn't strip anything, we can return here.
+ if (!result) return false;
+
+ if (decodedString.IsEmpty()) {
+ // We didn't decode anything, so just return a new string.
+ modifiedSubject.Assign(s);
+ return true;
+ }
+
+ // We decoded the string, so we need to encode it again. We always encode in
+ // UTF-8.
+ mimeConverter->EncodeMimePartIIStr_UTF8(
+ nsDependentCString(s), false, sizeof("Subject:"),
+ nsIMimeConverter::MIME_ENCODED_WORD_SIZE, modifiedSubject);
+ return true;
+}
+
+/* Very similar to strdup except it free's too
+ */
+char* NS_MsgSACopy(char** destination, const char* source) {
+ if (*destination) {
+ PR_Free(*destination);
+ *destination = 0;
+ }
+ if (!source)
+ *destination = nullptr;
+ else {
+ *destination = (char*)PR_Malloc(PL_strlen(source) + 1);
+ if (*destination == nullptr) return (nullptr);
+
+ PL_strcpy(*destination, source);
+ }
+ return *destination;
+}
+
+/* Again like strdup but it concatenates and free's and uses Realloc.
+ */
+char* NS_MsgSACat(char** destination, const char* source) {
+ if (source && *source) {
+ int destLength = *destination ? PL_strlen(*destination) : 0;
+ char* newDestination =
+ (char*)PR_Realloc(*destination, destLength + PL_strlen(source) + 1);
+ if (newDestination == nullptr) return nullptr;
+
+ *destination = newDestination;
+ PL_strcpy(*destination + destLength, source);
+ }
+ return *destination;
+}
+
+nsresult NS_MsgEscapeEncodeURLPath(const nsAString& aStr, nsCString& aResult) {
+ return MsgEscapeString(NS_ConvertUTF16toUTF8(aStr),
+ nsINetUtil::ESCAPE_URL_PATH, aResult);
+}
+
+nsresult NS_MsgDecodeUnescapeURLPath(const nsACString& aPath,
+ nsAString& aResult) {
+ nsAutoCString unescapedName;
+ MsgUnescapeString(
+ aPath,
+ nsINetUtil::ESCAPE_URL_FILE_BASENAME | nsINetUtil::ESCAPE_URL_FORCED,
+ unescapedName);
+ CopyUTF8toUTF16(unescapedName, aResult);
+ return NS_OK;
+}
+
+bool WeAreOffline() {
+ bool offline = false;
+
+ nsCOMPtr<nsIIOService> ioService = mozilla::components::IO::Service();
+ if (ioService) ioService->GetOffline(&offline);
+
+ return offline;
+}
+
+// Find a folder by URL. If it doesn't exist, null will be returned
+// via aFolder.
+nsresult FindFolder(const nsACString& aFolderURI, nsIMsgFolder** aFolder) {
+ NS_ENSURE_ARG_POINTER(aFolder);
+
+ *aFolder = nullptr;
+
+ nsresult rv;
+ nsCOMPtr<nsIFolderLookupService> fls(do_GetService(NSIFLS_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // GetFolderForURL returns NS_OK and null for non-existent folders
+ rv = fls->GetFolderForURL(aFolderURI, aFolder);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+// Fetch an existing folder by URL
+// The returned aFolder will be non-null if and only if result is NS_OK.
+// NS_OK - folder was found
+// NS_MSG_FOLDER_MISSING - if aFolderURI not found
+nsresult GetExistingFolder(const nsACString& aFolderURI,
+ nsIMsgFolder** aFolder) {
+ nsresult rv = FindFolder(aFolderURI, aFolder);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return *aFolder ? NS_OK : NS_MSG_ERROR_FOLDER_MISSING;
+}
+
+nsresult GetOrCreateFolder(const nsACString& aFolderURI,
+ nsIMsgFolder** aFolder) {
+ NS_ENSURE_ARG_POINTER(aFolder);
+
+ *aFolder = nullptr;
+
+ nsresult rv;
+ nsCOMPtr<nsIFolderLookupService> fls(do_GetService(NSIFLS_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = fls->GetOrCreateFolderForURL(aFolderURI, aFolder);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return *aFolder ? NS_OK : NS_ERROR_FAILURE;
+}
+
+bool IsAFromSpaceLine(char* start, const char* end) {
+ bool rv = false;
+ while ((start < end) && (*start == '>')) start++;
+ // If the leading '>'s are followed by an 'F' then we have a possible case
+ // here.
+ if ((*start == 'F') && (end - start > 4) && !strncmp(start, "From ", 5))
+ rv = true;
+ return rv;
+}
+
+//
+// This function finds all lines starting with "From " or "From " preceding
+// with one or more '>' (ie, ">From", ">>From", etc) in the input buffer
+// (between 'start' and 'end') and prefix them with a ">" .
+//
+nsresult EscapeFromSpaceLine(nsIOutputStream* outputStream, char* start,
+ const char* end) {
+ nsresult rv;
+ char* pChar;
+ uint32_t written;
+
+ pChar = start;
+ while (start < end) {
+ while ((pChar < end) && (*pChar != '\r') && ((pChar + 1) < end) &&
+ (*(pChar + 1) != '\n'))
+ pChar++;
+ if ((pChar + 1) == end) pChar++;
+
+ if (pChar < end) {
+ // Found a line so check if it's a qualified "From " line.
+ if (IsAFromSpaceLine(start, pChar)) {
+ rv = outputStream->Write(">", 1, &written);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ int32_t lineTerminatorCount = (*(pChar + 1) == '\n') ? 2 : 1;
+ rv = outputStream->Write(start, pChar - start + lineTerminatorCount,
+ &written);
+ NS_ENSURE_SUCCESS(rv, rv);
+ pChar += lineTerminatorCount;
+ start = pChar;
+ } else if (start < end) {
+ // Check and flush out the remaining data and we're done.
+ if (IsAFromSpaceLine(start, end)) {
+ rv = outputStream->Write(">", 1, &written);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ rv = outputStream->Write(start, end - start, &written);
+ NS_ENSURE_SUCCESS(rv, rv);
+ break;
+ }
+ }
+ return NS_OK;
+}
+
+nsresult IsRFC822HeaderFieldName(const char* aHdr, bool* aResult) {
+ NS_ENSURE_ARG_POINTER(aHdr);
+ NS_ENSURE_ARG_POINTER(aResult);
+ uint32_t length = strlen(aHdr);
+ for (uint32_t i = 0; i < length; i++) {
+ char c = aHdr[i];
+ if (c < '!' || c == ':' || c > '~') {
+ *aResult = false;
+ return NS_OK;
+ }
+ }
+ *aResult = true;
+ return NS_OK;
+}
+
+// Warning, currently this routine only works for the Junk Folder
+nsresult GetOrCreateJunkFolder(const nsACString& aURI,
+ nsIUrlListener* aListener) {
+ nsresult rv;
+
+ nsCOMPtr<nsIMsgFolder> folder;
+ rv = GetOrCreateFolder(aURI, getter_AddRefs(folder));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // don't check validity of folder - caller will handle creating it
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ // make sure that folder hierarchy is built so that legitimate parent-child
+ // relationship is established
+ rv = folder->GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!server) return NS_ERROR_UNEXPECTED;
+
+ nsCOMPtr<nsIMsgFolder> msgFolder;
+ rv = server->GetMsgFolderFromURI(folder, aURI, getter_AddRefs(msgFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgFolder> parent;
+ rv = msgFolder->GetParent(getter_AddRefs(parent));
+ if (NS_FAILED(rv) || !parent) {
+ nsCOMPtr<nsIFile> folderPath;
+ // for local folders, path is to the berkeley mailbox.
+ // for imap folders, path needs to have .msf appended to the name
+ msgFolder->GetFilePath(getter_AddRefs(folderPath));
+
+ nsCOMPtr<nsIMsgProtocolInfo> protocolInfo;
+ rv = server->GetProtocolInfo(getter_AddRefs(protocolInfo));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool isAsyncFolder;
+ rv = protocolInfo->GetFoldersCreatedAsync(&isAsyncFolder);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // if we can't get the path from the folder, then try to create the storage.
+ // for imap, it doesn't matter if the .msf file exists - it still might not
+ // exist on the server, so we should try to create it
+ bool exists = false;
+ if (!isAsyncFolder && folderPath) folderPath->Exists(&exists);
+ if (!exists) {
+ // Hack to work around a localization bug with the Junk Folder.
+ // Please see Bug #270261 for more information...
+ nsString localizedJunkName;
+ msgFolder->GetName(localizedJunkName);
+
+ // force the junk folder name to be Junk so it gets created on disk
+ // correctly...
+ msgFolder->SetName(u"Junk"_ns);
+ msgFolder->SetFlag(nsMsgFolderFlags::Junk);
+ rv = msgFolder->CreateStorageIfMissing(aListener);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // now restore the localized folder name...
+ msgFolder->SetName(localizedJunkName);
+
+ // XXX TODO
+ // JUNK MAIL RELATED
+ // ugh, I hate this hack
+ // we have to do this (for now)
+ // because imap and local are different (one creates folder asynch, the
+ // other synch) one will notify the listener, one will not. I blame
+ // nsMsgCopy. we should look into making it so no matter what the folder
+ // type we always call the listener this code should move into local
+ // folder's version of CreateStorageIfMissing()
+ if (!isAsyncFolder && aListener) {
+ rv = aListener->OnStartRunningUrl(nullptr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aListener->OnStopRunningUrl(nullptr, NS_OK);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+ } else {
+ // if the folder exists, we should set the junk flag on it
+ // which is what the listener will do
+ if (aListener) {
+ rv = aListener->OnStartRunningUrl(nullptr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aListener->OnStopRunningUrl(nullptr, NS_OK);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult IsRSSArticle(nsIURI* aMsgURI, bool* aIsRSSArticle) {
+ nsresult rv;
+ *aIsRSSArticle = false;
+
+ nsCOMPtr<nsIMsgMessageUrl> msgUrl = do_QueryInterface(aMsgURI, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCString resourceURI;
+ msgUrl->GetUri(resourceURI);
+
+ // get the msg service for this URI
+ nsCOMPtr<nsIMsgMessageService> msgService;
+ rv = GetMessageServiceFromURI(resourceURI, getter_AddRefs(msgService));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Check if the message is a feed message, regardless of folder.
+ uint32_t flags;
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ rv = msgService->MessageURIToMsgHdr(resourceURI, getter_AddRefs(msgHdr));
+ NS_ENSURE_SUCCESS(rv, rv);
+ msgHdr->GetFlags(&flags);
+ if (flags & nsMsgMessageFlags::FeedMsg) {
+ *aIsRSSArticle = true;
+ return rv;
+ }
+
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(aMsgURI, &rv);
+ mozilla::Unused << mailnewsUrl;
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // get the folder and the server from the msghdr
+ nsCOMPtr<nsIMsgFolder> folder;
+ rv = msgHdr->GetFolder(getter_AddRefs(folder));
+ if (NS_SUCCEEDED(rv) && folder) {
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ folder->GetServer(getter_AddRefs(server));
+ nsCOMPtr<nsIRssIncomingServer> rssServer = do_QueryInterface(server);
+
+ if (rssServer) *aIsRSSArticle = true;
+ }
+
+ return rv;
+}
+
+// digest needs to be a pointer to a DIGEST_LENGTH (16) byte buffer
+nsresult MSGCramMD5(const char* text, int32_t text_len, const char* key,
+ int32_t key_len, unsigned char* digest) {
+ nsresult rv;
+
+ nsAutoCString hash;
+ nsCOMPtr<nsICryptoHash> hasher =
+ do_CreateInstance("@mozilla.org/security/hash;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // this code adapted from
+ // http://www.cis.ohio-state.edu/cgi-bin/rfc/rfc2104.html
+
+ char innerPad[65]; /* inner padding - key XORd with innerPad */
+ char outerPad[65]; /* outer padding - key XORd with outerPad */
+ int i;
+ /* if key is longer than 64 bytes reset it to key=MD5(key) */
+ if (key_len > 64) {
+ rv = hasher->Init(nsICryptoHash::MD5);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = hasher->Update((const uint8_t*)key, key_len);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = hasher->Finish(false, hash);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ key = hash.get();
+ key_len = DIGEST_LENGTH;
+ }
+
+ /*
+ * the HMAC_MD5 transform looks like:
+ *
+ * MD5(K XOR outerPad, MD5(K XOR innerPad, text))
+ *
+ * where K is an n byte key
+ * innerPad is the byte 0x36 repeated 64 times
+ * outerPad is the byte 0x5c repeated 64 times
+ * and text is the data being protected
+ */
+
+ /* start out by storing key in pads */
+ memset(innerPad, 0, sizeof innerPad);
+ memset(outerPad, 0, sizeof outerPad);
+ memcpy(innerPad, key, key_len);
+ memcpy(outerPad, key, key_len);
+
+ /* XOR key with innerPad and outerPad values */
+ for (i = 0; i < 64; i++) {
+ innerPad[i] ^= 0x36;
+ outerPad[i] ^= 0x5c;
+ }
+ /*
+ * perform inner MD5
+ */
+ nsAutoCString result;
+ rv = hasher->Init(nsICryptoHash::MD5); /* init context for 1st pass */
+ rv = hasher->Update((const uint8_t*)innerPad, 64); /* start with inner pad */
+ rv = hasher->Update((const uint8_t*)text,
+ text_len); /* then text of datagram */
+ rv = hasher->Finish(false, result); /* finish up 1st pass */
+
+ /*
+ * perform outer MD5
+ */
+ hasher->Init(nsICryptoHash::MD5); /* init context for 2nd pass */
+ rv = hasher->Update((const uint8_t*)outerPad, 64); /* start with outer pad */
+ rv = hasher->Update((const uint8_t*)result.get(),
+ 16); /* then results of 1st hash */
+ rv = hasher->Finish(false, result); /* finish up 2nd pass */
+
+ if (result.Length() != DIGEST_LENGTH) return NS_ERROR_UNEXPECTED;
+
+ memcpy(digest, result.get(), DIGEST_LENGTH);
+
+ return rv;
+}
+
+// digest needs to be a pointer to a DIGEST_LENGTH (16) byte buffer
+nsresult MSGApopMD5(const char* text, int32_t text_len, const char* password,
+ int32_t password_len, unsigned char* digest) {
+ nsresult rv;
+ nsAutoCString result;
+
+ nsCOMPtr<nsICryptoHash> hasher =
+ do_CreateInstance("@mozilla.org/security/hash;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = hasher->Init(nsICryptoHash::MD5);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = hasher->Update((const uint8_t*)text, text_len);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = hasher->Update((const uint8_t*)password, password_len);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = hasher->Finish(false, result);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (result.Length() != DIGEST_LENGTH) return NS_ERROR_UNEXPECTED;
+
+ memcpy(digest, result.get(), DIGEST_LENGTH);
+ return rv;
+}
+
+NS_MSG_BASE nsresult NS_GetPersistentFile(const char* relPrefName,
+ const char* absPrefName,
+ const char* dirServiceProp,
+ bool& gotRelPref, nsIFile** aFile,
+ nsIPrefBranch* prefBranch) {
+ NS_ENSURE_ARG_POINTER(aFile);
+ *aFile = nullptr;
+ NS_ENSURE_ARG(relPrefName);
+ NS_ENSURE_ARG(absPrefName);
+ gotRelPref = false;
+
+ nsCOMPtr<nsIPrefBranch> mainBranch;
+ if (!prefBranch) {
+ nsCOMPtr<nsIPrefService> prefService(
+ do_GetService(NS_PREFSERVICE_CONTRACTID));
+ if (!prefService) return NS_ERROR_FAILURE;
+ prefService->GetBranch(nullptr, getter_AddRefs(mainBranch));
+ if (!mainBranch) return NS_ERROR_FAILURE;
+ prefBranch = mainBranch;
+ }
+
+ nsCOMPtr<nsIFile> localFile;
+
+ // Get the relative first
+ nsCOMPtr<nsIRelativeFilePref> relFilePref;
+ prefBranch->GetComplexValue(relPrefName, NS_GET_IID(nsIRelativeFilePref),
+ getter_AddRefs(relFilePref));
+ if (relFilePref) {
+ relFilePref->GetFile(getter_AddRefs(localFile));
+ NS_ASSERTION(localFile, "An nsIRelativeFilePref has no file.");
+ if (localFile) gotRelPref = true;
+ }
+
+ // If not, get the old absolute
+ if (!localFile) {
+ prefBranch->GetComplexValue(absPrefName, NS_GET_IID(nsIFile),
+ getter_AddRefs(localFile));
+
+ // If not, and given a dirServiceProp, use directory service.
+ if (!localFile && dirServiceProp) {
+ nsCOMPtr<nsIProperties> dirService(
+ do_GetService("@mozilla.org/file/directory_service;1"));
+ if (!dirService) return NS_ERROR_FAILURE;
+ dirService->Get(dirServiceProp, NS_GET_IID(nsIFile),
+ getter_AddRefs(localFile));
+ if (!localFile) return NS_ERROR_FAILURE;
+ }
+ }
+
+ if (localFile) {
+ localFile->Normalize();
+ localFile.forget(aFile);
+ return NS_OK;
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+NS_MSG_BASE nsresult NS_SetPersistentFile(const char* relPrefName,
+ const char* absPrefName,
+ nsIFile* aFile,
+ nsIPrefBranch* prefBranch) {
+ NS_ENSURE_ARG(relPrefName);
+ NS_ENSURE_ARG(absPrefName);
+ NS_ENSURE_ARG(aFile);
+
+ nsCOMPtr<nsIPrefBranch> mainBranch;
+ if (!prefBranch) {
+ nsCOMPtr<nsIPrefService> prefService(
+ do_GetService(NS_PREFSERVICE_CONTRACTID));
+ if (!prefService) return NS_ERROR_FAILURE;
+ prefService->GetBranch(nullptr, getter_AddRefs(mainBranch));
+ if (!mainBranch) return NS_ERROR_FAILURE;
+ prefBranch = mainBranch;
+ }
+
+ // Write the absolute for backwards compatibilty's sake.
+ // Or, if aPath is on a different drive than the profile dir.
+ nsresult rv =
+ prefBranch->SetComplexValue(absPrefName, NS_GET_IID(nsIFile), aFile);
+
+ // Write the relative path.
+ nsCOMPtr<nsIRelativeFilePref> relFilePref = new nsRelativeFilePref();
+ mozilla::Unused << relFilePref->SetFile(aFile);
+ mozilla::Unused << relFilePref->SetRelativeToKey(
+ nsLiteralCString(NS_APP_USER_PROFILE_50_DIR));
+
+ nsresult rv2 = prefBranch->SetComplexValue(
+ relPrefName, NS_GET_IID(nsIRelativeFilePref), relFilePref);
+ if (NS_FAILED(rv2) && NS_SUCCEEDED(rv))
+ prefBranch->ClearUserPref(relPrefName);
+
+ return rv;
+}
+
+NS_MSG_BASE nsresult NS_GetUnicharPreferenceWithDefault(
+ nsIPrefBranch* prefBranch, // can be null, if so uses the root branch
+ const char* prefName, const nsAString& defValue, nsAString& prefValue) {
+ NS_ENSURE_ARG(prefName);
+
+ nsCOMPtr<nsIPrefBranch> pbr;
+ if (!prefBranch) {
+ pbr = do_GetService(NS_PREFSERVICE_CONTRACTID);
+ prefBranch = pbr;
+ }
+
+ nsCString valueUtf8;
+ nsresult rv =
+ prefBranch->GetStringPref(prefName, EmptyCString(), 0, valueUtf8);
+ if (NS_SUCCEEDED(rv))
+ CopyUTF8toUTF16(valueUtf8, prefValue);
+ else
+ prefValue = defValue;
+ return NS_OK;
+}
+
+NS_MSG_BASE nsresult NS_GetLocalizedUnicharPreferenceWithDefault(
+ nsIPrefBranch* prefBranch, // can be null, if so uses the root branch
+ const char* prefName, const nsAString& defValue, nsAString& prefValue) {
+ NS_ENSURE_ARG(prefName);
+
+ nsCOMPtr<nsIPrefBranch> pbr;
+ if (!prefBranch) {
+ pbr = do_GetService(NS_PREFSERVICE_CONTRACTID);
+ prefBranch = pbr;
+ }
+
+ nsCOMPtr<nsIPrefLocalizedString> str;
+ nsresult rv = prefBranch->GetComplexValue(
+ prefName, NS_GET_IID(nsIPrefLocalizedString), getter_AddRefs(str));
+ if (NS_SUCCEEDED(rv)) {
+ nsString tmpValue;
+ str->ToString(getter_Copies(tmpValue));
+ prefValue.Assign(tmpValue);
+ } else
+ prefValue = defValue;
+ return NS_OK;
+}
+
+NS_MSG_BASE nsresult NS_GetLocalizedUnicharPreference(
+ nsIPrefBranch* prefBranch, // can be null, if so uses the root branch
+ const char* prefName, nsAString& prefValue) {
+ NS_ENSURE_ARG_POINTER(prefName);
+
+ nsCOMPtr<nsIPrefBranch> pbr;
+ if (!prefBranch) {
+ pbr = do_GetService(NS_PREFSERVICE_CONTRACTID);
+ prefBranch = pbr;
+ }
+
+ nsCOMPtr<nsIPrefLocalizedString> str;
+ nsresult rv = prefBranch->GetComplexValue(
+ prefName, NS_GET_IID(nsIPrefLocalizedString), getter_AddRefs(str));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsString tmpValue;
+ str->ToString(getter_Copies(tmpValue));
+ prefValue.Assign(tmpValue);
+ return NS_OK;
+}
+
+void PRTime2Seconds(PRTime prTime, uint32_t* seconds) {
+ *seconds = (uint32_t)(prTime / PR_USEC_PER_SEC);
+}
+
+void PRTime2Seconds(PRTime prTime, int32_t* seconds) {
+ *seconds = (int32_t)(prTime / PR_USEC_PER_SEC);
+}
+
+void Seconds2PRTime(uint32_t seconds, PRTime* prTime) {
+ *prTime = (PRTime)seconds * PR_USEC_PER_SEC;
+}
+
+nsresult GetSummaryFileLocation(nsIFile* fileLocation,
+ nsIFile** summaryLocation) {
+ nsresult rv;
+ nsCOMPtr<nsIFile> newSummaryLocation =
+ do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ newSummaryLocation->InitWithFile(fileLocation);
+ nsString fileName;
+
+ rv = newSummaryLocation->GetLeafName(fileName);
+ if (NS_FAILED(rv)) return rv;
+
+ fileName.AppendLiteral(SUMMARY_SUFFIX);
+ rv = newSummaryLocation->SetLeafName(fileName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ newSummaryLocation.forget(summaryLocation);
+ return NS_OK;
+}
+
+void MsgGenerateNowStr(nsACString& nowStr) {
+ char dateBuf[100];
+ dateBuf[0] = '\0';
+ PRExplodedTime exploded;
+ PR_ExplodeTime(PR_Now(), PR_LocalTimeParameters, &exploded);
+ PR_FormatTimeUSEnglish(dateBuf, sizeof(dateBuf), "%a %b %d %H:%M:%S %Y",
+ &exploded);
+ nowStr.Assign(dateBuf);
+}
+
+// Gets a special directory and appends the supplied file name onto it.
+nsresult GetSpecialDirectoryWithFileName(const char* specialDirName,
+ const char* fileName,
+ nsIFile** result) {
+ nsresult rv = NS_GetSpecialDirectory(specialDirName, result);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return (*result)->AppendNative(nsDependentCString(fileName));
+}
+
+// Cleans up temp files with matching names
+nsresult MsgCleanupTempFiles(const char* fileName, const char* extension) {
+ nsCOMPtr<nsIFile> tmpFile;
+ nsCString rootName(fileName);
+ rootName.Append('.');
+ rootName.Append(extension);
+ nsresult rv = GetSpecialDirectoryWithFileName(NS_OS_TEMP_DIR, rootName.get(),
+ getter_AddRefs(tmpFile));
+
+ NS_ENSURE_SUCCESS(rv, rv);
+ int index = 1;
+ bool exists;
+ do {
+ tmpFile->Exists(&exists);
+ if (exists) {
+ tmpFile->Remove(false);
+ nsCString leafName(fileName);
+ leafName.Append('-');
+ leafName.AppendInt(index);
+ leafName.Append('.');
+ leafName.Append(extension);
+ // start with "Picture-1.jpg" after "Picture.jpg" exists
+ tmpFile->SetNativeLeafName(leafName);
+ }
+ } while (exists && index++ < 10000);
+ return NS_OK;
+}
+
+nsresult MsgGetFileStream(nsIFile* file, nsIOutputStream** fileStream) {
+ RefPtr<nsMsgFileStream> newFileStream = new nsMsgFileStream;
+ nsresult rv = newFileStream->InitWithFile(file);
+ NS_ENSURE_SUCCESS(rv, rv);
+ newFileStream.forget(fileStream);
+ return NS_OK;
+}
+
+nsresult MsgNewBufferedFileOutputStream(nsIOutputStream** aResult,
+ nsIFile* aFile, int32_t aIOFlags,
+ int32_t aPerm) {
+ nsCOMPtr<nsIOutputStream> stream;
+ nsresult rv = NS_NewLocalFileOutputStream(getter_AddRefs(stream), aFile,
+ aIOFlags, aPerm);
+ if (NS_SUCCEEDED(rv))
+ rv = NS_NewBufferedOutputStream(aResult, stream.forget(),
+ FILE_IO_BUFFER_SIZE);
+ return rv;
+}
+
+nsresult MsgNewSafeBufferedFileOutputStream(nsIOutputStream** aResult,
+ nsIFile* aFile, int32_t aIOFlags,
+ int32_t aPerm) {
+ nsCOMPtr<nsIOutputStream> stream;
+ nsresult rv = NS_NewSafeLocalFileOutputStream(getter_AddRefs(stream), aFile,
+ aIOFlags, aPerm);
+ if (NS_SUCCEEDED(rv))
+ rv = NS_NewBufferedOutputStream(aResult, stream.forget(),
+ FILE_IO_BUFFER_SIZE);
+ return rv;
+}
+
+bool MsgFindKeyword(const nsCString& keyword, nsCString& keywords,
+ int32_t* aStartOfKeyword, int32_t* aLength) {
+// nsTString_CharT::Find(const nsCString& aString,
+// bool aIgnoreCase=false,
+// int32_t aOffset=0,
+// int32_t aCount=-1 ) const;
+#define FIND_KEYWORD(keywords, keyword, offset) \
+ ((keywords).Find((keyword), (offset)))
+ // 'keyword' is the single keyword we're looking for
+ // 'keywords' is a space delimited list of keywords to be searched,
+ // which may be just a single keyword or even be empty
+ const int32_t kKeywordLen = keyword.Length();
+ const char* start = keywords.BeginReading();
+ const char* end = keywords.EndReading();
+ *aStartOfKeyword = FIND_KEYWORD(keywords, keyword, 0);
+ while (*aStartOfKeyword >= 0) {
+ const char* matchStart = start + *aStartOfKeyword;
+ const char* matchEnd = matchStart + kKeywordLen;
+ // For a real match, matchStart must be the start of keywords or preceded
+ // by a space and matchEnd must be the end of keywords or point to a space.
+ if ((matchStart == start || *(matchStart - 1) == ' ') &&
+ (matchEnd == end || *matchEnd == ' ')) {
+ *aLength = kKeywordLen;
+ return true;
+ }
+ *aStartOfKeyword =
+ FIND_KEYWORD(keywords, keyword, *aStartOfKeyword + kKeywordLen);
+ }
+
+ *aLength = 0;
+ return false;
+#undef FIND_KEYWORD
+}
+
+bool MsgHostDomainIsTrusted(nsCString& host, nsCString& trustedMailDomains) {
+ const char* end;
+ uint32_t hostLen, domainLen;
+ bool domainIsTrusted = false;
+
+ const char* domain = trustedMailDomains.BeginReading();
+ const char* domainEnd = trustedMailDomains.EndReading();
+ const char* hostStart = host.BeginReading();
+ hostLen = host.Length();
+
+ do {
+ // skip any whitespace
+ while (*domain == ' ' || *domain == '\t') ++domain;
+
+ // find end of this domain in the string
+ end = strchr(domain, ',');
+ if (!end) end = domainEnd;
+
+ // to see if the hostname is in the domain, check if the domain
+ // matches the end of the hostname.
+ domainLen = end - domain;
+ if (domainLen && hostLen >= domainLen) {
+ const char* hostTail = hostStart + hostLen - domainLen;
+ if (PL_strncasecmp(domain, hostTail, domainLen) == 0) {
+ // now, make sure either that the hostname is a direct match or
+ // that the hostname begins with a dot.
+ if (hostLen == domainLen || *hostTail == '.' ||
+ *(hostTail - 1) == '.') {
+ domainIsTrusted = true;
+ break;
+ }
+ }
+ }
+
+ domain = end + 1;
+ } while (*end);
+ return domainIsTrusted;
+}
+
+nsresult MsgGetLocalFileFromURI(const nsACString& aUTF8Path, nsIFile** aFile) {
+ nsresult rv;
+ nsCOMPtr<nsIURI> argURI;
+ rv = NS_NewURI(getter_AddRefs(argURI), aUTF8Path);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIFileURL> argFileURL(do_QueryInterface(argURI, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> argFile;
+ rv = argFileURL->GetFile(getter_AddRefs(argFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ argFile.forget(aFile);
+ return NS_OK;
+}
+
+NS_MSG_BASE void MsgStripQuotedPrintable(nsCString& aSrc) {
+ // decode quoted printable text in place
+
+ if (aSrc.IsEmpty()) return;
+
+ char* src = aSrc.BeginWriting();
+ char* dest = src;
+ int srcIdx = 0, destIdx = 0;
+
+ while (src[srcIdx] != 0) {
+ // Decode sequence of '=XY' into a character with code XY.
+ if (src[srcIdx] == '=') {
+ if (MsgIsHex((const char*)src + srcIdx + 1, 2)) {
+ // If we got here, we successfully decoded a quoted printable sequence,
+ // so bump each pointer past it and move on to the next char.
+ dest[destIdx++] = MsgUnhex((const char*)src + srcIdx + 1, 2);
+ srcIdx += 3;
+ } else {
+ // If first char after '=' isn't hex check if it's a normal char
+ // or a soft line break. If it's a soft line break, eat the
+ // CR/LF/CRLF.
+ if (src[srcIdx + 1] == '\r' || src[srcIdx + 1] == '\n') {
+ srcIdx++; // soft line break, ignore the '=';
+ if (src[srcIdx] == '\r' || src[srcIdx] == '\n') {
+ srcIdx++;
+ if (src[srcIdx] == '\n') srcIdx++;
+ }
+ } else // The first or second char after '=' isn't hex, just copy the
+ // '='.
+ {
+ dest[destIdx++] = src[srcIdx++];
+ }
+ continue;
+ }
+ } else
+ dest[destIdx++] = src[srcIdx++];
+ }
+
+ dest[destIdx] = src[srcIdx]; // null terminate
+ aSrc.SetLength(destIdx);
+}
+
+NS_MSG_BASE nsresult MsgEscapeString(const nsACString& aStr, uint32_t aType,
+ nsACString& aResult) {
+ nsresult rv;
+ nsCOMPtr<nsINetUtil> nu = do_GetService(NS_NETUTIL_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return nu->EscapeString(aStr, aType, aResult);
+}
+
+NS_MSG_BASE nsresult MsgUnescapeString(const nsACString& aStr, uint32_t aFlags,
+ nsACString& aResult) {
+ nsresult rv;
+ nsCOMPtr<nsINetUtil> nu = do_GetService(NS_NETUTIL_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return nu->UnescapeString(aStr, aFlags, aResult);
+}
+
+NS_MSG_BASE nsresult MsgEscapeURL(const nsACString& aStr, uint32_t aFlags,
+ nsACString& aResult) {
+ nsresult rv;
+ nsCOMPtr<nsINetUtil> nu = do_GetService(NS_NETUTIL_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return nu->EscapeURL(aStr, aFlags, aResult);
+}
+
+NS_MSG_BASE nsresult
+MsgGetHeadersFromKeys(nsIMsgDatabase* aDB, const nsTArray<nsMsgKey>& aMsgKeys,
+ nsTArray<RefPtr<nsIMsgDBHdr>>& aHeaders) {
+ NS_ENSURE_ARG_POINTER(aDB);
+ aHeaders.Clear();
+ aHeaders.SetCapacity(aMsgKeys.Length());
+
+ for (auto key : aMsgKeys) {
+ // This function silently skips when the key is not found. This is an
+ // expected case.
+ bool hasKey;
+ nsresult rv = aDB->ContainsKey(key, &hasKey);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (hasKey) {
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ rv = aDB->GetMsgHdrForKey(key, getter_AddRefs(msgHdr));
+ NS_ENSURE_SUCCESS(rv, rv);
+ aHeaders.AppendElement(msgHdr);
+ }
+ }
+ return NS_OK;
+}
+
+bool MsgAdvanceToNextLine(const char* buffer, uint32_t& bufferOffset,
+ uint32_t maxBufferOffset) {
+ bool result = false;
+ for (; bufferOffset < maxBufferOffset; bufferOffset++) {
+ if (buffer[bufferOffset] == '\r' || buffer[bufferOffset] == '\n') {
+ bufferOffset++;
+ if (buffer[bufferOffset - 1] == '\r' && buffer[bufferOffset] == '\n')
+ bufferOffset++;
+ result = true;
+ break;
+ }
+ }
+ return result;
+}
+
+NS_MSG_BASE nsresult MsgExamineForProxyAsync(nsIChannel* channel,
+ nsIProtocolProxyCallback* listener,
+ nsICancelable** result) {
+ nsresult rv;
+
+#ifdef DEBUG
+ nsCOMPtr<nsIURI> uri;
+ rv = channel->GetURI(getter_AddRefs(uri));
+ NS_ASSERTION(NS_SUCCEEDED(rv) && uri,
+ "The URI needs to be set before calling the proxy service");
+#endif
+
+ nsCOMPtr<nsIProtocolProxyService> pps =
+ do_GetService(NS_PROTOCOLPROXYSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return pps->AsyncResolve(channel, 0, listener, nullptr, result);
+}
+
+NS_MSG_BASE nsresult MsgPromptLoginFailed(nsIMsgWindow* aMsgWindow,
+ const nsACString& aHostname,
+ const nsACString& aUsername,
+ const nsAString& aAccountname,
+ int32_t* aResult) {
+ nsCOMPtr<mozIDOMWindowProxy> domWindow;
+ if (aMsgWindow) {
+ aMsgWindow->GetDomWindow(getter_AddRefs(domWindow));
+ }
+
+ nsresult rv;
+ nsCOMPtr<nsIPromptService> dlgService(
+ do_GetService(NS_PROMPTSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIStringBundleService> bundleSvc =
+ mozilla::components::StringBundle::Service();
+ NS_ENSURE_TRUE(bundleSvc, NS_ERROR_UNEXPECTED);
+
+ nsCOMPtr<nsIStringBundle> bundle;
+ rv = bundleSvc->CreateBundle("chrome://messenger/locale/messenger.properties",
+ getter_AddRefs(bundle));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsString message;
+ AutoTArray<nsString, 2> formatStrings;
+ CopyUTF8toUTF16(aHostname, *formatStrings.AppendElement());
+ CopyUTF8toUTF16(aUsername, *formatStrings.AppendElement());
+
+ rv = bundle->FormatStringFromName("mailServerLoginFailed2", formatStrings,
+ message);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsString title;
+ if (aAccountname.IsEmpty()) {
+ // Account name may be empty e.g. on a SMTP server.
+ rv = bundle->GetStringFromName("mailServerLoginFailedTitle", title);
+ } else {
+ AutoTArray<nsString, 1> formatStrings = {nsString(aAccountname)};
+ rv = bundle->FormatStringFromName("mailServerLoginFailedTitleWithAccount",
+ formatStrings, title);
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsString button0;
+ rv = bundle->GetStringFromName("mailServerLoginFailedRetryButton", button0);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsString button2;
+ rv = bundle->GetStringFromName("mailServerLoginFailedEnterNewPasswordButton",
+ button2);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool dummyValue = false;
+ return dlgService->ConfirmEx(
+ domWindow, title.get(), message.get(),
+ (nsIPrompt::BUTTON_TITLE_IS_STRING * nsIPrompt::BUTTON_POS_0) +
+ (nsIPrompt::BUTTON_TITLE_CANCEL * nsIPrompt::BUTTON_POS_1) +
+ (nsIPrompt::BUTTON_TITLE_IS_STRING * nsIPrompt::BUTTON_POS_2),
+ button0.get(), nullptr, button2.get(), nullptr, &dummyValue, aResult);
+}
+
+NS_MSG_BASE PRTime MsgConvertAgeInDaysToCutoffDate(int32_t ageInDays) {
+ PRTime now = PR_Now();
+
+ return now - PR_USEC_PER_DAY * ageInDays;
+}
+
+NS_MSG_BASE nsresult
+MsgTermListToString(nsTArray<RefPtr<nsIMsgSearchTerm>> const& aTermList,
+ nsCString& aOutString) {
+ nsresult rv = NS_OK;
+ for (nsIMsgSearchTerm* term : aTermList) {
+ nsAutoCString stream;
+
+ if (aOutString.Length() > 1) aOutString += ' ';
+
+ bool booleanAnd;
+ bool matchAll;
+ term->GetBooleanAnd(&booleanAnd);
+ term->GetMatchAll(&matchAll);
+ if (matchAll) {
+ aOutString += "ALL";
+ continue;
+ } else if (booleanAnd)
+ aOutString += "AND (";
+ else
+ aOutString += "OR (";
+
+ rv = term->GetTermAsString(stream);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aOutString += stream;
+ aOutString += ')';
+ }
+ return rv;
+}
+
+NS_MSG_BASE uint64_t ParseUint64Str(const char* str) {
+#ifdef XP_WIN
+ {
+ char* endPtr;
+ return _strtoui64(str, &endPtr, 10);
+ }
+#else
+ return strtoull(str, nullptr, 10);
+#endif
+}
+
+NS_MSG_BASE uint64_t MsgUnhex(const char* aHexString, size_t aNumChars) {
+ // Large numbers will not fit into uint64_t.
+ NS_ASSERTION(aNumChars <= 16, "Hex literal too long to convert!");
+
+ uint64_t result = 0;
+ for (size_t i = 0; i < aNumChars; i++) {
+ unsigned char c = aHexString[i];
+ uint8_t digit;
+ if ((c >= '0') && (c <= '9'))
+ digit = (c - '0');
+ else if ((c >= 'a') && (c <= 'f'))
+ digit = ((c - 'a') + 10);
+ else if ((c >= 'A') && (c <= 'F'))
+ digit = ((c - 'A') + 10);
+ else
+ break;
+
+ result = (result << 4) | digit;
+ }
+
+ return result;
+}
+
+NS_MSG_BASE bool MsgIsHex(const char* aHexString, size_t aNumChars) {
+ for (size_t i = 0; i < aNumChars; i++) {
+ if (!isxdigit(aHexString[i])) return false;
+ }
+ return true;
+}
+
+NS_MSG_BASE nsresult MsgStreamMsgHeaders(nsIInputStream* aInputStream,
+ nsIStreamListener* aConsumer) {
+ mozilla::UniquePtr<nsLineBuffer<char>> lineBuffer(new nsLineBuffer<char>);
+
+ nsresult rv;
+
+ nsAutoCString msgHeaders;
+ nsAutoCString curLine;
+
+ bool more = true;
+
+ // We want to NS_ReadLine until we get to a blank line (the end of the
+ // headers)
+ while (more) {
+ rv = NS_ReadLine(aInputStream, lineBuffer.get(), curLine, &more);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (curLine.IsEmpty()) break;
+ msgHeaders.Append(curLine);
+ msgHeaders.AppendLiteral("\r\n");
+ }
+ lineBuffer.reset();
+ nsCOMPtr<nsIStringInputStream> hdrsStream =
+ do_CreateInstance("@mozilla.org/io/string-input-stream;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ hdrsStream->SetData(msgHeaders.get(), msgHeaders.Length());
+
+ nsCOMPtr<nsIInputStreamPump> pump;
+ rv = NS_NewInputStreamPump(getter_AddRefs(pump), hdrsStream.forget());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return pump->AsyncRead(aConsumer);
+}
+
+NS_MSG_BASE nsresult MsgDetectCharsetFromFile(nsIFile* aFile,
+ nsACString& aCharset) {
+ // We do the detection in this order:
+ // Check BOM.
+ // If no BOM, run localized detection (Russian, Ukrainian or Japanese).
+ // We need to run this first, since ISO-2022-JP is 7bit ASCII and would be
+ // detected as UTF-8. If ISO-2022-JP not detected, check for UTF-8. If no
+ // UTF-8, but detector detected something, use that, otherwise return an
+ // error.
+ aCharset.Truncate();
+
+ nsresult rv;
+ nsCOMPtr<nsIInputStream> inputStream;
+ rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream), aFile);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Check the BOM.
+ char sniffBuf[3];
+ uint32_t numRead;
+ rv = inputStream->Read(sniffBuf, sizeof(sniffBuf), &numRead);
+
+ if (numRead >= 2 && sniffBuf[0] == (char)0xfe && sniffBuf[1] == (char)0xff) {
+ aCharset = "UTF-16BE";
+ } else if (numRead >= 2 && sniffBuf[0] == (char)0xff &&
+ sniffBuf[1] == (char)0xfe) {
+ aCharset = "UTF-16LE";
+ } else if (numRead >= 3 && sniffBuf[0] == (char)0xef &&
+ sniffBuf[1] == (char)0xbb && sniffBuf[2] == (char)0xbf) {
+ aCharset = "UTF-8";
+ }
+ if (!aCharset.IsEmpty()) return NS_OK;
+
+ // Position back to the beginning.
+ nsCOMPtr<nsISeekableStream> seekStream = do_QueryInterface(inputStream);
+ if (seekStream) seekStream->Seek(nsISeekableStream::NS_SEEK_SET, 0);
+
+ // Use detector.
+ mozilla::UniquePtr<mozilla::EncodingDetector> detector =
+ mozilla::EncodingDetector::Create();
+ char buffer[1024];
+ numRead = 0;
+ while (NS_SUCCEEDED(inputStream->Read(buffer, sizeof(buffer), &numRead))) {
+ mozilla::Span<const uint8_t> src =
+ mozilla::AsBytes(mozilla::Span(buffer, numRead));
+ Unused << detector->Feed(src, false);
+ if (numRead == 0) {
+ break;
+ }
+ }
+ Unused << detector->Feed(nullptr, true);
+ auto encoding = detector->Guess(nullptr, true);
+ encoding->Name(aCharset);
+ return NS_OK;
+}
+
+/*
+ * Converts a buffer to plain text. Some conversions may
+ * or may not work with certain end charsets which is why we
+ * need that as an argument to the function. If charset is
+ * unknown or deemed of no importance NULL could be passed.
+ */
+NS_MSG_BASE nsresult ConvertBufToPlainText(nsString& aConBuf, bool formatFlowed,
+ bool formatOutput,
+ bool disallowBreaks) {
+ if (aConBuf.IsEmpty()) return NS_OK;
+
+ int32_t wrapWidth = 72;
+ nsCOMPtr<nsIPrefBranch> pPrefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID));
+
+ if (pPrefBranch) {
+ pPrefBranch->GetIntPref("mailnews.wraplength", &wrapWidth);
+ // Let sanity reign!
+ if (wrapWidth == 0 || wrapWidth > 990)
+ wrapWidth = 990;
+ else if (wrapWidth < 10)
+ wrapWidth = 10;
+ }
+
+ uint32_t converterFlags = nsIDocumentEncoder::OutputPersistNBSP;
+ if (formatFlowed) converterFlags |= nsIDocumentEncoder::OutputFormatFlowed;
+ if (formatOutput) converterFlags |= nsIDocumentEncoder::OutputFormatted;
+ if (disallowBreaks)
+ converterFlags |= nsIDocumentEncoder::OutputDisallowLineBreaking;
+
+ nsCOMPtr<nsIParserUtils> utils = do_GetService(NS_PARSERUTILS_CONTRACTID);
+ return utils->ConvertToPlainText(aConBuf, converterFlags, wrapWidth, aConBuf);
+}
+
+NS_MSG_BASE nsMsgKey msgKeyFromInt(uint32_t aValue) { return aValue; }
+
+NS_MSG_BASE nsMsgKey msgKeyFromInt(uint64_t aValue) {
+ NS_ASSERTION(aValue <= PR_UINT32_MAX, "Msg key value too big!");
+ return aValue;
+}
+
+NS_MSG_BASE uint32_t msgKeyToInt(nsMsgKey aMsgKey) { return (uint32_t)aMsgKey; }
+
+// Helper function to extract a query qualifier.
+nsCString MsgExtractQueryPart(const nsACString& spec,
+ const char* queryToExtract) {
+ nsCString queryPart;
+ int32_t queryIndex = PromiseFlatCString(spec).Find(queryToExtract);
+ if (queryIndex == kNotFound) return queryPart;
+
+ int32_t queryEnd = spec.FindChar('&', queryIndex + 1);
+ if (queryEnd == kNotFound) queryEnd = spec.FindChar('?', queryIndex + 1);
+ if (queryEnd == kNotFound) {
+ // Nothing follows, so return from where the query qualifier started.
+ queryPart.Assign(Substring(spec, queryIndex));
+ } else {
+ // Return the substring that represents the query qualifier.
+ queryPart.Assign(Substring(spec, queryIndex, queryEnd - queryIndex));
+ }
+ return queryPart;
+}
+
+// Helper function to remove query part from URL spec or path.
+void MsgRemoveQueryPart(nsCString& aSpec) {
+ // Sadly the query part can have different forms, these were seen
+ // "in the wild", even with two ?:
+ // /;section=2?part=1.2&filename=A01.JPG
+ // ?section=2?part=1.2&filename=A01.JPG&type=image/jpeg&filename=A01.JPG
+ // ?header=quotebody/;section=2.2?part=1.2.2&filename=lijbmghmkilicioj.png
+ // ?part=1.2&type=image/jpeg&filename=IMG_C0030.jpg
+ // ?header=quotebody&part=1.2&filename=lijbmghmkilicioj.png
+
+ // Truncate path at the first of /; or ?
+ int32_t ind = aSpec.FindChar('?');
+ if (ind != kNotFound) aSpec.SetLength(ind);
+ ind = aSpec.Find("/;");
+ if (ind != kNotFound) aSpec.SetLength(ind);
+}
+
+// Perform C-style string escaping.
+// e.g. "foo\r\n" => "foo\\r\\n"
+// (See also CEscape(), in protobuf, for similar function).
+nsCString CEscapeString(nsACString const& s) {
+ nsCString out;
+ for (size_t i = 0; i < s.Length(); ++i) {
+ char c = s[i];
+ if (c & 0x80) {
+ out.AppendPrintf("\\x%02x", (uint8_t)c);
+ continue;
+ }
+ switch (c) {
+ case '\a':
+ out += "\\a";
+ break;
+ case '\b':
+ out += "\\b";
+ break;
+ case '\f':
+ out += "\\f";
+ break;
+ case '\n':
+ out += "\\n";
+ break;
+ case '\r':
+ out += "\\r";
+ break;
+ case '\t':
+ out += "\\t";
+ break;
+ case '\v':
+ out += "\\v";
+ break;
+ default:
+ if (c < ' ') {
+ out.AppendPrintf("\\x%02x", (uint8_t)c);
+ } else {
+ out += c;
+ }
+ break;
+ }
+ }
+ return out;
+}
+
+nsresult SyncCopyStream(nsIInputStream* src, nsIOutputStream* dest,
+ uint64_t& bytesCopied, size_t bufSize) {
+ mozilla::Buffer<char> buf(bufSize);
+ nsresult rv;
+
+ bytesCopied = 0;
+ while (1) {
+ uint32_t numRead;
+ rv = src->Read(buf.Elements(), buf.Length(), &numRead);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (numRead == 0) {
+ break; // EOF.
+ }
+ uint32_t pos = 0;
+ while (pos < numRead) {
+ uint32_t n;
+ rv = dest->Write(&buf[pos], numRead - pos, &n);
+ NS_ENSURE_SUCCESS(rv, rv);
+ pos += n;
+ bytesCopied += n;
+ }
+ }
+ return NS_OK;
+}
+
+// Used for "@mozilla.org/network/sync-stream-listener;1".
+already_AddRefed<nsIStreamListener> SyncStreamListenerCreate() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsCOMPtr<nsIStreamListener> listener;
+ nsCOMPtr<nsIInputStream> stream;
+ nsresult rv = NS_NewSyncStreamListener(getter_AddRefs(listener),
+ getter_AddRefs(stream));
+ NS_ENSURE_SUCCESS(rv, nullptr);
+ return listener.forget();
+}
+
+// Determine if folder1 and folder2 reside on the same server
+nsresult IsOnSameServer(nsIMsgFolder* folder1, nsIMsgFolder* folder2,
+ bool* sameServer) {
+ NS_ENSURE_ARG_POINTER(folder1);
+ NS_ENSURE_ARG_POINTER(folder2);
+ NS_ENSURE_ARG_POINTER(sameServer);
+
+ nsCOMPtr<nsIMsgIncomingServer> server1;
+ nsresult rv = folder1->GetServer(getter_AddRefs(server1));
+ NS_ENSURE_SUCCESS(rv, NS_MSG_INVALID_OR_MISSING_SERVER);
+
+ nsCOMPtr<nsIMsgIncomingServer> server2;
+ rv = folder2->GetServer(getter_AddRefs(server2));
+ NS_ENSURE_SUCCESS(rv, NS_MSG_INVALID_OR_MISSING_SERVER);
+
+ NS_ENSURE_TRUE(server2, NS_ERROR_NULL_POINTER);
+ return server2->Equals(server1, sameServer);
+}
diff --git a/comm/mailnews/base/src/nsMsgUtils.h b/comm/mailnews/base/src/nsMsgUtils.h
new file mode 100644
index 0000000000..6f289d4b37
--- /dev/null
+++ b/comm/mailnews/base/src/nsMsgUtils.h
@@ -0,0 +1,462 @@
+/* -*- 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 _NSMSGUTILS_H
+#define _NSMSGUTILS_H
+
+#include "nsIURL.h"
+#include "nsString.h"
+#include "msgCore.h"
+#include "nsCOMPtr.h"
+#include "MailNewsTypes2.h"
+#include "nsTArray.h"
+#include "nsInterfaceRequestorAgg.h"
+#include "nsILoadGroup.h"
+#include "nsINetUtil.h"
+#include "nsIRequest.h"
+#include "nsILoadInfo.h"
+#include "nsServiceManagerUtils.h"
+#include "nsUnicharUtils.h"
+#include "nsIFile.h"
+
+class nsIChannel;
+class nsIFile;
+class nsIPrefBranch;
+class nsIMsgFolder;
+class nsIMsgMessageService;
+class nsIUrlListener;
+class nsIOutputStream;
+class nsIInputStream;
+class nsIMsgDatabase;
+class nsIProxyInfo;
+class nsIMsgWindow;
+class nsIStreamListener;
+class nsICancelable;
+class nsIProtocolProxyCallback;
+class nsIMsgSearchTerm;
+
+#define FILE_IO_BUFFER_SIZE (16 * 1024)
+#define MSGS_URL "chrome://messenger/locale/messenger.properties"
+
+enum nsDateFormatSelectorComm : long {
+ kDateFormatNone = 0,
+ kDateFormatLong = 1,
+ kDateFormatShort = 2,
+ kDateFormatUnused = 3,
+ kDateFormatWeekday = 4
+};
+
+// These are utility functions that can used throughout the mailnews code
+
+NS_MSG_BASE nsresult GetMessageServiceContractIDForURI(const char* uri,
+ nsCString& contractID);
+
+NS_MSG_BASE nsresult GetMessageServiceFromURI(
+ const nsACString& uri, nsIMsgMessageService** aMessageService);
+
+NS_MSG_BASE nsresult GetMsgDBHdrFromURI(const nsACString& uri,
+ nsIMsgDBHdr** msgHdr);
+
+NS_MSG_BASE nsresult NS_MsgGetPriorityFromString(
+ const char* const priority, nsMsgPriorityValue& outPriority);
+
+NS_MSG_BASE nsresult NS_MsgGetPriorityValueString(const nsMsgPriorityValue p,
+ nsACString& outValueString);
+
+NS_MSG_BASE nsresult NS_MsgGetUntranslatedPriorityName(
+ const nsMsgPriorityValue p, nsACString& outName);
+
+NS_MSG_BASE nsresult NS_MsgHashIfNecessary(nsAutoString& name);
+NS_MSG_BASE nsresult NS_MsgHashIfNecessary(nsAutoCString& name);
+
+NS_MSG_BASE nsresult FormatFileSize(int64_t size, bool useKB,
+ nsAString& formattedSize);
+
+/**
+ * given a folder uri, return the path to folder in the user profile directory.
+ *
+ * @param aFolderURI uri of folder we want the path to, without the scheme
+ * @param[out] aPathString result path string
+ * @param aScheme scheme of the uri
+ * @param[optional] aIsNewsFolder is this a news folder?
+ */
+NS_MSG_BASE nsresult NS_MsgCreatePathStringFromFolderURI(
+ const char* aFolderURI, nsCString& aPathString, const nsCString& aScheme,
+ bool aIsNewsFolder = false);
+
+/**
+ * Given a string and a length, removes any "Re:" strings from the front.
+ * It also deals with that dumbass "Re[2]:" thing that some losing mailers do.
+ *
+ * If mailnews.localizedRe is set, it will also remove localized "Re:" strings.
+ *
+ * @return true if it made a change (in which case the caller should look to
+ * modifiedSubject for the result) and false otherwise (in which
+ * case the caller should look at subject for the result)
+ */
+NS_MSG_BASE bool NS_MsgStripRE(const nsCString& subject,
+ nsCString& modifiedSubject);
+
+NS_MSG_BASE char* NS_MsgSACopy(char** destination, const char* source);
+
+NS_MSG_BASE char* NS_MsgSACat(char** destination, const char* source);
+
+NS_MSG_BASE nsresult NS_MsgEscapeEncodeURLPath(const nsAString& aStr,
+ nsCString& aResult);
+
+NS_MSG_BASE nsresult NS_MsgDecodeUnescapeURLPath(const nsACString& aPath,
+ nsAString& aResult);
+
+NS_MSG_BASE bool WeAreOffline();
+
+// Get a folder by Uri, returning null if it doesn't exist (or if some
+// error occurs). A missing folder is not considered an error.
+NS_MSG_BASE nsresult FindFolder(const nsACString& aFolderURI,
+ nsIMsgFolder** aFolder);
+
+// Get a folder by Uri.
+// A missing folder is considered to be an error.
+// Returns a non-null folder if and only if result is NS_OK.
+NS_MSG_BASE nsresult GetExistingFolder(const nsACString& aFolderURI,
+ nsIMsgFolder** aFolder);
+
+// Get a folder by Uri, creating it if it doesn't already exist.
+// An error is returned if a folder cannot be found or created.
+// Created folders will be 'dangling' folders (ie not connected to a
+// parent).
+NS_MSG_BASE nsresult GetOrCreateFolder(const nsACString& aFolderURI,
+ nsIMsgFolder** aFolder);
+
+// Escape lines starting with "From ", ">From ", etc. in a buffer.
+NS_MSG_BASE nsresult EscapeFromSpaceLine(nsIOutputStream* ouputStream,
+ char* start, const char* end);
+NS_MSG_BASE bool IsAFromSpaceLine(char* start, const char* end);
+
+NS_MSG_BASE nsresult NS_GetPersistentFile(
+ const char* relPrefName, const char* absPrefName,
+ const char* dirServiceProp, // Can be NULL
+ bool& gotRelPref, nsIFile** aFile, nsIPrefBranch* prefBranch = nullptr);
+
+NS_MSG_BASE nsresult NS_SetPersistentFile(const char* relPrefName,
+ const char* absPrefName,
+ nsIFile* aFile,
+ nsIPrefBranch* prefBranch = nullptr);
+
+NS_MSG_BASE nsresult IsRFC822HeaderFieldName(const char* aHdr, bool* aResult);
+
+NS_MSG_BASE nsresult NS_GetUnicharPreferenceWithDefault(
+ nsIPrefBranch* prefBranch, // can be null, if so uses the root branch
+ const char* prefName, const nsAString& defValue, nsAString& prefValue);
+
+NS_MSG_BASE nsresult NS_GetLocalizedUnicharPreferenceWithDefault(
+ nsIPrefBranch* prefBranch, // can be null, if so uses the root branch
+ const char* prefName, const nsAString& defValue, nsAString& prefValue);
+
+NS_MSG_BASE nsresult NS_GetLocalizedUnicharPreference(
+ nsIPrefBranch* prefBranch, // can be null, if so uses the root branch
+ const char* prefName, nsAString& prefValue);
+
+/**
+ * this needs a listener, because we might have to create the folder
+ * on the server, and that is asynchronous
+ */
+NS_MSG_BASE nsresult GetOrCreateJunkFolder(const nsACString& aURI,
+ nsIUrlListener* aListener);
+
+// Returns true if the nsIURI is a message under an RSS account
+NS_MSG_BASE nsresult IsRSSArticle(nsIURI* aMsgURI, bool* aIsRSSArticle);
+
+// digest needs to be a pointer to a 16 byte buffer
+#define DIGEST_LENGTH 16
+
+NS_MSG_BASE nsresult MSGCramMD5(const char* text, int32_t text_len,
+ const char* key, int32_t key_len,
+ unsigned char* digest);
+NS_MSG_BASE nsresult MSGApopMD5(const char* text, int32_t text_len,
+ const char* password, int32_t password_len,
+ unsigned char* digest);
+
+// helper functions to convert a 64bits PRTime into a 32bits value (compatible
+// time_t) and vice versa.
+NS_MSG_BASE void PRTime2Seconds(PRTime prTime, uint32_t* seconds);
+NS_MSG_BASE void PRTime2Seconds(PRTime prTime, int32_t* seconds);
+NS_MSG_BASE void Seconds2PRTime(uint32_t seconds, PRTime* prTime);
+// helper function to generate current date+time as a string
+NS_MSG_BASE void MsgGenerateNowStr(nsACString& nowStr);
+
+// Appends the correct summary file extension onto the supplied fileLocation
+// and returns it in summaryLocation.
+NS_MSG_BASE nsresult GetSummaryFileLocation(nsIFile* fileLocation,
+ nsIFile** summaryLocation);
+
+// Gets a special directory and appends the supplied file name onto it.
+NS_MSG_BASE nsresult GetSpecialDirectoryWithFileName(const char* specialDirName,
+ const char* fileName,
+ nsIFile** result);
+
+// cleanup temp files with the given filename and extension, including
+// the consecutive -NNNN ones that we can find. If there are holes, e.g.,
+// <filename>-1-10,12.<extension> exist, but <filename>-11.<extension> does not
+// we'll clean up 1-10. If the leaks are common, I think the gaps will tend to
+// be filled.
+NS_MSG_BASE nsresult MsgCleanupTempFiles(const char* fileName,
+ const char* extension);
+
+NS_MSG_BASE nsresult MsgGetFileStream(nsIFile* file,
+ nsIOutputStream** fileStream);
+
+// Automatically creates an output stream with a suitable buffer
+NS_MSG_BASE nsresult MsgNewBufferedFileOutputStream(nsIOutputStream** aResult,
+ nsIFile* aFile,
+ int32_t aIOFlags = -1,
+ int32_t aPerm = -1);
+
+// Automatically creates an output stream with a suitable buffer, but write to a
+// temporary file first, then rename to aFile
+NS_MSG_BASE nsresult
+MsgNewSafeBufferedFileOutputStream(nsIOutputStream** aResult, nsIFile* aFile,
+ int32_t aIOFlags = -1, int32_t aPerm = -1);
+
+// fills in the position of the passed in keyword in the passed in keyword list
+// and returns false if the keyword isn't present
+NS_MSG_BASE bool MsgFindKeyword(const nsCString& keyword, nsCString& keywords,
+ int32_t* aStartOfKeyword, int32_t* aLength);
+
+NS_MSG_BASE bool MsgHostDomainIsTrusted(nsCString& host,
+ nsCString& trustedMailDomains);
+
+// gets an nsIFile from a UTF-8 file:// path
+NS_MSG_BASE nsresult MsgGetLocalFileFromURI(const nsACString& aUTF8Path,
+ nsIFile** aFile);
+
+NS_MSG_BASE void MsgStripQuotedPrintable(nsCString& aSrc);
+
+/*
+ * Utility functions that call functions from nsINetUtil
+ */
+
+NS_MSG_BASE nsresult MsgEscapeString(const nsACString& aStr, uint32_t aType,
+ nsACString& aResult);
+
+NS_MSG_BASE nsresult MsgUnescapeString(const nsACString& aStr, uint32_t aFlags,
+ nsACString& aResult);
+
+NS_MSG_BASE nsresult MsgEscapeURL(const nsACString& aStr, uint32_t aFlags,
+ nsACString& aResult);
+
+// Given a message db and a set of keys, fetch the corresponding message
+// headers.
+NS_MSG_BASE nsresult
+MsgGetHeadersFromKeys(nsIMsgDatabase* aDB, const nsTArray<nsMsgKey>& aKeys,
+ nsTArray<RefPtr<nsIMsgDBHdr>>& aHeaders);
+
+NS_MSG_BASE nsresult MsgExamineForProxyAsync(nsIChannel* channel,
+ nsIProtocolProxyCallback* listener,
+ nsICancelable** result);
+
+NS_MSG_BASE int32_t MsgFindCharInSet(const nsCString& aString,
+ const char* aChars, uint32_t aOffset = 0);
+NS_MSG_BASE int32_t MsgFindCharInSet(const nsString& aString,
+ const char16_t* aChars,
+ uint32_t aOffset = 0);
+
+// advances bufferOffset to the beginning of the next line, if we don't
+// get to maxBufferOffset first. Returns false if we didn't get to the
+// next line.
+NS_MSG_BASE bool MsgAdvanceToNextLine(const char* buffer,
+ uint32_t& bufferOffset,
+ uint32_t maxBufferOffset);
+
+/**
+ * Alerts the user that the login to the server failed. Asks whether the
+ * connection should: retry, cancel, or request a new password.
+ *
+ * @param aMsgWindow The message window associated with this action (cannot
+ * be null).
+ * @param aHostname The hostname of the server for which the login failed.
+ * @param aResult The button pressed. 0 for retry, 1 for cancel,
+ * 2 for enter a new password.
+ * @return NS_OK for success, NS_ERROR_* if there was a failure in
+ * creating the dialog.
+ */
+NS_MSG_BASE nsresult MsgPromptLoginFailed(nsIMsgWindow* aMsgWindow,
+ const nsACString& aHostname,
+ const nsACString& aUsername,
+ const nsAString& aAccountname,
+ int32_t* aResult);
+
+/**
+ * Calculate a PRTime value used to determine if a date is XX
+ * days ago. This is used by various retention setting algorithms.
+ */
+NS_MSG_BASE PRTime MsgConvertAgeInDaysToCutoffDate(int32_t ageInDays);
+
+/**
+ * Converts the passed in term list to its string representation.
+ *
+ * @param aTermList Array of nsIMsgSearchTerms
+ * @param[out] aOutString result representation of search terms.
+ *
+ */
+NS_MSG_BASE nsresult MsgTermListToString(
+ nsTArray<RefPtr<nsIMsgSearchTerm>> const& aTermList, nsCString& aOutString);
+
+NS_MSG_BASE nsresult MsgStreamMsgHeaders(nsIInputStream* aInputStream,
+ nsIStreamListener* aConsumer);
+
+/**
+ * convert string to uint64_t
+ *
+ * @param str converted string
+ * @returns uint64_t value for success, 0 for parse failure
+ */
+NS_MSG_BASE uint64_t ParseUint64Str(const char* str);
+
+/**
+ * Detect charset of file
+ *
+ * @param aFile The target of nsIFile
+ * @param[out] aCharset The charset string
+ */
+NS_MSG_BASE nsresult MsgDetectCharsetFromFile(nsIFile* aFile,
+ nsACString& aCharset);
+
+/*
+ * Converts a buffer to plain text. Some conversions may
+ * or may not work with certain end charsets which is why we
+ * need that as an argument to the function. If charset is
+ * unknown or deemed of no importance NULL could be passed.
+ * @param[in/out] aConBuf Variable with the text to convert
+ * @param formatFlowed Use format flowed?
+ * @param formatOutput Reformat the output?
+ & @param disallowBreaks Disallow breaks when formatting
+ */
+NS_MSG_BASE nsresult ConvertBufToPlainText(nsString& aConBuf, bool formatFlowed,
+ bool formatOutput,
+ bool disallowBreaks);
+
+#include "nsEscape.h"
+
+/**
+ * Converts a hex string into an integer.
+ * Processes up to aNumChars characters or the first non-hex char.
+ * It is not an error if less than aNumChars valid hex digits are found.
+ */
+NS_MSG_BASE uint64_t MsgUnhex(const char* aHexString, size_t aNumChars);
+
+/**
+ * Checks if a string is a valid hex literal containing at least aNumChars
+ * digits.
+ */
+NS_MSG_BASE bool MsgIsHex(const char* aHexString, size_t aNumChars);
+
+/**
+ * Convert an uint32_t to a nsMsgKey.
+ * Currently they are mostly the same but we need to preserve the notion that
+ * nsMsgKey is an opaque value that can't be treated as a generic integer
+ * (except when storing it into the database). It enables type safety checks and
+ * may prevent coding errors.
+ */
+NS_MSG_BASE nsMsgKey msgKeyFromInt(uint32_t aValue);
+
+NS_MSG_BASE nsMsgKey msgKeyFromInt(uint64_t aValue);
+
+NS_MSG_BASE uint32_t msgKeyToInt(nsMsgKey aMsgKey);
+
+/**
+ * Helper function to extract query part from URL spec.
+ */
+nsCString MsgExtractQueryPart(const nsACString& spec,
+ const char* queryToExtract);
+/**
+ * Helper function to remove query part from URL spec or path.
+ */
+void MsgRemoveQueryPart(nsCString& aSpec);
+
+/**
+ * Helper macro for defining getter/setters. Ported from nsISupportsObsolete.h
+ */
+#define NS_IMPL_GETSET(clazz, attr, type, member) \
+ NS_IMETHODIMP clazz::Get##attr(type* result) { \
+ NS_ENSURE_ARG_POINTER(result); \
+ *result = member; \
+ return NS_OK; \
+ } \
+ NS_IMETHODIMP clazz::Set##attr(type aValue) { \
+ member = aValue; \
+ return NS_OK; \
+ }
+
+/**
+ * Macro and helper function for reporting an error, warning or
+ * informational message to the Error Console
+ *
+ * This will require the inclusion of the following files in the source file
+ * #include "nsIScriptError.h"
+ * #include "nsIConsoleService.h"
+ *
+ */
+
+NS_MSG_BASE
+void MsgLogToConsole4(const nsAString& aErrorText, const nsAString& aFilename,
+ uint32_t aLine, uint32_t flags);
+
+// Macro with filename and line number
+#define MSG_LOG_TO_CONSOLE(_text, _flag) \
+ MsgLogToConsole4(NS_LITERAL_STRING_FROM_CSTRING(_text), \
+ NS_LITERAL_STRING_FROM_CSTRING(__FILE__), __LINE__, _flag)
+#define MSG_LOG_ERR_TO_CONSOLE(_text) \
+ MSG_LOG_TO_CONSOLE(_text, nsIScriptError::errorFlag)
+#define MSG_LOG_WARN_TO_CONSOLE(_text) \
+ MSG_LOG_TO_CONSOLE(_text, nsIScriptError::warningFlag)
+#define MSG_LOG_INFO_TO_CONSOLE(_text) \
+ MSG_LOG_TO_CONSOLE(_text, nsIScriptError::infoFlag)
+
+// Helper macros to cope with shoddy I/O error reporting (or lack thereof)
+#define MSG_NS_ERROR(_txt) \
+ do { \
+ NS_ERROR(_txt); \
+ MSG_LOG_ERR_TO_CONSOLE(_txt); \
+ } while (0)
+#define MSG_NS_WARNING(_txt) \
+ do { \
+ NS_WARNING(_txt); \
+ MSG_LOG_WARN_TO_CONSOLE(_txt); \
+ } while (0)
+#define MSG_NS_WARN_IF_FALSE(_val, _txt) \
+ do { \
+ if (!(_val)) { \
+ NS_WARNING(_txt); \
+ MSG_LOG_WARN_TO_CONSOLE(_txt); \
+ } \
+ } while (0)
+#define MSG_NS_INFO(_txt) \
+ do { \
+ MSG_LOCAL_INFO_TO_CONSOLE(_txt); \
+ fprintf(stderr, "(info) %s (%s:%d)\n", _txt, __FILE__, __LINE__); \
+ } while (0)
+
+/**
+ * Perform C-style string escaping. E.g. "foo\r\n" => "foo\\r\\n"
+ * This is primarily intended for debuggin purposes.
+ */
+nsCString CEscapeString(nsACString const& s);
+
+/**
+ * Synchronously copy the contents of src to dest, until EOF is encountered
+ * or an error occurs.
+ * The total number of bytes copied is returned in bytesCopied.
+ */
+nsresult SyncCopyStream(nsIInputStream* src, nsIOutputStream* dest,
+ uint64_t& bytesCopied,
+ size_t bufSize = FILE_IO_BUFFER_SIZE);
+
+// Used for "@mozilla.org/network/sync-stream-listener;1".
+already_AddRefed<nsIStreamListener> SyncStreamListenerCreate();
+
+nsresult IsOnSameServer(nsIMsgFolder* folder1, nsIMsgFolder* folder2,
+ bool* sameServer);
+
+#endif
diff --git a/comm/mailnews/base/src/nsMsgWindow.cpp b/comm/mailnews/base/src/nsMsgWindow.cpp
new file mode 100644
index 0000000000..1b0129d8f8
--- /dev/null
+++ b/comm/mailnews/base/src/nsMsgWindow.cpp
@@ -0,0 +1,327 @@
+/* -*- 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 "nsMsgWindow.h"
+#include "nsIURILoader.h"
+#include "nsCURILoader.h"
+#include "nsIDocShell.h"
+#include "nsIDocShellTreeItem.h"
+#include "mozIDOMWindow.h"
+#include "nsTransactionManagerCID.h"
+#include "nsIComponentManager.h"
+#include "nsILoadGroup.h"
+#include "nsIMsgMailNewsUrl.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIWebProgress.h"
+#include "nsIWebProgressListener.h"
+#include "nsPIDOMWindow.h"
+#include "nsIPrompt.h"
+#include "nsICharsetConverterManager.h"
+#include "nsIChannel.h"
+#include "nsIRequestObserver.h"
+#include "netCore.h"
+#include "prmem.h"
+#include "plbase64.h"
+#include "nsMsgI18N.h"
+#include "nsIWebNavigation.h"
+#include "nsContentUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "nsIAuthPrompt.h"
+#include "nsMsgUtils.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/TransactionManager.h"
+#include "mozilla/dom/LoadURIOptionsBinding.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/XULFrameElement.h"
+#include "nsFrameLoader.h"
+
+NS_IMPL_ISUPPORTS(nsMsgWindow, nsIMsgWindow, nsIURIContentListener,
+ nsISupportsWeakReference)
+
+nsMsgWindow::nsMsgWindow() {
+ mCharsetOverride = false;
+ m_stopped = false;
+}
+
+nsMsgWindow::~nsMsgWindow() { CloseWindow(); }
+
+nsresult nsMsgWindow::Init() {
+ // create Undo/Redo Transaction Manager
+ mTransactionManager = new mozilla::TransactionManager();
+ return mTransactionManager->SetMaxTransactionCount(-1);
+}
+
+NS_IMETHODIMP nsMsgWindow::GetMessageWindowDocShell(nsIDocShell** aDocShell) {
+ *aDocShell = nullptr;
+
+ nsCOMPtr<nsIDocShell> docShell(do_QueryReferent(mMessageWindowDocShellWeak));
+ nsCOMPtr<nsIDocShell> rootShell(do_QueryReferent(mRootDocShellWeak));
+ if (rootShell) {
+ // There seem to be some issues with shutdown (see Bug 1610406).
+ // This workaround should prevent the GetElementById() call dying horribly
+ // but really, we shouldn't even get here in such cases.
+ bool doomed;
+ rootShell->IsBeingDestroyed(&doomed);
+ if (doomed) {
+ return NS_ERROR_ILLEGAL_DURING_SHUTDOWN;
+ }
+
+ RefPtr<mozilla::dom::Element> el =
+ rootShell->GetDocument()->GetElementById(u"messagepane"_ns);
+ RefPtr<mozilla::dom::XULFrameElement> frame =
+ mozilla::dom::XULFrameElement::FromNodeOrNull(el);
+ NS_ENSURE_TRUE(frame, NS_ERROR_FAILURE);
+ RefPtr<mozilla::dom::Document> doc = frame->GetContentDocument();
+ NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
+ docShell = doc->GetDocShell();
+ NS_ENSURE_TRUE(docShell, NS_ERROR_FAILURE);
+
+ // we don't own mMessageWindowDocShell so don't try to keep a reference to
+ // it!
+ mMessageWindowDocShellWeak = do_GetWeakReference(docShell);
+ }
+
+ NS_ENSURE_TRUE(docShell, NS_ERROR_FAILURE);
+ docShell.forget(aDocShell);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgWindow::CloseWindow() {
+ mStatusFeedback = nullptr;
+
+ StopUrls();
+
+ nsCOMPtr<nsIDocShell> messagePaneDocShell(
+ do_QueryReferent(mMessageWindowDocShellWeak));
+ if (messagePaneDocShell) {
+ nsCOMPtr<nsIURIContentListener> listener(
+ do_GetInterface(messagePaneDocShell));
+ if (listener) listener->SetParentContentListener(nullptr);
+ SetRootDocShell(nullptr);
+ mMessageWindowDocShellWeak = nullptr;
+ }
+
+ // in case nsMsgWindow leaks, make sure other stuff doesn't leak.
+ mTransactionManager = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgWindow::GetStatusFeedback(
+ nsIMsgStatusFeedback** aStatusFeedback) {
+ NS_ENSURE_ARG_POINTER(aStatusFeedback);
+ NS_IF_ADDREF(*aStatusFeedback = mStatusFeedback);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgWindow::SetStatusFeedback(
+ nsIMsgStatusFeedback* aStatusFeedback) {
+ mStatusFeedback = aStatusFeedback;
+ nsCOMPtr<nsIDocShell> messageWindowDocShell;
+ GetMessageWindowDocShell(getter_AddRefs(messageWindowDocShell));
+
+ // register our status feedback object as a web progress listener
+ nsCOMPtr<nsIWebProgress> webProgress(do_GetInterface(messageWindowDocShell));
+ if (webProgress && mStatusFeedback && messageWindowDocShell) {
+ nsCOMPtr<nsIWebProgressListener> webProgressListener =
+ do_QueryInterface(mStatusFeedback);
+ webProgress->AddProgressListener(webProgressListener,
+ nsIWebProgress::NOTIFY_ALL);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgWindow::GetTransactionManager(
+ nsITransactionManager** aTransactionManager) {
+ NS_ENSURE_ARG_POINTER(aTransactionManager);
+ NS_IF_ADDREF(*aTransactionManager = mTransactionManager);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgWindow::SetTransactionManager(
+ nsITransactionManager* aTransactionManager) {
+ mTransactionManager = aTransactionManager;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgWindow::GetOpenFolder(nsIMsgFolder** aOpenFolder) {
+ NS_ENSURE_ARG_POINTER(aOpenFolder);
+ NS_IF_ADDREF(*aOpenFolder = mOpenFolder);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgWindow::SetOpenFolder(nsIMsgFolder* aOpenFolder) {
+ mOpenFolder = aOpenFolder;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgWindow::GetRootDocShell(nsIDocShell** aDocShell) {
+ if (mRootDocShellWeak)
+ CallQueryReferent(mRootDocShellWeak.get(), aDocShell);
+ else
+ *aDocShell = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgWindow::SetRootDocShell(nsIDocShell* aDocShell) {
+ // Query for the doc shell and release it
+ mRootDocShellWeak = nullptr;
+ if (aDocShell) {
+ mRootDocShellWeak = do_GetWeakReference(aDocShell);
+
+ nsCOMPtr<nsIDocShell> messagePaneDocShell;
+ GetMessageWindowDocShell(getter_AddRefs(messagePaneDocShell));
+ nsCOMPtr<nsIURIContentListener> listener(
+ do_GetInterface(messagePaneDocShell));
+ if (listener) listener->SetParentContentListener(this);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgWindow::GetDomWindow(mozIDOMWindowProxy** aWindow) {
+ NS_ENSURE_ARG_POINTER(aWindow);
+ if (mDomWindow)
+ CallQueryReferent(mDomWindow.get(), aWindow);
+ else
+ *aWindow = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgWindow::SetDomWindow(mozIDOMWindowProxy* aWindow) {
+ NS_ENSURE_ARG_POINTER(aWindow);
+ mDomWindow = do_GetWeakReference(aWindow);
+
+ nsCOMPtr<nsPIDOMWindowOuter> win = nsPIDOMWindowOuter::From(aWindow);
+ nsIDocShell* docShell = nullptr;
+ if (win) docShell = win->GetDocShell();
+
+ nsCOMPtr<nsIDocShellTreeItem> docShellAsItem(docShell);
+
+ if (docShellAsItem) {
+ nsCOMPtr<nsIDocShellTreeItem> rootAsItem;
+ docShellAsItem->GetInProcessSameTypeRootTreeItem(
+ getter_AddRefs(rootAsItem));
+
+ nsCOMPtr<nsIDocShell> rootAsShell(do_QueryInterface(rootAsItem));
+ SetRootDocShell(rootAsShell);
+
+ // force ourselves to figure out the message pane
+ nsCOMPtr<nsIDocShell> messageWindowDocShell;
+ GetMessageWindowDocShell(getter_AddRefs(messageWindowDocShell));
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgWindow::SetNotificationCallbacks(
+ nsIInterfaceRequestor* aNotificationCallbacks) {
+ mNotificationCallbacks = aNotificationCallbacks;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgWindow::GetNotificationCallbacks(
+ nsIInterfaceRequestor** aNotificationCallbacks) {
+ NS_ENSURE_ARG_POINTER(aNotificationCallbacks);
+ NS_IF_ADDREF(*aNotificationCallbacks = mNotificationCallbacks);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgWindow::StopUrls() {
+ m_stopped = true;
+ nsCOMPtr<nsIWebNavigation> webnav(do_QueryReferent(mRootDocShellWeak));
+ return webnav ? webnav->Stop(nsIWebNavigation::STOP_NETWORK)
+ : NS_ERROR_FAILURE;
+}
+
+// nsIURIContentListener support
+
+NS_IMETHODIMP nsMsgWindow::DoContent(const nsACString& aContentType,
+ bool aIsContentPreferred,
+ nsIRequest* request,
+ nsIStreamListener** aContentHandler,
+ bool* aAbortProcess) {
+ if (!aContentType.IsEmpty()) {
+ // forward the DoContent call to our docshell
+ nsCOMPtr<nsIDocShell> messageWindowDocShell;
+ GetMessageWindowDocShell(getter_AddRefs(messageWindowDocShell));
+ nsCOMPtr<nsIURIContentListener> ctnListener =
+ do_QueryInterface(messageWindowDocShell);
+ if (ctnListener) {
+ nsCOMPtr<nsIChannel> aChannel = do_QueryInterface(request);
+ if (!aChannel) return NS_ERROR_FAILURE;
+
+ // get the url for the channel...let's hope it is a mailnews url so we can
+ // set our msg hdr sink on it.. right now, this is the only way I can
+ // think of to force the msg hdr sink into the mime converter so it can
+ // get too it later...
+ nsCOMPtr<nsIURI> uri;
+ aChannel->GetURI(getter_AddRefs(uri));
+ if (uri) {
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl(do_QueryInterface(uri));
+ if (mailnewsUrl) mailnewsUrl->SetMsgWindow(this);
+ }
+ return ctnListener->DoContent(aContentType, aIsContentPreferred, request,
+ aContentHandler, aAbortProcess);
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgWindow::IsPreferred(const char* aContentType, char** aDesiredContentType,
+ bool* aCanHandleContent) {
+ // We don't want to handle opening any attachments inside the
+ // message pane, but want to let nsIExternalHelperAppService take care.
+ *aCanHandleContent = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgWindow::CanHandleContent(const char* aContentType,
+ bool aIsContentPreferred,
+ char** aDesiredContentType,
+ bool* aCanHandleContent)
+
+{
+ // the mail window knows nothing about the default content types
+ // its docshell can handle...ask the content area if it can handle
+ // the content type...
+
+ nsCOMPtr<nsIDocShell> messageWindowDocShell;
+ GetMessageWindowDocShell(getter_AddRefs(messageWindowDocShell));
+ nsCOMPtr<nsIURIContentListener> ctnListener(
+ do_GetInterface(messageWindowDocShell));
+ if (ctnListener)
+ return ctnListener->CanHandleContent(aContentType, aIsContentPreferred,
+ aDesiredContentType,
+ aCanHandleContent);
+ else
+ *aCanHandleContent = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgWindow::GetParentContentListener(
+ nsIURIContentListener** aParent) {
+ NS_ENSURE_ARG_POINTER(aParent);
+ *aParent = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgWindow::SetParentContentListener(
+ nsIURIContentListener* aParent) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgWindow::GetLoadCookie(nsISupports** aLoadCookie) {
+ NS_ENSURE_ARG_POINTER(aLoadCookie);
+ *aLoadCookie = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgWindow::SetLoadCookie(nsISupports* aLoadCookie) {
+ return NS_OK;
+}
+
+NS_IMPL_GETSET(nsMsgWindow, Stopped, bool, m_stopped)
diff --git a/comm/mailnews/base/src/nsMsgWindow.h b/comm/mailnews/base/src/nsMsgWindow.h
new file mode 100644
index 0000000000..1ca2370552
--- /dev/null
+++ b/comm/mailnews/base/src/nsMsgWindow.h
@@ -0,0 +1,52 @@
+/* -*- 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 _nsMsgWindow_h
+#define _nsMsgWindow_h
+
+#include "nsIMsgWindow.h"
+#include "nsIMsgStatusFeedback.h"
+#include "nsITransactionManager.h"
+#include "nsIMsgFolder.h"
+#include "nsCOMPtr.h"
+#include "nsIDocShell.h"
+#include "nsIURIContentListener.h"
+#include "nsWeakReference.h"
+#include "nsIWeakReferenceUtils.h"
+#include "nsIInterfaceRequestor.h"
+
+class nsMsgWindow : public nsIMsgWindow,
+ public nsIURIContentListener,
+ public nsSupportsWeakReference {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ nsMsgWindow();
+ nsresult Init();
+ NS_DECL_NSIMSGWINDOW
+ NS_DECL_NSIURICONTENTLISTENER
+
+ protected:
+ virtual ~nsMsgWindow();
+ nsCOMPtr<nsIMsgStatusFeedback> mStatusFeedback;
+ nsCOMPtr<nsITransactionManager> mTransactionManager;
+ nsCOMPtr<nsIMsgFolder> mOpenFolder;
+ // These are used by the backend protocol code to attach
+ // notification callbacks to channels, e.g., nsIBadCertListner2.
+ nsCOMPtr<nsIInterfaceRequestor> mNotificationCallbacks;
+ // authorization prompt used during testing only
+ nsCOMPtr<nsIAuthPrompt> mAuthPrompt;
+
+ // let's not make this a strong ref - we don't own it.
+ nsWeakPtr mRootDocShellWeak;
+ nsWeakPtr mMessageWindowDocShellWeak;
+ nsWeakPtr mDomWindow;
+
+ nsCString mMailCharacterSet;
+ bool mCharsetOverride;
+ bool m_stopped;
+};
+
+#endif
diff --git a/comm/mailnews/base/src/nsMsgXFViewThread.cpp b/comm/mailnews/base/src/nsMsgXFViewThread.cpp
new file mode 100644
index 0000000000..0180a7c910
--- /dev/null
+++ b/comm/mailnews/base/src/nsMsgXFViewThread.cpp
@@ -0,0 +1,444 @@
+/* -*- 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 "msgCore.h"
+#include "nsMsgXFViewThread.h"
+#include "nsMsgSearchDBView.h"
+#include "nsMsgMessageFlags.h"
+
+NS_IMPL_ISUPPORTS(nsMsgXFViewThread, nsIMsgThread)
+
+nsMsgXFViewThread::nsMsgXFViewThread(nsMsgSearchDBView* view,
+ nsMsgKey threadId) {
+ m_numUnreadChildren = 0;
+ m_numChildren = 0;
+ m_flags = 0;
+ m_newestMsgDate = 0;
+ m_view = view;
+ m_threadId = threadId;
+}
+
+nsMsgXFViewThread::~nsMsgXFViewThread() {}
+
+NS_IMETHODIMP
+nsMsgXFViewThread::SetThreadKey(nsMsgKey threadKey) {
+ m_threadId = threadKey;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgXFViewThread::GetThreadKey(nsMsgKey* aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = m_threadId;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgXFViewThread::GetFlags(uint32_t* aFlags) {
+ NS_ENSURE_ARG_POINTER(aFlags);
+ *aFlags = m_flags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgXFViewThread::SetFlags(uint32_t aFlags) {
+ m_flags = aFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgXFViewThread::SetSubject(const nsACString& aSubject) {
+ NS_ASSERTION(false, "shouldn't call this");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsMsgXFViewThread::GetSubject(nsACString& result) {
+ NS_ASSERTION(false, "shouldn't call this");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsMsgXFViewThread::GetNumChildren(uint32_t* aNumChildren) {
+ NS_ENSURE_ARG_POINTER(aNumChildren);
+ *aNumChildren = m_keys.Length();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgXFViewThread::GetNumUnreadChildren(uint32_t* aNumUnreadChildren) {
+ NS_ENSURE_ARG_POINTER(aNumUnreadChildren);
+ *aNumUnreadChildren = m_numUnreadChildren;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgXFViewThread::AddChild(nsIMsgDBHdr* aNewHdr, nsIMsgDBHdr* aInReplyTo,
+ bool aThreadInThread,
+ nsIDBChangeAnnouncer* aAnnouncer) {
+ uint32_t whereInserted;
+ return AddHdr(aNewHdr, false, whereInserted, nullptr);
+}
+
+// Returns the parent of the newly added header. If reparentChildren
+// is true, we believe that the new header is a parent of an existing
+// header, and we should find it, and reparent it.
+nsresult nsMsgXFViewThread::AddHdr(nsIMsgDBHdr* newHdr, bool reparentChildren,
+ uint32_t& whereInserted,
+ nsIMsgDBHdr** outParent) {
+ nsCOMPtr<nsIMsgFolder> newHdrFolder;
+ newHdr->GetFolder(getter_AddRefs(newHdrFolder));
+
+ uint32_t newHdrFlags = 0;
+ uint32_t msgDate;
+ nsMsgKey newHdrKey = 0;
+
+ newHdr->GetMessageKey(&newHdrKey);
+ newHdr->GetDateInSeconds(&msgDate);
+ newHdr->GetFlags(&newHdrFlags);
+ if (msgDate > m_newestMsgDate) SetNewestMsgDate(msgDate);
+
+ if (newHdrFlags & nsMsgMessageFlags::Watched)
+ SetFlags(m_flags | nsMsgMessageFlags::Watched);
+
+ ChangeChildCount(1);
+ if (!(newHdrFlags & nsMsgMessageFlags::Read)) ChangeUnreadChildCount(1);
+
+ if (m_numChildren == 1) {
+ m_keys.InsertElementAt(0, newHdrKey);
+ m_levels.InsertElementAt(0, 0);
+ m_folders.InsertObjectAt(newHdrFolder, 0);
+ if (outParent) *outParent = nullptr;
+
+ whereInserted = 0;
+ return NS_OK;
+ }
+
+ // Find our parent, if any, in the thread. Starting at the newest
+ // reference, and working our way back, see if we've mapped that reference
+ // to this thread.
+ uint16_t numReferences;
+ newHdr->GetNumReferences(&numReferences);
+ nsCOMPtr<nsIMsgDBHdr> parent;
+ int32_t parentIndex = -1;
+
+ for (int32_t i = numReferences - 1; i >= 0; i--) {
+ nsAutoCString reference;
+ newHdr->GetStringReference(i, reference);
+ if (reference.IsEmpty()) break;
+
+ // I could look for the thread from the reference, but getting
+ // the header directly should be fine. If it's not, that means
+ // that the parent isn't in this thread, though it should be.
+ m_view->GetMsgHdrFromHash(reference, getter_AddRefs(parent));
+ if (parent) {
+ parentIndex = HdrIndex(parent);
+ if (parentIndex == -1) {
+ NS_ERROR("how did we get in the wrong thread?");
+ parent = nullptr;
+ }
+
+ break;
+ }
+ }
+
+ if (parent) {
+ uint32_t parentLevel = m_levels[parentIndex];
+ nsMsgKey parentKey;
+ parent->GetMessageKey(&parentKey);
+ nsCOMPtr<nsIMsgFolder> parentFolder;
+ parent->GetFolder(getter_AddRefs(parentFolder));
+
+ if (outParent) parent.forget(outParent);
+
+ // Iterate over our parents' children until we find one we're older than,
+ // and insert ourselves before it, or as the last child. In other words,
+ // insert, sorted by date.
+ uint32_t msgDate, childDate;
+ newHdr->GetDateInSeconds(&msgDate);
+ nsCOMPtr<nsIMsgDBHdr> child;
+ nsMsgViewIndex i;
+ nsMsgViewIndex insertIndex = m_keys.Length();
+ uint32_t insertLevel = parentLevel + 1;
+ for (i = parentIndex;
+ i < m_keys.Length() &&
+ (i == (nsMsgViewIndex)parentIndex || m_levels[i] >= parentLevel);
+ i++) {
+ GetChildHdrAt(i, getter_AddRefs(child));
+ if (child) {
+ if (reparentChildren && IsHdrParentOf(newHdr, child)) {
+ insertIndex = i;
+ // Bump all the children of the current child, and the child.
+ nsMsgViewIndex j = insertIndex;
+ uint8_t childLevel = m_levels[insertIndex];
+ do {
+ m_levels[j] = m_levels[j] + 1;
+ j++;
+ } while (j < m_keys.Length() && m_levels[j] > childLevel);
+ break;
+ } else if (m_levels[i] == parentLevel + 1) {
+ // Possible sibling.
+ child->GetDateInSeconds(&childDate);
+ if (msgDate < childDate) {
+ // If we think we need to reparent, remember this insert index,
+ // but keep looking for children.
+ insertIndex = i;
+ insertLevel = m_levels[i];
+ // If the sibling we're inserting after has children, we need
+ // to go after the children.
+ while (insertIndex + 1 < m_keys.Length() &&
+ m_levels[insertIndex + 1] > insertLevel) {
+ insertIndex++;
+ }
+
+ if (!reparentChildren) break;
+ }
+ }
+ }
+ }
+
+ m_keys.InsertElementAt(insertIndex, newHdrKey);
+ m_levels.InsertElementAt(insertIndex, insertLevel);
+ m_folders.InsertObjectAt(newHdrFolder, insertIndex);
+ whereInserted = insertIndex;
+ } else {
+ if (outParent) *outParent = nullptr;
+
+ nsCOMPtr<nsIMsgDBHdr> rootHdr;
+ GetChildHdrAt(0, getter_AddRefs(rootHdr));
+ // If the new header is a parent of the root then it should be promoted.
+ if (rootHdr && IsHdrParentOf(newHdr, rootHdr)) {
+ m_keys.InsertElementAt(0, newHdrKey);
+ m_levels.InsertElementAt(0, 0);
+ m_folders.InsertObjectAt(newHdrFolder, 0);
+ whereInserted = 0;
+ // Adjust level of old root hdr and its children
+ for (nsMsgViewIndex i = 1; i < m_keys.Length(); i++)
+ m_levels[i] = m_levels[1] + 1;
+ } else {
+ m_keys.AppendElement(newHdrKey);
+ m_levels.AppendElement(1);
+ m_folders.AppendObject(newHdrFolder);
+ if (outParent) rootHdr.forget(outParent);
+
+ whereInserted = m_keys.Length() - 1;
+ }
+ }
+
+ // ### TODO handle the case where the root header starts
+ // with Re, and the new one doesn't, and is earlier. In that
+ // case, we want to promote the new header to root.
+ // PRTime newHdrDate;
+ // newHdr->GetDate(&newHdrDate);
+ // if (numChildren > 0 && !(newHdrFlags & nsMsgMessageFlags::HasRe)) {
+ // PRTime topLevelHdrDate;
+ // nsCOMPtr<nsIMsgDBHdr> topLevelHdr;
+ // rv = GetRootHdr(getter_AddRefs(topLevelHdr));
+ // if (NS_SUCCEEDED(rv) && topLevelHdr) {
+ // topLevelHdr->GetDate(&topLevelHdrDate);
+ // if (newHdrDate < topLevelHdrDate) ?? and now ??
+ // }
+ // }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgXFViewThread::GetChildHdrAt(uint32_t aIndex, nsIMsgDBHdr** aResult) {
+ if (aIndex >= m_keys.Length()) return NS_MSG_MESSAGE_NOT_FOUND;
+
+ nsCOMPtr<nsIMsgDatabase> db;
+ nsresult rv = m_folders[aIndex]->GetMsgDatabase(getter_AddRefs(db));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return db->GetMsgHdrForKey(m_keys[aIndex], aResult);
+}
+
+NS_IMETHODIMP
+nsMsgXFViewThread::RemoveChildAt(uint32_t aIndex) {
+ m_keys.RemoveElementAt(aIndex);
+ m_levels.RemoveElementAt(aIndex);
+ m_folders.RemoveObjectAt(aIndex);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgXFViewThread::RemoveChildHdr(nsIMsgDBHdr* child,
+ nsIDBChangeAnnouncer* announcer) {
+ NS_ENSURE_ARG_POINTER(child);
+ nsMsgKey msgKey;
+ uint32_t msgFlags;
+ child->GetMessageKey(&msgKey);
+ child->GetFlags(&msgFlags);
+ nsCOMPtr<nsIMsgFolder> msgFolder;
+ child->GetFolder(getter_AddRefs(msgFolder));
+ // If this was the newest msg, clear the newest msg date so we'll recalc.
+ uint32_t date;
+ child->GetDateInSeconds(&date);
+ if (date == m_newestMsgDate) SetNewestMsgDate(0);
+
+ for (uint32_t childIndex = 0; childIndex < m_keys.Length(); childIndex++) {
+ if (m_keys[childIndex] == msgKey && m_folders[childIndex] == msgFolder) {
+ uint8_t levelRemoved = m_keys[childIndex];
+ // Adjust the levels of all the children of this header.
+ nsMsgViewIndex i;
+ for (i = childIndex + 1;
+ i < m_keys.Length() && m_levels[i] > levelRemoved; i++) {
+ m_levels[i] = m_levels[i] - 1;
+ }
+
+ m_view->NoteChange(childIndex + 1, i - childIndex + 1,
+ nsMsgViewNotificationCode::changed);
+ m_keys.RemoveElementAt(childIndex);
+ m_levels.RemoveElementAt(childIndex);
+ m_folders.RemoveObjectAt(childIndex);
+ if (!(msgFlags & nsMsgMessageFlags::Read)) ChangeUnreadChildCount(-1);
+
+ ChangeChildCount(-1);
+ return NS_OK;
+ }
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsMsgXFViewThread::GetRootHdr(nsIMsgDBHdr** aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+ return GetChildHdrAt(0, aResult);
+}
+
+NS_IMETHODIMP
+nsMsgXFViewThread::GetChildKeyAt(uint32_t aIndex, nsMsgKey* aResult) {
+ NS_ASSERTION(false, "shouldn't call this");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsMsgXFViewThread::GetChild(nsMsgKey msgKey, nsIMsgDBHdr** aResult) {
+ NS_ASSERTION(false, "shouldn't call this");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+int32_t nsMsgXFViewThread::HdrIndex(nsIMsgDBHdr* hdr) {
+ nsMsgKey msgKey;
+ nsCOMPtr<nsIMsgFolder> folder;
+ hdr->GetMessageKey(&msgKey);
+ hdr->GetFolder(getter_AddRefs(folder));
+ for (uint32_t i = 0; i < m_keys.Length(); i++) {
+ if (m_keys[i] == msgKey && m_folders[i] == folder) return i;
+ }
+
+ return -1;
+}
+
+void nsMsgXFViewThread::ChangeUnreadChildCount(int32_t delta) {
+ m_numUnreadChildren += delta;
+}
+
+void nsMsgXFViewThread::ChangeChildCount(int32_t delta) {
+ m_numChildren += delta;
+}
+
+bool nsMsgXFViewThread::IsHdrParentOf(nsIMsgDBHdr* possibleParent,
+ nsIMsgDBHdr* possibleChild) {
+ uint16_t referenceToCheck = 0;
+ possibleChild->GetNumReferences(&referenceToCheck);
+ nsAutoCString reference;
+
+ nsCString messageId;
+ possibleParent->GetMessageId(getter_Copies(messageId));
+
+ while (referenceToCheck > 0) {
+ possibleChild->GetStringReference(referenceToCheck - 1, reference);
+
+ if (reference.Equals(messageId)) return true;
+
+ // If reference didn't match, check if this ref is for a non-existent
+ // header. If it is, continue looking at ancestors.
+ nsCOMPtr<nsIMsgDBHdr> refHdr;
+ m_view->GetMsgHdrFromHash(reference, getter_AddRefs(refHdr));
+ if (refHdr) break;
+
+ referenceToCheck--;
+ }
+
+ return false;
+}
+
+NS_IMETHODIMP
+nsMsgXFViewThread::GetNewestMsgDate(uint32_t* aResult) {
+ // If this hasn't been set, figure it out by enumerating the msgs in the
+ // thread.
+ if (!m_newestMsgDate) {
+ uint32_t numChildren;
+ nsresult rv = NS_OK;
+
+ GetNumChildren(&numChildren);
+
+ if ((int32_t)numChildren < 0) numChildren = 0;
+
+ for (uint32_t childIndex = 0; childIndex < numChildren; childIndex++) {
+ nsCOMPtr<nsIMsgDBHdr> child;
+ rv = GetChildHdrAt(childIndex, getter_AddRefs(child));
+ if (NS_SUCCEEDED(rv) && child) {
+ uint32_t msgDate;
+ child->GetDateInSeconds(&msgDate);
+ if (msgDate > m_newestMsgDate) m_newestMsgDate = msgDate;
+ }
+ }
+ }
+
+ *aResult = m_newestMsgDate;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgXFViewThread::SetNewestMsgDate(uint32_t aNewestMsgDate) {
+ m_newestMsgDate = aNewestMsgDate;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgXFViewThread::MarkChildRead(bool aRead) {
+ ChangeUnreadChildCount(aRead ? -1 : 1);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgXFViewThread::GetFirstUnreadChild(nsIMsgDBHdr** aResult) {
+ NS_ENSURE_ARG(aResult);
+ uint32_t numChildren;
+ GetNumChildren(&numChildren);
+
+ if ((int32_t)numChildren < 0) numChildren = 0;
+
+ for (uint32_t childIndex = 0; childIndex < numChildren; childIndex++) {
+ nsCOMPtr<nsIMsgDBHdr> child;
+ nsresult rv = GetChildHdrAt(childIndex, getter_AddRefs(child));
+ if (NS_SUCCEEDED(rv) && child) {
+ nsMsgKey msgKey;
+ child->GetMessageKey(&msgKey);
+
+ bool isRead;
+ nsCOMPtr<nsIMsgDatabase> db;
+ nsresult rv = m_folders[childIndex]->GetMsgDatabase(getter_AddRefs(db));
+ if (NS_SUCCEEDED(rv)) rv = db->IsRead(msgKey, &isRead);
+
+ if (NS_SUCCEEDED(rv) && !isRead) {
+ child.forget(aResult);
+ break;
+ }
+ }
+ }
+
+ return (*aResult) ? NS_OK : NS_ERROR_NULL_POINTER;
+}
+
+NS_IMETHODIMP
+nsMsgXFViewThread::EnumerateMessages(nsMsgKey aParentKey,
+ nsIMsgEnumerator** aResult) {
+ NS_ERROR("shouldn't call this");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
diff --git a/comm/mailnews/base/src/nsMsgXFViewThread.h b/comm/mailnews/base/src/nsMsgXFViewThread.h
new file mode 100644
index 0000000000..ff3276bf3f
--- /dev/null
+++ b/comm/mailnews/base/src/nsMsgXFViewThread.h
@@ -0,0 +1,51 @@
+/* -*- 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 nsMsgXFViewThread_h__
+#define nsMsgXFViewThread_h__
+
+#include "msgCore.h"
+#include "nsCOMArray.h"
+#include "nsIMsgThread.h"
+#include "MailNewsTypes.h"
+#include "nsTArray.h"
+#include "nsIMsgDatabase.h"
+#include "nsIMsgHdr.h"
+#include "nsMsgDBView.h"
+
+class nsMsgSearchDBView;
+
+class nsMsgXFViewThread : public nsIMsgThread {
+ public:
+ nsMsgXFViewThread(nsMsgSearchDBView* view, nsMsgKey threadId);
+
+ NS_DECL_NSIMSGTHREAD
+ NS_DECL_ISUPPORTS
+
+ bool IsHdrParentOf(nsIMsgDBHdr* possibleParent, nsIMsgDBHdr* possibleChild);
+
+ void ChangeUnreadChildCount(int32_t delta);
+ void ChangeChildCount(int32_t delta);
+
+ nsresult AddHdr(nsIMsgDBHdr* newHdr, bool reparentChildren,
+ uint32_t& whereInserted, nsIMsgDBHdr** outParent);
+ int32_t HdrIndex(nsIMsgDBHdr* hdr);
+ uint32_t ChildLevelAt(uint32_t msgIndex) { return m_levels[msgIndex]; }
+ uint32_t MsgCount() { return m_numChildren; };
+
+ protected:
+ virtual ~nsMsgXFViewThread();
+
+ nsMsgSearchDBView* m_view;
+ uint32_t m_numUnreadChildren;
+ uint32_t m_numChildren;
+ uint32_t m_flags;
+ uint32_t m_newestMsgDate;
+ nsMsgKey m_threadId;
+ nsTArray<nsMsgKey> m_keys;
+ nsCOMArray<nsIMsgFolder> m_folders;
+ nsTArray<uint8_t> m_levels;
+};
+
+#endif
diff --git a/comm/mailnews/base/src/nsMsgXFVirtualFolderDBView.cpp b/comm/mailnews/base/src/nsMsgXFVirtualFolderDBView.cpp
new file mode 100644
index 0000000000..fc2a1886eb
--- /dev/null
+++ b/comm/mailnews/base/src/nsMsgXFVirtualFolderDBView.cpp
@@ -0,0 +1,514 @@
+/* -*- 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 "msgCore.h"
+#include "nsMsgXFVirtualFolderDBView.h"
+#include "nsIMsgHdr.h"
+#include "nsIMsgThread.h"
+#include "nsQuickSort.h"
+#include "nsIDBFolderInfo.h"
+#include "nsIMsgCopyService.h"
+#include "nsMsgUtils.h"
+#include "nsIMsgSearchSession.h"
+#include "nsIMsgSearchTerm.h"
+#include "nsMsgMessageFlags.h"
+#include "nsServiceManagerUtils.h"
+
+nsMsgXFVirtualFolderDBView::nsMsgXFVirtualFolderDBView() {
+ mSuppressMsgDisplay = false;
+ m_doingSearch = false;
+ m_doingQuickSearch = false;
+ m_totalMessagesInView = 0;
+ m_curFolderHasCachedHits = false;
+}
+
+nsMsgXFVirtualFolderDBView::~nsMsgXFVirtualFolderDBView() {}
+
+NS_IMETHODIMP
+nsMsgXFVirtualFolderDBView::Open(nsIMsgFolder* folder,
+ nsMsgViewSortTypeValue sortType,
+ nsMsgViewSortOrderValue sortOrder,
+ nsMsgViewFlagsTypeValue viewFlags,
+ int32_t* pCount) {
+ m_viewFolder = folder;
+ return nsMsgSearchDBView::Open(folder, sortType, sortOrder, viewFlags,
+ pCount);
+}
+
+void nsMsgXFVirtualFolderDBView::RemovePendingDBListeners() {
+ nsresult rv;
+ nsCOMPtr<nsIMsgDBService> msgDBService =
+ do_GetService("@mozilla.org/msgDatabase/msgDBService;1", &rv);
+
+ // UnregisterPendingListener will return an error when there are no more
+ // instances of this object registered as pending listeners.
+ while (NS_SUCCEEDED(rv)) rv = msgDBService->UnregisterPendingListener(this);
+}
+
+NS_IMETHODIMP
+nsMsgXFVirtualFolderDBView::Close() {
+ RemovePendingDBListeners();
+ return nsMsgSearchDBView::Close();
+}
+
+NS_IMETHODIMP
+nsMsgXFVirtualFolderDBView::CloneDBView(nsIMessenger* aMessengerInstance,
+ nsIMsgWindow* aMsgWindow,
+ nsIMsgDBViewCommandUpdater* aCmdUpdater,
+ nsIMsgDBView** _retval) {
+ nsMsgXFVirtualFolderDBView* newMsgDBView = new nsMsgXFVirtualFolderDBView();
+ nsresult rv =
+ CopyDBView(newMsgDBView, aMessengerInstance, aMsgWindow, aCmdUpdater);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_IF_ADDREF(*_retval = newMsgDBView);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgXFVirtualFolderDBView::CopyDBView(
+ nsMsgDBView* aNewMsgDBView, nsIMessenger* aMessengerInstance,
+ nsIMsgWindow* aMsgWindow, nsIMsgDBViewCommandUpdater* aCmdUpdater) {
+ nsMsgSearchDBView::CopyDBView(aNewMsgDBView, aMessengerInstance, aMsgWindow,
+ aCmdUpdater);
+
+ nsMsgXFVirtualFolderDBView* newMsgDBView =
+ (nsMsgXFVirtualFolderDBView*)aNewMsgDBView;
+
+ newMsgDBView->m_viewFolder = m_viewFolder;
+ newMsgDBView->m_searchSession = m_searchSession;
+
+ int32_t scopeCount;
+ nsresult rv;
+ nsCOMPtr<nsIMsgSearchSession> searchSession =
+ do_QueryReferent(m_searchSession, &rv);
+ // It's OK not to have a search session.
+ NS_ENSURE_SUCCESS(rv, NS_OK);
+ nsCOMPtr<nsIMsgDBService> msgDBService =
+ do_GetService("@mozilla.org/msgDatabase/msgDBService;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ searchSession->CountSearchScopes(&scopeCount);
+ for (int32_t i = 0; i < scopeCount; i++) {
+ nsMsgSearchScopeValue scopeId;
+ nsCOMPtr<nsIMsgFolder> searchFolder;
+ searchSession->GetNthSearchScope(i, &scopeId, getter_AddRefs(searchFolder));
+ if (searchFolder)
+ msgDBService->RegisterPendingListener(searchFolder, newMsgDBView);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgXFVirtualFolderDBView::GetViewType(nsMsgViewTypeValue* aViewType) {
+ NS_ENSURE_ARG_POINTER(aViewType);
+ *aViewType = nsMsgViewType::eShowVirtualFolderResults;
+ return NS_OK;
+}
+
+nsresult nsMsgXFVirtualFolderDBView::OnNewHeader(nsIMsgDBHdr* newHdr,
+ nsMsgKey aParentKey,
+ bool /*ensureListed*/) {
+ if (newHdr) {
+ bool match = false;
+ nsCOMPtr<nsIMsgSearchSession> searchSession =
+ do_QueryReferent(m_searchSession);
+
+ if (searchSession) searchSession->MatchHdr(newHdr, m_db, &match);
+
+ if (!match) match = WasHdrRecentlyDeleted(newHdr);
+
+ if (match) {
+ nsCOMPtr<nsIMsgFolder> folder;
+ newHdr->GetFolder(getter_AddRefs(folder));
+ bool saveDoingSearch = m_doingSearch;
+ m_doingSearch = false;
+ OnSearchHit(newHdr, folder);
+ m_doingSearch = saveDoingSearch;
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgXFVirtualFolderDBView::OnHdrPropertyChanged(
+ nsIMsgDBHdr* aHdrChanged, const nsACString& property, bool aPreChange,
+ uint32_t* aStatus, nsIDBChangeListener* aInstigator) {
+ // If the junk mail plugin just activated on a message, then
+ // we'll allow filters to remove from view.
+ // Otherwise, just update the view line.
+ //
+ // Note this will not add newly matched headers to the view. This is
+ // probably a bug that needs fixing.
+
+ NS_ENSURE_ARG_POINTER(aStatus);
+ NS_ENSURE_ARG_POINTER(aHdrChanged);
+
+ nsMsgViewIndex index = FindHdr(aHdrChanged);
+ // Message does not appear in view.
+ if (index == nsMsgViewIndex_None) return NS_OK;
+
+ nsCString originStr;
+ (void)aHdrChanged->GetStringProperty("junkscoreorigin", originStr);
+ // Check for "plugin" with only first character for performance.
+ bool plugin = (originStr.get()[0] == 'p');
+
+ if (aPreChange) {
+ // First call, done prior to the change.
+ *aStatus = plugin;
+ return NS_OK;
+ }
+
+ // Second call, done after the change.
+ bool wasPlugin = *aStatus;
+
+ bool match = true;
+ nsCOMPtr<nsIMsgSearchSession> searchSession(
+ do_QueryReferent(m_searchSession));
+ if (searchSession) searchSession->MatchHdr(aHdrChanged, m_db, &match);
+
+ if (!match && plugin && !wasPlugin)
+ // Remove hdr from view.
+ RemoveByIndex(index);
+ else
+ NoteChange(index, 1, nsMsgViewNotificationCode::changed);
+
+ return NS_OK;
+}
+
+void nsMsgXFVirtualFolderDBView::UpdateCacheAndViewForFolder(
+ nsIMsgFolder* folder, nsTArray<nsMsgKey> const& newHits) {
+ nsCOMPtr<nsIMsgDatabase> db;
+ nsresult rv = folder->GetMsgDatabase(getter_AddRefs(db));
+ if (NS_SUCCEEDED(rv) && db) {
+ nsCString searchUri;
+ m_viewFolder->GetURI(searchUri);
+ nsTArray<nsMsgKey> badHits;
+ rv = db->RefreshCache(searchUri, newHits, badHits);
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIMsgDBHdr> badHdr;
+ for (nsMsgKey badKey : badHits) {
+ // ### of course, this isn't quite right, since we should be
+ // using FindHdr, and we shouldn't be expanding the threads.
+ db->GetMsgHdrForKey(badKey, getter_AddRefs(badHdr));
+ // Let nsMsgSearchDBView decide what to do about this header
+ // getting removed.
+ if (badHdr) OnHdrDeleted(badHdr, nsMsgKey_None, 0, this);
+ }
+ }
+ }
+}
+
+void nsMsgXFVirtualFolderDBView::UpdateCacheAndViewForPrevSearchedFolders(
+ nsIMsgFolder* curSearchFolder) {
+ // Handle the most recent folder with hits, if any.
+ if (m_curFolderGettingHits) {
+ uint32_t count = m_hdrHits.Count();
+ nsTArray<nsMsgKey> newHits;
+ newHits.SetLength(count);
+ for (uint32_t i = 0; i < count; i++)
+ m_hdrHits[i]->GetMessageKey(&newHits[i]);
+
+ newHits.Sort();
+ UpdateCacheAndViewForFolder(m_curFolderGettingHits, newHits);
+ m_foldersSearchingOver.RemoveObject(m_curFolderGettingHits);
+ }
+
+ while (m_foldersSearchingOver.Count() > 0) {
+ // This new folder has cached hits.
+ if (m_foldersSearchingOver[0] == curSearchFolder) {
+ m_curFolderHasCachedHits = true;
+ m_foldersSearchingOver.RemoveObjectAt(0);
+ break;
+ } else {
+ // This must be a folder that had no hits with the current search.
+ // So all cached hits, if any, need to be removed.
+ nsTArray<nsMsgKey> noHits;
+ UpdateCacheAndViewForFolder(m_foldersSearchingOver[0], noHits);
+ m_foldersSearchingOver.RemoveObjectAt(0);
+ }
+ }
+}
+NS_IMETHODIMP
+nsMsgXFVirtualFolderDBView::OnSearchHit(nsIMsgDBHdr* aMsgHdr,
+ nsIMsgFolder* aFolder) {
+ NS_ENSURE_ARG(aMsgHdr);
+ NS_ENSURE_ARG(aFolder);
+
+ if (m_curFolderGettingHits != aFolder && m_doingSearch &&
+ !m_doingQuickSearch) {
+ m_curFolderHasCachedHits = false;
+ // Since we've gotten a hit for a new folder, the searches for
+ // any previous folders are done, so deal with stale cached hits
+ // for those folders now.
+ UpdateCacheAndViewForPrevSearchedFolders(aFolder);
+ m_curFolderGettingHits = aFolder;
+ m_hdrHits.Clear();
+ m_curFolderStartKeyIndex = m_keys.Length();
+ }
+
+ bool hdrInCache = false;
+ if (!m_doingQuickSearch) {
+ nsCOMPtr<nsIMsgDatabase> dbToUse;
+ nsCOMPtr<nsIDBFolderInfo> dummyInfo;
+ nsresult rv = aFolder->GetDBFolderInfoAndDB(getter_AddRefs(dummyInfo),
+ getter_AddRefs(dbToUse));
+ if (NS_SUCCEEDED(rv)) {
+ nsCString searchUri;
+ m_viewFolder->GetURI(searchUri);
+ dbToUse->HdrIsInCache(searchUri, aMsgHdr, &hdrInCache);
+ }
+ }
+
+ if (!m_doingSearch || !m_curFolderHasCachedHits || !hdrInCache) {
+ if (m_viewFlags & nsMsgViewFlagsType::kGroupBySort)
+ nsMsgGroupView::OnNewHeader(aMsgHdr, nsMsgKey_None, true);
+ else if (m_sortValid)
+ InsertHdrFromFolder(aMsgHdr, aFolder);
+ else
+ AddHdrFromFolder(aMsgHdr, aFolder);
+ }
+
+ m_hdrHits.AppendObject(aMsgHdr);
+ m_totalMessagesInView++;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgXFVirtualFolderDBView::OnSearchDone(nsresult status) {
+ // This batch began in OnNewSearch.
+ if (mJSTree) mJSTree->EndUpdateBatch();
+
+ NS_ENSURE_TRUE(m_viewFolder, NS_ERROR_NOT_INITIALIZED);
+
+ // Handle any non verified hits we haven't handled yet.
+ if (NS_SUCCEEDED(status) && !m_doingQuickSearch &&
+ status != NS_MSG_SEARCH_INTERRUPTED)
+ UpdateCacheAndViewForPrevSearchedFolders(nullptr);
+
+ m_doingSearch = false;
+ // We want to set imap delete model once the search is over because setting
+ // next message after deletion will happen before deleting the message and
+ // search scope can change with every search.
+
+ // Set to default in case it is non-imap folder.
+ mDeleteModel = nsMsgImapDeleteModels::MoveToTrash;
+ nsIMsgFolder* curFolder = m_folders.SafeObjectAt(0);
+ if (curFolder) GetImapDeleteModel(curFolder);
+
+ nsCOMPtr<nsIMsgDatabase> virtDatabase;
+ nsCOMPtr<nsIDBFolderInfo> dbFolderInfo;
+ nsresult rv = m_viewFolder->GetDBFolderInfoAndDB(
+ getter_AddRefs(dbFolderInfo), getter_AddRefs(virtDatabase));
+ NS_ENSURE_SUCCESS(rv, rv);
+ // Count up the number of unread and total messages from the view, and set
+ // those in the folder - easier than trying to keep the count up to date in
+ // the face of search hits coming in while the user is reading/deleting
+ // messages.
+ uint32_t numUnread = 0;
+ for (uint32_t i = 0; i < m_flags.Length(); i++) {
+ if (m_flags[i] & nsMsgMessageFlags::Elided) {
+ nsCOMPtr<nsIMsgThread> thread;
+ GetThreadContainingIndex(i, getter_AddRefs(thread));
+ if (thread) {
+ uint32_t unreadInThread;
+ thread->GetNumUnreadChildren(&unreadInThread);
+ numUnread += unreadInThread;
+ }
+ } else {
+ if (!(m_flags[i] & nsMsgMessageFlags::Read)) numUnread++;
+ }
+ }
+
+ dbFolderInfo->SetNumUnreadMessages(numUnread);
+ dbFolderInfo->SetNumMessages(m_totalMessagesInView);
+ // Force update from db.
+ m_viewFolder->UpdateSummaryTotals(true);
+ virtDatabase->Commit(nsMsgDBCommitType::kLargeCommit);
+ if (!m_sortValid && m_sortType != nsMsgViewSortType::byThread &&
+ !(m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay)) {
+ // Sort the results.
+ m_sortValid = false;
+ Sort(m_sortType, m_sortOrder);
+ }
+
+ m_foldersSearchingOver.Clear();
+ m_curFolderGettingHits = nullptr;
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMsgXFVirtualFolderDBView::OnNewSearch() {
+ int32_t oldSize = GetSize();
+
+ RemovePendingDBListeners();
+ m_doingSearch = true;
+ m_totalMessagesInView = 0;
+ m_folders.Clear();
+ m_keys.Clear();
+ m_levels.Clear();
+ m_flags.Clear();
+
+ // Needs to happen after we remove the keys, since RowCountChanged() will
+ // call our GetRowCount().
+ if (mTree) mTree->RowCountChanged(0, -oldSize);
+ if (mJSTree) mJSTree->RowCountChanged(0, -oldSize);
+
+ // To use the search results cache, we'll need to iterate over the scopes
+ // in the search session, calling getNthSearchScope
+ // for i = 0; i < searchSession.countSearchScopes; i++
+ // and for each folder, then open the db and pull out the cached hits,
+ // add them to the view. For each hit in a new folder, we'll then clean up
+ // the stale hits from the previous folder(s).
+
+ int32_t scopeCount;
+ nsCOMPtr<nsIMsgSearchSession> searchSession =
+ do_QueryReferent(m_searchSession);
+ // Just ignore.
+ NS_ENSURE_TRUE(searchSession, NS_OK);
+ nsCOMPtr<nsIMsgDBService> msgDBService =
+ do_GetService("@mozilla.org/msgDatabase/msgDBService;1");
+ searchSession->CountSearchScopes(&scopeCount);
+
+ // Figure out how many search terms the virtual folder has.
+ nsCOMPtr<nsIMsgDatabase> virtDatabase;
+ nsCOMPtr<nsIDBFolderInfo> dbFolderInfo;
+ nsresult rv = m_viewFolder->GetDBFolderInfoAndDB(
+ getter_AddRefs(dbFolderInfo), getter_AddRefs(virtDatabase));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCString terms;
+ dbFolderInfo->GetCharProperty("searchStr", terms);
+ nsTArray<RefPtr<nsIMsgSearchTerm>> searchTerms;
+ rv = searchSession->GetSearchTerms(searchTerms);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCString curSearchAsString;
+
+ rv = MsgTermListToString(searchTerms, curSearchAsString);
+ // Trim off the initial AND/OR, which is irrelevant and inconsistent between
+ // what SearchSpec.jsm generates, and what's in virtualFolders.dat.
+ curSearchAsString.Cut(0,
+ StringBeginsWith(curSearchAsString, "AND"_ns) ? 3 : 2);
+ terms.Cut(0, StringBeginsWith(terms, "AND"_ns) ? 3 : 2);
+
+ NS_ENSURE_SUCCESS(rv, rv);
+ // If the search session search string doesn't match the vf search str,
+ // then we're doing quick search, which means we don't want to invalidate
+ // cached results, or used cached results.
+ m_doingQuickSearch = !curSearchAsString.Equals(terms);
+
+ if (!m_doingQuickSearch) {
+ if (mTree) mTree->BeginUpdateBatch();
+ if (mJSTree) mJSTree->BeginUpdateBatch();
+ }
+
+ for (int32_t i = 0; i < scopeCount; i++) {
+ nsMsgSearchScopeValue scopeId;
+ nsCOMPtr<nsIMsgFolder> searchFolder;
+ searchSession->GetNthSearchScope(i, &scopeId, getter_AddRefs(searchFolder));
+ if (searchFolder) {
+ nsCOMPtr<nsIMsgDatabase> searchDB;
+ nsCString searchUri;
+ m_viewFolder->GetURI(searchUri);
+ nsresult rv = searchFolder->GetMsgDatabase(getter_AddRefs(searchDB));
+ if (NS_SUCCEEDED(rv) && searchDB) {
+ if (msgDBService)
+ msgDBService->RegisterPendingListener(searchFolder, this);
+
+ m_foldersSearchingOver.AppendObject(searchFolder);
+ // Ignore cached hits in quick search case.
+ if (m_doingQuickSearch) continue;
+
+ nsCOMPtr<nsIMsgEnumerator> cachedHits;
+ searchDB->GetCachedHits(searchUri, getter_AddRefs(cachedHits));
+ bool hasMore;
+ if (cachedHits) {
+ cachedHits->HasMoreElements(&hasMore);
+ if (hasMore) {
+ mozilla::DebugOnly<nsMsgKey> prevKey = nsMsgKey_None;
+ while (hasMore) {
+ nsCOMPtr<nsIMsgDBHdr> header;
+ nsresult rv = cachedHits->GetNext(getter_AddRefs(header));
+ if (header && NS_SUCCEEDED(rv)) {
+ nsMsgKey msgKey;
+ header->GetMessageKey(&msgKey);
+ NS_ASSERTION(prevKey == nsMsgKey_None || msgKey > prevKey,
+ "cached Hits not sorted");
+#ifdef DEBUG
+ prevKey = msgKey;
+#endif
+ AddHdrFromFolder(header, searchFolder);
+ } else {
+ break;
+ }
+
+ cachedHits->HasMoreElements(&hasMore);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if (!m_doingQuickSearch) {
+ if (mTree) mTree->EndUpdateBatch();
+ if (mJSTree) mJSTree->EndUpdateBatch();
+ }
+
+ m_curFolderStartKeyIndex = 0;
+ m_curFolderGettingHits = nullptr;
+ m_curFolderHasCachedHits = false;
+
+ // If we have cached hits, sort them.
+ if (GetSize() > 0) {
+ // Currently, we keep threaded views sorted while we build them.
+ if (m_sortType != nsMsgViewSortType::byThread &&
+ !(m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay)) {
+ // Sort the results.
+ m_sortValid = false;
+ Sort(m_sortType, m_sortOrder);
+ } else if (mJSTree) {
+ mJSTree->Invalidate();
+ }
+ }
+
+ // Prevent updates for every message found. This batch ends in OnSearchDone.
+ if (mJSTree) mJSTree->BeginUpdateBatch();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgXFVirtualFolderDBView::DoCommand(nsMsgViewCommandTypeValue command) {
+ return nsMsgSearchDBView::DoCommand(command);
+}
+
+NS_IMETHODIMP
+nsMsgXFVirtualFolderDBView::GetMsgFolder(nsIMsgFolder** aMsgFolder) {
+ NS_ENSURE_ARG_POINTER(aMsgFolder);
+ NS_IF_ADDREF(*aMsgFolder = m_viewFolder);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgXFVirtualFolderDBView::SetViewFlags(nsMsgViewFlagsTypeValue aViewFlags) {
+ nsresult rv = NS_OK;
+ // If the grouping/threading has changed, rebuild the view.
+ if ((m_viewFlags & (nsMsgViewFlagsType::kGroupBySort |
+ nsMsgViewFlagsType::kThreadedDisplay)) !=
+ (aViewFlags & (nsMsgViewFlagsType::kGroupBySort |
+ nsMsgViewFlagsType::kThreadedDisplay))) {
+ rv = RebuildView(aViewFlags);
+ }
+
+ nsMsgDBView::SetViewFlags(aViewFlags);
+ return rv;
+}
+
+nsresult nsMsgXFVirtualFolderDBView::GetMessageEnumerator(
+ nsIMsgEnumerator** enumerator) {
+ return GetViewEnumerator(enumerator);
+}
diff --git a/comm/mailnews/base/src/nsMsgXFVirtualFolderDBView.h b/comm/mailnews/base/src/nsMsgXFVirtualFolderDBView.h
new file mode 100644
index 0000000000..5b8b627347
--- /dev/null
+++ b/comm/mailnews/base/src/nsMsgXFVirtualFolderDBView.h
@@ -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/. */
+
+#ifndef _nsMsgXFVirtualFolderDBView_H_
+#define _nsMsgXFVirtualFolderDBView_H_
+
+#include "mozilla/Attributes.h"
+#include "nsMsgSearchDBView.h"
+#include "nsIMsgCopyServiceListener.h"
+#include "nsIMsgSearchNotify.h"
+#include "nsCOMArray.h"
+
+class nsMsgGroupThread;
+
+class nsMsgXFVirtualFolderDBView : public nsMsgSearchDBView {
+ public:
+ nsMsgXFVirtualFolderDBView();
+ virtual ~nsMsgXFVirtualFolderDBView();
+
+ // we override all the methods, currently. Might change...
+ NS_DECL_NSIMSGSEARCHNOTIFY
+
+ virtual const char* GetViewName(void) override {
+ return "XFVirtualFolderView";
+ }
+ NS_IMETHOD Open(nsIMsgFolder* folder, nsMsgViewSortTypeValue sortType,
+ nsMsgViewSortOrderValue sortOrder,
+ nsMsgViewFlagsTypeValue viewFlags, int32_t* pCount) override;
+ NS_IMETHOD CloneDBView(nsIMessenger* aMessengerInstance,
+ nsIMsgWindow* aMsgWindow,
+ nsIMsgDBViewCommandUpdater* aCmdUpdater,
+ nsIMsgDBView** _retval) override;
+ NS_IMETHOD CopyDBView(nsMsgDBView* aNewMsgDBView,
+ nsIMessenger* aMessengerInstance,
+ nsIMsgWindow* aMsgWindow,
+ nsIMsgDBViewCommandUpdater* aCmdUpdater) override;
+ NS_IMETHOD Close() override;
+ NS_IMETHOD GetViewType(nsMsgViewTypeValue* aViewType) override;
+ NS_IMETHOD DoCommand(nsMsgViewCommandTypeValue command) override;
+ NS_IMETHOD SetViewFlags(nsMsgViewFlagsTypeValue aViewFlags) override;
+ NS_IMETHOD OnHdrPropertyChanged(nsIMsgDBHdr* aHdrToChange,
+ const nsACString& property, bool aPreChange,
+ uint32_t* aStatus,
+ nsIDBChangeListener* aInstigator) override;
+ NS_IMETHOD GetMsgFolder(nsIMsgFolder** aMsgFolder) override;
+
+ virtual nsresult OnNewHeader(nsIMsgDBHdr* newHdr, nsMsgKey parentKey,
+ bool ensureListed) override;
+ void UpdateCacheAndViewForPrevSearchedFolders(nsIMsgFolder* curSearchFolder);
+ void UpdateCacheAndViewForFolder(nsIMsgFolder* folder,
+ nsTArray<nsMsgKey> const& newHits);
+ void RemovePendingDBListeners();
+
+ protected:
+ virtual nsresult GetMessageEnumerator(nsIMsgEnumerator** enumerator) override;
+
+ nsCOMArray<nsIMsgFolder> m_foldersSearchingOver;
+ nsCOMArray<nsIMsgDBHdr> m_hdrHits;
+ nsCOMPtr<nsIMsgFolder> m_curFolderGettingHits;
+ // keeps track of the index of the first hit from the cur folder
+ uint32_t m_curFolderStartKeyIndex;
+ bool m_curFolderHasCachedHits;
+ bool m_doingSearch;
+ // Are we doing a quick search on top of the virtual folder search?
+ bool m_doingQuickSearch;
+};
+
+#endif
diff --git a/comm/mailnews/base/src/nsNewMailnewsURI.cpp b/comm/mailnews/base/src/nsNewMailnewsURI.cpp
new file mode 100644
index 0000000000..305e9273a4
--- /dev/null
+++ b/comm/mailnews/base/src/nsNewMailnewsURI.cpp
@@ -0,0 +1,155 @@
+/* -*- 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 "nsNewMailnewsURI.h"
+#include "nsURLHelper.h"
+#include "nsSimpleURI.h"
+#include "nsStandardURL.h"
+#include "nsThreadUtils.h"
+#include "MainThreadUtils.h"
+#include "mozilla/SyncRunnable.h"
+#include "nsIMsgProtocolHandler.h"
+#include "nsIComponentRegistrar.h"
+#include "nsXULAppAPI.h"
+
+#include "../../local/src/nsPop3URL.h"
+#include "../../local/src/nsMailboxService.h"
+#include "../../compose/src/nsSmtpUrl.h"
+#include "../../addrbook/src/nsLDAPURL.h"
+#include "../../imap/src/nsImapService.h"
+#include "../../news/src/nsNntpUrl.h"
+#include "../src/nsCidProtocolHandler.h"
+
+nsresult NS_NewMailnewsURI(nsIURI** aURI, const nsACString& aSpec,
+ const char* aCharset /* = nullptr */,
+ nsIURI* aBaseURI /* = nullptr */) {
+ // Mailnews URIs aren't allowed in child processes.
+ if (!XRE_IsParentProcess()) {
+ return NS_ERROR_UNKNOWN_PROTOCOL;
+ }
+
+ nsAutoCString scheme;
+ nsresult rv = net_ExtractURLScheme(aSpec, scheme);
+ if (NS_FAILED(rv)) {
+ // then aSpec is relative
+ if (!aBaseURI) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ rv = aBaseURI->GetScheme(scheme);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ // Creating IMAP/mailbox URIs off the main thread can lead to crashes.
+ // Seems to happen when viewing PDFs.
+ if (scheme.EqualsLiteral("mailbox") ||
+ scheme.EqualsLiteral("mailbox-message")) {
+ if (NS_IsMainThread()) {
+ return nsMailboxService::NewURI(aSpec, aCharset, aBaseURI, aURI);
+ }
+ auto NewURI = [&aSpec, &aCharset, &aBaseURI, aURI, &rv ]() -> auto{
+ rv = nsMailboxService::NewURI(aSpec, aCharset, aBaseURI, aURI);
+ };
+ nsCOMPtr<nsIRunnable> task = NS_NewRunnableFunction("NewURI", NewURI);
+ mozilla::SyncRunnable::DispatchToThread(
+ mozilla::GetMainThreadSerialEventTarget(), task);
+ return rv;
+ }
+ if (scheme.EqualsLiteral("imap") || scheme.EqualsLiteral("imap-message")) {
+ if (NS_IsMainThread()) {
+ return nsImapService::NewURI(aSpec, aCharset, aBaseURI, aURI);
+ }
+ auto NewURI = [&aSpec, &aCharset, &aBaseURI, aURI, &rv ]() -> auto{
+ rv = nsImapService::NewURI(aSpec, aCharset, aBaseURI, aURI);
+ };
+ nsCOMPtr<nsIRunnable> task = NS_NewRunnableFunction("NewURI", NewURI);
+ mozilla::SyncRunnable::DispatchToThread(
+ mozilla::GetMainThreadSerialEventTarget(), task);
+ return rv;
+ }
+ if (scheme.EqualsLiteral("smtp") || scheme.EqualsLiteral("smtps")) {
+ return nsSmtpUrl::NewSmtpURI(aSpec, aBaseURI, aURI);
+ }
+ if (scheme.EqualsLiteral("mailto")) {
+ if (NS_IsMainThread()) {
+ return nsMailtoUrl::NewMailtoURI(aSpec, aBaseURI, aURI);
+ }
+ // If we're for some reason not on the main thread, dispatch to main
+ // or else we'll crash.
+ auto NewURI = [&aSpec, &aBaseURI, aURI, &rv ]() -> auto{
+ rv = nsMailtoUrl::NewMailtoURI(aSpec, aBaseURI, aURI);
+ };
+ nsCOMPtr<nsIRunnable> task = NS_NewRunnableFunction("NewURI", NewURI);
+ mozilla::SyncRunnable::DispatchToThread(
+ mozilla::GetMainThreadSerialEventTarget(), task);
+ return rv;
+ }
+ if (scheme.EqualsLiteral("pop") || scheme.EqualsLiteral("pop3")) {
+ return nsPop3URL::NewURI(aSpec, aBaseURI, aURI);
+ }
+ if (scheme.EqualsLiteral("news") || scheme.EqualsLiteral("snews") ||
+ scheme.EqualsLiteral("news-message") || scheme.EqualsLiteral("nntp")) {
+ return nsNntpUrl::NewURI(aSpec, aBaseURI, aURI);
+ }
+ if (scheme.EqualsLiteral("cid")) {
+ return nsCidProtocolHandler::NewURI(aSpec, aCharset, aBaseURI, aURI);
+ }
+ if (scheme.EqualsLiteral("ldap") || scheme.EqualsLiteral("ldaps")) {
+ nsCOMPtr<nsILDAPURL> url = do_CreateInstance(NS_LDAPURL_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = url->Init(nsIStandardURL::URLTYPE_STANDARD,
+ scheme.EqualsLiteral("ldap") ? 389 : 636, aSpec, aCharset,
+ aBaseURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+ url.forget(aURI);
+ return NS_OK;
+ }
+ if (scheme.EqualsLiteral("smile")) {
+ return NS_MutateURI(new mozilla::net::nsSimpleURI::Mutator())
+ .SetSpec(aSpec)
+ .Finalize(aURI);
+ }
+ if (scheme.EqualsLiteral("moz-cal-handle-itip")) {
+ return NS_MutateURI(new mozilla::net::nsStandardURL::Mutator())
+ .SetSpec(aSpec)
+ .Finalize(aURI);
+ }
+ if (scheme.EqualsLiteral("webcal") || scheme.EqualsLiteral("webcals")) {
+ return NS_MutateURI(new mozilla::net::nsStandardURL::Mutator())
+ .SetSpec(aSpec)
+ .Finalize(aURI);
+ }
+
+ rv = NS_ERROR_UNKNOWN_PROTOCOL; // Let M-C handle it by default.
+
+ nsCOMPtr<nsIComponentRegistrar> compMgr;
+ NS_GetComponentRegistrar(getter_AddRefs(compMgr));
+ if (compMgr) {
+ nsAutoCString contractID(NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX);
+ contractID += scheme;
+ bool isRegistered = false;
+ compMgr->IsContractIDRegistered(contractID.get(), &isRegistered);
+ if (isRegistered) {
+ auto NewURI =
+ [&aSpec, &aCharset, &aBaseURI, aURI, &contractID, &rv ]() -> auto{
+ nsCOMPtr<nsIMsgProtocolHandler> handler(
+ do_GetService(contractID.get()));
+ if (handler) {
+ // We recognise this URI. Use the protocol handler's result.
+ rv = handler->NewURI(aSpec, aCharset, aBaseURI, aURI);
+ }
+ };
+ if (NS_IsMainThread()) {
+ NewURI();
+ } else {
+ nsCOMPtr<nsIRunnable> task = NS_NewRunnableFunction("NewURI", NewURI);
+ mozilla::SyncRunnable::DispatchToThread(
+ mozilla::GetMainThreadSerialEventTarget(), task);
+ }
+ }
+ }
+
+ return rv;
+}
diff --git a/comm/mailnews/base/src/nsNewMailnewsURI.h b/comm/mailnews/base/src/nsNewMailnewsURI.h
new file mode 100644
index 0000000000..a5775d1978
--- /dev/null
+++ b/comm/mailnews/base/src/nsNewMailnewsURI.h
@@ -0,0 +1,15 @@
+/* -*- 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 nsNewMailnewsURI_h__
+#define nsNewMailnewsURI_h__
+
+#include "nsIURI.h"
+#include "nsNetUtil.h"
+
+nsresult NS_NewMailnewsURI(nsIURI** aURI, const nsACString& aSpec,
+ const char* aCharset /* = nullptr */,
+ nsIURI* aBaseURI /* = nullptr */);
+#endif
diff --git a/comm/mailnews/base/src/nsQuarantinedOutputStream.cpp b/comm/mailnews/base/src/nsQuarantinedOutputStream.cpp
new file mode 100644
index 0000000000..1f325edadf
--- /dev/null
+++ b/comm/mailnews/base/src/nsQuarantinedOutputStream.cpp
@@ -0,0 +1,234 @@
+/* -*- 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 "nsQuarantinedOutputStream.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsIInputStream.h"
+#include "nsISeekableStream.h"
+#include "nsIFile.h"
+#include "nsNetUtil.h"
+#include "mozilla/UniquePtr.h"
+
+NS_IMPL_ISUPPORTS(nsQuarantinedOutputStream, nsIOutputStream,
+ nsISafeOutputStream)
+
+nsQuarantinedOutputStream::~nsQuarantinedOutputStream() { Close(); }
+
+// Initialise mTempFile and open it for writing (mTempStream).
+nsresult nsQuarantinedOutputStream::InitTemp() {
+ MOZ_ASSERT(mState == eUninitialized);
+ MOZ_ASSERT(!mTempFile);
+ MOZ_ASSERT(!mTempStream);
+ // Create a unique temp file.
+ {
+ nsCOMPtr<nsIFile> file;
+ nsresult rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(file));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = file->Append(u"newmsg"_ns);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = file->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600);
+ NS_ENSURE_SUCCESS(rv, rv);
+ mTempFile = std::move(file);
+ }
+
+ // Open the temp file for writing.
+ {
+ nsCOMPtr<nsIOutputStream> stream;
+ nsresult rv = NS_NewLocalFileOutputStream(getter_AddRefs(stream), mTempFile,
+ -1, 0600);
+ NS_ENSURE_SUCCESS(rv, rv);
+ mTempStream = std::move(stream);
+ }
+
+ return NS_OK;
+}
+
+// Put us into the error state and clean up (by deleting the temp file
+// if it exists).
+void nsQuarantinedOutputStream::EnterErrorState(nsresult status) {
+ mState = eError;
+ mError = status;
+ mTarget = nullptr;
+
+ if (mTempStream) {
+ mTempStream = nullptr;
+ }
+ if (mTempFile) {
+ mTempFile->Remove(false);
+ mTempFile = nullptr;
+ }
+}
+
+// copyStream copies all the data in the input stream to the output stream.
+// It keeps going until it sees an EOF on the input.
+static nsresult copyStream(nsIInputStream* in, nsIOutputStream* out) {
+ constexpr uint32_t BUFSIZE = 8192;
+ auto buf = mozilla::MakeUnique<char[]>(BUFSIZE);
+ while (true) {
+ // Read input stream into buf.
+ uint32_t bufCnt;
+ nsresult rv = in->Read(buf.get(), BUFSIZE, &bufCnt);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (bufCnt == 0) {
+ break; // EOF. We're all done!
+ }
+ // Write buf to output stream.
+ uint32_t pos = 0;
+ while (pos < bufCnt) {
+ uint32_t writeCnt;
+ rv = out->Write(buf.get() + pos, bufCnt - pos, &writeCnt);
+ NS_ENSURE_SUCCESS(rv, rv);
+ pos += writeCnt;
+ }
+ }
+ return NS_OK;
+}
+
+// copyStreamSafely() wraps copyStream(). If the output stream is seekable,
+// it will try to roll it back if an error occurs during the copy.
+static nsresult copyStreamSafely(nsIInputStream* in, nsIOutputStream* out) {
+ nsCOMPtr<nsISeekableStream> outSeekable = do_QueryInterface(out);
+ if (!outSeekable) {
+ // It's not seekable, so we jump out without a parachute.
+ return copyStream(in, out);
+ }
+ int64_t initialOffset;
+ nsresult rv = outSeekable->Tell(&initialOffset);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = copyStream(in, out);
+ if (NS_FAILED(rv)) {
+ // Uhoh... the copy failed! Try to remove the partially-written data.
+ rv = outSeekable->Seek(nsISeekableStream::NS_SEEK_SET, initialOffset);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = outSeekable->SetEOF();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsQuarantinedOutputStream::Close() {
+ if (mState != eOpen) {
+ // Already failed or closed or no data written. That's OK.
+ return NS_OK;
+ }
+ nsresult rv = NS_OK;
+ if (mTempStream) {
+ rv = mTempStream->Close();
+ mTempStream = nullptr;
+ }
+ if (mTempFile) {
+ mTempFile->Remove(false);
+ mTempFile = nullptr;
+ }
+ mTarget->Close();
+ mTarget = nullptr;
+ mState = eClosed;
+ return rv;
+}
+
+NS_IMETHODIMP nsQuarantinedOutputStream::Finish() {
+ // Fail here if there was a previous error.
+ if (mState == eError) {
+ return mError;
+ }
+ if (mState != eOpen) {
+ // Already closed or no data written. That's OK.
+ return NS_OK;
+ }
+
+ // Flush and close the temp file. Hopefully any virus checker will now act
+ // and prevent us reopening any suspicious-looking file.
+ MOZ_ASSERT(mTempStream);
+ MOZ_ASSERT(mTempFile);
+ mTempStream->Flush();
+ nsresult rv = mTempStream->Close();
+ if (NS_FAILED(rv)) {
+ EnterErrorState(rv);
+ return rv;
+ }
+ mTempStream = nullptr;
+
+ // Write the tempfile out to the target stream
+ {
+ nsCOMPtr<nsIInputStream> ins;
+ // If a virus checker smells something bad, it should show up here as a
+ // failure to (re)open the temp file.
+ rv = NS_NewLocalFileInputStream(getter_AddRefs(ins), mTempFile);
+ if (NS_FAILED(rv)) {
+ EnterErrorState(rv);
+ return rv;
+ }
+ rv = copyStreamSafely(ins, mTarget);
+ if (NS_FAILED(rv)) {
+ EnterErrorState(rv);
+ return rv;
+ }
+ }
+
+ // All done!
+ mTarget->Close();
+ mTempFile->Remove(false);
+ mTempFile = nullptr;
+ mState = eClosed;
+ mTarget = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsQuarantinedOutputStream::Flush() {
+ if (mState != eOpen) {
+ return NS_OK; // Don't rock the boat.
+ }
+ nsresult rv = mTempStream->Flush();
+ if (NS_FAILED(rv)) {
+ EnterErrorState(rv);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsQuarantinedOutputStream::Write(const char* buf, uint32_t count,
+ uint32_t* result) {
+ if (mState == eUninitialized) {
+ // Lazy open.
+ nsresult rv = InitTemp();
+ if NS_FAILED (rv) {
+ EnterErrorState(rv);
+ return rv;
+ }
+ mState = eOpen;
+ }
+
+ if (mState != eOpen) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ nsresult rv = mTempStream->Write(buf, count, result);
+ if (NS_FAILED(rv)) {
+ EnterErrorState(rv);
+ return rv;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsQuarantinedOutputStream::WriteFrom(nsIInputStream* fromStream,
+ uint32_t count,
+ uint32_t* retval) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsQuarantinedOutputStream::WriteSegments(nsReadSegmentFun reader,
+ void* closure,
+ uint32_t count,
+ uint32_t* retval) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsQuarantinedOutputStream::IsNonBlocking(bool* nonBlocking) {
+ *nonBlocking = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsQuarantinedOutputStream::StreamStatus() {
+ return mState == eOpen ? NS_OK : NS_BASE_STREAM_CLOSED;
+}
diff --git a/comm/mailnews/base/src/nsQuarantinedOutputStream.h b/comm/mailnews/base/src/nsQuarantinedOutputStream.h
new file mode 100644
index 0000000000..515440494b
--- /dev/null
+++ b/comm/mailnews/base/src/nsQuarantinedOutputStream.h
@@ -0,0 +1,72 @@
+/* -*- 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 nsQuarantinedOutputStream_h__
+#define nsQuarantinedOutputStream_h__
+
+// #include "nsISupports.h"
+#include "nsIOutputStream.h"
+#include "nsISafeOutputStream.h"
+#include "nsCOMPtr.h"
+class nsIFile;
+
+/**
+ * nsQuarantinedOutputStream layers on top of an existing target output stream.
+ * The idea is to let an OS virus checker quarantine individual messages
+ * _before_ they hit the mbox. You don't want entire mboxes embargoed if
+ * you can avoid it.
+ *
+ * It works by buffering all writes to a temporary file.
+ * When finish() is called the temporary file is closed, reopened,
+ * then copied into a pre-existing target stream. There's no special OS
+ * virus-checker integration - the assumption is that the checker will hook
+ * into the filesystem and prevent us from opening a file it has flagged as
+ * dodgy. Hence the temp file close/reopen before the final write.
+ *
+ * If the nsQuarantinedOutputStream is closed (or released) without calling
+ * finish(), the write is discarded (as per nsISafeOutputStream requirements).
+ *
+ * Upon close() or finish(), the underlying target file is also closed.
+ */
+class nsQuarantinedOutputStream : public nsIOutputStream, nsISafeOutputStream {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOUTPUTSTREAM
+ NS_DECL_NSISAFEOUTPUTSTREAM
+
+ /**
+ * Pass the target output stream in during construction. Upon Close(),
+ * the written data will be copied here.
+ */
+ explicit nsQuarantinedOutputStream(nsIOutputStream* target)
+ : mTarget(target) {}
+ nsQuarantinedOutputStream() = delete;
+
+ protected:
+ virtual ~nsQuarantinedOutputStream();
+
+ // Set up mTempFile and mTempStream (called at
+ // (lazily set up, upon first write).
+ nsresult InitTemp();
+ nsresult PerformAppend();
+ void EnterErrorState(nsresult status);
+
+ // The temporary file and stream we're writing to.
+ nsCOMPtr<nsIFile> mTempFile;
+ nsCOMPtr<nsIOutputStream> mTempStream;
+
+ // The stream we'll be appending to if it all succeeds.
+ nsCOMPtr<nsIOutputStream> mTarget;
+
+ enum {
+ eUninitialized, // No temp file yet.
+ eOpen, // We're up and running.
+ eClosed, // The file has been closed.
+ eError // An error has occurred (stored in mError).
+ } mState{eUninitialized};
+ nsresult mError{NS_OK};
+};
+
+#endif // nsQuarantinedOutputStream_h__
diff --git a/comm/mailnews/base/src/nsSpamSettings.cpp b/comm/mailnews/base/src/nsSpamSettings.cpp
new file mode 100644
index 0000000000..61dc1e8f43
--- /dev/null
+++ b/comm/mailnews/base/src/nsSpamSettings.cpp
@@ -0,0 +1,806 @@
+/* -*- 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 "nsSpamSettings.h"
+#include "nsIFile.h"
+#include "plstr.h"
+#include "prmem.h"
+#include "nsIMsgHdr.h"
+#include "nsNetUtil.h"
+#include "nsIMsgFolder.h"
+#include "nsMsgUtils.h"
+#include "nsMsgFolderFlags.h"
+#include "nsImapCore.h"
+#include "nsIImapIncomingServer.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+#include "nsIStringBundle.h"
+#include "mozilla/Components.h"
+#include "mozilla/mailnews/MimeHeaderParser.h"
+#include "nsMailDirServiceDefs.h"
+#include "nsDirectoryServiceUtils.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsISimpleEnumerator.h"
+#include "nsIAbCard.h"
+#include "nsIAbManager.h"
+#include "nsIMsgAccountManager.h"
+#include "mozilla/intl/AppDateTimeFormat.h"
+
+using namespace mozilla::mailnews;
+
+nsSpamSettings::nsSpamSettings() {
+ mLevel = 0;
+ mMoveOnSpam = false;
+ mMoveTargetMode = nsISpamSettings::MOVE_TARGET_MODE_ACCOUNT;
+ mPurge = false;
+ mPurgeInterval = 14; // 14 days
+
+ mServerFilterTrustFlags = 0;
+ mInhibitWhiteListingIdentityUser = false;
+ mInhibitWhiteListingIdentityDomain = false;
+ mUseWhiteList = false;
+ mUseServerFilter = false;
+
+ nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
+ getter_AddRefs(mLogFile));
+ if (NS_SUCCEEDED(rv)) mLogFile->Append(u"junklog.html"_ns);
+}
+
+nsSpamSettings::~nsSpamSettings() {}
+
+NS_IMPL_ISUPPORTS(nsSpamSettings, nsISpamSettings, nsIUrlListener)
+
+NS_IMETHODIMP
+nsSpamSettings::GetLevel(int32_t* aLevel) {
+ NS_ENSURE_ARG_POINTER(aLevel);
+ *aLevel = mLevel;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsSpamSettings::SetLevel(int32_t aLevel) {
+ NS_ASSERTION((aLevel >= 0 && aLevel <= 100), "bad level");
+ mLevel = aLevel;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSpamSettings::GetMoveTargetMode(int32_t* aMoveTargetMode) {
+ NS_ENSURE_ARG_POINTER(aMoveTargetMode);
+ *aMoveTargetMode = mMoveTargetMode;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsSpamSettings::SetMoveTargetMode(int32_t aMoveTargetMode) {
+ NS_ASSERTION((aMoveTargetMode == nsISpamSettings::MOVE_TARGET_MODE_FOLDER ||
+ aMoveTargetMode == nsISpamSettings::MOVE_TARGET_MODE_ACCOUNT),
+ "bad move target mode");
+ mMoveTargetMode = aMoveTargetMode;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsSpamSettings::GetManualMark(bool* aManualMark) {
+ NS_ENSURE_ARG_POINTER(aManualMark);
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> prefBranch(
+ do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return prefBranch->GetBoolPref("mail.spam.manualMark", aManualMark);
+}
+
+NS_IMETHODIMP nsSpamSettings::GetManualMarkMode(int32_t* aManualMarkMode) {
+ NS_ENSURE_ARG_POINTER(aManualMarkMode);
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> prefBranch(
+ do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return prefBranch->GetIntPref("mail.spam.manualMarkMode", aManualMarkMode);
+}
+
+NS_IMETHODIMP nsSpamSettings::GetLoggingEnabled(bool* aLoggingEnabled) {
+ NS_ENSURE_ARG_POINTER(aLoggingEnabled);
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> prefBranch(
+ do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return prefBranch->GetBoolPref("mail.spam.logging.enabled", aLoggingEnabled);
+}
+
+NS_IMETHODIMP nsSpamSettings::GetMarkAsReadOnSpam(bool* aMarkAsReadOnSpam) {
+ NS_ENSURE_ARG_POINTER(aMarkAsReadOnSpam);
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> prefBranch(
+ do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return prefBranch->GetBoolPref("mail.spam.markAsReadOnSpam",
+ aMarkAsReadOnSpam);
+}
+
+NS_IMPL_GETSET(nsSpamSettings, MoveOnSpam, bool, mMoveOnSpam)
+NS_IMPL_GETSET(nsSpamSettings, Purge, bool, mPurge)
+NS_IMPL_GETSET(nsSpamSettings, UseWhiteList, bool, mUseWhiteList)
+NS_IMPL_GETSET(nsSpamSettings, UseServerFilter, bool, mUseServerFilter)
+
+NS_IMETHODIMP nsSpamSettings::GetWhiteListAbURI(nsACString& aWhiteListAbURI) {
+ aWhiteListAbURI = mWhiteListAbURI;
+ return NS_OK;
+}
+NS_IMETHODIMP nsSpamSettings::SetWhiteListAbURI(
+ const nsACString& aWhiteListAbURI) {
+ mWhiteListAbURI = aWhiteListAbURI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsSpamSettings::GetActionTargetAccount(
+ nsACString& aActionTargetAccount) {
+ aActionTargetAccount = mActionTargetAccount;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsSpamSettings::SetActionTargetAccount(
+ const nsACString& aActionTargetAccount) {
+ mActionTargetAccount = aActionTargetAccount;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsSpamSettings::GetActionTargetFolder(
+ nsACString& aActionTargetFolder) {
+ aActionTargetFolder = mActionTargetFolder;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsSpamSettings::SetActionTargetFolder(
+ const nsACString& aActionTargetFolder) {
+ mActionTargetFolder = aActionTargetFolder;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsSpamSettings::GetPurgeInterval(int32_t* aPurgeInterval) {
+ NS_ENSURE_ARG_POINTER(aPurgeInterval);
+ *aPurgeInterval = mPurgeInterval;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsSpamSettings::SetPurgeInterval(int32_t aPurgeInterval) {
+ NS_ASSERTION(aPurgeInterval >= 0, "bad purge interval");
+ mPurgeInterval = aPurgeInterval;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSpamSettings::SetLogStream(nsIOutputStream* aLogStream) {
+ // if there is a log stream already, close it
+ if (mLogStream) {
+ // will flush
+ nsresult rv = mLogStream->Close();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ mLogStream = aLogStream;
+ return NS_OK;
+}
+
+#define LOG_HEADER \
+ "<!DOCTYPE html>\n<html>\n<head>\n<meta charset=\"UTF-8\">\n<style " \
+ "type=\"text/css\">body{font-family:Consolas,\"Lucida " \
+ "Console\",Monaco,\"Courier " \
+ "New\",Courier,monospace;font-size:small}</style>\n</head>\n<body>\n"
+#define LOG_HEADER_LEN (strlen(LOG_HEADER))
+
+NS_IMETHODIMP
+nsSpamSettings::GetLogStream(nsIOutputStream** aLogStream) {
+ NS_ENSURE_ARG_POINTER(aLogStream);
+
+ nsresult rv;
+
+ if (!mLogStream) {
+ // append to the end of the log file
+ rv = MsgNewBufferedFileOutputStream(getter_AddRefs(mLogStream), mLogFile,
+ PR_CREATE_FILE | PR_WRONLY | PR_APPEND,
+ 0600);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int64_t fileSize;
+ rv = mLogFile->GetFileSize(&fileSize);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // write the header at the start
+ if (fileSize == 0) {
+ uint32_t writeCount;
+
+ rv = mLogStream->Write(LOG_HEADER, LOG_HEADER_LEN, &writeCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ASSERTION(writeCount == LOG_HEADER_LEN,
+ "failed to write out log header");
+ }
+ }
+
+ NS_ADDREF(*aLogStream = mLogStream);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsSpamSettings::Initialize(nsIMsgIncomingServer* aServer) {
+ NS_ENSURE_ARG_POINTER(aServer);
+ nsresult rv;
+ int32_t spamLevel;
+ rv = aServer->GetIntValue("spamLevel", &spamLevel);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = SetLevel(spamLevel);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool moveOnSpam;
+ rv = aServer->GetBoolValue("moveOnSpam", &moveOnSpam);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = SetMoveOnSpam(moveOnSpam);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t moveTargetMode;
+ rv = aServer->GetIntValue("moveTargetMode", &moveTargetMode);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = SetMoveTargetMode(moveTargetMode);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCString spamActionTargetAccount;
+ rv =
+ aServer->GetCharValue("spamActionTargetAccount", spamActionTargetAccount);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = SetActionTargetAccount(spamActionTargetAccount);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsString spamActionTargetFolder;
+ rv = aServer->GetUnicharValue("spamActionTargetFolder",
+ spamActionTargetFolder);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = SetActionTargetFolder(NS_ConvertUTF16toUTF8(spamActionTargetFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool useWhiteList;
+ rv = aServer->GetBoolValue("useWhiteList", &useWhiteList);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = SetUseWhiteList(useWhiteList);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCString whiteListAbURI;
+ rv = aServer->GetCharValue("whiteListAbURI", whiteListAbURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = SetWhiteListAbURI(whiteListAbURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool purgeSpam;
+ rv = aServer->GetBoolValue("purgeSpam", &purgeSpam);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = SetPurge(purgeSpam);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t purgeSpamInterval;
+ rv = aServer->GetIntValue("purgeSpamInterval", &purgeSpamInterval);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = SetPurgeInterval(purgeSpamInterval);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool useServerFilter;
+ rv = aServer->GetBoolValue("useServerFilter", &useServerFilter);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = SetUseServerFilter(useServerFilter);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCString serverFilterName;
+ rv = aServer->GetCharValue("serverFilterName", serverFilterName);
+ if (NS_SUCCEEDED(rv)) SetServerFilterName(serverFilterName);
+ int32_t serverFilterTrustFlags = 0;
+ rv = aServer->GetIntValue("serverFilterTrustFlags", &serverFilterTrustFlags);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = SetServerFilterTrustFlags(serverFilterTrustFlags);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIPrefBranch> prefBranch(
+ do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ if (prefBranch)
+ prefBranch->GetCharPref("mail.trusteddomains", mTrustedMailDomains);
+
+ mWhiteListDirArray.Clear();
+ if (!mWhiteListAbURI.IsEmpty()) {
+ nsCOMPtr<nsIAbManager> abManager(
+ do_GetService("@mozilla.org/abmanager;1", &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsTArray<nsCString> whiteListArray;
+ ParseString(mWhiteListAbURI, ' ', whiteListArray);
+
+ for (uint32_t index = 0; index < whiteListArray.Length(); index++) {
+ nsCOMPtr<nsIAbDirectory> directory;
+ rv = abManager->GetDirectory(whiteListArray[index],
+ getter_AddRefs(directory));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (directory) mWhiteListDirArray.AppendObject(directory);
+ }
+ }
+
+ // the next two preferences affect whether we try to whitelist our own
+ // address or domain. Spammers send emails with spoofed from address matching
+ // either the email address of the recipient, or the recipient's domain,
+ // hoping to get whitelisted.
+ //
+ // The terms to describe this get wrapped up in chains of negatives. A full
+ // definition of the boolean inhibitWhiteListingIdentityUser is "Suppress
+ // address book whitelisting if the sender matches an identity's email
+ // address"
+
+ rv = aServer->GetBoolValue("inhibitWhiteListingIdentityUser",
+ &mInhibitWhiteListingIdentityUser);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = aServer->GetBoolValue("inhibitWhiteListingIdentityDomain",
+ &mInhibitWhiteListingIdentityDomain);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // collect lists of identity users if needed
+ if (mInhibitWhiteListingIdentityDomain || mInhibitWhiteListingIdentityUser) {
+ nsCOMPtr<nsIMsgAccountManager> accountManager(
+ do_GetService("@mozilla.org/messenger/account-manager;1", &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgAccount> account;
+ rv = accountManager->FindAccountForServer(aServer, getter_AddRefs(account));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString accountKey;
+ if (account) account->GetKey(accountKey);
+
+ // Loop through all accounts, adding emails from this account, as well as
+ // from any accounts that defer to this account.
+ mEmails.Clear();
+ nsTArray<RefPtr<nsIMsgAccount>> accounts;
+ // No sense scanning accounts if we've nothing to match.
+ if (account) {
+ rv = accountManager->GetAccounts(accounts);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ for (auto loopAccount : accounts) {
+ if (!loopAccount) continue;
+ nsAutoCString loopAccountKey;
+ loopAccount->GetKey(loopAccountKey);
+ nsCOMPtr<nsIMsgIncomingServer> loopServer;
+ loopAccount->GetIncomingServer(getter_AddRefs(loopServer));
+ nsAutoCString deferredToAccountKey;
+ if (loopServer)
+ loopServer->GetCharValue("deferred_to_account", deferredToAccountKey);
+
+ // Add the emails for any account that defers to this one, or for the
+ // account itself.
+ if (accountKey.Equals(deferredToAccountKey) ||
+ accountKey.Equals(loopAccountKey)) {
+ nsTArray<RefPtr<nsIMsgIdentity>> identities;
+ loopAccount->GetIdentities(identities);
+ for (auto identity : identities) {
+ nsAutoCString email;
+ identity->GetEmail(email);
+ if (!email.IsEmpty()) mEmails.AppendElement(email);
+ }
+ }
+ }
+ }
+
+ return UpdateJunkFolderState();
+}
+
+nsresult nsSpamSettings::UpdateJunkFolderState() {
+ nsresult rv;
+
+ // if the spam folder uri changed on us, we need to unset the junk flag
+ // on the old spam folder
+ nsCString newJunkFolderURI;
+ rv = GetSpamFolderURI(newJunkFolderURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!mCurrentJunkFolderURI.IsEmpty() &&
+ !mCurrentJunkFolderURI.Equals(newJunkFolderURI)) {
+ nsCOMPtr<nsIMsgFolder> oldJunkFolder;
+ rv = FindFolder(mCurrentJunkFolderURI, getter_AddRefs(oldJunkFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (oldJunkFolder) {
+ // remove the nsMsgFolderFlags::Junk on the old junk folder
+ // XXX TODO
+ // JUNK MAIL RELATED
+ // (in ClearFlag?) we need to make sure that this folder
+ // is not the junk folder for another account
+ // the same goes for set flag. have fun with all that.
+ oldJunkFolder->ClearFlag(nsMsgFolderFlags::Junk);
+ }
+ }
+
+ mCurrentJunkFolderURI = newJunkFolderURI;
+
+ // only try to create the junk folder if we are moving junk
+ // and we have a non-empty uri
+ if (mMoveOnSpam && !mCurrentJunkFolderURI.IsEmpty()) {
+ // as the url listener, the spam settings will set the
+ // nsMsgFolderFlags::Junk folder flag on the junk mail folder, after it is
+ // created
+ rv = GetOrCreateJunkFolder(mCurrentJunkFolderURI, this);
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP nsSpamSettings::Clone(nsISpamSettings* aSpamSettings) {
+ NS_ENSURE_ARG_POINTER(aSpamSettings);
+
+ nsresult rv = aSpamSettings->GetUseWhiteList(&mUseWhiteList);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ (void)aSpamSettings->GetMoveOnSpam(&mMoveOnSpam);
+ (void)aSpamSettings->GetPurge(&mPurge);
+ (void)aSpamSettings->GetUseServerFilter(&mUseServerFilter);
+
+ rv = aSpamSettings->GetPurgeInterval(&mPurgeInterval);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aSpamSettings->GetLevel(&mLevel);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aSpamSettings->GetMoveTargetMode(&mMoveTargetMode);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCString actionTargetAccount;
+ rv = aSpamSettings->GetActionTargetAccount(actionTargetAccount);
+ NS_ENSURE_SUCCESS(rv, rv);
+ mActionTargetAccount = actionTargetAccount;
+
+ nsCString actionTargetFolder;
+ rv = aSpamSettings->GetActionTargetFolder(actionTargetFolder);
+ NS_ENSURE_SUCCESS(rv, rv);
+ mActionTargetFolder = actionTargetFolder;
+
+ nsCString whiteListAbURI;
+ rv = aSpamSettings->GetWhiteListAbURI(whiteListAbURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+ mWhiteListAbURI = whiteListAbURI;
+
+ aSpamSettings->GetServerFilterName(mServerFilterName);
+ aSpamSettings->GetServerFilterTrustFlags(&mServerFilterTrustFlags);
+
+ return rv;
+}
+
+NS_IMETHODIMP nsSpamSettings::GetSpamFolderURI(nsACString& aSpamFolderURI) {
+ if (mMoveTargetMode == nsISpamSettings::MOVE_TARGET_MODE_FOLDER)
+ return GetActionTargetFolder(aSpamFolderURI);
+
+ // if the mode is nsISpamSettings::MOVE_TARGET_MODE_ACCOUNT
+ // the spam folder URI = account uri + "/Junk"
+ nsCString folderURI;
+ nsresult rv = GetActionTargetAccount(folderURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // we might be trying to get the old spam folder uri
+ // in order to clear the flag
+ // if we didn't have one, bail out.
+ if (folderURI.IsEmpty()) return NS_OK;
+
+ nsCOMPtr<nsIMsgFolder> folder;
+ rv = GetOrCreateFolder(folderURI, getter_AddRefs(folder));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = folder->GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // see nsMsgFolder::SetPrettyName() for where the pretty name is set.
+
+ // Check for an existing junk folder - this will do a case-insensitive
+ // search by URI - if we find a junk folder, use its URI.
+ nsCOMPtr<nsIMsgFolder> junkFolder;
+ folderURI.AppendLiteral("/Junk");
+ if (NS_SUCCEEDED(server->GetMsgFolderFromURI(nullptr, folderURI,
+ getter_AddRefs(junkFolder))) &&
+ junkFolder)
+ junkFolder->GetURI(folderURI);
+
+ // XXX todo
+ // better not to make base depend in imap
+ // but doing it here, like in nsMsgCopy.cpp
+ // one day, we'll fix this (and nsMsgCopy.cpp) to use GetMsgFolderFromURI()
+ nsCOMPtr<nsIImapIncomingServer> imapServer = do_QueryInterface(server);
+ if (imapServer) {
+ // Make sure an specific IMAP folder has correct personal namespace
+ // see bug #197043
+ nsCString folderUriWithNamespace;
+ (void)imapServer->GetUriWithNamespacePrefixIfNecessary(
+ kPersonalNamespace, folderURI, folderUriWithNamespace);
+ if (!folderUriWithNamespace.IsEmpty()) folderURI = folderUriWithNamespace;
+ }
+
+ aSpamFolderURI = folderURI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsSpamSettings::GetServerFilterName(nsACString& aFilterName) {
+ aFilterName = mServerFilterName;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsSpamSettings::SetServerFilterName(
+ const nsACString& aFilterName) {
+ mServerFilterName = aFilterName;
+ mServerFilterFile = nullptr; // clear out our stored location value
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsSpamSettings::GetServerFilterFile(nsIFile** aFile) {
+ NS_ENSURE_ARG_POINTER(aFile);
+ if (!mServerFilterFile) {
+ nsresult rv;
+ nsAutoCString serverFilterFileName;
+ GetServerFilterName(serverFilterFileName);
+ serverFilterFileName.AppendLiteral(".sfd");
+
+ nsCOMPtr<nsIProperties> dirSvc =
+ do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Walk through the list of isp directories
+ nsCOMPtr<nsISimpleEnumerator> ispDirectories;
+ rv = dirSvc->Get(ISP_DIRECTORY_LIST, NS_GET_IID(nsISimpleEnumerator),
+ getter_AddRefs(ispDirectories));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool hasMore;
+ nsCOMPtr<nsIFile> file;
+ while (NS_SUCCEEDED(ispDirectories->HasMoreElements(&hasMore)) && hasMore) {
+ nsCOMPtr<nsISupports> elem;
+ ispDirectories->GetNext(getter_AddRefs(elem));
+ file = do_QueryInterface(elem);
+
+ if (file) {
+ // append our desired leaf name then test to see if the file exists. If
+ // it does, we've found mServerFilterFile.
+ file->AppendNative(serverFilterFileName);
+ bool exists;
+ if (NS_SUCCEEDED(file->Exists(&exists)) && exists) {
+ file.swap(mServerFilterFile);
+ break;
+ }
+ } // if file
+ } // until we find the location of mServerFilterName
+ } // if we haven't already stored mServerFilterFile
+
+ NS_IF_ADDREF(*aFile = mServerFilterFile);
+ return NS_OK;
+}
+
+NS_IMPL_GETSET(nsSpamSettings, ServerFilterTrustFlags, int32_t,
+ mServerFilterTrustFlags)
+
+#define LOG_ENTRY_START_TAG "<p>\n"
+#define LOG_ENTRY_START_TAG_LEN (strlen(LOG_ENTRY_START_TAG))
+#define LOG_ENTRY_END_TAG "</p>\n"
+#define LOG_ENTRY_END_TAG_LEN (strlen(LOG_ENTRY_END_TAG))
+// Does this need to be localizable?
+#define LOG_ENTRY_TIMESTAMP "[$S] "
+
+NS_IMETHODIMP nsSpamSettings::LogJunkHit(nsIMsgDBHdr* aMsgHdr,
+ bool aMoveMessage) {
+ bool loggingEnabled;
+ nsresult rv = GetLoggingEnabled(&loggingEnabled);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!loggingEnabled) return NS_OK;
+
+ PRTime date;
+
+ nsString authorValue;
+ nsString subjectValue;
+ nsString dateValue;
+
+ (void)aMsgHdr->GetDate(&date);
+ PRExplodedTime exploded;
+ PR_ExplodeTime(date, PR_LocalTimeParameters, &exploded);
+
+ mozilla::intl::DateTimeFormat::StyleBag style;
+ style.date = mozilla::Some(mozilla::intl::DateTimeFormat::Style::Short);
+ style.time = mozilla::Some(mozilla::intl::DateTimeFormat::Style::Long);
+ mozilla::intl::AppDateTimeFormat::Format(style, &exploded, dateValue);
+
+ (void)aMsgHdr->GetMime2DecodedAuthor(authorValue);
+ (void)aMsgHdr->GetMime2DecodedSubject(subjectValue);
+
+ nsCString buffer;
+ // this is big enough to hold a log entry.
+ // do this so we avoid growing and copying as we append to the log.
+ buffer.SetCapacity(512);
+
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ mozilla::components::StringBundle::Service();
+ NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED);
+
+ nsCOMPtr<nsIStringBundle> bundle;
+ rv = bundleService->CreateBundle(
+ "chrome://messenger/locale/filter.properties", getter_AddRefs(bundle));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ AutoTArray<nsString, 3> junkLogDetectFormatStrings = {
+ authorValue, subjectValue, dateValue};
+ nsString junkLogDetectStr;
+ rv = bundle->FormatStringFromName(
+ "junkLogDetectStr", junkLogDetectFormatStrings, junkLogDetectStr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ buffer += NS_ConvertUTF16toUTF8(junkLogDetectStr);
+ buffer += "\n";
+
+ if (aMoveMessage) {
+ nsCString msgId;
+ aMsgHdr->GetMessageId(getter_Copies(msgId));
+
+ nsCString junkFolderURI;
+ rv = GetSpamFolderURI(junkFolderURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ AutoTArray<nsString, 2> logMoveFormatStrings;
+ CopyASCIItoUTF16(msgId, *logMoveFormatStrings.AppendElement());
+ CopyASCIItoUTF16(junkFolderURI, *logMoveFormatStrings.AppendElement());
+ nsString logMoveStr;
+ rv = bundle->FormatStringFromName("logMoveStr", logMoveFormatStrings,
+ logMoveStr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ buffer += NS_ConvertUTF16toUTF8(logMoveStr);
+ buffer += "\n";
+ }
+
+ return LogJunkString(buffer.get());
+}
+
+NS_IMETHODIMP nsSpamSettings::LogJunkString(const char* string) {
+ bool loggingEnabled;
+ nsresult rv = GetLoggingEnabled(&loggingEnabled);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!loggingEnabled) return NS_OK;
+
+ nsString dateValue;
+ PRExplodedTime exploded;
+ PR_ExplodeTime(PR_Now(), PR_LocalTimeParameters, &exploded);
+
+ mozilla::intl::DateTimeFormat::StyleBag style;
+ style.date = mozilla::Some(mozilla::intl::DateTimeFormat::Style::Short);
+ style.time = mozilla::Some(mozilla::intl::DateTimeFormat::Style::Long);
+ mozilla::intl::AppDateTimeFormat::Format(style, &exploded, dateValue);
+
+ nsCString timestampString(LOG_ENTRY_TIMESTAMP);
+ timestampString.ReplaceSubstring("$S",
+ NS_ConvertUTF16toUTF8(dateValue).get());
+
+ nsCOMPtr<nsIOutputStream> logStream;
+ rv = GetLogStream(getter_AddRefs(logStream));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t writeCount;
+
+ rv = logStream->Write(LOG_ENTRY_START_TAG, LOG_ENTRY_START_TAG_LEN,
+ &writeCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ASSERTION(writeCount == LOG_ENTRY_START_TAG_LEN,
+ "failed to write out start log tag");
+
+ rv = logStream->Write(timestampString.get(), timestampString.Length(),
+ &writeCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ASSERTION(writeCount == timestampString.Length(),
+ "failed to write out timestamp");
+
+ // HTML-escape the log for security reasons.
+ // We don't want someone to send us a message with a subject with
+ // HTML tags, especially <script>.
+ nsCString escapedBuffer;
+ nsAppendEscapedHTML(nsDependentCString(string), escapedBuffer);
+
+ uint32_t escapedBufferLen = escapedBuffer.Length();
+ rv = logStream->Write(escapedBuffer.get(), escapedBufferLen, &writeCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ASSERTION(writeCount == escapedBufferLen, "failed to write out log hit");
+
+ rv = logStream->Write(LOG_ENTRY_END_TAG, LOG_ENTRY_END_TAG_LEN, &writeCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ASSERTION(writeCount == LOG_ENTRY_END_TAG_LEN,
+ "failed to write out end log tag");
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsSpamSettings::OnStartRunningUrl(nsIURI* aURL) {
+ // do nothing
+ // all the action happens in OnStopRunningUrl()
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsSpamSettings::OnStopRunningUrl(nsIURI* aURL,
+ nsresult exitCode) {
+ nsCString junkFolderURI;
+ nsresult rv = GetSpamFolderURI(junkFolderURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (junkFolderURI.IsEmpty()) return NS_ERROR_UNEXPECTED;
+
+ // when we get here, the folder should exist.
+ nsCOMPtr<nsIMsgFolder> junkFolder;
+ rv = GetExistingFolder(junkFolderURI, getter_AddRefs(junkFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = junkFolder->SetFlag(nsMsgFolderFlags::Junk);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return rv;
+}
+
+NS_IMETHODIMP nsSpamSettings::CheckWhiteList(nsIMsgDBHdr* aMsgHdr,
+ bool* aResult) {
+ NS_ENSURE_ARG_POINTER(aMsgHdr);
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = false; // default in case of error or no whitelisting
+
+ if (!mUseWhiteList ||
+ (!mWhiteListDirArray.Count() && mTrustedMailDomains.IsEmpty()))
+ return NS_OK;
+
+ // do per-message processing
+
+ nsCString author;
+ aMsgHdr->GetAuthor(getter_Copies(author));
+
+ nsAutoCString authorEmailAddress;
+ ExtractEmail(EncodedHeader(author), authorEmailAddress);
+
+ if (authorEmailAddress.IsEmpty()) return NS_OK;
+
+ // should we skip whitelisting for the identity email?
+ if (mInhibitWhiteListingIdentityUser) {
+ for (uint32_t i = 0; i < mEmails.Length(); ++i) {
+ if (mEmails[i].Equals(authorEmailAddress,
+ nsCaseInsensitiveCStringComparator))
+ return NS_OK;
+ }
+ }
+
+ if (!mTrustedMailDomains.IsEmpty() || mInhibitWhiteListingIdentityDomain) {
+ nsAutoCString domain;
+ int32_t atPos = authorEmailAddress.FindChar('@');
+ if (atPos >= 0) domain = Substring(authorEmailAddress, atPos + 1);
+ if (!domain.IsEmpty()) {
+ if (!mTrustedMailDomains.IsEmpty() &&
+ MsgHostDomainIsTrusted(domain, mTrustedMailDomains)) {
+ *aResult = true;
+ return NS_OK;
+ }
+
+ if (mInhibitWhiteListingIdentityDomain) {
+ for (uint32_t i = 0; i < mEmails.Length(); ++i) {
+ nsAutoCString identityDomain;
+ int32_t atPos = mEmails[i].FindChar('@');
+ if (atPos >= 0) {
+ identityDomain = Substring(mEmails[i], atPos + 1);
+ if (identityDomain.Equals(domain,
+ nsCaseInsensitiveCStringComparator))
+ return NS_OK; // don't whitelist
+ }
+ }
+ }
+ }
+ }
+
+ if (mWhiteListDirArray.Count()) {
+ nsCOMPtr<nsIAbCard> cardForAddress;
+ for (int32_t index = 0;
+ index < mWhiteListDirArray.Count() && !cardForAddress; index++) {
+ mWhiteListDirArray[index]->CardForEmailAddress(
+ authorEmailAddress, getter_AddRefs(cardForAddress));
+ }
+ if (cardForAddress) {
+ *aResult = true;
+ return NS_OK;
+ }
+ }
+ return NS_OK; // default return is false
+}
diff --git a/comm/mailnews/base/src/nsSpamSettings.h b/comm/mailnews/base/src/nsSpamSettings.h
new file mode 100644
index 0000000000..56725f7fe4
--- /dev/null
+++ b/comm/mailnews/base/src/nsSpamSettings.h
@@ -0,0 +1,68 @@
+/* -*- 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 nsSpamSettings_h__
+#define nsSpamSettings_h__
+
+#include "nsCOMPtr.h"
+#include "nsISpamSettings.h"
+#include "nsString.h"
+#include "nsIOutputStream.h"
+#include "nsIMsgIncomingServer.h"
+#include "nsIUrlListener.h"
+#include "nsCOMArray.h"
+#include "nsIAbDirectory.h"
+#include "nsTArray.h"
+
+class nsSpamSettings : public nsISpamSettings, public nsIUrlListener {
+ public:
+ nsSpamSettings();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISPAMSETTINGS
+ NS_DECL_NSIURLLISTENER
+
+ private:
+ virtual ~nsSpamSettings();
+
+ nsCOMPtr<nsIOutputStream> mLogStream;
+ nsCOMPtr<nsIFile> mLogFile;
+
+ int32_t mLevel;
+ int32_t mPurgeInterval;
+ int32_t mMoveTargetMode;
+
+ bool mPurge;
+ bool mUseWhiteList;
+ bool mMoveOnSpam;
+ bool mUseServerFilter;
+
+ nsCString mActionTargetAccount;
+ nsCString mActionTargetFolder;
+ nsCString mWhiteListAbURI;
+ // used to detect changes to the spam folder in ::initialize
+ nsCString mCurrentJunkFolderURI;
+
+ nsCString mServerFilterName;
+ nsCOMPtr<nsIFile> mServerFilterFile;
+ int32_t mServerFilterTrustFlags;
+
+ // array of address directories to use in junk whitelisting
+ nsCOMArray<nsIAbDirectory> mWhiteListDirArray;
+ // mail domains to use in junk whitelisting
+ nsCString mTrustedMailDomains;
+ // should we inhibit whitelisting address of identity?
+ bool mInhibitWhiteListingIdentityUser;
+ // should we inhibit whitelisting domain of identity?
+ bool mInhibitWhiteListingIdentityDomain;
+ // email addresses associated with this server
+ nsTArray<nsCString> mEmails;
+
+ // helper routine used by Initialize which unsets the junk flag on the
+ // previous junk folder for this account, and sets it on the new junk folder.
+ nsresult UpdateJunkFolderState();
+};
+
+#endif /* nsSpamSettings_h__ */
diff --git a/comm/mailnews/base/src/nsStatusBarBiffManager.cpp b/comm/mailnews/base/src/nsStatusBarBiffManager.cpp
new file mode 100644
index 0000000000..df85a7bcee
--- /dev/null
+++ b/comm/mailnews/base/src/nsStatusBarBiffManager.cpp
@@ -0,0 +1,254 @@
+/* -*- 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 "nsStatusBarBiffManager.h"
+#include "nsMsgBiffManager.h"
+#include "nsIMsgMailSession.h"
+#include "nsIMsgAccountManager.h"
+#include "nsIObserverService.h"
+#include "nsIWindowMediator.h"
+#include "nsIMsgMailSession.h"
+#include "MailNewsTypes.h"
+#include "nsIMsgFolder.h" // TO include biffState enum. Change to bool later...
+#include "nsMsgDBFolder.h"
+#include "nsIFileChannel.h"
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+#include "nsIURL.h"
+#include "nsNetUtil.h"
+#include "nsIFileURL.h"
+#include "nsIFile.h"
+#include "nsMsgUtils.h"
+#include "mozilla/Services.h"
+
+// QueryInterface, AddRef, and Release
+//
+NS_IMPL_ISUPPORTS(nsStatusBarBiffManager, nsIStatusBarBiffManager,
+ nsIFolderListener, nsIObserver)
+
+nsStatusBarBiffManager::nsStatusBarBiffManager()
+ : mInitialized(false),
+ mCurrentBiffState(nsIMsgFolder::nsMsgBiffState_Unknown) {}
+
+nsStatusBarBiffManager::~nsStatusBarBiffManager() {}
+
+#define NEW_MAIL_PREF_BRANCH "mail.biff."
+#define CHAT_PREF_BRANCH "mail.chat."
+#define FEED_PREF_BRANCH "mail.feed."
+#define PREF_PLAY_SOUND "play_sound"
+#define PREF_SOUND_URL "play_sound.url"
+#define PREF_SOUND_TYPE "play_sound.type"
+#define SYSTEM_SOUND_TYPE 0
+#define CUSTOM_SOUND_TYPE 1
+#define PREF_CHAT_ENABLED "mail.chat.enabled"
+#define PLAY_CHAT_NOTIFICATION_SOUND "play-chat-notification-sound"
+
+nsresult nsStatusBarBiffManager::Init() {
+ if (mInitialized) return NS_ERROR_ALREADY_INITIALIZED;
+
+ nsresult rv;
+
+ nsCOMPtr<nsIMsgMailSession> mailSession =
+ do_GetService("@mozilla.org/messenger/services/session;1", &rv);
+ if (NS_SUCCEEDED(rv))
+ mailSession->AddFolderListener(this, nsIFolderListener::intPropertyChanged);
+
+ nsCOMPtr<nsIPrefBranch> pref(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool chatEnabled = false;
+ if (NS_SUCCEEDED(rv)) rv = pref->GetBoolPref(PREF_CHAT_ENABLED, &chatEnabled);
+ if (NS_SUCCEEDED(rv) && chatEnabled) {
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (observerService)
+ observerService->AddObserver(this, PLAY_CHAT_NOTIFICATION_SOUND, false);
+ }
+
+ mInitialized = true;
+ return NS_OK;
+}
+
+nsresult nsStatusBarBiffManager::PlayBiffSound(const char* aPrefBranch) {
+ nsresult rv;
+ nsCOMPtr<nsIPrefService> prefSvc =
+ (do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIPrefBranch> pref;
+ rv = prefSvc->GetBranch(aPrefBranch, getter_AddRefs(pref));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool playSound;
+ if (mServerType.EqualsLiteral("rss")) {
+ nsCOMPtr<nsIPrefBranch> prefFeed;
+ rv = prefSvc->GetBranch(FEED_PREF_BRANCH, getter_AddRefs(prefFeed));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = prefFeed->GetBoolPref(PREF_PLAY_SOUND, &playSound);
+ } else {
+ rv = pref->GetBoolPref(PREF_PLAY_SOUND, &playSound);
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!playSound) return NS_OK;
+
+ // lazily create the sound instance
+ if (!mSound) mSound = do_CreateInstance("@mozilla.org/sound;1");
+
+ int32_t soundType = SYSTEM_SOUND_TYPE;
+ rv = pref->GetIntPref(PREF_SOUND_TYPE, &soundType);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+#ifndef XP_MACOSX
+ bool customSoundPlayed = false;
+#endif
+
+ if (soundType == CUSTOM_SOUND_TYPE) {
+ nsCString soundURLSpec;
+ rv = pref->GetCharPref(PREF_SOUND_URL, soundURLSpec);
+
+ if (NS_SUCCEEDED(rv) && !soundURLSpec.IsEmpty()) {
+ if (!strncmp(soundURLSpec.get(), "file://", 7)) {
+ nsCOMPtr<nsIURI> fileURI;
+ rv = NS_NewURI(getter_AddRefs(fileURI), soundURLSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIFileURL> soundURL = do_QueryInterface(fileURI, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIFile> soundFile;
+ rv = soundURL->GetFile(getter_AddRefs(soundFile));
+ if (NS_SUCCEEDED(rv)) {
+ bool soundFileExists = false;
+ rv = soundFile->Exists(&soundFileExists);
+ if (NS_SUCCEEDED(rv) && soundFileExists) {
+ rv = mSound->Play(soundURL);
+#ifndef XP_MACOSX
+ if (NS_SUCCEEDED(rv)) customSoundPlayed = true;
+#endif
+ }
+ }
+ }
+ }
+ // XXX TODO: See if we can create a nsIFile using the string as a native
+ // path.
+ }
+ }
+#ifndef XP_MACOSX
+ // if nothing played, play the default system sound
+ if (!customSoundPlayed) {
+ rv = mSound->PlayEventSound(nsISound::EVENT_NEW_MAIL_RECEIVED);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+#endif
+ return rv;
+}
+
+// nsIFolderListener methods....
+NS_IMETHODIMP
+nsStatusBarBiffManager::OnFolderAdded(nsIMsgFolder* parent,
+ nsIMsgFolder* child) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStatusBarBiffManager::OnMessageAdded(nsIMsgFolder* parent, nsIMsgDBHdr* msg) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStatusBarBiffManager::OnFolderRemoved(nsIMsgFolder* parent,
+ nsIMsgFolder* child) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStatusBarBiffManager::OnMessageRemoved(nsIMsgFolder* parent,
+ nsIMsgDBHdr* msg) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStatusBarBiffManager::OnFolderPropertyChanged(nsIMsgFolder* folder,
+ const nsACString& property,
+ const nsACString& oldValue,
+ const nsACString& newValue) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStatusBarBiffManager::OnFolderIntPropertyChanged(nsIMsgFolder* folder,
+ const nsACString& property,
+ int64_t oldValue,
+ int64_t newValue) {
+ if (property.Equals(kBiffState) && mCurrentBiffState != newValue) {
+ // if we got new mail, attempt to play a sound.
+ // if we fail along the way, don't return.
+ // we still need to update the UI.
+ if (newValue == nsIMsgFolder::nsMsgBiffState_NewMail) {
+ // Get the folder's server type.
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ nsresult rv = folder->GetServer(getter_AddRefs(server));
+ if (NS_SUCCEEDED(rv) && server) server->GetType(mServerType);
+
+ // if we fail to play the biff sound, keep going.
+ (void)PlayBiffSound(NEW_MAIL_PREF_BRANCH);
+ }
+ mCurrentBiffState = newValue;
+
+ // don't care if notification fails
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+
+ if (observerService)
+ observerService->NotifyObservers(
+ static_cast<nsIStatusBarBiffManager*>(this),
+ "mail:biff-state-changed", nullptr);
+ } else if (property.Equals(kNewMailReceived)) {
+ (void)PlayBiffSound(NEW_MAIL_PREF_BRANCH);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStatusBarBiffManager::OnFolderBoolPropertyChanged(nsIMsgFolder* folder,
+ const nsACString& property,
+ bool oldValue,
+ bool newValue) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStatusBarBiffManager::OnFolderUnicharPropertyChanged(
+ nsIMsgFolder* folder, const nsACString& property, const nsAString& oldValue,
+ const nsAString& newValue) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStatusBarBiffManager::OnFolderPropertyFlagChanged(nsIMsgDBHdr* msg,
+ const nsACString& property,
+ uint32_t oldFlag,
+ uint32_t newFlag) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStatusBarBiffManager::OnFolderEvent(nsIMsgFolder* folder,
+ const nsACString& event) {
+ return NS_OK;
+}
+
+// nsIObserver implementation
+NS_IMETHODIMP
+nsStatusBarBiffManager::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) {
+ return PlayBiffSound(CHAT_PREF_BRANCH);
+}
+
+// nsIStatusBarBiffManager method....
+NS_IMETHODIMP
+nsStatusBarBiffManager::GetBiffState(int32_t* aBiffState) {
+ NS_ENSURE_ARG_POINTER(aBiffState);
+ *aBiffState = mCurrentBiffState;
+ return NS_OK;
+}
diff --git a/comm/mailnews/base/src/nsStatusBarBiffManager.h b/comm/mailnews/base/src/nsStatusBarBiffManager.h
new file mode 100644
index 0000000000..bd4d5d5bed
--- /dev/null
+++ b/comm/mailnews/base/src/nsStatusBarBiffManager.h
@@ -0,0 +1,37 @@
+/* -*- 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 nsStatusBarBiffManager_h__
+#define nsStatusBarBiffManager_h__
+
+#include "nsIStatusBarBiffManager.h"
+
+#include "msgCore.h"
+#include "nsCOMPtr.h"
+#include "nsISound.h"
+#include "nsIObserver.h"
+
+class nsStatusBarBiffManager : public nsIStatusBarBiffManager,
+ public nsIObserver {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIFOLDERLISTENER
+ NS_DECL_NSISTATUSBARBIFFMANAGER
+ NS_DECL_NSIOBSERVER
+
+ nsStatusBarBiffManager();
+ nsresult Init();
+
+ private:
+ virtual ~nsStatusBarBiffManager();
+
+ bool mInitialized;
+ int32_t mCurrentBiffState;
+ nsCString mServerType;
+ nsCOMPtr<nsISound> mSound;
+ nsresult PlayBiffSound(const char* aPrefBranch);
+};
+
+#endif // nsStatusBarBiffManager_h__
diff --git a/comm/mailnews/base/src/nsStopwatch.cpp b/comm/mailnews/base/src/nsStopwatch.cpp
new file mode 100644
index 0000000000..585450d113
--- /dev/null
+++ b/comm/mailnews/base/src/nsStopwatch.cpp
@@ -0,0 +1,164 @@
+/* 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 "nsStopwatch.h"
+
+#include <stdio.h>
+#include <time.h>
+#if defined(XP_UNIX)
+# include <unistd.h>
+# include <sys/times.h>
+# include <sys/time.h>
+# include <errno.h>
+#elif defined(XP_WIN)
+# include "windows.h"
+#endif // elif defined(XP_WIN)
+
+#include "nsMemory.h"
+/*
+ * This basis for the logic in this file comes from (will used to come from):
+ * (mozilla/)modules/libutil/public/stopwatch.cpp.
+ *
+ * It was no longer used in the mozilla tree, and is being migrated to
+ * comm-central where we actually have a need for it. ("Being" in the sense
+ * that it will not be removed immediately from mozilla-central.)
+ *
+ * Simplification and general clean-up has been performed and the fix for
+ * bug 96669 has been integrated.
+ */
+
+NS_IMPL_ISUPPORTS(nsStopwatch, nsIStopwatch)
+
+#if defined(XP_UNIX)
+/** the number of ticks per second */
+static double gTicks = 0;
+# define MICRO_SECONDS_TO_SECONDS_MULT static_cast<double>(1.0e-6)
+#elif defined(WIN32)
+# ifdef DEBUG
+# include "nsPrintfCString.h"
+# endif
+// 1 tick per 100ns = 10 per us = 10 * 1,000 per ms = 10 * 1,000 * 1,000 per
+// sec.
+# define WIN32_TICK_RESOLUTION static_cast<double>(1.0e-7)
+// subtract off to get to the unix epoch
+# define UNIX_EPOCH_IN_FILE_TIME 116444736000000000L
+#endif // elif defined(WIN32)
+
+nsStopwatch::nsStopwatch()
+ : fTotalRealTimeSecs(0.0), fTotalCpuTimeSecs(0.0), fRunning(false) {
+#if defined(XP_UNIX)
+ // idempotent in the event of a race under all coherency models
+ if (!gTicks) {
+ // we need to clear errno because sysconf's spec says it leaves it the same
+ // on success and only sets it on failure.
+ errno = 0;
+ gTicks = (clock_t)sysconf(_SC_CLK_TCK);
+ // in event of failure, pick an arbitrary value so we don't divide by zero.
+ if (errno) gTicks = 1000000L;
+ }
+#endif
+}
+
+nsStopwatch::~nsStopwatch() {}
+
+NS_IMETHODIMP nsStopwatch::Start() {
+ fTotalRealTimeSecs = 0.0;
+ fTotalCpuTimeSecs = 0.0;
+ return Resume();
+}
+
+NS_IMETHODIMP nsStopwatch::Stop() {
+ fStopRealTimeSecs = GetRealTime();
+ fStopCpuTimeSecs = GetCPUTime();
+ if (fRunning) {
+ fTotalCpuTimeSecs += fStopCpuTimeSecs - fStartCpuTimeSecs;
+ fTotalRealTimeSecs += fStopRealTimeSecs - fStartRealTimeSecs;
+ }
+ fRunning = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsStopwatch::Resume() {
+ if (!fRunning) {
+ fStartRealTimeSecs = GetRealTime();
+ fStartCpuTimeSecs = GetCPUTime();
+ }
+ fRunning = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsStopwatch::GetCpuTimeSeconds(double* result) {
+ NS_ENSURE_ARG_POINTER(result);
+ *result = fTotalCpuTimeSecs;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsStopwatch::GetRealTimeSeconds(double* result) {
+ NS_ENSURE_ARG_POINTER(result);
+ *result = fTotalRealTimeSecs;
+ return NS_OK;
+}
+
+double nsStopwatch::GetRealTime() {
+#if defined(XP_UNIX)
+ struct timeval t;
+ gettimeofday(&t, NULL);
+ return t.tv_sec + t.tv_usec * MICRO_SECONDS_TO_SECONDS_MULT;
+#elif defined(WIN32)
+ union {
+ FILETIME ftFileTime;
+ __int64 ftInt64;
+ } ftRealTime; // time the process has spent in kernel mode
+ SYSTEMTIME st;
+ GetSystemTime(&st);
+ SystemTimeToFileTime(&st, &ftRealTime.ftFileTime);
+ return (ftRealTime.ftInt64 - UNIX_EPOCH_IN_FILE_TIME) * WIN32_TICK_RESOLUTION;
+#else
+# error "nsStopwatch not supported on this platform."
+#endif
+}
+
+double nsStopwatch::GetCPUTime() {
+#if defined(XP_UNIX)
+ struct tms cpt;
+ times(&cpt);
+ return (double)(cpt.tms_utime + cpt.tms_stime) / gTicks;
+#elif defined(WIN32)
+ FILETIME ftCreate, // when the process was created
+ ftExit; // when the process exited
+
+ union {
+ FILETIME ftFileTime;
+ __int64 ftInt64;
+ } ftKernel; // time the process has spent in kernel mode
+
+ union {
+ FILETIME ftFileTime;
+ __int64 ftInt64;
+ } ftUser; // time the process has spent in user mode
+
+ HANDLE hProcess = GetCurrentProcess();
+# ifdef DEBUG
+ BOOL ret =
+# endif
+ GetProcessTimes(hProcess, &ftCreate, &ftExit, &ftKernel.ftFileTime,
+ &ftUser.ftFileTime);
+# ifdef DEBUG
+ if (!ret)
+ NS_ERROR(nsPrintfCString("GetProcessTimes() failed, error=0x%lx.",
+ GetLastError())
+ .get());
+# endif
+
+ /*
+ * Process times are returned in a 64-bit structure, as the number of
+ * 100 nanosecond ticks since 1 January 1601. User mode and kernel mode
+ * times for this process are in separate 64-bit structures.
+ * Add them and convert the result to seconds.
+ */
+ return (ftKernel.ftInt64 + ftUser.ftInt64) * WIN32_TICK_RESOLUTION;
+#else
+# error "nsStopwatch not supported on this platform."
+#endif
+}
diff --git a/comm/mailnews/base/src/nsStopwatch.h b/comm/mailnews/base/src/nsStopwatch.h
new file mode 100644
index 0000000000..9873519809
--- /dev/null
+++ b/comm/mailnews/base/src/nsStopwatch.h
@@ -0,0 +1,51 @@
+/* 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 _nsStopwatch_h_
+#define _nsStopwatch_h_
+
+#include "nsIStopwatch.h"
+
+#include "msgCore.h"
+
+#define NS_STOPWATCH_CID \
+ { \
+ 0x6ef7eafd, 0x72d0, 0x4c56, { \
+ 0x94, 0x09, 0x67, 0xe1, 0x6d, 0x0f, 0x25, 0x5b \
+ } \
+ }
+
+#define NS_STOPWATCH_CONTRACTID "@mozilla.org/stopwatch;1"
+
+class nsStopwatch : public nsIStopwatch {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISTOPWATCH
+
+ nsStopwatch();
+
+ private:
+ virtual ~nsStopwatch();
+
+ /// Wall-clock start time in seconds since unix epoch.
+ double fStartRealTimeSecs;
+ /// Wall-clock stop time in seconds since unix epoch.
+ double fStopRealTimeSecs;
+ /// CPU-clock start time in seconds (of CPU time used since app start)
+ double fStartCpuTimeSecs;
+ /// CPU-clock stop time in seconds (of CPU time used since app start)
+ double fStopCpuTimeSecs;
+ /// Total wall-clock time elapsed in seconds.
+ double fTotalRealTimeSecs;
+ /// Total CPU time elapsed in seconds.
+ double fTotalCpuTimeSecs;
+
+ /// Is the timer running?
+ bool fRunning;
+
+ static double GetRealTime();
+ static double GetCPUTime();
+};
+
+#endif // _nsStopwatch_h_
diff --git a/comm/mailnews/base/src/nsSubscribableServer.cpp b/comm/mailnews/base/src/nsSubscribableServer.cpp
new file mode 100644
index 0000000000..043ef2e4d3
--- /dev/null
+++ b/comm/mailnews/base/src/nsSubscribableServer.cpp
@@ -0,0 +1,867 @@
+/* -*- 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 "nsSubscribableServer.h"
+#include "nsIMsgIncomingServer.h"
+#include "nsIServiceManager.h"
+#include "nsMsgI18N.h"
+#include "nsMsgUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "nsTreeColumns.h"
+#include "mozilla/dom/DataTransfer.h"
+
+/**
+ * The basic structure for the tree of the implementation.
+ *
+ * These elements are stored in reverse alphabetical order.
+ * Each node owns its children and subsequent siblings.
+ */
+struct SubscribeTreeNode {
+ nsCString name;
+ nsCString path;
+ bool isSubscribed;
+ SubscribeTreeNode* prevSibling;
+ SubscribeTreeNode* nextSibling;
+ SubscribeTreeNode* firstChild;
+ SubscribeTreeNode* lastChild;
+ SubscribeTreeNode* parent;
+ // Stores the child considered most likely to be next searched for - usually
+ // the most recently-added child. If names match the search can early-out.
+ SubscribeTreeNode* cachedChild;
+ bool isSubscribable;
+ bool isOpen;
+};
+
+nsSubscribableServer::nsSubscribableServer(void) {
+ mDelimiter = '.';
+ mShowFullName = true;
+ mTreeRoot = nullptr;
+ mStopped = false;
+}
+
+nsresult nsSubscribableServer::Init() { return NS_OK; }
+
+nsSubscribableServer::~nsSubscribableServer(void) {
+ FreeRows();
+ if (mTreeRoot) {
+ FreeSubtree(mTreeRoot);
+ }
+}
+
+NS_IMPL_ISUPPORTS(nsSubscribableServer, nsISubscribableServer, nsITreeView)
+
+NS_IMETHODIMP
+nsSubscribableServer::SetIncomingServer(nsIMsgIncomingServer* aServer) {
+ if (!aServer) {
+ mIncomingServerUri.AssignLiteral("");
+ mServerType.Truncate();
+ return NS_OK;
+ }
+ aServer->GetType(mServerType);
+
+ // We intentionally do not store a pointer to the aServer here
+ // as it would create reference loops, because nsIImapIncomingServer
+ // and nsINntpIncomingServer keep a reference to an internal
+ // nsISubscribableServer object.
+ // We only need the URI of the server anyway.
+ return aServer->GetServerURI(mIncomingServerUri);
+}
+
+NS_IMETHODIMP
+nsSubscribableServer::GetDelimiter(char* aDelimiter) {
+ NS_ENSURE_ARG_POINTER(aDelimiter);
+ *aDelimiter = mDelimiter;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSubscribableServer::SetDelimiter(char aDelimiter) {
+ mDelimiter = aDelimiter;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSubscribableServer::SetAsSubscribed(const nsACString& path) {
+ nsresult rv = NS_OK;
+
+ SubscribeTreeNode* node = nullptr;
+ rv = FindAndCreateNode(path, &node);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ASSERTION(node, "didn't find the node");
+ if (!node) return NS_ERROR_FAILURE;
+ node->isSubscribable = true;
+ node->isSubscribed = true;
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsSubscribableServer::AddTo(const nsACString& aName, bool aAddAsSubscribed,
+ bool aSubscribable, bool aChangeIfExists) {
+ nsresult rv = NS_OK;
+
+ if (mStopped) {
+ return NS_ERROR_FAILURE;
+ }
+
+ SubscribeTreeNode* node = nullptr;
+
+ // todo, shouldn't we pass in aAddAsSubscribed, for the
+ // default value if we create it?
+ rv = FindAndCreateNode(aName, &node);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ASSERTION(node, "didn't find the node");
+ if (!node) return NS_ERROR_FAILURE;
+
+ if (aChangeIfExists) {
+ node->isSubscribed = aAddAsSubscribed;
+ }
+
+ node->isSubscribable = aSubscribable;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSubscribableServer::SetState(const nsACString& aPath, bool aState,
+ bool* aStateChanged) {
+ nsresult rv = NS_OK;
+ NS_ASSERTION(!aPath.IsEmpty() && aStateChanged, "no path or stateChanged");
+ if (aPath.IsEmpty() || !aStateChanged) return NS_ERROR_NULL_POINTER;
+
+ NS_ASSERTION(mozilla::IsUtf8(aPath), "aPath is not in UTF-8");
+
+ *aStateChanged = false;
+
+ SubscribeTreeNode* node = nullptr;
+ rv = FindAndCreateNode(aPath, &node);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ASSERTION(node, "didn't find the node");
+ if (!node) return NS_ERROR_FAILURE;
+
+ NS_ASSERTION(node->isSubscribable, "fix this");
+ if (!node->isSubscribable) {
+ return NS_OK;
+ }
+
+ if (node->isSubscribed == aState) {
+ return NS_OK;
+ } else {
+ node->isSubscribed = aState;
+ *aStateChanged = true;
+
+ // Repaint the tree row to show/clear the check mark.
+ if (mTree) {
+ bool dummy;
+ int32_t index = GetRow(node, &dummy);
+ if (index >= 0) mTree->InvalidateRow(index);
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSubscribableServer::SetSubscribeListener(nsISubscribeListener* aListener) {
+ mSubscribeListener = aListener;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSubscribableServer::GetSubscribeListener(nsISubscribeListener** aListener) {
+ NS_ENSURE_ARG_POINTER(aListener);
+ NS_IF_ADDREF(*aListener = mSubscribeListener);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSubscribableServer::SubscribeCleanup() {
+ NS_ASSERTION(false, "override this.");
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsSubscribableServer::StartPopulatingWithUri(nsIMsgWindow* aMsgWindow,
+ bool aForceToServer,
+ const nsACString& uri) {
+ mStopped = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSubscribableServer::StartPopulating(nsIMsgWindow* aMsgWindow,
+ bool aForceToServer,
+ bool aGetOnlyNew /*ignored*/) {
+ mStopped = false;
+
+ FreeRows();
+ if (mTreeRoot) {
+ FreeSubtree(mTreeRoot);
+ mTreeRoot = nullptr;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSubscribableServer::StopPopulating(nsIMsgWindow* aMsgWindow) {
+ mStopped = true;
+
+ FreeRows();
+
+ if (mTreeRoot) {
+ SubscribeTreeNode* node = mTreeRoot->lastChild;
+ // Add top level items as closed.
+ while (node) {
+ node->isOpen = false;
+ mRowMap.AppendElement(node);
+ node = node->prevSibling;
+ }
+
+ // Invalidate the whole thing.
+ if (mTree) mTree->RowCountChanged(0, mRowMap.Length());
+
+ // Open all the top level items if they are containers.
+ uint32_t topRows = mRowMap.Length();
+ for (int32_t i = topRows - 1; i >= 0; i--) {
+ bool isContainer = false;
+ IsContainer(i, &isContainer);
+ if (isContainer) ToggleOpenState(i);
+ }
+ }
+
+ if (mSubscribeListener) mSubscribeListener->OnDonePopulating();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSubscribableServer::UpdateSubscribed() {
+ NS_ASSERTION(false, "override this.");
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsSubscribableServer::Subscribe(const char16_t* aName) {
+ NS_ASSERTION(false, "override this.");
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsSubscribableServer::Unsubscribe(const char16_t* aName) {
+ NS_ASSERTION(false, "override this.");
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsSubscribableServer::SetShowFullName(bool showFullName) {
+ mShowFullName = showFullName;
+ return NS_OK;
+}
+
+void nsSubscribableServer::FreeSubtree(SubscribeTreeNode* node) {
+ // NOTE: this doesn't cleanly detach each node, but that's not an issue
+ // here. This is a nuking, not a surgical removal.
+
+ // recursively free the children
+ if (node->firstChild) {
+ // will free node->firstChild
+ FreeSubtree(node->firstChild);
+ node->firstChild = nullptr;
+ }
+
+ // recursively free the siblings
+ if (node->nextSibling) {
+ FreeSubtree(node->nextSibling);
+ node->nextSibling = nullptr;
+ }
+
+ delete node;
+}
+
+void nsSubscribableServer::FreeRows() {
+ int32_t rowCount = mRowMap.Length();
+ mRowMap.Clear();
+ if (mTree) mTree->RowCountChanged(0, -rowCount);
+}
+
+SubscribeTreeNode* nsSubscribableServer::CreateNode(SubscribeTreeNode* parent,
+ nsACString const& name,
+ nsACString const& path) {
+ SubscribeTreeNode* node = new SubscribeTreeNode();
+
+ node->name.Assign(name);
+ node->path.Assign(path);
+ node->parent = parent;
+ node->prevSibling = nullptr;
+ node->nextSibling = nullptr;
+ node->firstChild = nullptr;
+ node->lastChild = nullptr;
+ node->isSubscribed = false;
+ node->isSubscribable = false;
+ node->isOpen = true;
+ node->cachedChild = nullptr;
+ if (parent) {
+ parent->cachedChild = node;
+ }
+ return node;
+}
+
+nsresult nsSubscribableServer::AddChildNode(SubscribeTreeNode* parent,
+ nsACString const& name,
+ const nsACString& path,
+ SubscribeTreeNode** child) {
+ nsresult rv = NS_OK;
+ NS_ASSERTION(parent && child && !name.IsEmpty() && !path.IsEmpty(),
+ "parent, child or name is null");
+ if (!parent || !child || name.IsEmpty() || path.IsEmpty())
+ return NS_ERROR_NULL_POINTER;
+
+ // Adding to a node with no children?
+ if (!parent->firstChild) {
+ // CreateNode will set the parent->cachedChild
+ *child = CreateNode(parent, name, path);
+ parent->firstChild = *child;
+ parent->lastChild = *child;
+ return NS_OK;
+ }
+
+ // Did we just add a child of this name?
+ if (parent->cachedChild) {
+ if (parent->cachedChild->name.Equals(name)) {
+ *child = parent->cachedChild;
+ return NS_OK;
+ }
+ }
+
+ SubscribeTreeNode* current = parent->firstChild;
+
+ /*
+ * Insert in reverse alphabetical order.
+ * This will reduce the # of strcmps since this is faster assuming:
+ * 1) the hostinfo.dat feeds us the groups in alphabetical order
+ * since we control the hostinfo.dat file, we can guarantee this.
+ * 2) the server gives us the groups in alphabetical order
+ * we can't guarantee this, but it seems to be a common thing.
+ *
+ * Because we have firstChild, lastChild, nextSibling, prevSibling,
+ * we can efficiently reverse the order when dumping to hostinfo.dat
+ * or to GetTargets().
+ */
+ int32_t compare = Compare(current->name, name);
+
+ while (current && (compare != 0)) {
+ if (compare < 0) {
+ // CreateNode will set the parent->cachedChild
+ *child = CreateNode(parent, name, path);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ (*child)->nextSibling = current;
+ (*child)->prevSibling = current->prevSibling;
+ current->prevSibling = (*child);
+ if (!(*child)->prevSibling) {
+ parent->firstChild = (*child);
+ } else {
+ (*child)->prevSibling->nextSibling = (*child);
+ }
+
+ return NS_OK;
+ }
+ current = current->nextSibling;
+ if (current) {
+ NS_ASSERTION(!current->name.IsEmpty(), "no name!");
+ compare = Compare(current->name, name);
+ } else {
+ compare = -1; // anything but 0, since that would be a match
+ }
+ }
+
+ if (compare == 0) {
+ // already exists;
+ *child = current;
+ parent->cachedChild = *child;
+ return NS_OK;
+ }
+
+ // CreateNode will set the parent->cachedChild
+ *child = CreateNode(parent, name, path);
+
+ (*child)->prevSibling = parent->lastChild;
+ (*child)->nextSibling = nullptr;
+ parent->lastChild->nextSibling = *child;
+ parent->lastChild = *child;
+
+ return NS_OK;
+}
+
+nsresult nsSubscribableServer::FindAndCreateNode(const nsACString& aPath,
+ SubscribeTreeNode** aResult) {
+ nsresult rv = NS_OK;
+ NS_ASSERTION(aResult, "no result");
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ if (!mTreeRoot) {
+ // the root has no parent, and its name is server uri
+ mTreeRoot = CreateNode(nullptr, mIncomingServerUri, EmptyCString());
+ }
+
+ if (aPath.IsEmpty()) {
+ *aResult = mTreeRoot;
+ return NS_OK;
+ }
+
+ *aResult = nullptr;
+
+ SubscribeTreeNode* parent = mTreeRoot;
+ SubscribeTreeNode* child = nullptr;
+
+ uint32_t tokenStart = 0;
+ // Special case paths that start with the hierarchy delimiter.
+ // We want to include that delimiter in the first token name.
+ // So start from position 1.
+ int32_t tokenEnd = aPath.FindChar(mDelimiter, tokenStart + 1);
+ while (true) {
+ if (tokenEnd == kNotFound) {
+ if (tokenStart >= aPath.Length()) break;
+ tokenEnd = aPath.Length();
+ }
+ nsCString token(Substring(aPath, tokenStart, tokenEnd - tokenStart));
+ rv = AddChildNode(parent, token, Substring(aPath, 0, tokenEnd), &child);
+ if (NS_FAILED(rv)) return rv;
+ tokenStart = tokenEnd + 1;
+ tokenEnd = aPath.FindChar(mDelimiter, tokenStart);
+ parent = child;
+ }
+
+ // the last child we add is the result
+ *aResult = child;
+ return rv;
+}
+
+NS_IMETHODIMP
+nsSubscribableServer::HasChildren(const nsACString& aPath, bool* aHasChildren) {
+ NS_ASSERTION(aHasChildren, "no hasChildren");
+ NS_ENSURE_ARG_POINTER(aHasChildren);
+
+ *aHasChildren = false;
+
+ SubscribeTreeNode* node = nullptr;
+ nsresult rv = FindAndCreateNode(aPath, &node);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ASSERTION(node, "didn't find the node");
+ if (!node) return NS_ERROR_FAILURE;
+
+ *aHasChildren = (node->firstChild != nullptr);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSubscribableServer::IsSubscribed(const nsACString& aPath,
+ bool* aIsSubscribed) {
+ NS_ENSURE_ARG_POINTER(aIsSubscribed);
+
+ *aIsSubscribed = false;
+
+ SubscribeTreeNode* node = nullptr;
+ nsresult rv = FindAndCreateNode(aPath, &node);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ASSERTION(node, "didn't find the node");
+ if (!node) return NS_ERROR_FAILURE;
+
+ *aIsSubscribed = node->isSubscribed;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSubscribableServer::IsSubscribable(const nsACString& aPath,
+ bool* aIsSubscribable) {
+ NS_ENSURE_ARG_POINTER(aIsSubscribable);
+
+ *aIsSubscribable = false;
+
+ SubscribeTreeNode* node = nullptr;
+ nsresult rv = FindAndCreateNode(aPath, &node);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ASSERTION(node, "didn't find the node");
+ if (!node) return NS_ERROR_FAILURE;
+
+ *aIsSubscribable = node->isSubscribable;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSubscribableServer::GetLeafName(const nsACString& aPath,
+ nsAString& aLeafName) {
+ SubscribeTreeNode* node = nullptr;
+ nsresult rv = FindAndCreateNode(aPath, &node);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ASSERTION(node, "didn't find the node");
+ if (!node) return NS_ERROR_FAILURE;
+
+ // XXX TODO FIXME
+ // I'm assuming that mShowFullName is true for NNTP, false for IMAP.
+ // For imap, the node name is in MUTF-7; for news, the path is escaped UTF-8.
+ // When we switch to using the tree, this hack will go away.
+ if (mShowFullName) {
+ return NS_MsgDecodeUnescapeURLPath(aPath, aLeafName);
+ }
+
+ return CopyFolderNameToUTF16(node->name, aLeafName);
+}
+
+NS_IMETHODIMP
+nsSubscribableServer::GetFirstChildURI(const nsACString& aPath,
+ nsACString& aResult) {
+ aResult.Truncate();
+
+ SubscribeTreeNode* node = nullptr;
+ nsresult rv = FindAndCreateNode(aPath, &node);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ASSERTION(node, "didn't find the node");
+ if (!node) return NS_ERROR_FAILURE;
+
+ // no children
+ if (!node->firstChild) return NS_ERROR_FAILURE;
+
+ aResult.Assign(node->firstChild->path);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSubscribableServer::GetChildURIs(const nsACString& aPath,
+ nsTArray<nsCString>& aResult) {
+ aResult.Clear();
+ SubscribeTreeNode* node = nullptr;
+ nsresult rv = FindAndCreateNode(aPath, &node);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ASSERTION(node, "didn't find the node");
+ if (!node) return NS_ERROR_FAILURE;
+
+ NS_ASSERTION(mTreeRoot, "no tree root!");
+ if (!mTreeRoot) return NS_ERROR_UNEXPECTED;
+
+ // We inserted them in reverse alphabetical order.
+ // So pull them out in reverse to get the right order
+ // in the subscribe dialog.
+ SubscribeTreeNode* current = node->lastChild;
+ // return failure if there are no children.
+ if (!current) return NS_ERROR_FAILURE;
+
+ while (current) {
+ NS_ASSERTION(!current->name.IsEmpty(), "no name");
+ if (current->name.IsEmpty()) {
+ return NS_ERROR_FAILURE;
+ }
+ aResult.AppendElement(current->path);
+ current = current->prevSibling;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSubscribableServer::CommitSubscribeChanges() {
+ NS_ASSERTION(false, "override this.");
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsSubscribableServer::SetSearchValue(const nsAString& aSearchValue) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsSubscribableServer::GetSupportsSubscribeSearch(bool* retVal) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsSubscribableServer::GetFolderView(nsITreeView** aView) {
+ NS_ENSURE_ARG_POINTER(aView);
+ return this->QueryInterface(NS_GET_IID(nsITreeView), (void**)aView);
+}
+
+int32_t nsSubscribableServer::GetRow(SubscribeTreeNode* node, bool* open) {
+ int32_t parentRow = -1;
+ if (node->parent) parentRow = GetRow(node->parent, open);
+
+ // If the parent wasn't opened, we're not in the row map
+ if (open && *open == false) return -1;
+
+ if (open) *open = node->isOpen;
+
+ for (uint32_t row = parentRow + 1; row < mRowMap.Length(); row++) {
+ if (mRowMap[row] == node) return row;
+ }
+
+ // Apparently, we're not in the map
+ return -1;
+}
+
+NS_IMETHODIMP
+nsSubscribableServer::GetSelection(nsITreeSelection** selection) {
+ NS_IF_ADDREF(*selection = mSelection);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSubscribableServer::SetSelection(nsITreeSelection* selection) {
+ mSelection = selection;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSubscribableServer::GetRowCount(int32_t* rowCount) {
+ *rowCount = mRowMap.Length();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSubscribableServer::SetTree(mozilla::dom::XULTreeElement* aTree) {
+ mTree = aTree;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSubscribableServer::IsContainer(int32_t aIndex, bool* retval) {
+ *retval = !!mRowMap[aIndex]->firstChild;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSubscribableServer::IsContainerEmpty(int32_t aIndex, bool* retval) {
+ *retval = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSubscribableServer::IsContainerOpen(int32_t aIndex, bool* retval) {
+ *retval = mRowMap[aIndex]->isOpen;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSubscribableServer::GetParentIndex(int32_t aIndex, int32_t* retval) {
+ SubscribeTreeNode* parent = mRowMap[aIndex]->parent;
+ if (!parent) {
+ *retval = -1;
+ return NS_OK;
+ }
+
+ int32_t index;
+ for (index = aIndex - 1; index >= 0; index--) {
+ if (mRowMap[index] == parent) {
+ *retval = index;
+ return NS_OK;
+ }
+ }
+ *retval = -1;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSubscribableServer::HasNextSibling(int32_t aRowIndex, int32_t aAfterIndex,
+ bool* retval) {
+ // This looks odd, but is correct. Using ->nextSibling gives a bad tree.
+ *retval = !!mRowMap[aRowIndex]->prevSibling;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSubscribableServer::GetLevel(int32_t aIndex, int32_t* retval) {
+ // When starting with -2, we increase twice and return 0 for a top level node.
+ int32_t level = -2;
+ SubscribeTreeNode* node = mRowMap[aIndex];
+ while (node) {
+ node = node->parent;
+ level++;
+ }
+
+ *retval = level;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSubscribableServer::ToggleOpenState(int32_t aIndex) {
+ SubscribeTreeNode* node = mRowMap[aIndex];
+ if (node->isOpen) {
+ node->isOpen = false;
+
+ // Remove subtree by deleting elements from array up to next sibling.
+ int32_t count = 0;
+ do {
+ // Find next sibling or the beginning of the next subtree.
+ if (node->prevSibling) {
+ count = mRowMap.IndexOf(node->prevSibling, aIndex) - aIndex - 1;
+ } else {
+ node = node->parent;
+ // When node reaches the root, delete the rest of the array.
+ if (!node->parent) {
+ count = mRowMap.Length() - aIndex - 1;
+ }
+ }
+ } while (!count && node->parent);
+ mRowMap.RemoveElementsAt(aIndex + 1, count);
+ if (mTree) {
+ mTree->RowCountChanged(aIndex + 1, -count);
+ mTree->InvalidateRow(aIndex);
+ }
+ } else {
+ // Recursively add the children nodes (i.e., remember open)
+ node->isOpen = true;
+ int32_t total = 0;
+ node = node->lastChild;
+ while (node) {
+ total += AddSubtree(node, aIndex + 1 + total);
+ node = node->prevSibling;
+ }
+ if (mTree) {
+ mTree->RowCountChanged(aIndex + 1, total);
+ mTree->InvalidateRow(aIndex);
+ }
+ }
+ return NS_OK;
+}
+
+int32_t nsSubscribableServer::AddSubtree(SubscribeTreeNode* node,
+ int32_t index) {
+ mRowMap.InsertElementAt(index, node);
+ int32_t total = 1;
+ if (node->isOpen) {
+ node = node->lastChild;
+ while (node) {
+ total += AddSubtree(node, index + total);
+ node = node->prevSibling;
+ }
+ }
+ return total;
+}
+
+NS_IMETHODIMP
+nsSubscribableServer::GetCellText(int32_t aRow, nsTreeColumn* aCol,
+ nsAString& retval) {
+ nsString colId;
+ aCol->GetId(colId);
+ if (colId.EqualsLiteral("nameColumn")) {
+ nsCString path(mRowMap[aRow]->path);
+ GetLeafName(path, retval);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSubscribableServer::GetCellValue(int32_t aRow, nsTreeColumn* aCol,
+ nsAString& retval) {
+ nsString colId;
+ aCol->GetId(colId);
+ if (colId.EqualsLiteral("nameColumn"))
+ retval = NS_ConvertUTF8toUTF16(mRowMap[aRow]->path);
+ if (colId.EqualsLiteral("subscribedColumn")) {
+ retval = mRowMap[aRow]->isSubscribed ? u"true"_ns : u"false"_ns;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSubscribableServer::SetCellText(int32_t aRow, nsTreeColumn* aCol,
+ const nsAString& aText) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSubscribableServer::SetCellValue(int32_t aRow, nsTreeColumn* aCol,
+ const nsAString& aText) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSubscribableServer::GetCellProperties(int32_t aRow, nsTreeColumn* aCol,
+ nsAString& aProps) {
+ SubscribeTreeNode* node = mRowMap[aRow];
+ if (node->isSubscribable)
+ aProps.AssignLiteral("subscribable-true");
+ else
+ aProps.AssignLiteral("subscribable-false");
+
+ nsString colId;
+ aCol->GetId(colId);
+ if (colId.EqualsLiteral("subscribedColumn")) {
+ if (node->isSubscribed)
+ aProps.AppendLiteral(" subscribed-true");
+ else
+ aProps.AppendLiteral(" subscribed-false");
+ } else if (colId.EqualsLiteral("nameColumn")) {
+ aProps.AppendLiteral(" serverType-");
+ aProps.Append(NS_ConvertUTF8toUTF16(mServerType));
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSubscribableServer::GetRowProperties(int32_t aRow, nsAString& aProps) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSubscribableServer::GetColumnProperties(nsTreeColumn* aCol,
+ nsAString& aProps) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSubscribableServer::IsEditable(int32_t aRow, nsTreeColumn* aCol,
+ bool* retval) {
+ *retval = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSubscribableServer::IsSeparator(int32_t aRowIndex, bool* retval) {
+ *retval = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSubscribableServer::IsSorted(bool* retval) {
+ *retval = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSubscribableServer::CanDrop(int32_t aIndex, int32_t aOrientation,
+ mozilla::dom::DataTransfer* aData, bool* retval) {
+ *retval = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSubscribableServer::Drop(int32_t aRow, int32_t aOrientation,
+ mozilla::dom::DataTransfer* aData) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSubscribableServer::GetImageSrc(int32_t aRow, nsTreeColumn* aCol,
+ nsAString& retval) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSubscribableServer::CycleHeader(nsTreeColumn* aCol) { return NS_OK; }
+
+NS_IMETHODIMP
+nsSubscribableServer::SelectionChangedXPCOM() { return NS_OK; }
+
+NS_IMETHODIMP
+nsSubscribableServer::CycleCell(int32_t aRow, nsTreeColumn* aCol) {
+ return NS_OK;
+}
diff --git a/comm/mailnews/base/src/nsSubscribableServer.h b/comm/mailnews/base/src/nsSubscribableServer.h
new file mode 100644
index 0000000000..7fbafd0838
--- /dev/null
+++ b/comm/mailnews/base/src/nsSubscribableServer.h
@@ -0,0 +1,59 @@
+/* -*- 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 nsSubscribableServer_h__
+#define nsSubscribableServer_h__
+
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "mozilla/dom/XULTreeElement.h"
+#include "nsITreeSelection.h"
+#include "nsITreeView.h"
+#include "nsISubscribableServer.h"
+#include "nsTArray.h"
+
+struct SubscribeTreeNode;
+
+class nsSubscribableServer : public nsISubscribableServer, public nsITreeView {
+ public:
+ nsSubscribableServer();
+
+ nsresult Init();
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSISUBSCRIBABLESERVER
+ NS_DECL_NSITREEVIEW
+
+ private:
+ virtual ~nsSubscribableServer();
+
+ nsresult ConvertNameToUnichar(const char* inStr, char16_t** outStr);
+ nsCOMPtr<nsISubscribeListener> mSubscribeListener;
+ nsCString mIncomingServerUri;
+ char mDelimiter;
+ bool mShowFullName;
+ bool mStopped;
+ nsCString mServerType;
+
+ // root of the folder tree while items are discovered on the server
+ SubscribeTreeNode* mTreeRoot;
+ // array of nodes representing the rows for the tree element
+ nsTArray<SubscribeTreeNode*> mRowMap;
+ nsCOMPtr<nsITreeSelection> mSelection;
+ RefPtr<mozilla::dom::XULTreeElement> mTree;
+ void FreeSubtree(SubscribeTreeNode* node);
+ void FreeRows();
+ SubscribeTreeNode* CreateNode(SubscribeTreeNode* parent,
+ nsACString const& name, nsACString const& path);
+ nsresult AddChildNode(SubscribeTreeNode* parent, nsACString const& name,
+ const nsACString& aPath, SubscribeTreeNode** child);
+ nsresult FindAndCreateNode(const nsACString& aPath,
+ SubscribeTreeNode** aResult);
+
+ int32_t GetRow(SubscribeTreeNode* node, bool* open);
+ int32_t AddSubtree(SubscribeTreeNode* node, int32_t index);
+};
+
+#endif // nsSubscribableServer_h__
diff --git a/comm/mailnews/base/src/nsUserInfo.h b/comm/mailnews/base/src/nsUserInfo.h
new file mode 100644
index 0000000000..df3f81adef
--- /dev/null
+++ b/comm/mailnews/base/src/nsUserInfo.h
@@ -0,0 +1,23 @@
+/* -*- 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 __nsUserInfo_h
+#define __nsUserInfo_h
+
+#include "nsIUserInfo.h"
+
+class nsUserInfo : public nsIUserInfo
+
+{
+ public:
+ nsUserInfo(void);
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIUSERINFO
+
+ protected:
+ virtual ~nsUserInfo();
+};
+
+#endif /* __nsUserInfo_h */
diff --git a/comm/mailnews/base/src/nsUserInfoMac.mm b/comm/mailnews/base/src/nsUserInfoMac.mm
new file mode 100644
index 0000000000..a98d1f72ea
--- /dev/null
+++ b/comm/mailnews/base/src/nsUserInfoMac.mm
@@ -0,0 +1,70 @@
+/* -*- Mode: Objective-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 "nsUserInfo.h"
+#include "nsObjCExceptions.h"
+#include "nsString.h"
+#include "nsCocoaUtils.h"
+
+#import <Cocoa/Cocoa.h>
+#import <AddressBook/AddressBook.h>
+
+NS_IMPL_ISUPPORTS(nsUserInfo, nsIUserInfo)
+
+nsUserInfo::nsUserInfo() {}
+
+nsUserInfo::~nsUserInfo() {}
+
+NS_IMETHODIMP
+nsUserInfo::GetFullname(nsAString& aFullname) {
+ NS_OBJC_BEGIN_TRY_IGNORE_BLOCK
+
+ nsCocoaUtils::GetStringForNSString(NSFullUserName(), aFullname);
+
+ NS_OBJC_END_TRY_IGNORE_BLOCK
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsUserInfo::GetUsername(nsAString& aUsername) {
+ NS_OBJC_BEGIN_TRY_IGNORE_BLOCK
+
+ nsCocoaUtils::GetStringForNSString(NSUserName(), aUsername);
+
+ NS_OBJC_END_TRY_IGNORE_BLOCK
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsUserInfo::GetEmailAddress(nsAString& aEmailAddress) {
+ NS_OBJC_BEGIN_TRY_IGNORE_BLOCK
+
+ aEmailAddress.Truncate();
+ // Try to get this user's primary email from the system addressbook's "me card"
+ // (if they've filled it)
+ ABPerson* me = [[ABAddressBook sharedAddressBook] me];
+ ABMultiValue* emailAddresses = [me valueForProperty:kABEmailProperty];
+ if ([emailAddresses count] > 0) {
+ // get the index of the primary email, in case there are more than one
+ int primaryEmailIndex = [emailAddresses indexForIdentifier:[emailAddresses primaryIdentifier]];
+ nsCocoaUtils::GetStringForNSString([emailAddresses valueAtIndex:primaryEmailIndex],
+ aEmailAddress);
+ }
+
+ NS_OBJC_END_TRY_IGNORE_BLOCK
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsUserInfo::GetDomain(nsAString& aDomain) {
+ GetEmailAddress(aDomain);
+ int32_t index = aDomain.FindChar('@');
+ if (index != -1) {
+ // chop off everything before, and including the '@'
+ aDomain.Cut(0, index + 1);
+ }
+ return NS_OK;
+}
diff --git a/comm/mailnews/base/src/nsUserInfoUnix.cpp b/comm/mailnews/base/src/nsUserInfoUnix.cpp
new file mode 100644
index 0000000000..559b68f062
--- /dev/null
+++ b/comm/mailnews/base/src/nsUserInfoUnix.cpp
@@ -0,0 +1,124 @@
+/* -*- 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 "nsUserInfo.h"
+#include "nsCRT.h"
+
+#include <pwd.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <sys/utsname.h>
+
+#include "nsString.h"
+#include "nsReadableUtils.h"
+#include "nsNativeCharsetUtils.h"
+
+nsUserInfo::nsUserInfo() {}
+
+nsUserInfo::~nsUserInfo() {}
+
+NS_IMPL_ISUPPORTS(nsUserInfo, nsIUserInfo)
+
+NS_IMETHODIMP
+nsUserInfo::GetFullname(nsAString& aFullname) {
+ aFullname.Truncate();
+ struct passwd* pw = nullptr;
+
+ pw = getpwuid(geteuid());
+
+ if (!pw || !pw->pw_gecos) return NS_OK;
+
+ nsAutoCString fullname(pw->pw_gecos);
+
+ // now try to parse the GECOS information, which will be in the form
+ // Full Name, <other stuff> - eliminate the ", <other stuff>
+ // also, sometimes GECOS uses "&" to mean "the user name" so do
+ // the appropriate substitution
+
+ // truncate at first comma (field delimiter)
+ int32_t index;
+ if ((index = fullname.Find(",")) != kNotFound) fullname.Truncate(index);
+
+ // replace ampersand with username
+ if (pw->pw_name) {
+ nsAutoCString username(pw->pw_name);
+ if (!username.IsEmpty())
+ username.SetCharAt(nsCRT::ToUpper(username.CharAt(0)), 0);
+
+ fullname.ReplaceSubstring("&", username.get());
+ }
+
+ CopyUTF8toUTF16(fullname, aFullname);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsUserInfo::GetUsername(nsAString& aUsername) {
+ aUsername.Truncate();
+ struct passwd* pw = nullptr;
+
+ // is this portable? those are POSIX compliant calls, but I need to check
+ pw = getpwuid(geteuid());
+
+ if (!pw || !pw->pw_name) return NS_OK;
+
+ CopyUTF8toUTF16(mozilla::MakeStringSpan(pw->pw_name), aUsername);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsUserInfo::GetDomain(nsAString& aDomain) {
+ aDomain.Truncate();
+ struct utsname buf;
+ char* domainname = nullptr;
+
+ if (uname(&buf) < 0) {
+ return NS_OK;
+ }
+
+#if defined(__linux__)
+ domainname = buf.domainname;
+#endif
+
+ if (domainname && domainname[0]) {
+ CopyUTF8toUTF16(mozilla::MakeStringSpan(domainname), aDomain);
+ } else {
+ // try to get the hostname from the nodename
+ // on machines that use DHCP, domainname may not be set
+ // but the nodename might.
+ if (buf.nodename[0]) {
+ // if the nodename is foo.bar.org, use bar.org as the domain
+ char* pos = strchr(buf.nodename, '.');
+ if (pos) {
+ CopyUTF8toUTF16(mozilla::MakeStringSpan(pos + 1), aDomain);
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsUserInfo::GetEmailAddress(nsAString& aEmailAddress) {
+ // use username + "@" + domain for the email address
+
+ nsString username;
+ nsString domain;
+
+ GetUsername(username);
+ GetDomain(domain);
+
+ if (!username.IsEmpty() && !domain.IsEmpty()) {
+ aEmailAddress = username;
+ aEmailAddress.Append('@');
+ aEmailAddress += domain;
+ } else {
+ aEmailAddress.Truncate();
+ }
+
+ return NS_OK;
+}
diff --git a/comm/mailnews/base/src/nsUserInfoWin.cpp b/comm/mailnews/base/src/nsUserInfoWin.cpp
new file mode 100644
index 0000000000..a5b69cf9a2
--- /dev/null
+++ b/comm/mailnews/base/src/nsUserInfoWin.cpp
@@ -0,0 +1,99 @@
+/* -*- 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 "nsUserInfo.h"
+
+#include "mozilla/ArrayUtils.h" // ArrayLength
+#include "nsString.h"
+#include "windows.h"
+#include "nsCRT.h"
+
+#define SECURITY_WIN32
+#include "lm.h"
+#include "security.h"
+
+nsUserInfo::nsUserInfo() {}
+
+nsUserInfo::~nsUserInfo() {}
+
+NS_IMPL_ISUPPORTS(nsUserInfo, nsIUserInfo)
+
+NS_IMETHODIMP
+nsUserInfo::GetUsername(nsAString& aUsername) {
+ aUsername.Truncate();
+
+ // UNLEN is the max username length as defined in lmcons.h
+ wchar_t username[UNLEN + 1];
+ DWORD size = mozilla::ArrayLength(username);
+ if (!GetUserNameW(username, &size)) return NS_OK;
+
+ aUsername.Assign(username);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsUserInfo::GetFullname(nsAString& aFullname) {
+ aFullname.Truncate();
+
+ wchar_t fullName[512];
+ DWORD size = mozilla::ArrayLength(fullName);
+
+ if (GetUserNameExW(NameDisplay, fullName, &size)) {
+ aFullname.Assign(fullName);
+ } else {
+ // Try to use the net APIs regardless of the error because it may be
+ // able to obtain the information.
+ wchar_t username[UNLEN + 1];
+ size = mozilla::ArrayLength(username);
+ if (!GetUserNameW(username, &size)) {
+ return NS_OK;
+ }
+
+ const DWORD level = 2;
+ LPBYTE info;
+ // If the NetUserGetInfo function has no full name info it will return
+ // success with an empty string.
+ NET_API_STATUS status = NetUserGetInfo(nullptr, username, level, &info);
+ if (status != NERR_Success) {
+ return NS_OK;
+ }
+
+ aFullname.Assign(reinterpret_cast<USER_INFO_2*>(info)->usri2_full_name);
+ NetApiBufferFree(info);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsUserInfo::GetDomain(nsAString& aDomain) {
+ aDomain.Truncate();
+
+ const DWORD level = 100;
+ LPBYTE info;
+ NET_API_STATUS status = NetWkstaGetInfo(nullptr, level, &info);
+ if (status == NERR_Success) {
+ aDomain.Assign(reinterpret_cast<WKSTA_INFO_100*>(info)->wki100_langroup);
+ NetApiBufferFree(info);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsUserInfo::GetEmailAddress(nsAString& aEmailAddress) {
+ aEmailAddress.Truncate();
+
+ // RFC3696 says max length of an email address is 254
+ wchar_t emailAddress[255];
+ DWORD size = mozilla::ArrayLength(emailAddress);
+
+ if (!GetUserNameExW(NameUserPrincipal, emailAddress, &size)) {
+ return NS_OK;
+ }
+
+ aEmailAddress.Assign(emailAddress);
+ return NS_OK;
+}