summaryrefslogtreecommitdiffstats
path: root/comm/mailnews/imap
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/imap
parentInitial commit. (diff)
downloadthunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.tar.xz
thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.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 'comm/mailnews/imap')
-rw-r--r--comm/mailnews/imap/public/moz.build29
-rw-r--r--comm/mailnews/imap/public/nsIAutoSyncFolderStrategy.idl23
-rw-r--r--comm/mailnews/imap/public/nsIAutoSyncManager.idl192
-rw-r--r--comm/mailnews/imap/public/nsIAutoSyncMsgStrategy.idl35
-rw-r--r--comm/mailnews/imap/public/nsIAutoSyncState.idl157
-rw-r--r--comm/mailnews/imap/public/nsIImapFlagAndUidState.idl86
-rw-r--r--comm/mailnews/imap/public/nsIImapHeaderXferInfo.idl22
-rw-r--r--comm/mailnews/imap/public/nsIImapHostSessionList.h129
-rw-r--r--comm/mailnews/imap/public/nsIImapIncomingServer.idl114
-rw-r--r--comm/mailnews/imap/public/nsIImapMailFolderSink.idl119
-rw-r--r--comm/mailnews/imap/public/nsIImapMessageSink.idl89
-rw-r--r--comm/mailnews/imap/public/nsIImapMockChannel.idl49
-rw-r--r--comm/mailnews/imap/public/nsIImapOfflineSync.idl19
-rw-r--r--comm/mailnews/imap/public/nsIImapProtocol.idl90
-rw-r--r--comm/mailnews/imap/public/nsIImapProtocolSink.idl33
-rw-r--r--comm/mailnews/imap/public/nsIImapServerSink.idl178
-rw-r--r--comm/mailnews/imap/public/nsIImapService.idl253
-rw-r--r--comm/mailnews/imap/public/nsIImapUrl.idl210
-rw-r--r--comm/mailnews/imap/public/nsIMailboxSpec.idl47
-rw-r--r--comm/mailnews/imap/public/nsIMsgImapMailFolder.idl253
-rw-r--r--comm/mailnews/imap/src/ImapChannel.jsm318
-rw-r--r--comm/mailnews/imap/src/ImapClient.jsm1895
-rw-r--r--comm/mailnews/imap/src/ImapFolderContentHandler.sys.mjs71
-rw-r--r--comm/mailnews/imap/src/ImapIncomingServer.jsm783
-rw-r--r--comm/mailnews/imap/src/ImapMessageService.jsm292
-rw-r--r--comm/mailnews/imap/src/ImapModuleLoader.jsm131
-rw-r--r--comm/mailnews/imap/src/ImapProtocolHandler.jsm40
-rw-r--r--comm/mailnews/imap/src/ImapProtocolInfo.jsm44
-rw-r--r--comm/mailnews/imap/src/ImapResponse.jsm479
-rw-r--r--comm/mailnews/imap/src/ImapService.jsm518
-rw-r--r--comm/mailnews/imap/src/ImapUtils.jsm197
-rw-r--r--comm/mailnews/imap/src/components.conf76
-rw-r--r--comm/mailnews/imap/src/moz.build56
-rw-r--r--comm/mailnews/imap/src/nsAutoSyncManager.cpp1372
-rw-r--r--comm/mailnews/imap/src/nsAutoSyncManager.h265
-rw-r--r--comm/mailnews/imap/src/nsAutoSyncState.cpp782
-rw-r--r--comm/mailnews/imap/src/nsAutoSyncState.h106
-rw-r--r--comm/mailnews/imap/src/nsImapBodyShell.cpp1060
-rw-r--r--comm/mailnews/imap/src/nsImapBodyShell.h357
-rw-r--r--comm/mailnews/imap/src/nsImapCore.h191
-rw-r--r--comm/mailnews/imap/src/nsImapFlagAndUidState.cpp315
-rw-r--r--comm/mailnews/imap/src/nsImapFlagAndUidState.h56
-rw-r--r--comm/mailnews/imap/src/nsImapGenericParser.cpp407
-rw-r--r--comm/mailnews/imap/src/nsImapGenericParser.h74
-rw-r--r--comm/mailnews/imap/src/nsImapHostSessionList.cpp595
-rw-r--r--comm/mailnews/imap/src/nsImapHostSessionList.h169
-rw-r--r--comm/mailnews/imap/src/nsImapIncomingServer.cpp3032
-rw-r--r--comm/mailnews/imap/src/nsImapIncomingServer.h150
-rw-r--r--comm/mailnews/imap/src/nsImapMailFolder.cpp9095
-rw-r--r--comm/mailnews/imap/src/nsImapMailFolder.h599
-rw-r--r--comm/mailnews/imap/src/nsImapNamespace.cpp513
-rw-r--r--comm/mailnews/imap/src/nsImapNamespace.h87
-rw-r--r--comm/mailnews/imap/src/nsImapOfflineSync.cpp1175
-rw-r--r--comm/mailnews/imap/src/nsImapOfflineSync.h95
-rw-r--r--comm/mailnews/imap/src/nsImapProtocol.cpp9915
-rw-r--r--comm/mailnews/imap/src/nsImapProtocol.h848
-rw-r--r--comm/mailnews/imap/src/nsImapSearchResults.cpp72
-rw-r--r--comm/mailnews/imap/src/nsImapSearchResults.h40
-rw-r--r--comm/mailnews/imap/src/nsImapServerResponseParser.cpp2640
-rw-r--r--comm/mailnews/imap/src/nsImapServerResponseParser.h275
-rw-r--r--comm/mailnews/imap/src/nsImapService.cpp3091
-rw-r--r--comm/mailnews/imap/src/nsImapService.h122
-rw-r--r--comm/mailnews/imap/src/nsImapStringBundle.cpp37
-rw-r--r--comm/mailnews/imap/src/nsImapStringBundle.h17
-rw-r--r--comm/mailnews/imap/src/nsImapUndoTxn.cpp647
-rw-r--r--comm/mailnews/imap/src/nsImapUndoTxn.h87
-rw-r--r--comm/mailnews/imap/src/nsImapUrl.cpp1276
-rw-r--r--comm/mailnews/imap/src/nsImapUrl.h132
-rw-r--r--comm/mailnews/imap/src/nsImapUtils.cpp336
-rw-r--r--comm/mailnews/imap/src/nsImapUtils.h77
-rw-r--r--comm/mailnews/imap/src/nsSyncRunnableHelpers.cpp596
-rw-r--r--comm/mailnews/imap/src/nsSyncRunnableHelpers.h149
-rw-r--r--comm/mailnews/imap/test/TestImapFlagAndUidState.cpp166
-rw-r--r--comm/mailnews/imap/test/TestImapHdrXferInfo.cpp101
-rw-r--r--comm/mailnews/imap/test/moz.build27
-rw-r--r--comm/mailnews/imap/test/unit/head_imap_maildir.js9
-rw-r--r--comm/mailnews/imap/test/unit/head_server.js201
-rw-r--r--comm/mailnews/imap/test/unit/test_ImapResponse.js288
-rw-r--r--comm/mailnews/imap/test/unit/test_autosync_date_constraints.js88
-rw-r--r--comm/mailnews/imap/test/unit/test_bccProperty.js52
-rw-r--r--comm/mailnews/imap/test/unit/test_bug460636.js82
-rw-r--r--comm/mailnews/imap/test/unit/test_chunkLastLF.js108
-rw-r--r--comm/mailnews/imap/test/unit/test_compactOfflineStore.js194
-rw-r--r--comm/mailnews/imap/test/unit/test_converterImap.js110
-rw-r--r--comm/mailnews/imap/test/unit/test_copyThenMove.js200
-rw-r--r--comm/mailnews/imap/test/unit/test_customCommandReturnsFetchResponse.js134
-rw-r--r--comm/mailnews/imap/test/unit/test_dontStatNoSelect.js149
-rw-r--r--comm/mailnews/imap/test/unit/test_downloadOffline.js75
-rw-r--r--comm/mailnews/imap/test/unit/test_fetchCustomAttribute.js105
-rw-r--r--comm/mailnews/imap/test/unit/test_filterCustomHeaders.js66
-rw-r--r--comm/mailnews/imap/test/unit/test_filterNeedsBody.js113
-rw-r--r--comm/mailnews/imap/test/unit/test_folderOfflineFlags.js108
-rw-r--r--comm/mailnews/imap/test/unit/test_gmailAttributes.js91
-rw-r--r--comm/mailnews/imap/test/unit/test_gmailOfflineMsgStore.js229
-rw-r--r--comm/mailnews/imap/test/unit/test_imapAttachmentSaves.js199
-rw-r--r--comm/mailnews/imap/test/unit/test_imapAuthMethods.js165
-rw-r--r--comm/mailnews/imap/test/unit/test_imapAutoSync.js239
-rw-r--r--comm/mailnews/imap/test/unit/test_imapChunks.js114
-rw-r--r--comm/mailnews/imap/test/unit/test_imapClientid.js64
-rw-r--r--comm/mailnews/imap/test/unit/test_imapContentLength.js98
-rw-r--r--comm/mailnews/imap/test/unit/test_imapCopyTimeout.js120
-rw-r--r--comm/mailnews/imap/test/unit/test_imapFilterActions.js597
-rw-r--r--comm/mailnews/imap/test/unit/test_imapFilterActionsPostplugin.js428
-rw-r--r--comm/mailnews/imap/test/unit/test_imapFlagChange.js216
-rw-r--r--comm/mailnews/imap/test/unit/test_imapFolderCopy.js137
-rw-r--r--comm/mailnews/imap/test/unit/test_imapHdrChunking.js168
-rw-r--r--comm/mailnews/imap/test/unit/test_imapHdrStreaming.js74
-rw-r--r--comm/mailnews/imap/test/unit/test_imapHighWater.js194
-rw-r--r--comm/mailnews/imap/test/unit/test_imapID.js40
-rw-r--r--comm/mailnews/imap/test/unit/test_imapMove.js88
-rw-r--r--comm/mailnews/imap/test/unit/test_imapPasswordFailure.js179
-rw-r--r--comm/mailnews/imap/test/unit/test_imapProtocols.js59
-rw-r--r--comm/mailnews/imap/test/unit/test_imapProxy.js68
-rw-r--r--comm/mailnews/imap/test/unit/test_imapRename.js43
-rw-r--r--comm/mailnews/imap/test/unit/test_imapSearch.js348
-rw-r--r--comm/mailnews/imap/test/unit/test_imapStatusCloseDBs.js49
-rw-r--r--comm/mailnews/imap/test/unit/test_imapStoreMsgOffline.js221
-rw-r--r--comm/mailnews/imap/test/unit/test_imapUndo.js160
-rw-r--r--comm/mailnews/imap/test/unit/test_imapUrls.js31
-rw-r--r--comm/mailnews/imap/test/unit/test_largeOfflineStore.js141
-rw-r--r--comm/mailnews/imap/test/unit/test_listClosesDB.js58
-rw-r--r--comm/mailnews/imap/test/unit/test_listSubscribed.js123
-rw-r--r--comm/mailnews/imap/test/unit/test_localToImapFilter.js159
-rw-r--r--comm/mailnews/imap/test/unit/test_localToImapFilterQuarantine.js121
-rw-r--r--comm/mailnews/imap/test/unit/test_lsub.js76
-rw-r--r--comm/mailnews/imap/test/unit/test_mailboxes.js80
-rw-r--r--comm/mailnews/imap/test/unit/test_nsIMsgFolderListenerIMAP.js363
-rw-r--r--comm/mailnews/imap/test/unit/test_offlineCopy.js271
-rw-r--r--comm/mailnews/imap/test/unit/test_offlineDraftDataloss.js152
-rw-r--r--comm/mailnews/imap/test/unit/test_offlineMoveLocalToIMAP.js125
-rw-r--r--comm/mailnews/imap/test/unit/test_offlinePlayback.js187
-rw-r--r--comm/mailnews/imap/test/unit/test_offlineStoreLocking.js258
-rw-r--r--comm/mailnews/imap/test/unit/test_preserveDataOnMove.js90
-rw-r--r--comm/mailnews/imap/test/unit/test_saveImapDraft.js119
-rw-r--r--comm/mailnews/imap/test/unit/test_saveTemplate.js96
-rw-r--r--comm/mailnews/imap/test/unit/test_starttlsFailure.js79
-rw-r--r--comm/mailnews/imap/test/unit/test_stopMovingToLocalFolder.js95
-rw-r--r--comm/mailnews/imap/test/unit/test_subfolderLocation.js83
-rw-r--r--comm/mailnews/imap/test/unit/test_syncChanges.js80
-rw-r--r--comm/mailnews/imap/test/unit/test_trustSpamAssassin.js146
-rw-r--r--comm/mailnews/imap/test/unit/xpcshell-cpp.ini14
-rw-r--r--comm/mailnews/imap/test/unit/xpcshell-shared.ini58
-rw-r--r--comm/mailnews/imap/test/unit/xpcshell.ini12
-rw-r--r--comm/mailnews/imap/test/unit/xpcshell_maildir-cpp.ini14
-rw-r--r--comm/mailnews/imap/test/unit/xpcshell_maildir.ini9
145 files changed, 57683 insertions, 0 deletions
diff --git a/comm/mailnews/imap/public/moz.build b/comm/mailnews/imap/public/moz.build
new file mode 100644
index 0000000000..d6e40a3e80
--- /dev/null
+++ b/comm/mailnews/imap/public/moz.build
@@ -0,0 +1,29 @@
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+XPIDL_SOURCES += [
+ "nsIAutoSyncFolderStrategy.idl",
+ "nsIAutoSyncManager.idl",
+ "nsIAutoSyncMsgStrategy.idl",
+ "nsIAutoSyncState.idl",
+ "nsIImapFlagAndUidState.idl",
+ "nsIImapHeaderXferInfo.idl",
+ "nsIImapIncomingServer.idl",
+ "nsIImapMailFolderSink.idl",
+ "nsIImapMessageSink.idl",
+ "nsIImapMockChannel.idl",
+ "nsIImapOfflineSync.idl",
+ "nsIImapProtocol.idl",
+ "nsIImapProtocolSink.idl",
+ "nsIImapServerSink.idl",
+ "nsIImapService.idl",
+ "nsIImapUrl.idl",
+ "nsIMailboxSpec.idl",
+ "nsIMsgImapMailFolder.idl",
+]
+
+XPIDL_MODULE = "msgimap"
+
+EXPORTS += []
diff --git a/comm/mailnews/imap/public/nsIAutoSyncFolderStrategy.idl b/comm/mailnews/imap/public/nsIAutoSyncFolderStrategy.idl
new file mode 100644
index 0000000000..23ada6d468
--- /dev/null
+++ b/comm/mailnews/imap/public/nsIAutoSyncFolderStrategy.idl
@@ -0,0 +1,23 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+#include "nsIAutoSyncMsgStrategy.idl"
+
+interface nsIMsgFolder;
+
+[scriptable, uuid(d3bf91cc-37bb-4752-9994-1a8473e46a90)]
+interface nsIAutoSyncFolderStrategy : nsISupports {
+
+ /**
+ * Returns a relative-priority for the second folder by comparing with the first one.
+ */
+ nsAutoSyncStrategyDecisionType sort(in nsIMsgFolder aFolder1, in nsIMsgFolder aFolder2);
+
+ /**
+ * Tests whether the given folder should be excluded or not.
+ */
+ boolean isExcluded(in nsIMsgFolder aFolder);
+
+};
diff --git a/comm/mailnews/imap/public/nsIAutoSyncManager.idl b/comm/mailnews/imap/public/nsIAutoSyncManager.idl
new file mode 100644
index 0000000000..531b9e702b
--- /dev/null
+++ b/comm/mailnews/imap/public/nsIAutoSyncManager.idl
@@ -0,0 +1,192 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIAutoSyncMsgStrategy;
+interface nsIAutoSyncFolderStrategy;
+interface nsIMsgDBHdr;
+interface nsIAutoSyncState;
+interface nsIAutoSyncMgrListener;
+interface nsIMsgFolder;
+
+[scriptable, uuid(41ec36a7-1a53-4ca3-b698-dca6452a8761)]
+interface nsIAutoSyncMgrListener : nsISupports {
+
+ /**
+ * Queue types
+ */
+ const long PriorityQueue = 1;
+ const long UpdateQueue = 2;
+ const long DiscoveryQueue = 3;
+
+ /**
+ * It is called on the listener when a new folder is added into
+ * the queue
+ *
+ * @param aQType type of the queue
+ * @param aFolder folder that is added into the queue
+ */
+ void onFolderAddedIntoQ(in long aQType, in nsIMsgFolder aFolder);
+
+ /**
+ * It is called on the listener when a folder is removed from
+ * the queue
+ *
+ * @param aQType type of the queue
+ * @param aFolder folder that is removed from the queue
+ */
+ void onFolderRemovedFromQ(in long aQType, in nsIMsgFolder aFolder);
+
+ /**
+ * It is called on the listener when a message download is successfully started
+ *
+ * @param aFolder folder in which the download is started
+ * @param aNumberOfMessages number of the messages that will be downloaded
+ * @param aTotalPending total number of messages waiting to be downloaded
+ */
+ void onDownloadStarted(in nsIMsgFolder aFolder, in unsigned long aNumberOfMessages,
+ in unsigned long aTotalPending);
+ /**
+ * It is called on the listener when a message download on the given folder
+ * is completed
+ */
+ void onDownloadCompleted(in nsIMsgFolder aFolder);
+
+ /**
+ * It is called on the listener when an error occurs during the message download
+ */
+ void onDownloadError(in nsIMsgFolder aFolder);
+
+ /*
+ * Auto-Sync manager is running or waiting for idle
+ */
+ void onStateChanged(in boolean aRunning);
+
+ /**
+ * It is called on the listener after the auto-sync manager starts to process
+ * existing headers of the given folder to find missing message bodies
+ * (mostly for debugging purposes)
+ */
+ void onDiscoveryQProcessed(in nsIMsgFolder aFolder, in unsigned long aNumberOfHdrsProcessed,
+ in unsigned long aLeftToProcess);
+
+ /**
+ * It is called on the listener after the auto-sync manager updates the given folder
+ * (mostly for debugging purposes)
+ */
+ void onAutoSyncInitiated(in nsIMsgFolder aFolder);
+};
+
+
+[scriptable, uuid(7fe0b48e-f5d8-4747-beb7-888c9cced3a5)]
+interface nsIAutoSyncManager : nsISupports {
+
+ /**
+ * Download models
+ */
+ const long dmParallel = 0;
+ const long dmChained = 1;
+
+ /**
+ * Suggested minimum grouping size in bytes for message downloads.
+ * Setting this attribute to 0 resets its value to the
+ * hardcoded default.
+ */
+ attribute unsigned long groupSize;
+
+ /**
+ * Active strategy function to prioritize
+ * messages in the download queue
+ */
+ attribute nsIAutoSyncMsgStrategy msgStrategy;
+
+ /**
+ * Active strategy function to prioritize
+ * folders in the download queue
+ */
+ attribute nsIAutoSyncFolderStrategy folderStrategy;
+
+ /**
+ * Adds a listener to notify about auto-sync events
+ */
+ void addListener(in nsIAutoSyncMgrListener aListener);
+
+ /**
+ * Removes the listener from notification list
+ */
+ void removeListener(in nsIAutoSyncMgrListener aListener);
+
+ /**
+ * Tests the given message to make sure that whether
+ * it fits the download criteria or not
+ */
+ boolean doesMsgFitDownloadCriteria(in nsIMsgDBHdr aMsgHdr);
+
+ /**
+ * Called by the nsAutoSyncState object when the download
+ * queue is changed. Given interface is already addref'd.
+ */
+ void onDownloadQChanged(in nsIAutoSyncState aAutoSyncStateObj);
+
+ /**
+ * Called by the nsAutoSyncState object when the download
+ * is started. Given interface is already addref'd.
+ */
+ void onDownloadStarted(in nsIAutoSyncState aAutoSyncStateObj, in nsresult aStartCode);
+
+ /**
+ * Called by the nsAutoSyncState object when the download
+ * completed. Given interface is already addref'd.
+ */
+ void onDownloadCompleted(in nsIAutoSyncState aAutoSyncStateObj, in nsresult aExitCode);
+
+ /**
+ * Number of elements in the discovery queue.
+ * @see nsAutoSyncManager.h for details
+ */
+ readonly attribute unsigned long discoveryQLength;
+
+ /**
+ * Number of elements in the update queue.
+ * @see nsAutoSyncManager.h for details
+ */
+ readonly attribute unsigned long updateQLength;
+
+ /**
+ * Number of elements in the download queue (a.k.a priority queue).
+ * @see nsAutoSyncManager.h for details
+ */
+ readonly attribute unsigned long downloadQLength;
+
+ /**
+ * Active download model; Chained (serial), or Parallel
+ */
+ attribute long downloadModel;
+
+ /**
+ * The imap folder corresponding to aAutoSyncState has had a message
+ * added to it. Autosync may want to add this folder to the update q.
+ *
+ * @param aAutoSyncState state obj for folder needing updating
+ */
+ void onFolderHasPendingMsgs(in nsIAutoSyncState aAutoSyncState);
+
+ /// Pause autosync (e.g., we're downloading for offline).
+ void pause();
+
+ /// Resume normal autosync activities (e.g., we've come back online).
+ void resume();
+};
+
+%{C++
+#define NS_AUTOSYNCMANAGER_CID \
+{ /* C358C568-47B2-42b2-8146-3C0F8D1FAD6E */ \
+ 0xc358c568, 0x47b2, 0x42b2, \
+ { 0x81, 0x46, 0x3c, 0xf, 0x8d, 0x1f, 0xad, 0x6e }}
+#define NS_AUTOSYNCMANAGER_CLASSNAME \
+ "Auto-Sync Manager"
+#define NS_AUTOSYNCMANAGER_CONTRACTID \
+ "@mozilla.org/imap/autosyncmgr;1"
+%}
diff --git a/comm/mailnews/imap/public/nsIAutoSyncMsgStrategy.idl b/comm/mailnews/imap/public/nsIAutoSyncMsgStrategy.idl
new file mode 100644
index 0000000000..773c62426d
--- /dev/null
+++ b/comm/mailnews/imap/public/nsIAutoSyncMsgStrategy.idl
@@ -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 "nsISupports.idl"
+
+interface nsIMsgFolder;
+interface nsIMsgDBHdr;
+
+typedef long nsAutoSyncStrategyDecisionType;
+
+[scriptable,uuid(0365bec5-3753-43c2-b13e-441747815f37)]
+interface nsAutoSyncStrategyDecisions : nsISupports
+{
+ /// same priority
+ const nsAutoSyncStrategyDecisionType Same = 0x00000001;
+ /// higher priority
+ const nsAutoSyncStrategyDecisionType Higher = 0x00000002;
+ /// lower priority
+ const nsAutoSyncStrategyDecisionType Lower = 0x00000004;
+};
+
+[scriptable, uuid(9cb4baff-3112-4cf8-8463-f81b0aa78f93)]
+interface nsIAutoSyncMsgStrategy : nsISupports {
+
+ /**
+ * Returns a relative-priority for the second message by comparing with the first message.
+ */
+ nsAutoSyncStrategyDecisionType sort(in nsIMsgFolder aFolder, in nsIMsgDBHdr aMsgHdr1, in nsIMsgDBHdr aMsgHdr2);
+
+ /**
+ * Tests whether the given message should be excluded or not.
+ */
+ boolean isExcluded(in nsIMsgFolder aFolder, in nsIMsgDBHdr aMsgHdr);
+};
diff --git a/comm/mailnews/imap/public/nsIAutoSyncState.idl b/comm/mailnews/imap/public/nsIAutoSyncState.idl
new file mode 100644
index 0000000000..7d2c155435
--- /dev/null
+++ b/comm/mailnews/imap/public/nsIAutoSyncState.idl
@@ -0,0 +1,157 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIMsgFolder;
+interface nsIMsgDBHdr;
+
+[scriptable, uuid(7512f927-b8f0-48c4-b101-03e859e61281)]
+interface nsIAutoSyncState : nsISupports {
+
+ /**
+ * Auto-Sync states.
+ *
+ * ***WARNING***: If you change these, be sure to update stateStrings in
+ * nsAutoSyncState.cpp. If you do not, out-of-bounds memory accesses may
+ * happen.
+ */
+
+ /**
+ * Initial state. Returned to after new message bodies downloaded or if
+ * it is determined that no body fetches are actually needed. The next state
+ * is typically stStatusIssued but a transition directly to stUpdateNeeded
+ * may also occur.
+ */
+ const long stCompletedIdle = 0;
+
+ /**
+ * STATUS issued (URL "folderstatus"). Will check to see if new messages
+ * are present. If new messages are detected, the next state is
+ * stUpdateIssued; otherwise, a return to stCompleteIdle occurs. The previous
+ * state is always stCompleteIdle.
+ */
+ const long stStatusIssued = 1;
+
+ /**
+ * Occurs when imap APPEND and sometimes COPY or MOVE imap commands add
+ * messages that may not be detected in autosync stStatusIssued state. These
+ * are the "pending" messages referred to in the autosync code.
+ * stUpdateIssued state (URL "select") will occur next.
+ * The previous state is always stCompleteIdle, skipping stStatusIssued.
+ */
+ const long stUpdateNeeded = 2;
+
+ /**
+ * Update issued (URL "select"). Will figure out if there are any bodies to
+ * download after new headers are fetched and, if so, move to state
+ * stReadyToDownload. Otherwise, returns to stCompleteIdle.
+ */
+ const long stUpdateIssued = 3;
+
+ /**
+ * Preparing to download the next group of message bodies then move to
+ * stDownloadInProgress
+ */
+ const long stReadyToDownload = 4;
+
+ /**
+ * Fetch bodies issued (URL "fetch"). Group of message bodies download in
+ * progress. If more are needed, next state is stReadyToDownload; otherwise,
+ * when all are received, the next state is stCompleteIdle.
+ */
+ const long stDownloadInProgress = 5;
+
+ /**
+ * Puts the download queue offset to its previous position.
+ */
+ void rollback();
+
+ /**
+ * Clears the download queue. Resets the offsets.
+ */
+ void resetDownloadQ();
+
+ /**
+ * Rollbacks the offset to the previous position and
+ * changes the state to ready-to-download.
+ */
+ void tryCurrentGroupAgain(in unsigned long aRetryCount);
+
+ /**
+ * Resets the retry counter.
+ */
+ void resetRetryCounter();
+
+ /**
+ * Tests whether the given folder has the same imap server.
+ */
+ boolean isSibling(in nsIAutoSyncState aAnotherStateObj);
+
+ /**
+ * Update the folder to find new message headers to download
+ */
+ void updateFolder();
+
+ /**
+ * Downloads the bodies of the given messages from the server.
+ */
+ void downloadMessagesForOffline(in Array<nsIMsgDBHdr> aMessageList);
+
+ /**
+ * Returns an array containing the nsIMsgDBHdrs of the messages that will
+ * be downloaded next.
+ *
+ * @param aSuggestedGroupSizeLimit suggested size per group in bytes
+ * @param aActualGroupSize total size of the messages in bytes in the group
+ */
+ Array<nsIMsgDBHdr> getNextGroupOfMessages(in unsigned long aSuggestedGroupSizeLimit,
+ out unsigned long aActualGroupSize);
+
+ /**
+ * Iterates through the existing headers of the folder to find
+ * the messages not downloaded yet.
+ *
+ * @param aNumberOfHeadersToProcess number of headers to be processed
+ * at this pass
+ *
+ * @return the number of headers left to process
+ */
+ unsigned long processExistingHeaders(in unsigned long aNumberOfHeadersToProcess);
+
+ /**
+ * Tests whether the download queue is empty.
+ */
+ boolean isDownloadQEmpty();
+
+ /**
+ * Last time the existing headers are completely processed.
+ */
+ [noscript]readonly attribute PRTime lastSyncTime;
+
+ /**
+ * Last time the owner folder is updated.
+ */
+ [noscript]attribute PRTime lastUpdateTime;
+
+ /**
+ * Download operation state.
+ */
+ attribute long state;
+
+ /**
+ * Number of messages waiting to be downloaded.
+ */
+ readonly attribute long pendingMessageCount;
+
+ /**
+ * Total number of messages in the download queue.
+ */
+ readonly attribute long totalMessageCount;
+
+ /**
+ * The folder this auto-sync object is related to.
+ */
+ readonly attribute nsIMsgFolder ownerFolder;
+};
diff --git a/comm/mailnews/imap/public/nsIImapFlagAndUidState.idl b/comm/mailnews/imap/public/nsIImapFlagAndUidState.idl
new file mode 100644
index 0000000000..a65d1a81c2
--- /dev/null
+++ b/comm/mailnews/imap/public/nsIImapFlagAndUidState.idl
@@ -0,0 +1,86 @@
+/* -*- Mode: IDL; 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/. */
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(360848be-f694-40db-80ef-793a2c43ddcb)]
+interface nsIImapFlagAndUidState : nsISupports
+{
+ readonly attribute long numberOfMessages;
+ readonly attribute long numberOfRecentMessages;
+
+ /**
+ * If a full update, the total number of deleted messages
+ * in the folder; if a partial update, the number of deleted
+ * messages in the partial update
+ **/
+ readonly attribute long numberOfDeletedMessages;
+
+ /**
+ * If this is true, instead of fetching 1:* (FLAGS), and putting all
+ * UIDs and flags in the array, we only fetched the uids and flags
+ * that changed since the last time we were selected on this folder.
+ * This means we have a sparse array, and should not assume missing
+ * UIDs have been deleted.
+ **/
+ readonly attribute boolean partialUIDFetch;
+
+ /**
+ * Set of flags the server supports storing per message. See nsImapCore.h
+ * for the set of flags.
+ */
+ readonly attribute unsigned short supportedUserFlags;
+
+ /**
+ * Check if a UID is in the state.
+ * @param uid - The message UID.
+ * @returns True if UID is in the state.
+ */
+ boolean hasMessage(in unsigned long uid);
+
+ /**
+ * OR's the passed in flags with the previous flags because we want to
+ * accumulate the FLAGS and PERMANENTFLAGS response.
+ *
+ * @param aFlags - flags to OR with current flags.
+ */
+ void orSupportedUserFlags(in unsigned short aFlags);
+
+ unsigned long getUidOfMessage(in long zeroBasedIndex);
+ unsigned short getMessageFlags(in long zeroBasedIndex);
+ void setMessageFlags(in long zeroBasedIndex, in unsigned short flags);
+ void expungeByIndex(in unsigned long zeroBasedIndex);
+ void addUidFlagPair(in unsigned long uid, in unsigned short flags, in unsigned long zeroBasedIndex);
+ void addUidCustomFlagPair(in unsigned long uid, in string customFlag);
+ /**
+ * Get the message flags by the message UID.
+ * @param uid - The message UID.
+ * @returns The message flags.
+ */
+ unsigned short getMessageFlagsByUid(in unsigned long uid);
+ string getCustomFlags(in unsigned long uid); // returns space-separated keywords
+ void reset();
+ void clearCustomFlags(in unsigned long uid);
+ /**
+ * Adds custom attributes to a hash table for the purpose of storing them
+ * them.
+ * @param aUid UID of the associated msg
+ * @param aCustomAttributeName Name of the custom attribute value
+ * @param aCustomAttributeValue Value of the attribute,
+ */
+ void setCustomAttribute(in unsigned long aUid,
+ in ACString aCustomAttributeName,
+ in ACString aCustomAttributeValue);
+
+ /**
+ * Gets the custom attributes from the hash table where they were stored earlier
+ * them.
+ * @param aUid UID of the associated msg
+ * @param aCustomAttributeName Name of the custom attribute value
+ * @param aCustomAttributeValue Value of the attribute,
+ */
+ ACString getCustomAttribute(in unsigned long aUid,
+ in ACString aCustomAttributeName);
+};
diff --git a/comm/mailnews/imap/public/nsIImapHeaderXferInfo.idl b/comm/mailnews/imap/public/nsIImapHeaderXferInfo.idl
new file mode 100644
index 0000000000..74718e056e
--- /dev/null
+++ b/comm/mailnews/imap/public/nsIImapHeaderXferInfo.idl
@@ -0,0 +1,22 @@
+/* -*- 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/. */
+
+#include "nsISupports.idl"
+#include "MailNewsTypes2.idl"
+
+[scriptable, uuid(38f8f784-b092-11d6-ba4b-00108335942a)]
+interface nsIImapHeaderInfo : nsISupports {
+ attribute nsMsgKey msgUid;
+ attribute long msgSize;
+ readonly attribute ACString msgHdrs;
+ void cacheLine(in string line, in unsigned long uid);
+ void resetCache();
+};
+
+[scriptable, uuid(f0842eda-af29-4ecd-82e1-fba91bd65d66)]
+interface nsIImapHeaderXferInfo : nsISupports {
+ readonly attribute long numHeaders;
+ nsIImapHeaderInfo getHeader(in long hdrIndex);
+};
diff --git a/comm/mailnews/imap/public/nsIImapHostSessionList.h b/comm/mailnews/imap/public/nsIImapHostSessionList.h
new file mode 100644
index 0000000000..456bbdf7dc
--- /dev/null
+++ b/comm/mailnews/imap/public/nsIImapHostSessionList.h
@@ -0,0 +1,129 @@
+/* -*- 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 _nsIImapHostSessionList_H_
+#define _nsIImapHostSessionList_H_
+
+#include "nsISupports.h"
+#include "nsImapCore.h"
+#include "../src/nsImapNamespace.h"
+
+class nsIImapIncomingServer;
+
+// f4d89e3e-77da-492c-962b-7835f0742c22
+#define NS_IIMAPHOSTSESSIONLIST_IID \
+ { \
+ 0xf4d89e3e, 0x77da, 0x492c, { \
+ 0x96, 0x2b, 0x78, 0x35, 0xf0, 0x74, 0x2c, 0x22 \
+ } \
+ }
+
+// this is an interface to a linked list of host info's
+class nsIImapHostSessionList : public nsISupports {
+ public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_IIMAPHOSTSESSIONLIST_IID)
+
+ // Host List
+ NS_IMETHOD AddHostToList(const char* serverKey,
+ nsIImapIncomingServer* server) = 0;
+ NS_IMETHOD ResetAll() = 0;
+
+ // Capabilities
+ NS_IMETHOD GetHostHasAdminURL(const char* serverKey, bool& result) = 0;
+ NS_IMETHOD SetHostHasAdminURL(const char* serverKey, bool hasAdminUrl) = 0;
+ // Subscription
+ NS_IMETHOD GetHostIsUsingSubscription(const char* serverKey,
+ bool& result) = 0;
+ NS_IMETHOD SetHostIsUsingSubscription(const char* serverKey,
+ bool usingSubscription) = 0;
+
+ // Passwords
+ NS_IMETHOD GetPasswordForHost(const char* serverKey, nsString& result) = 0;
+ NS_IMETHOD SetPasswordForHost(const char* serverKey,
+ const nsAString& password) = 0;
+ NS_IMETHOD GetPasswordVerifiedOnline(const char* serverKey, bool& result) = 0;
+ NS_IMETHOD SetPasswordVerifiedOnline(const char* serverKey) = 0;
+
+ // OnlineDir
+ NS_IMETHOD GetOnlineDirForHost(const char* serverKey, nsString& result) = 0;
+ NS_IMETHOD SetOnlineDirForHost(const char* serverKey,
+ const char* onlineDir) = 0;
+
+ // Delete is move to trash folder
+ NS_IMETHOD GetDeleteIsMoveToTrashForHost(const char* serverKey,
+ bool& result) = 0;
+ NS_IMETHOD SetDeleteIsMoveToTrashForHost(const char* serverKey,
+ bool isMoveToTrash) = 0;
+ NS_IMETHOD GetShowDeletedMessagesForHost(const char* serverKey,
+ bool& result) = 0;
+
+ NS_IMETHOD SetShowDeletedMessagesForHost(const char* serverKey,
+ bool showDeletedMessages) = 0;
+
+ // Get namespaces
+ NS_IMETHOD GetGotNamespacesForHost(const char* serverKey, bool& result) = 0;
+ NS_IMETHOD SetGotNamespacesForHost(const char* serverKey,
+ bool gotNamespaces) = 0;
+
+ // Folders
+ NS_IMETHOD SetHaveWeEverDiscoveredFoldersForHost(const char* serverKey,
+ bool discovered) = 0;
+ NS_IMETHOD GetHaveWeEverDiscoveredFoldersForHost(const char* serverKey,
+ bool& result) = 0;
+ NS_IMETHOD SetDiscoveryForHostInProgress(const char* serverKey,
+ bool inProgress) = 0;
+ NS_IMETHOD GetDiscoveryForHostInProgress(const char* serverKey,
+ bool& result) = 0;
+
+ // Trash Folder
+ NS_IMETHOD SetOnlineTrashFolderExistsForHost(const char* serverKey,
+ bool exists) = 0;
+ NS_IMETHOD GetOnlineTrashFolderExistsForHost(const char* serverKey,
+ bool& result) = 0;
+
+ // INBOX
+ NS_IMETHOD GetOnlineInboxPathForHost(const char* serverKey,
+ nsString& result) = 0;
+ NS_IMETHOD GetShouldAlwaysListInboxForHost(const char* serverKey,
+ bool& result) = 0;
+ NS_IMETHOD SetShouldAlwaysListInboxForHost(const char* serverKey,
+ bool shouldList) = 0;
+
+ // Namespaces
+ NS_IMETHOD GetNamespaceForMailboxForHost(const char* serverKey,
+ const char* mailbox_name,
+ nsImapNamespace*& result) = 0;
+ NS_IMETHOD SetNamespaceFromPrefForHost(const char* serverKey,
+ const char* namespacePref,
+ EIMAPNamespaceType type) = 0;
+ NS_IMETHOD AddNewNamespaceForHost(const char* serverKey,
+ nsImapNamespace* ns) = 0;
+ NS_IMETHOD ClearServerAdvertisedNamespacesForHost(const char* serverKey) = 0;
+ NS_IMETHOD ClearPrefsNamespacesForHost(const char* serverKey) = 0;
+ NS_IMETHOD GetDefaultNamespaceOfTypeForHost(const char* serverKey,
+ EIMAPNamespaceType type,
+ nsImapNamespace*& result) = 0;
+ NS_IMETHOD SetNamespacesOverridableForHost(const char* serverKey,
+ bool overridable) = 0;
+ NS_IMETHOD GetNamespacesOverridableForHost(const char* serverKey,
+ bool& result) = 0;
+ NS_IMETHOD GetNumberOfNamespacesForHost(const char* serverKey,
+ uint32_t& result) = 0;
+ NS_IMETHOD GetNamespaceNumberForHost(const char* serverKey, int32_t n,
+ nsImapNamespace*& result) = 0;
+ // ### dmb hoo boy, how are we going to do this?
+ NS_IMETHOD CommitNamespacesForHost(nsIImapIncomingServer* server) = 0;
+ NS_IMETHOD FlushUncommittedNamespacesForHost(const char* serverKey,
+ bool& result) = 0;
+
+ // Hierarchy Delimiters
+ NS_IMETHOD SetNamespaceHierarchyDelimiterFromMailboxForHost(
+ const char* serverKey, const char* boxName, char delimiter) = 0;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsIImapHostSessionList,
+ NS_IIMAPHOSTSESSIONLIST_IID)
+
+#endif
diff --git a/comm/mailnews/imap/public/nsIImapIncomingServer.idl b/comm/mailnews/imap/public/nsIImapIncomingServer.idl
new file mode 100644
index 0000000000..25ff1c59c0
--- /dev/null
+++ b/comm/mailnews/imap/public/nsIImapIncomingServer.idl
@@ -0,0 +1,114 @@
+/* -*- Mode: IDL; 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/. */
+
+#include "nsISupports.idl"
+
+interface nsIURI;
+interface nsIImapUrl;
+interface nsIImapProtocol;
+interface nsIMsgFolder;
+interface nsIMsgWindow;
+
+typedef long nsMsgImapDeleteModel;
+
+[scriptable, uuid(bbfc33de-fe89-11d3-a564-0060b0fc04b7)]
+interface nsMsgImapDeleteModels : nsISupports
+{
+ const long IMAPDelete = 0; /* delete with a big red x */
+ const long MoveToTrash = 1; /* delete moves message to the trash */
+ const long DeleteNoTrash = 2; /* delete is shift delete - don't create or use trash */
+};
+
+[scriptable, uuid(ea6a0765-07b8-40df-924c-9004ed707251)]
+interface nsIImapIncomingServer : nsISupports {
+
+ attribute long maximumConnectionsNumber;
+ attribute boolean forceSelect;
+ attribute long timeOutLimits;
+ attribute AUTF8String adminUrl;
+ attribute ACString serverDirectory;
+ /// RFC 2971 ID response stored as a pref
+ attribute AUTF8String serverIDPref;
+ attribute boolean cleanupInboxOnExit;
+ attribute nsMsgImapDeleteModel deleteModel;
+ attribute boolean dualUseFolders;
+ attribute ACString personalNamespace;
+ attribute ACString publicNamespace;
+ attribute ACString otherUsersNamespace;
+ attribute boolean offlineDownload;
+ attribute boolean overrideNamespaces;
+ attribute boolean usingSubscription;
+ attribute AUTF8String manageMailAccountUrl;
+ attribute boolean fetchByChunks;
+ attribute boolean sendID;
+ attribute boolean isAOLServer;
+ attribute boolean useIdle;
+ attribute boolean checkAllFoldersForNew;
+
+ /// Is this a GMail Server?
+ attribute boolean isGMailServer;
+
+ /**
+ * See IMAP RFC 4551
+ **/
+ attribute boolean useCondStore;
+
+ /**
+ * See IMAP RFC 4978
+ */
+ attribute boolean useCompressDeflate;
+
+ /**
+ * This contains a folder path, for example INBOX/Trash. Note that the
+ * account manager sets this attribute to the path of the trash folder the
+ * user has chosen.
+ */
+ attribute AString trashFolderName;
+
+ attribute boolean downloadBodiesOnGetNewMail;
+ attribute boolean autoSyncOfflineStores;
+
+ /// Max age of messages we will autosync to, or keep in offline store.
+ attribute long autoSyncMaxAgeDays;
+
+ /**
+ * See IMAP RFC 6855
+ */
+ attribute boolean allowUTF8Accept;
+
+ void GetImapConnectionAndLoadUrl(in nsIImapUrl aImapUrl,
+ in nsISupports aConsumer);
+
+ void RemoveConnection(in nsIImapProtocol aImapConnection);
+ void ResetNamespaceReferences();
+ void pseudoInterruptMsgLoad(in nsIMsgFolder aImapFolder, in nsIMsgWindow aMsgWindow, out boolean interrupted);
+ void ResetConnection(in AUTF8String folderName);
+ void CloseConnectionForFolder(in nsIMsgFolder aMsgFolder);
+ void reDiscoverAllFolders();
+ nsIURI subscribeToFolder(in AString name, in boolean subscribe);
+ void GetNewMessagesForNonInboxFolders(in nsIMsgFolder aRootFolder,
+ in nsIMsgWindow aWindow,
+ in boolean forceAllFolders,
+ in boolean performingBiff);
+ unsigned long long getCapability();
+
+ /**
+ * Get the password from the nsIMsgIncomingServer. May prompt the user
+ * if there's no password in the password manager or cached in the
+ * server object.
+ * @param aWindow msgWindow to associate the password prompt with
+ * @return Password string.
+ * @exception NS_ERROR_FAILURE The password could not be obtained.
+ * @note NS_MSG_PASSWORD_PROMPT_CANCELLED is a success code that is returned
+ * if the prompt was presented to the user but the user cancelled the
+ * prompt.
+ */
+ AString PromptPassword(in nsIMsgWindow aWindow);
+ attribute boolean doingLsub;
+
+ ACString getUriWithNamespacePrefixIfNecessary(in long namespaceType, in AUTF8String originalUri);
+ attribute boolean shuttingDown;
+ attribute boolean utf8AcceptEnabled;
+};
diff --git a/comm/mailnews/imap/public/nsIImapMailFolderSink.idl b/comm/mailnews/imap/public/nsIImapMailFolderSink.idl
new file mode 100644
index 0000000000..6868682640
--- /dev/null
+++ b/comm/mailnews/imap/public/nsIImapMailFolderSink.idl
@@ -0,0 +1,119 @@
+/* -*- 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/. */
+
+#include "nsISupports.idl"
+#include "MailNewsTypes2.idl"
+
+#include "nsIImapProtocol.idl"
+#include "nsIMailboxSpec.idl"
+
+interface nsIMsgMailNewsUrl;
+interface nsIImapMockChannel;
+interface nsIImapHeaderXferInfo;
+
+typedef long ImapOnlineCopyState;
+
+[scriptable, uuid(5f7484b0-68b4-11d3-a53e-0060b0fc04b7)]
+interface ImapOnlineCopyStateType : nsISupports
+{
+ const long kInProgress = 0;
+ const long kSuccessfulCopy = 1;
+ const long kSuccessfulMove = 2;
+ const long kSuccessfulDelete = 3;
+ const long kFailedDelete = 4;
+ const long kReadyForAppendData = 5;
+ const long kFailedAppend = 6;
+ const long kInterruptedState = 7;
+ const long kFailedCopy = 8;
+ const long kFailedMove = 9;
+};
+
+/**
+ * nsIImapMailFolderSink provides a way for the IMAP system to communicate
+ * with the local folder representation.
+ *
+ * The IMAP system could poke folders directly, but going through this
+ * interface has a couple of benefits:
+ *
+ * 1. It better defines the public coupling between the two systems.
+ * 2. It's easier to wrap with a proxy class so the IMAP system can safely
+ * call the methods across thread boundaries (see ImapMailFolderSinkProxy).
+ */
+[scriptable, uuid(525e1278-a39d-46d6-9dbc-b48c7e1d4faa)]
+interface nsIImapMailFolderSink : nsISupports {
+ attribute boolean folderNeedsACLListed;
+ attribute boolean folderNeedsSubscribing;
+ attribute boolean folderNeedsAdded;
+ attribute unsigned long aclFlags;
+ attribute long uidValidity;
+ /**
+ * Whether we have asked the server for this folder's quota information.
+ * If the server supports quotas, this occurs when the folder is opened.
+ */
+ attribute boolean folderQuotaCommandIssued;
+
+ /**
+ * Set FolderQuotaData information
+ * @param aAction Invalidate, store or validate the quota data.
+ * Remaining params are relevant only for store.
+ * @param aFolderQuotaRoot The IMAP quotaroot and resource names for this
+ * folder separated by a slash as obtained from the
+ * GETQUOTAROOT IMAP command response.
+ * @param aFolderQuotaUsage Amount of resourse in use, in KB for STORAGE
+ * resource.
+ * @param aFolderQuotaLimit Maximum usage allowed for this resource.
+ **/
+ void setFolderQuotaData(
+ in unsigned long aAction, in ACString aFolderQuotaRoot,
+ in unsigned long long aFolderQuotaUsed, in unsigned long long aFolderQuotaLimit);
+
+ /// Should we download all the rfc822 headers of messages, instead of subset.
+ readonly attribute boolean shouldDownloadAllHeaders;
+ readonly attribute char onlineDelimiter;
+ void OnNewIdleMessages();
+ // Tell mail master about the newly selected mailbox
+ void UpdateImapMailboxInfo(in nsIImapProtocol aProtocol,
+ in nsIMailboxSpec aSpec);
+ void UpdateImapMailboxStatus(in nsIImapProtocol aProtocol,
+ in nsIMailboxSpec aSpec);
+ /**
+ * Used when downloading headers in chunks.
+ * @param aSpec Mailbox spec of folder we're downloading headers for.
+ * @returns true if more to download, false otherwise.
+ * @returns total count of headers to download (across all chunks)
+ * @returns an array of msg keys to download, array size is this chunk's size
+ */
+ void getMsgHdrsToDownload(out boolean aMore, out long aTotalCount,
+ out Array<nsMsgKey> aKeys);
+ void parseMsgHdrs(in nsIImapProtocol aProtocol, in nsIImapHeaderXferInfo aHdrXferInfo);
+ void AbortHeaderParseStream(in nsIImapProtocol aProtocol) ;
+
+ void OnlineCopyCompleted(in nsIImapProtocol aProtocol, in ImapOnlineCopyState aCopyState);
+ void StartMessage(in nsIMsgMailNewsUrl aUrl);
+ void EndMessage(in nsIMsgMailNewsUrl aUrl, in nsMsgKey uidOfMessage);
+
+ void NotifySearchHit(in nsIMsgMailNewsUrl aUrl, in string hitLine);
+
+ void copyNextStreamMessage(in boolean copySucceeded, in nsISupports copyState);
+ void closeMockChannel(in nsIImapMockChannel aChannel);
+ void setUrlState(in nsIImapProtocol aProtocol, in nsIMsgMailNewsUrl aUrl,
+ in boolean isRunning, in boolean aSuspend,
+ in nsresult status);
+ void releaseUrlCacheEntry(in nsIMsgMailNewsUrl aUrl);
+
+ void headerFetchCompleted(in nsIImapProtocol aProtocol);
+ void setBiffStateAndUpdate(in long biffState);
+ void progressStatusString(in nsIImapProtocol aProtocol, in string aMsgId, in wstring extraInfo);
+ void percentProgress(in nsIImapProtocol aProtocol,
+ in ACString aFmtStringName, in AString aMailboxName,
+ in long long aCurrentProgress, in long long aMaxProgressProgressInfo);
+
+ void clearFolderRights();
+ void setCopyResponseUid(in string msgIdString,
+ in nsIImapUrl aUrl);
+ void setAppendMsgUid(in nsMsgKey newKey,
+ in nsIImapUrl aUrl);
+ ACString getMessageId(in nsIImapUrl aUrl);
+};
diff --git a/comm/mailnews/imap/public/nsIImapMessageSink.idl b/comm/mailnews/imap/public/nsIImapMessageSink.idl
new file mode 100644
index 0000000000..858522856c
--- /dev/null
+++ b/comm/mailnews/imap/public/nsIImapMessageSink.idl
@@ -0,0 +1,89 @@
+/* -*- 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/. */
+
+#include "nsISupports.idl"
+#include "MailNewsTypes2.idl"
+#include "nsIImapUrl.idl"
+
+interface nsIFile;
+
+[scriptable, uuid(6ffb6a92-e43a-405f-92ea-92cf81a5e17b)]
+
+/**
+ * nsIImapMessageSink provides a way for the IMAP system to exchange
+ * message-related information with the local folder representation.
+ *
+ * The IMAP system could poke folders directly, but going through this
+ * interface has a couple of benefits:
+ *
+ * 1. It better defines the public coupling between the two systems.
+ * 2. It's easier to wrap with a proxy class so the IMAP system can safely
+ * call the methods across thread boundaries (see ImapMessageSinkProxy).
+ */
+interface nsIImapMessageSink : nsISupports {
+ // set up message download output stream
+ void setupMsgWriteStream(in nsIFile aFile, in boolean aAppendDummyEnvelope);
+
+ /**
+ * Used by the imap protocol code to notify the core backend code about
+ * downloaded imap messages.
+ *
+ * @param aAdoptedMsgLine a string with a lot of message lines,
+ * separated by native line terminators.
+ * @param aUidOfMsg IMAP UID of the fetched message.
+ * @param aImapUrl IMAP Url used to fetch the message.
+ */
+ void parseAdoptedMsgLine(in string aAdoptedMsgLine, in nsMsgKey aUidOfMsg,
+ in nsIImapUrl aImapUrl);
+
+ /**
+ * Notify the backend that the imap protocol is done downloading a message
+ *
+ * @param aUidOfMsg IMAP UID of the fetched message.
+ * @param aMarkMsgRead Set the SEEN flag on the message.
+ * @param aImapUrl IMAP Url used to fetch the message.
+ * @param aUpdatedMessageSize if this parameter is not -1, the stored size of the message
+ * should be set to this value to reflect the actual size of
+ * the downloaded message.
+ */
+ void normalEndMsgWriteStream(in nsMsgKey aUidOfMessage,
+ in boolean aMarkMsgRead, in nsIImapUrl aImapUrl,
+ in long aUpdatedMessageSize);
+
+ void abortMsgWriteStream();
+
+ void beginMessageUpload();
+
+ /**
+ * Notify the message sink that one or more flags have changed
+ * For Condstore servers, also update the highestMod Sequence
+ * @param aFlags - The new flags for the message
+ * @param aKeywords keywords for the message
+ * @param aMessageKey - The UID of the message that changed
+ * @param aHighestModSeq - The highest mod seq the parser has seen
+ * for this folder
+ **/
+ void notifyMessageFlags(in unsigned long aFlags, in ACString aKeywords,
+ in nsMsgKey aMessageKey,
+ in unsigned long long aHighestModSeq);
+
+ void notifyMessageDeleted(in string aOnlineFolderName,in boolean aDeleteAllMsgs,in string aMsgIdString);
+
+ void getMessageSizeFromDB(in string aId, out unsigned long aSize);
+
+ /**
+ * For a message stored in a file, get the message metadata needed to copy
+ * that message to an imap folder
+ *
+ * @param aRunningUrl message URL
+ * @param aDate message date
+ * @param aKeywords message custom keywords (if supported by the server),
+ * including messages tags and junk status
+ *
+ * @return message flags
+ */
+ unsigned long getCurMoveCopyMessageInfo(in nsIImapUrl aRunningUrl,
+ out PRTime aDate, out ACString aKeywords);
+};
diff --git a/comm/mailnews/imap/public/nsIImapMockChannel.idl b/comm/mailnews/imap/public/nsIImapMockChannel.idl
new file mode 100644
index 0000000000..cb624f314f
--- /dev/null
+++ b/comm/mailnews/imap/public/nsIImapMockChannel.idl
@@ -0,0 +1,49 @@
+/* -*- 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/. */
+
+
+/*
+ Because imap protocol connections (which are channels) run through a cache,
+ it isn't always the case that when you want to run a url, you actually get
+ a connection back right away. Often times, the url goes into a queue until
+ a connection becomes available.
+
+ Unfortunately, if we want to be a truly pluggable protocol with necko, necko
+ requires the ability to get a channel back right away when it wants to run
+ a url. It doesn't let you wait until an imap connection becomes available.
+
+ So I've created the notion of a "mock channel". This mock channel is what
+ gets returned to necko (or other callers) when they ask the imap service
+ for a new channel for a url. The mock channel has "mock" implementations
+ of nsIChannel. Eventually, when we actually assign the url to a real
+ channel, we set the real channel on the mock channel. From that point forward,
+ the mock channel forwards channel calls directly to the real channel.
+
+ In short, this class is how I'm solving the problem where necko wants
+ a channel back as soon as they ask for when with the fact that it
+ may be a while until the url is loaded into a connection.
+ */
+
+#include "nsISupports.idl"
+#include "nsIChannel.idl"
+
+interface nsIStreamListener;
+interface nsIProgressEventSink;
+interface nsIURI;
+interface nsIImapProtocol;
+
+[scriptable, uuid(e0178cd5-d37b-4bde-9ab8-752083536225)]
+interface nsIImapMockChannel : nsIChannel
+{
+ attribute nsIProgressEventSink progressEventSink;
+ void GetChannelListener(out nsIStreamListener aChannelListener);
+ void Close();
+ void setImapProtocol(in nsIImapProtocol aProtocol);
+ [noscript] void setSecurityInfo(in nsITransportSecurityInfo securityInfo);
+
+ void setURI(in nsIURI uri);
+ void readFromImapConnection();
+ attribute boolean writingToCache;
+};
diff --git a/comm/mailnews/imap/public/nsIImapOfflineSync.idl b/comm/mailnews/imap/public/nsIImapOfflineSync.idl
new file mode 100644
index 0000000000..c7f55cfa9c
--- /dev/null
+++ b/comm/mailnews/imap/public/nsIImapOfflineSync.idl
@@ -0,0 +1,19 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIMsgFolder;
+interface nsIMsgWindow;
+interface nsIUrlListener;
+
+[scriptable, uuid(ffb683c5-f2c5-490a-b569-2f6b0de0a241)]
+interface nsIImapOfflineSync : nsISupports {
+ void init(in nsIMsgWindow window,
+ in nsIUrlListener listener,
+ in nsIMsgFolder folder,
+ in bool isPseudoOffline);
+
+ void processNextOperation();
+};
diff --git a/comm/mailnews/imap/public/nsIImapProtocol.idl b/comm/mailnews/imap/public/nsIImapProtocol.idl
new file mode 100644
index 0000000000..7f7ea50720
--- /dev/null
+++ b/comm/mailnews/imap/public/nsIImapProtocol.idl
@@ -0,0 +1,90 @@
+/* -*- Mode: IDL; 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/. */
+
+#include "nsISupports.idl"
+#include "MailNewsTypes2.idl" // for nsMsgKey
+
+interface nsIURI;
+interface nsIImapUrl;
+interface nsIImapIncomingServer;
+interface nsIMsgFolder;
+interface nsIMsgWindow;
+interface nsIImapFlagAndUidState;
+
+[ptr] native nsIImapHostSessionList(nsIImapHostSessionList);
+%{C++
+class nsIImapHostSessionList;
+%}
+
+[scriptable, uuid(6ef189e5-8711-4845-9625-d1c74c22c4b5)]
+interface nsIImapProtocol : nsISupports {
+ /**
+ * Set up this connection to run a URL.
+ * Called by nsImapIncomingServer to process a queued URL when it spots a
+ * free connection.
+ * Because nsImapProtocol is really a connection and doesn't follow the
+ * usual nsIChannel lifecycle, this function is provided to allow reuse.
+ * Over and over again.
+ */
+ void LoadImapUrl(in nsIURI aUrl, in nsISupports aConsumer);
+
+ /**
+ * IsBusy returns true if the connection is currently processing a URL
+ * and false otherwise.
+ */
+ void IsBusy(out boolean aIsConnectionBusy,
+ out boolean isInboxConnection);
+
+ /**
+ * Protocol instance examines the URL, looking at the host name,
+ * user name and folder the action would be on in order to figure out
+ * if it can process this URL. I decided to push the semantics about
+ * whether a connection can handle a URL down into the connection level
+ * instead of in the connection cache.
+ */
+ void CanHandleUrl(in nsIImapUrl aImapUrl, out boolean aCanRunUrl,
+ out boolean hasToWait);
+
+ /**
+ * Initialize a protocol object.
+ * @param aHostSessionList host session list service
+ * @param aServer imap server the protocol object will be talking to
+ */
+ void Initialize(in nsIImapHostSessionList aHostSessionList, in nsIImapIncomingServer aServer);
+
+ void NotifyBodysToDownload(in Array<nsMsgKey> keys);
+
+ // methods to get data from the imap parser flag state.
+ void GetFlagsForUID(in unsigned long uid, out boolean foundIt, out unsigned short flags, out string customFlags);
+ void GetSupportedUserFlags(out unsigned short flags);
+
+ void GetRunningImapURL(out nsIImapUrl aImapUrl);
+
+ void GetRunningUrl(out nsIURI aUrl);
+
+ readonly attribute nsIImapFlagAndUidState flagAndUidState;
+ /**
+ * Tell thread to die - only call from the UI thread
+ *
+ * @param aIsSafeToClose false if we're dropping a timed out connection.
+ */
+ void tellThreadToDie(in boolean aIsSafeToClose);
+
+ // Get last active time stamp
+ void GetLastActiveTimeStamp(out PRTime aTimeStamp);
+
+ void pseudoInterruptMsgLoad(in nsIMsgFolder imapFolder, in nsIMsgWindow aMsgWindow, out boolean interrupted);
+
+ /**
+ * Produce a pseudo-interrupt to trigger an abort of an imap mssage fetch.
+ *
+ * @param aInterrupt true to initiate a pseudo-interrupt; otherwise set false.
+ */
+ void pseudoInterrupt(in boolean aInterrupt);
+
+ void GetSelectedMailboxName(out string folderName);
+ // Reset folder connection to authenticated state
+ void ResetToAuthenticatedState();
+};
diff --git a/comm/mailnews/imap/public/nsIImapProtocolSink.idl b/comm/mailnews/imap/public/nsIImapProtocolSink.idl
new file mode 100644
index 0000000000..b3cda13d6e
--- /dev/null
+++ b/comm/mailnews/imap/public/nsIImapProtocolSink.idl
@@ -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 "nsISupports.idl"
+
+interface nsIMsgWindow;
+interface nsIMsgMailNewsUrl;
+
+/**
+ * Helper interface that contains operations MUST be proxied
+ * over UI thread.
+ */
+[scriptable, uuid(1217cd9d-7678-4026-b323-0d4b45816af0)]
+interface nsIImapProtocolSink : nsISupports {
+
+ /**
+ * Does general cleanup for the imap protocol object.
+ */
+ void closeStreams();
+ /**
+ * Get the msg window associated with a url
+ *
+ * @param aUrl url whose msgWindow we want.
+ * @returns msgWindow associated with url.
+ */
+ nsIMsgWindow getUrlWindow(in nsIMsgMailNewsUrl aUrl);
+
+ /**
+ * Setup main thread proxies.
+ */
+ void setupMainThreadProxies();
+};
diff --git a/comm/mailnews/imap/public/nsIImapServerSink.idl b/comm/mailnews/imap/public/nsIImapServerSink.idl
new file mode 100644
index 0000000000..736487877e
--- /dev/null
+++ b/comm/mailnews/imap/public/nsIImapServerSink.idl
@@ -0,0 +1,178 @@
+/* -*- 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/. */
+
+#include "nsISupports.idl"
+#include "MailNewsTypes2.idl"
+
+interface nsIMsgWindow;
+interface nsIMsgMailNewsUrl;
+interface nsIImapProtocol;
+interface nsIImapUrl;
+interface nsIImapMockChannel;
+
+/**
+ * nsIImapServerSink is designed to be used as a proxy to the application's UI
+ * thread from the running IMAP threads.
+ */
+[scriptable, uuid(2160c641-e4fa-4bbc-ab8b-d9ba45069027)]
+interface nsIImapServerSink : nsISupports {
+ /**
+ * Check if the given folder path is a possible IMAP mailbox.
+ * @param folderPath folder path to check
+ * @param hierarchyDelimiter IMAP hierarchy delimiter in canonical format,
+ * i.e., hierarchy delimiter has been replaced
+ * with '/'
+ * @param boxFlags IMAP folder flags (for subscription, namespaces etc.)
+ * @return true if it's a new mailbox
+ */
+ boolean possibleImapMailbox(in AUTF8String folderPath,
+ in char hierarchyDelimiter, in long boxFlags);
+ boolean folderNeedsACLInitialized(in AUTF8String folderPath);
+ void addFolderRights(in AUTF8String folderPath, in ACString userName, in ACString rights);
+ void refreshFolderRights(in AUTF8String folderPath);
+ void discoveryDone();
+ void onlineFolderDelete(in AUTF8String folderName);
+ void onlineFolderCreateFailed(in AUTF8String aFolderName);
+ void onlineFolderRename(in nsIMsgWindow msgWindow, in AUTF8String oldName, in AUTF8String newName);
+ boolean folderIsNoSelect(in AUTF8String folderName);
+ void setFolderAdminURL(in AUTF8String folderName, in AUTF8String adminUrl);
+ boolean folderVerifiedOnline(in AUTF8String folderName);
+
+ void setCapability(in unsigned long long capability);
+ /// RFC 2971 ID server response
+ void setServerID(in ACString aServerID);
+ boolean loadNextQueuedUrl(in nsIImapProtocol protocol);
+
+ /**
+ * Prepare to retry the given URL.
+ * @param imapUrl the url we're going to retry
+ * @return channel to associate with the url. We return this because access
+ * to the channel should only happen on the ui thread.
+ */
+ nsIImapMockChannel prepareToRetryUrl(in nsIImapUrl imapUrl);
+
+ /**
+ * Suspend the url. This puts it at the end of the queue. If the queue is
+ * empty, the url will get resumed immediately. Currently, the plan is
+ * do this when we have to download a lot of headers in chunks, though we
+ * could find other uses for it.
+ * @param imapUrl url to suspend
+ */
+ void suspendUrl(in nsIImapUrl aImapUrl);
+
+ /**
+ * Retry the given URL.
+ * @param imapUrl url to retry
+ * @param channel the channel to associate with the url
+ */
+ void retryUrl(in nsIImapUrl imapUrl, in nsIImapMockChannel channel);
+
+ /**
+ * If previous URL failed, this gives server chance to abort URLs with same
+ * mock channel.
+ */
+ void abortQueuedUrls();
+ AString getImapStringByName(in string msgName);
+ /**
+ * Alerts the user that the login to the IMAP 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).
+ * @return The button pressed. 0 for retry, 1 for cancel,
+ * 2 for enter a new password.
+ */
+ int32_t promptLoginFailed(in nsIMsgWindow aMsgWindow);
+
+ /**
+ * Alerts the user with the given string (FE = 'Front End').
+ *
+ * @param aAlertString The string to alert the user with.
+ * @param aUrl The running url.
+ */
+ void fEAlert(in AString aAlertString, in nsIMsgMailNewsUrl aUrl);
+
+ /**
+ * Alerts the user with a localized string. It will attempt to fill in
+ * the hostname into the string if necessary.
+ *
+ * @param aMsgName The id of the string to present to the user..
+ * @param aUrl The running url.
+ */
+ void fEAlertWithName(in string aMsgName, in nsIMsgMailNewsUrl aUrl);
+ /**
+ * Takes a response from the server and prepends it with IMAP_SERVER_SAID
+ *
+ * @param aServerString The string to alert the user with.
+ * @param url The running url.
+ */
+ void fEAlertFromServer(in ACString aServerString, in nsIMsgMailNewsUrl aUrl);
+
+ void commitNamespaces();
+
+ /**
+ * Returns a password via the out param, if we were able to prompt for one,
+ * or had one stored.
+ * If there is already a password prompt up, we return false, but we
+ * ask the async prompt service to notify us when we can put up a prompt.
+ * When that notification is received, we prompt the user and set the
+ * password on the protocol object, and signal a monitor that the imap
+ * thread should be waiting on.
+ *
+ * rv is NS_MSG_PASSWORD_PROMPT_CANCELLED if the user cancels the
+ * password prompt. That's not an exception, however.
+ *
+ * @param aProtocol imap protocol object requesting the password.
+ * @param aNewPasswordRequested Forces password prompt immediately
+ * @param aPassword returns the password, unless we had to prompt or use the,
+ * login manager and there was already a prompt up.
+ */
+ void asyncGetPassword(in nsIImapProtocol aProtocol,
+ in boolean aNewPasswordRequested,
+ out AString aPassword);
+
+ /**
+ * Returns a password via the out param if password is stored in login mgr.
+ * If no password is stored, this function returns NS_ERROR_NOT_AVAILABLE.
+ * This never triggers a password prompt.
+ *
+ * @param aPassword returns the stored password or empty string if not stored.
+ */
+ void syncGetPassword(out AString aPassword);
+
+ attribute boolean userAuthenticated;
+ void setMailServerUrls(in AUTF8String manageMailAccount, in AUTF8String manageLists, in AUTF8String manageFilters);
+
+ /** Used by the imap thread when upgrading from the socketType
+ * trySTARTTLS.
+ * @param aSucceeded whether STARTTLS succeeded. If it did, the server
+ * will set the socket type to alwaysSTARTTLS, otherwise plain.
+ */
+ void UpdateTrySTARTTLSPref(in boolean aSucceeded);
+
+ readonly attribute AUTF8String arbitraryHeaders;
+ void forgetPassword();
+
+ readonly attribute boolean showAttachmentsInline;
+ string cramMD5Hash(in string decodedChallenge, in string key);
+ /// String to send to the imap server as the login user name.
+ readonly attribute ACString loginUsername;
+ /// String to send to the imap server as the user name.
+ readonly attribute ACString originalUsername;
+ /// Internal pref key, unique over all servers
+ readonly attribute ACString serverKey;
+ /// password for server login
+ readonly attribute AString serverPassword;
+ /// remove a connection to the server
+ void removeServerConnection(in nsIImapProtocol aProtocol);
+ /// is the imap server shutting down?
+ readonly attribute boolean serverShuttingDown;
+ /// reset the connection for a particular folder
+ void resetServerConnection(in AUTF8String aFolderName);
+ /// tell the server if listing using lsub command
+ void setServerDoingLsub(in boolean aDoingLsub);
+ /// set whether UTF8=ACCEPT enabled or not
+ void setServerUtf8AcceptEnabled(in boolean aEnabled);
+};
diff --git a/comm/mailnews/imap/public/nsIImapService.idl b/comm/mailnews/imap/public/nsIImapService.idl
new file mode 100644
index 0000000000..04b2667467
--- /dev/null
+++ b/comm/mailnews/imap/public/nsIImapService.idl
@@ -0,0 +1,253 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+////////////////////////////////////////////////////////////////////////////////////////
+// The IMAP Service is an interfaced designed to make building and running imap urls
+// easier. Clients typically go to the imap service and ask it do things such as:
+// get new mail, etc....
+//
+// Oh and in case you couldn't tell by the name, the imap service is a service! and you
+// should go through the service manager to obtain an instance of it.
+////////////////////////////////////////////////////////////////////////////////////////
+
+#include "nsISupports.idl"
+#include "nsIImapUrl.idl"
+
+interface nsIImapMessageSink;
+interface nsIUrlListener;
+interface nsIURI;
+interface nsIFile;
+interface nsIMsgFolder;
+interface nsIMsgWindow;
+interface nsIMsgMailNewsUrl;
+interface nsIImapIncomingServer;
+interface nsICacheStorage;
+
+/**
+ * Most of the nsIImapService methods are friendly front ends for composing and
+ * issuing "imap://" protocol operations. Usually a nsImapUrl will be returned.
+ * This url object is stateful and tracks the issued request.
+ */
+[scriptable, uuid(aba44b3d-7a0f-4987-8794-96d2de66d966)]
+interface nsIImapService : nsISupports
+{
+ nsIURI selectFolder(in nsIMsgFolder aImapMailFolder,
+ in nsIUrlListener aUrlListener,
+ in nsIMsgWindow aMsgWindow);
+
+ /**
+ * Select the folder on the imap server without doing a sync of flags or
+ * headers. This is used for offline playback, where we don't want to
+ * download hdrs we don't have, because they may have been offline deleted.
+ *
+ * @param aImapMailFolder the folder to select
+ * @param aUrlListener url listener, can be null
+ * @param aMsgWindow msg window url is running in, can be null
+ *
+ * @returns the url created to run the lite select in.
+ */
+ nsIURI liteSelectFolder(in nsIMsgFolder aImapMailFolder,
+ in nsIUrlListener aUrlListener,
+ in nsIMsgWindow aMsgWindow);
+
+ void addImapFetchToUrl(in nsIMsgMailNewsUrl aURL,
+ in nsIMsgFolder aImapMailFolder,
+ in ACString aMessageIdentifierList,
+ in ACString aAdditionalHeader);
+
+ void fetchMessage(in nsIImapUrl aUrl,
+ in nsImapState aImapAction,
+ in nsIMsgFolder aImapMailFolder,
+ in nsIImapMessageSink aImapMessageSink,
+ in nsIMsgWindow aMsgWindow,
+ in nsISupports aConsumer,
+ in ACString aMessageIdentifierList,
+ in boolean convertDataToText,
+ out nsIURI aOutURL);
+
+ void noop(in nsIMsgFolder aImapMailFolder,
+ in nsIUrlListener aUrlListener,
+ out nsIURI aURL);
+
+ void getHeaders(in nsIMsgFolder aImapMailFolder,
+ in nsIUrlListener aUrlListener,
+ out nsIURI aURL,
+ in ACString aMessageIdentifierList,
+ in boolean aMessageIdsAreUID);
+
+ nsIURI getBodyStart(in nsIMsgFolder aImapMailFolder,
+ in nsIUrlListener aUrlListener,
+ in ACString aMessageIdentifierList,
+ in long numBytes);
+
+ /**
+ * Issue an EXPUNGE on the target folder.
+ *
+ * @param aImapMailFolder the folder to expunge
+ * @param aUrlListener url listener, can be null
+ * @param aMsgWindow msg window url is running in, can be null
+ */
+ void expunge(in nsIMsgFolder aImapMailFolder,
+ in nsIUrlListener aUrlListener,
+ in nsIMsgWindow aMsgWindow);
+
+ /**
+ * Issue a STATUS on the target folder.
+ *
+ * @param aImapMailFolder the folder to expunge
+ * @param aUrlListener url listener, can be null
+ *
+ * @returns the url created to run the status.
+ */
+ nsIURI updateFolderStatus(in nsIMsgFolder aImapMailFolder,
+ in nsIUrlListener aUrlListener);
+
+ /**
+ * Verify that we can login.
+ *
+ * @param aImapMailFolder - any old imap folder - we just need it to
+ * set url sinks.
+ * @param aMsgWindow - nsIMsgWindow to use for notification callbacks.
+ * @return - the url that we run.
+ */
+ nsIURI verifyLogon(in nsIMsgFolder aImapMailFolder,
+ in nsIUrlListener aUrlListener,
+ in nsIMsgWindow aMsgWindow);
+
+ void biff(in nsIMsgFolder aImapMailFolder,
+ in nsIUrlListener aUrlListener,
+ out nsIURI aURL,
+ in unsigned long aUidHighWater);
+
+ void deleteMessages(in nsIMsgFolder aImapMailFolder,
+ in nsIUrlListener aUrlListener,
+ out nsIURI aURL,
+ in ACString aMessageIdentifierList,
+ in boolean aMessageIdsAreUID);
+
+ void deleteAllMessages(in nsIMsgFolder aImapMailFolder,
+ in nsIUrlListener aUrlListener);
+
+ void addMessageFlags(in nsIMsgFolder aImapMailFolder,
+ in nsIUrlListener aUrlListener,
+ in ACString aMessageIdentifierList,
+ in imapMessageFlagsType aFlags,
+ in boolean aMessageIdsAreUID);
+
+ void subtractMessageFlags(in nsIMsgFolder aImapMailFolder,
+ in nsIUrlListener aUrlListener,
+ in ACString aMessageIdentifierList,
+ in imapMessageFlagsType aFlags,
+ in boolean aMessageIdsAreUID);
+
+ void setMessageFlags(in nsIMsgFolder aImapMailFolder,
+ in nsIUrlListener aUrlListener,
+ out nsIURI aURL,
+ in ACString aMessageIdentifierList,
+ in imapMessageFlagsType aFlags,
+ in boolean aMessageIdsAreUID);
+
+ void discoverAllFolders(in nsIMsgFolder aImapMailFolder,
+ in nsIUrlListener aUrlListener,
+ in nsIMsgWindow aMsgWindow);
+
+ void discoverAllAndSubscribedFolders(in nsIMsgFolder aImapMailFolder,
+ in nsIUrlListener aUrlListener,
+ in nsIMsgWindow aMsgWindow);
+
+ void discoverChildren(in nsIMsgFolder aImapMailFolder,
+ in nsIUrlListener aUrlListener,
+ in ACString folderPath);
+
+ void onlineMessageCopy(in nsIMsgFolder aSrcFolder,
+ in ACString aMessageIds,
+ in nsIMsgFolder aDstFolder,
+ in boolean aIdsAreUids,
+ in boolean aIsMove,
+ in nsIUrlListener aUrlListener,
+ out nsIURI aURL,
+ in nsISupports aCopyState,
+ in nsIMsgWindow aWindow);
+
+
+ void appendMessageFromFile(in nsIFile aFile,
+ in nsIMsgFolder aDstFolder,
+ in ACString aMessageId,
+ in boolean idsAreUids,
+ in boolean aInSelectedState,
+ in nsIUrlListener aUrlListener,
+ in nsISupports aCopyState,
+ in nsIMsgWindow aMsgWindow);
+
+ void downloadMessagesForOffline(in ACString aMessageIds, in nsIMsgFolder aSrcFolder,
+ in nsIUrlListener aListener, in nsIMsgWindow aMsgWindow);
+
+ void moveFolder(in nsIMsgFolder aSrcFolder,
+ in nsIMsgFolder aDstFolder,
+ in nsIUrlListener aUrlListener,
+ in nsIMsgWindow msgWindow);
+
+ void renameLeaf(in nsIMsgFolder aSrcFolder,
+ in AString aLeafName,
+ in nsIUrlListener aUrlListener,
+ in nsIMsgWindow msgWindow);
+
+ void deleteFolder(in nsIMsgFolder aFolder,
+ in nsIUrlListener aUrlListener,
+ in nsIMsgWindow aMsgWindow);
+
+ nsIURI createFolder(in nsIMsgFolder aParentFolder,
+ in AString aLeafName,
+ in nsIUrlListener aUrlListener);
+
+ void listFolder(in nsIMsgFolder aMailFolder,
+ in nsIUrlListener aUrlListener);
+
+ nsIURI subscribeFolder(in nsIMsgFolder aMailFolder,
+ in AString mailboxName,
+ in nsIUrlListener aUrlListener);
+
+ nsIURI unsubscribeFolder(in nsIMsgFolder aMailFolder,
+ in AString mailboxName,
+ in nsIUrlListener aUrlListener);
+
+ // this method will first check if the folder exists but is
+ // not subscribed to, in which case it will subscribe to the folder.
+ // otherwise, it will try to create the folder. It will try to do this
+ // with one url.
+ void ensureFolderExists(in nsIMsgFolder aParentFolder,
+ in AString aLeafName,
+ in nsIMsgWindow aMsgWindow,
+ in nsIUrlListener aUrlListener);
+
+
+ nsIURI getFolderAdminUrl(in nsIMsgFolder aMailFolder,
+ in nsIMsgWindow aMsgWindow,
+ in nsIUrlListener aUrlListener);
+
+ nsIURI issueCommandOnMsgs(in nsIMsgFolder aMailFolder,
+ in nsIMsgWindow aMsgWindow,
+ in ACString aCommand,
+ in ACString aMessageIdentifierList);
+
+ nsIURI fetchCustomMsgAttribute(in nsIMsgFolder aMailFolder,
+ in nsIMsgWindow aMsgWindow,
+ in ACString aAttribute,
+ in ACString aMessageIdentifierList);
+
+ nsIURI storeCustomKeywords(in nsIMsgFolder aMailFolder,
+ in nsIMsgWindow aMsgWindow,
+ in ACString flagsToAdd,
+ in ACString flagsToSubtract,
+ in ACString aMessageIdentifierList);
+
+ void getListOfFoldersOnServer(in nsIImapIncomingServer aServer, in nsIMsgWindow aMsgWindow);
+ void getListOfFoldersWithPath(in nsIImapIncomingServer aServer, in nsIMsgWindow aMsgWindow, in ACString folderPath);
+
+ nsISupports playbackAllOfflineOperations(in nsIMsgWindow aMsgWindow, in nsIUrlListener aListener);
+ void downloadAllOffineImapFolders(in nsIMsgWindow aMsgWindow, in nsIUrlListener aListener);
+
+ readonly attribute nsICacheStorage cacheStorage;
+};
diff --git a/comm/mailnews/imap/public/nsIImapUrl.idl b/comm/mailnews/imap/public/nsIImapUrl.idl
new file mode 100644
index 0000000000..75f85b93a5
--- /dev/null
+++ b/comm/mailnews/imap/public/nsIImapUrl.idl
@@ -0,0 +1,210 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIImapMailFolderSink;
+interface nsIImapMessageSink;
+interface nsIImapServerSink;
+interface nsIImapMockChannel;
+interface nsIFile;
+
+typedef long nsImapAction;
+typedef long nsImapState;
+
+typedef unsigned short imapMessageFlagsType;
+
+typedef long nsImapContentModifiedType;
+
+[scriptable, uuid(2e91901e-ff6c-11d3-b9fa-00108335942a)]
+interface nsImapContentModifiedTypes : nsISupports
+{
+ const long IMAP_CONTENT_NOT_MODIFIED = 0;
+ const long IMAP_CONTENT_MODIFIED_VIEW_INLINE = 1;
+ const long IMAP_CONTENT_MODIFIED_VIEW_AS_LINKS = 2;
+ const long IMAP_CONTENT_FORCE_CONTENT_NOT_MODIFIED = 3;
+} ;
+
+[scriptable, uuid(fe2a8f9e-2886-4146-9896-27fff660c69f)]
+interface nsIImapUrl : nsISupports
+{
+ ///////////////////////////////////////////////////////////////////////////////
+ // Getters and Setters for the imap specific event sinks to bind to the url
+ ///////////////////////////////////////////////////////////////////////////////
+ attribute nsIImapMailFolderSink imapMailFolderSink;
+ attribute nsIImapMessageSink imapMessageSink;
+ attribute nsIImapServerSink imapServerSink;
+
+ ///////////////////////////////////////////////////////////////////////////////
+ // Getters and Setters for the imap url state
+ ///////////////////////////////////////////////////////////////////////////////
+ attribute nsImapAction imapAction;
+ readonly attribute nsImapState requiredImapState;
+ readonly attribute string imapPartToFetch;
+ readonly attribute ACString customAttributeToFetch;
+ attribute ACString customAttributeResult;
+ readonly attribute ACString command;
+ attribute ACString customCommandResult;
+ readonly attribute ACString customAddFlags;
+ readonly attribute ACString customSubtractFlags;
+ void allocateCanonicalPath(in string aServerPath, in char aOnlineDelimiter, out string aAllocatedPath);
+ void allocateServerPath(in string aCanonicalPath, in char aOnlineDelimiter, out string aAllocatedPath);
+ string createServerSourceFolderPathString();
+ string createCanonicalSourceFolderPathString();
+ string createServerDestinationFolderPathString();
+
+ string addOnlineDirectoryIfNecessary(in string onlineMailboxName);
+ void createSearchCriteriaString(out string aResult);
+ readonly attribute ACString listOfMessageIds;
+
+ boolean messageIdsAreUids();
+ readonly attribute imapMessageFlagsType msgFlags; // kAddMsgFlags or kSubtractMsgFlags only
+
+ readonly attribute long numBytesToFetch;
+ attribute char onlineSubDirSeparator;
+ attribute boolean mimePartSelectorDetected;
+ attribute boolean msgLoadingFromCache; // true if this msg load is coming from a cache, so we can know to mark it read
+ attribute boolean externalLinkUrl; // true if we ran this url because the user clicked on a link.
+ attribute boolean validUrl; // false if we couldn't parse url for whatever reason.
+ /**
+ * copyState is used by some IMAP copy operations. The exact type stashed
+ * here depends on the operation being performed. For online move/copy,
+ * it'll be an nsImapMailCopyState (private to nsImapMailFolder). For
+ * other operations it might be (say), an nsIStreamListener.
+ */
+ attribute nsISupports copyState;
+ attribute nsIFile msgFile;
+ attribute nsIImapMockChannel mockChannel;
+ /**
+ * Set to true if we should store the msg(s) for offline use if we can,
+ * e.g., we're fetching a message and the folder is configured for offline
+ * use and we're not doing mime parts on demand.
+ */
+ attribute boolean storeResultsOffline;
+ /**
+ * If we fallback from fetching by parts to fetching the whole message,
+ * because all the parts were inline, this tells us we should store
+ * the message offline.
+ */
+ attribute boolean storeOfflineOnFallback;
+
+ /**
+ * This attribute defaults to false, but if we only want to use the offline
+ * cache (disk, memory, or offline store) to fetch the message, then we set
+ * this to true. Currently, nsIMsgMessageService.streamMessage does this.
+ */
+ attribute boolean localFetchOnly;
+
+ /// Server disconnected first time so we're retrying.
+ attribute boolean rerunningUrl;
+
+ /**
+ * Do we have more headers to download? This is set when we decide to
+ * download newest headers first, followed by older headers in a subsequent
+ * run of the url, which allows other urls to run against the folder in the
+ * meantime.
+ */
+ attribute boolean moreHeadersToDownload;
+
+ /**
+ * @{
+ * This is used to tell the runner of the url more about the status of
+ * the command, beyond whether it was successful or not. For example,
+ * subtracting flags from a UID that doesn't exist isn't an error
+ * (the server returns OK), but the backend code may want to know about it.
+ */
+ attribute long extraStatus;
+
+ /**
+ * Current possible extra status values
+ */
+ const long ImapStatusNone = 0;
+ const long ImapStatusFlagChangeFailed = 1;
+ const long ImapStatusFlagsNotSettable = 2;
+ /** @} */
+
+ ///////////////////////////////////////////////////////////////////////////////
+ // Enumerated types specific to imap urls...
+ ///////////////////////////////////////////////////////////////////////////////
+
+ // the following are nsImapState enums.
+ // we have a basic set of imap url actions. These actions are nsImapActions.
+ // Certain actions require us to be in the authenticated state and others require us to
+ // be in the selected state. nsImapState is used to store the state the url needs to
+ // be in. You'll later see us refer to the imap url state in the imap protocol when we
+ // are processing the current url. Don't confuse nsImapState with the generic url state
+ // used to keep track of whether the url is running or not...
+ const long nsImapAuthenticatedState = 0;
+ const long nsImapSelectedState = 1;
+
+ const long nsImapActionSendText = 0; // a state used for testing purposes to send raw url text straight to the server....
+ // nsImapAuthenticatedStateUrl urls
+ // since the following url actions require us to be in the authenticated
+ // state, the high bit is left blank....
+ const long nsImapTest = 0x00000001;
+ const long nsImapCreateFolder = 0x00000005;
+ const long nsImapDeleteFolder = 0x00000006;
+ const long nsImapRenameFolder = 0x00000007;
+ const long nsImapMoveFolderHierarchy = 0x00000008;
+ const long nsImapLsubFolders = 0x00000009;
+ const long nsImapGetMailAccountUrl = 0x0000000A;
+ const long nsImapDiscoverChildrenUrl = 0x0000000B;
+ const long nsImapDiscoverAllBoxesUrl = 0x0000000D;
+ const long nsImapDiscoverAllAndSubscribedBoxesUrl = 0x0000000E;
+ const long nsImapAppendMsgFromFile = 0x0000000F;
+ const long nsImapSubscribe = 0x00000010;
+ const long nsImapUnsubscribe = 0x00000011;
+ const long nsImapRefreshACL = 0x00000012;
+ const long nsImapRefreshAllACLs = 0x00000013;
+ const long nsImapListFolder = 0x00000014;
+ const long nsImapUpgradeToSubscription = 0x00000015;
+ const long nsImapFolderStatus = 0x00000016;
+ const long nsImapRefreshFolderUrls = 0x00000017;
+ const long nsImapEnsureExistsFolder = 0x00000018;
+ const long nsImapOfflineToOnlineCopy = 0x00000019;
+ const long nsImapOfflineToOnlineMove = 0x0000001A;
+ const long nsImapVerifylogon = 0x0000001B;
+ // it's okay to add more imap actions that require us to
+ // be in the authenticated state here without renumbering
+ // the imap selected state url actions. just make sure you don't
+ // set the high bit...
+
+ // nsImapSelectedState urls. Note, the high bit is always set for
+ // imap actions which require us to be in the selected state
+ const long nsImapSelectFolder = 0x10000002;
+ const long nsImapLiteSelectFolder = 0x10000003;
+ const long nsImapExpungeFolder = 0x10000004;
+ const long nsImapMsgFetch = 0x10000018;
+ const long nsImapMsgHeader = 0x10000019;
+ const long nsImapSearch = 0x1000001A;
+ const long nsImapDeleteMsg = 0x1000001B;
+ const long nsImapDeleteAllMsgs = 0x1000001C;
+ const long nsImapAddMsgFlags = 0x1000001D;
+ const long nsImapSubtractMsgFlags = 0x1000001E;
+ const long nsImapSetMsgFlags = 0x1000001F;
+ const long nsImapOnlineCopy = 0x10000020;
+ const long nsImapOnlineMove = 0x10000021;
+ const long nsImapOnlineToOfflineCopy = 0x10000022;
+ const long nsImapOnlineToOfflineMove = 0x10000023;
+ const long nsImapMsgPreview = 0x10000024;
+ const long nsImapBiff = 0x10000026;
+ const long nsImapSelectNoopFolder = 0x10000027;
+ const long nsImapAppendDraftFromFile = 0x10000028;
+ const long nsImapUidExpunge = 0x10000029;
+ const long nsImapSaveMessageToDisk = 0x10000030;
+ const long nsImapOpenMimePart = 0x10000031;
+ const long nsImapMsgDownloadForOffline = 0x10000032;
+ const long nsImapDeleteFolderAndMsgs = 0x10000033;
+ const long nsImapUserDefinedMsgCommand = 0x10000034;
+ const long nsImapUserDefinedFetchAttribute = 0x10000035;
+ const long nsImapMsgFetchPeek = 0x10000036;
+ const long nsImapMsgStoreCustomKeywords = 0x10000037;
+
+ /// Constant for the default IMAP port number
+ const int32_t DEFAULT_IMAP_PORT = 143;
+
+ /// Constant for the default IMAP over ssl port number
+ const int32_t DEFAULT_IMAPS_PORT = 993;
+};
diff --git a/comm/mailnews/imap/public/nsIMailboxSpec.idl b/comm/mailnews/imap/public/nsIMailboxSpec.idl
new file mode 100644
index 0000000000..729b867075
--- /dev/null
+++ b/comm/mailnews/imap/public/nsIMailboxSpec.idl
@@ -0,0 +1,47 @@
+/* -*- Mode: IDL; 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/. */
+
+#include "nsISupports.idl"
+
+interface nsIImapFlagAndUidState;
+
+[ptr] native nsImapNamespace(nsImapNamespace);
+%{C++
+class nsImapNamespace;
+%}
+
+[scriptable, uuid(a9fbbc80-5291-4ed8-a7f7-c2fcad231269)]
+interface nsIMailboxSpec : nsISupports
+{
+ attribute long folder_UIDVALIDITY;
+ /**
+ * The highest modification sequence number the parser has seen
+ * for this mailbox. See IMAP RFC 4551
+ **/
+ attribute unsigned long long highestModSeq;
+ attribute long numMessages;
+ attribute long numUnseenMessages;
+ attribute long numRecentMessages;
+
+ /// If server supports UIDNEXT, we store the result here.
+ attribute long nextUID;
+
+ attribute unsigned long box_flags;
+ attribute unsigned long supportedUserFlags;
+
+ attribute ACString allocatedPathName;
+ attribute AString unicharPathName;
+ attribute char hierarchyDelimiter;
+ attribute ACString hostName;
+
+ attribute nsIImapFlagAndUidState flagState;
+
+ attribute boolean folderSelected;
+ attribute boolean discoveredFromLsub;
+
+ attribute boolean onlineVerified;
+
+ [noscript] attribute nsImapNamespace namespaceForFolder;
+};
diff --git a/comm/mailnews/imap/public/nsIMsgImapMailFolder.idl b/comm/mailnews/imap/public/nsIMsgImapMailFolder.idl
new file mode 100644
index 0000000000..070276508a
--- /dev/null
+++ b/comm/mailnews/imap/public/nsIMsgImapMailFolder.idl
@@ -0,0 +1,253 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+#include "MailNewsTypes2.idl"
+
+interface nsIMsgFolder;
+interface nsIUTF8StringEnumerator;
+interface nsIUrlListener;
+interface nsIURI;
+interface nsIMsgDBHdr;
+interface nsIMsgWindow;
+interface nsIImapIncomingServer;
+interface nsIMsgParseMailMsgState;
+interface nsIAutoSyncState;
+
+/**
+ * nsIMsgQuota defines the quota for a resource within a quota root.
+ * @see RFC 2087
+ */
+[scriptable, uuid(9db60f97-45c1-45c2-8ab1-395690228f3f)]
+interface nsIMsgQuota : nsISupports {
+ /**
+ * If quota root name not empty, the concatenation of the quota root name
+ * and the resource name separated by a slash , e.g.,
+ * "User Quota / MESSAGE" or with empty quota root name, just resource
+ * name, e.g., "STORAGE".
+ */
+ attribute AUTF8String name;
+
+ /**
+ * Amount of resource in use for this quota root. E.g., number of messages
+ * or storage in KB (1024 octets).
+ */
+
+ attribute unsigned long long usage;
+ /**
+ * Maximum amount of usage permitted by the server for this quota root
+ * resource.
+ */
+ attribute unsigned long long limit;
+};
+
+/**
+ * nsIMsgImapFolderProps is a simple interface which allows the IMAP folder to
+ * update some values that the folder props js code will use to update the
+ * sharing and quota tabs in the folder properties.
+ */
+[scriptable, uuid(09D99F2C-3E23-4f8c-A536-5C277BAA9585)]
+interface nsIMsgImapFolderProps : nsISupports {
+
+ void setFolderType(in AString folderType);
+ void setFolderTypeDescription(in AString folderTypeDescription);
+ void setFolderPermissions(in AString permissions);
+ void serverDoesntSupportACL();
+
+ /**
+ * Toggles the display of quota information in the Quota tab of the folder properties.
+ * If on, the quota root, usage, and percentage used are displayed.
+ * If off, a status message is displayed. The status message can be set with setQuotaStatus().
+ * @param showData If true, display the quota root, usage information and usage percentage bar.
+ * If false, display the status message.
+ */
+ void showQuotaData(in boolean showData);
+
+ /**
+ * Sets the status string displayed in the Quota tab of the folder properties if quota
+ * information is not visible.
+ */
+ void setQuotaStatus(in AString folderQuotaStatus);
+
+ /**
+ * Sets the quota data displayed in the folder properties Quota tab. An
+ * array of quota items is passed in.
+ */
+ void setQuotaData(in Array<nsIMsgQuota> quotaArray);
+};
+
+[scriptable, uuid(fea0f455-7adf-4683-bf2f-c95c3fff03df)]
+interface nsIMsgImapMailFolder : nsISupports {
+ /**
+ * Remove the local version of this folder (used to clean up local folders
+ * which don't correspond to ones on the server).
+ */
+ void removeLocalSelf();
+ void createClientSubfolderInfo(in AUTF8String folderName, in char hierarchyDelimiter,
+ in long flags, in boolean suppressNotification);
+ void list();
+ void renameLocal(in AUTF8String newname, in nsIMsgFolder parent);
+ void prepareToRename();
+ void performExpand(in nsIMsgWindow aMsgWindow);
+ void recursiveCloseActiveConnections(in nsIImapIncomingServer aImapServer);
+ void renameClient(in nsIMsgWindow msgWindow, in nsIMsgFolder msgFolder, in AUTF8String oldName, in AUTF8String newName);
+
+ // these are used for offline synchronization
+ void storeImapFlags(in long aFlags, in boolean aAddFlags,
+ in Array<nsMsgKey> aKeysToFlag, in nsIUrlListener aUrlListener);
+ nsIURI setImapFlags(in string uids, in long flags);
+ void replayOfflineMoveCopy(in Array<nsMsgKey> keys, in boolean isMove,
+ in nsIMsgFolder aDstFolder, in nsIUrlListener aUrlListener,
+ in nsIMsgWindow aWindow, in boolean srcFolderOffline);
+ nsIURI playbackOfflineFolderCreate(in AString folderName, in nsIMsgWindow aWindow);
+ /**
+ * This is called by the offline sync code to tell the imap folder to
+ * remember info about the header with this key (messageId and key) because
+ * it's an offline move result header, and we need to generate an
+ * nsIMsgFolderListener.msgKeyChanged notification when we download the
+ * real header from the imap server.
+ *
+ * @param aMsgKey msg key of move result pseudo hdr.
+ */
+ void addMoveResultPseudoKey(in nsMsgKey aMsgKey);
+ /**
+ * Select this folder on the imap server without doing a sync of flags or
+ * headers. This is used for offline playback, where we don't want to
+ * download hdrs we don't have, because they may have been offline deleted.
+ *
+ * @param aUrlListener url listener, can be null
+ * @param aWindow msg window url is running in, can be null
+ */
+ void liteSelect(in nsIUrlListener aUrlListener, in nsIMsgWindow aWindow);
+
+ void fillInFolderProps(in nsIMsgImapFolderProps aFolderProps);
+ void resetNamespaceReferences();
+ void folderPrivileges(in nsIMsgWindow aWindow);
+ nsIMsgImapMailFolder findOnlineSubFolder(in AUTF8String onlineName);
+ void addFolderRights(in ACString userName, in ACString rights);
+ void refreshFolderRights();
+
+ /**
+ * Mark/unmark the header as pending removal from the offline store. If mark,
+ * this also increases the expungedBytes count on the folder so we know
+ * there's more local disk space to be reclaimed.
+ *
+ * @param aHdr msg hdr to mark pending removal from offline store.
+ * @param aMark whether to set or clear the pending removal status.
+ *
+ */
+ void markPendingRemoval(in nsIMsgDBHdr aHdr, in boolean aMark);
+
+ /**
+ * Issue an expunge of this folder to the imap server.
+ *
+ * @param aUrlListener url listener, can be null
+ * @param aWindow msg window url is running in, can be null
+ *
+ * @returns status of attempt to run url.
+ */
+ void expunge(in nsIUrlListener aListener, in nsIMsgWindow aMsgWindow);
+
+ void updateStatus(in nsIUrlListener aListener, in nsIMsgWindow aMsgWindow);
+ void updateFolderWithListener(in nsIMsgWindow aMsgWindow, in nsIUrlListener aListener);
+ // this is used to issue an arbitrary imap command on the passed in msgs.
+ // It assumes the command needs to be run in the selected state.
+ nsIURI issueCommandOnMsgs(in ACString command, in string uids, in nsIMsgWindow aWindow);
+ nsIURI fetchCustomMsgAttribute(in ACString msgAttribute, in string uids, in nsIMsgWindow aWindow);
+ nsIURI storeCustomKeywords(in nsIMsgWindow aMsgWindow,
+ in ACString aFlagsToAdd,
+ in ACString aFlagsToSubtract,
+ in Array<nsMsgKey> aKeysToStore);
+
+ void notifyIfNewMail();
+
+ void initiateAutoSync(in nsIUrlListener aUrlListener);
+
+ attribute boolean verifiedAsOnlineFolder;
+ attribute boolean explicitlyVerify;
+ attribute char hierarchyDelimiter;
+ attribute long boxFlags;
+ /**
+ * onlineName is the IMAP name of the mailbox that this folder represents.
+ * It's a path with components separated by hierarchyDelimiter.
+ * For example, "INBOX/bar/wibble", "INBOX.bar.wibble", etc...
+ */
+ attribute AUTF8String onlineName;
+ attribute boolean isNamespace;
+ readonly attribute boolean canOpenFolder;
+ attribute AUTF8String adminUrl;
+ readonly attribute boolean hasAdminUrl;
+ attribute boolean performingBiff;
+ readonly attribute nsIMsgParseMailMsgState hdrParser;
+ readonly attribute nsIImapIncomingServer imapIncomingServer;
+ readonly attribute nsIAutoSyncState autoSyncStateObj;
+ readonly attribute boolean shouldUseUtf8FolderName;
+ /**
+ * @{
+ * These are used to access the response to the STATUS or SELECT command.
+ * The counts include deleted messages, or headers we haven't downloaded yet.
+ */
+ readonly attribute long serverTotal;
+ readonly attribute long serverUnseen;
+ readonly attribute long serverRecent;
+ readonly attribute long serverNextUID;
+ /** @} */
+
+ /**
+ * Return an array of quota items of type nsIMsgQuota defined above.
+ * A not-empty array indicates that the server has provided one or more
+ * sets of quota information on this folder. The array will be empty
+ * - if the server does not supports quotas,
+ * - if there are no resource quotas on this folder, or
+ * - if the folder has yet to be opened (selected) by the user.
+ */
+ Array<nsIMsgQuota> getQuota();
+
+ /**
+ * List all (human) users apart from the current user who have access to
+ * this folder.
+ *
+ * You can find out which rights they have with getRightsForUser().
+ */
+ nsIUTF8StringEnumerator getOtherUsersWithAccess();
+
+ /**
+ * Which access rights a certain user has for this folder.
+ *
+ * @return list of flags
+ * e.g. "lrswipcd" for write access and "lrs" for read only access.
+ *
+ * See RFC 2086 (e.g. Cyrus) and RFC 4314 (e.g. dovecot)
+ *
+ * l = locate = visible in folder list
+ * r = read = list mails, get/read mail contents
+ * s = set seen flag = mark read. Does not affect other users.
+ * d (or t) = delete mails
+ * w = write = change (other) flags of existing mails
+ * i = insert = add mails to this folder
+ * p = post = send mail directly to the submission address for folder
+ * c (or k) = create subfolders
+ * (e = expunge = compress)
+ * (x = delete folder)
+ * a = admin = change permissions
+ */
+ ACString getPermissionsForUser(in ACString username);
+
+ /**
+ * Change the number of "pending" messages in a folder,
+ * messages we know about, but don't have the headers for yet
+ *
+ * @param aDelta amount to change total by.
+ */
+ void changePendingTotal(in long aDelta);
+
+ /**
+ * Change the number of "pending" unread messages in a folder,
+ * unread messages we know about, but don't have the headers for yet
+ *
+ * @param aDelta amount to change the unread count by.
+ */
+ void changePendingUnread(in long aDelta);
+};
diff --git a/comm/mailnews/imap/src/ImapChannel.jsm b/comm/mailnews/imap/src/ImapChannel.jsm
new file mode 100644
index 0000000000..0e5c333d9a
--- /dev/null
+++ b/comm/mailnews/imap/src/ImapChannel.jsm
@@ -0,0 +1,318 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 = ["ImapChannel"];
+
+const { ImapUtils } = ChromeUtils.import("resource:///modules/ImapUtils.jsm");
+const { MailChannel } = ChromeUtils.importESModule(
+ "resource:///modules/MailChannel.sys.mjs"
+);
+const { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+/**
+ * A channel to interact with IMAP server.
+ *
+ * @implements {nsIChannel}
+ * @implements {nsIRequest}
+ * @implements {nsICacheEntryOpenCallback}
+ */
+class ImapChannel extends MailChannel {
+ QueryInterface = ChromeUtils.generateQI([
+ "nsIMailChannel",
+ "nsIChannel",
+ "nsIRequest",
+ "nsIWritablePropertyBag",
+ "nsICacheEntryOpenCallback",
+ ]);
+
+ _logger = ImapUtils.logger;
+ _status = Cr.NS_OK;
+
+ /**
+ * @param {nsIURI} uri - The uri to construct the channel from.
+ * @param {nsILoadInfo} loadInfo - The loadInfo associated with the channel.
+ */
+ constructor(uri, loadInfo) {
+ super();
+ this._server = MailServices.accounts
+ .findServerByURI(uri)
+ .QueryInterface(Ci.nsIImapIncomingServer);
+
+ // nsIChannel attributes.
+ this.originalURI = uri;
+ this.loadInfo = loadInfo;
+ this.contentLength = 0;
+
+ this.uri = uri;
+
+ uri = uri.QueryInterface(Ci.nsIMsgMessageUrl);
+ try {
+ this.contentLength = uri.messageHeader.messageSize;
+ } catch (e) {
+ // Got passed an IMAP folder URL.
+ this._isFolderURL = this._server && !/#(\d+)$/.test(uri.spec);
+ }
+ }
+
+ /**
+ * @see nsIRequest
+ * @returns {string}
+ */
+ get name() {
+ return this.URI?.spec;
+ }
+
+ /**
+ * @see nsIRequest
+ * @returns {boolean}
+ */
+ isPending() {
+ return !!this._pending;
+ }
+
+ /**
+ * @see nsIRequest
+ * @returns {nsresult}
+ */
+ get status() {
+ return this._status;
+ }
+
+ /**
+ * @see nsICacheEntryOpenCallback
+ */
+ onCacheEntryAvailable(entry, isNew, status) {
+ if (!Components.isSuccessCode(status)) {
+ // If memory cache doesn't work, read from the server.
+ this._readFromServer();
+ return;
+ }
+
+ if (isNew) {
+ if (Services.io.offline) {
+ this._status = Cr.NS_ERROR_OFFLINE;
+ return;
+ }
+ // It's a new entry, needs to read from the server.
+ let tee = Cc["@mozilla.org/network/stream-listener-tee;1"].createInstance(
+ Ci.nsIStreamListenerTee
+ );
+ let outStream = entry.openOutputStream(0, -1);
+ // When the tee stream receives data from the server, it writes to both
+ // the original listener and outStream (memory cache).
+ tee.init(this._listener, outStream, null);
+ this._listener = tee;
+ this._cacheEntry = entry;
+ this._readFromServer();
+ return;
+ }
+
+ // It's an old entry, read from the memory cache.
+ this._readFromCacheStream(entry.openInputStream(0));
+ }
+
+ onCacheEntryCheck(entry) {
+ return Ci.nsICacheEntryOpenCallback.ENTRY_WANTED;
+ }
+
+ /**
+ * Get readonly URI.
+ * @see nsIChannel
+ */
+ get URI() {
+ return this.uri;
+ }
+
+ get contentType() {
+ return this._contentType || "message/rfc822";
+ }
+
+ set contentType(value) {
+ this._contentType = value;
+ }
+
+ get isDocument() {
+ return true;
+ }
+
+ open() {
+ throw Components.Exception(
+ "ImapChannel.open() not implemented",
+ Cr.NS_ERROR_NOT_IMPLEMENTED
+ );
+ }
+
+ asyncOpen(listener) {
+ this._logger.debug(`asyncOpen ${this.URI.spec}`);
+ if (this._isFolderURL) {
+ let handler = Cc[
+ "@mozilla.org/uriloader/content-handler;1?type=x-application-imapfolder"
+ ].createInstance(Ci.nsIContentHandler);
+ handler.handleContent("x-application-imapfolder", null, this);
+ return;
+ }
+
+ let url = new URL(this.URI.spec);
+ this._listener = listener;
+ if (url.searchParams.get("part")) {
+ let converter = Cc["@mozilla.org/streamConverters;1"].getService(
+ Ci.nsIStreamConverterService
+ );
+ this._listener = converter.asyncConvertData(
+ "message/rfc822",
+ "*/*",
+ listener,
+ this
+ );
+ }
+
+ let msgIds = this.URI.QueryInterface(Ci.nsIImapUrl).QueryInterface(
+ Ci.nsIMsgMailNewsUrl
+ ).listOfMessageIds;
+ this._msgKey = parseInt(msgIds);
+ this.contentLength = 0;
+ try {
+ if (this.readFromLocalCache()) {
+ this._logger.debug("Read from local cache");
+ return;
+ }
+ } catch (e) {
+ this._logger.warn(e);
+ }
+
+ try {
+ let uri = this.URI;
+ if (this.URI.spec.includes("?")) {
+ uri = uri.mutate().setQuery("").finalize();
+ }
+ // Check if a memory cache is available for the current URI.
+ MailServices.imap.cacheStorage.asyncOpenURI(
+ uri,
+ "",
+ this.URI.QueryInterface(Ci.nsIImapUrl).storeResultsOffline
+ ? // Don't write to the memory cache if storing offline.
+ Ci.nsICacheStorage.OPEN_READONLY
+ : Ci.nsICacheStorage.OPEN_NORMALLY,
+ this
+ );
+ } catch (e) {
+ this._logger.warn(e);
+ this._readFromServer();
+ }
+ if (this._status == Cr.NS_ERROR_OFFLINE) {
+ throw new Components.Exception(
+ "The requested action could not be completed in the offline state",
+ Cr.NS_ERROR_OFFLINE
+ );
+ }
+ }
+
+ /**
+ * Try to read the message from the offline storage.
+ *
+ * @returns {boolean} True if successfully read from the offline storage.
+ */
+ readFromLocalCache() {
+ if (
+ !this.URI.QueryInterface(Ci.nsIImapUrl).QueryInterface(
+ Ci.nsIMsgMailNewsUrl
+ ).msgIsInLocalCache &&
+ !this.URI.folder.hasMsgOffline(this._msgKey, null, 10)
+ ) {
+ return false;
+ }
+
+ let hdr = this.URI.folder.GetMessageHeader(this._msgKey);
+ let stream = this.URI.folder.getLocalMsgStream(hdr);
+ this._readFromCacheStream(stream);
+ return true;
+ }
+
+ /**
+ * Read the message from the a stream.
+ *
+ * @param {nsIInputStream} cacheStream - The input stream to read.
+ */
+ _readFromCacheStream(stream) {
+ let pump = Cc["@mozilla.org/network/input-stream-pump;1"].createInstance(
+ Ci.nsIInputStreamPump
+ );
+ this._contentType = "";
+ pump.init(stream, 0, 0, true);
+ pump.asyncRead({
+ onStartRequest: () => {
+ this._listener.onStartRequest(this);
+ this.URI.SetUrlState(true, Cr.NS_OK);
+ this._pending = true;
+ },
+ onStopRequest: (request, status) => {
+ this._listener.onStopRequest(this, status);
+ this.URI.SetUrlState(false, status);
+ try {
+ this.loadGroup?.removeRequest(this, null, Cr.NS_OK);
+ } catch (e) {}
+ this._pending = false;
+ },
+ onDataAvailable: (request, stream, offset, count) => {
+ this.contentLength += count;
+ this._listener.onDataAvailable(this, stream, offset, count);
+ try {
+ if (!stream.available()) {
+ stream.close();
+ }
+ } catch (e) {}
+ },
+ });
+ }
+
+ /**
+ * Retrieve the message from the server.
+ */
+ _readFromServer() {
+ this._logger.debug("Read from server");
+ let pipe = Cc["@mozilla.org/pipe;1"].createInstance(Ci.nsIPipe);
+ pipe.init(true, true, 0, 0);
+ let inputStream = pipe.inputStream;
+ let outputStream = pipe.outputStream;
+
+ this._server.wrappedJSObject.withClient(this.URI.folder, client => {
+ client.startRunningUrl(null, null, this.URI);
+ client.channel = this;
+ this._listener.onStartRequest(this);
+ this._pending = true;
+ client.onReady = () => {
+ client.fetchMessage(this.URI.folder, this._msgKey);
+ };
+
+ client.onData = data => {
+ this.contentLength += data.length;
+ outputStream.write(data, data.length);
+ this._listener.onDataAvailable(this, inputStream, 0, data.length);
+ };
+
+ client.onDone = status => {
+ try {
+ this.loadGroup?.removeRequest(this, null, status);
+ } catch (e) {}
+ this._listener.onStopRequest(this, status);
+ };
+ this._pending = false;
+ });
+ }
+
+ /** @see nsIWritablePropertyBag */
+ getProperty(key) {
+ return this[key];
+ }
+
+ setProperty(key, value) {
+ this[key] = value;
+ }
+
+ deleteProperty(key) {
+ delete this[key];
+ }
+}
diff --git a/comm/mailnews/imap/src/ImapClient.jsm b/comm/mailnews/imap/src/ImapClient.jsm
new file mode 100644
index 0000000000..319a03b958
--- /dev/null
+++ b/comm/mailnews/imap/src/ImapClient.jsm
@@ -0,0 +1,1895 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 = ["ImapClient"];
+
+var { AppConstants } = ChromeUtils.importESModule(
+ "resource://gre/modules/AppConstants.sys.mjs"
+);
+var { clearTimeout, setTimeout } = ChromeUtils.importESModule(
+ "resource://gre/modules/Timer.sys.mjs"
+);
+var { MailStringUtils } = ChromeUtils.import(
+ "resource:///modules/MailStringUtils.jsm"
+);
+var { ImapAuthenticator } = ChromeUtils.import(
+ "resource:///modules/MailAuthenticator.jsm"
+);
+var { ImapResponse } = ChromeUtils.import(
+ "resource:///modules/ImapResponse.jsm"
+);
+var { ImapUtils } = ChromeUtils.import("resource:///modules/ImapUtils.jsm");
+
+// There can be multiple ImapClient running concurrently, assign each logger a
+// unique prefix.
+let loggerInstanceId = 0;
+
+const PR_UINT32_MAX = 0xffffffff;
+
+/**
+ * A class to interact with IMAP server.
+ */
+class ImapClient {
+ _logger = console.createInstance({
+ prefix: `mailnews.imap.${loggerInstanceId++}`,
+ maxLogLevel: "Warn",
+ maxLogLevelPref: "mailnews.imap.loglevel",
+ });
+
+ /**
+ * @param {nsIImapIncomingServer} server - The associated server instance.
+ */
+ constructor(server) {
+ this._server = server.QueryInterface(Ci.nsIMsgIncomingServer);
+ this._serverSink = this._server.QueryInterface(Ci.nsIImapServerSink);
+ this._authenticator = new ImapAuthenticator(server);
+
+ // Auth methods detected from the CAPABILITY response.
+ this._supportedAuthMethods = [];
+ // Subset of _supportedAuthMethods that are allowed by user preference.
+ this._possibleAuthMethods = [];
+ // Auth methods set by user preference.
+ this._preferredAuthMethods =
+ {
+ [Ci.nsMsgAuthMethod.passwordCleartext]: ["PLAIN", "LOGIN"],
+ [Ci.nsMsgAuthMethod.passwordEncrypted]: ["CRAM-MD5"],
+ [Ci.nsMsgAuthMethod.GSSAPI]: ["GSSAPI"],
+ [Ci.nsMsgAuthMethod.NTLM]: ["NTLM"],
+ [Ci.nsMsgAuthMethod.OAuth2]: ["XOAUTH2"],
+ [Ci.nsMsgAuthMethod.External]: ["EXTERNAL"],
+ }[server.authMethod] || [];
+ // The next auth method to try if the current failed.
+ this._nextAuthMethod = null;
+
+ this._tag = Math.floor(100 * Math.random());
+ this._charsetManager = Cc[
+ "@mozilla.org/charset-converter-manager;1"
+ ].getService(Ci.nsICharsetConverterManager);
+
+ this._messageUids = [];
+ this._messages = new Map();
+
+ this._loadPrefs();
+ }
+
+ /**
+ * @type {boolean} - Whether the socket is open.
+ */
+ get isOnline() {
+ return this._socket?.readyState == "open";
+ }
+
+ /**
+ * Load imap related preferences, many behaviors depend on these pref values.
+ */
+ _loadPrefs() {
+ this._prefs = {
+ tcpTimeout: Services.prefs.getIntPref("mailnews.tcptimeout"),
+ };
+ }
+
+ /**
+ * Reset some internal states to be safely reused.
+ */
+ _reset() {
+ this.onData = () => {};
+ this.onDone = () => {};
+
+ this._actionAfterDiscoverAllFolders = null;
+ this.channel = null;
+ this._urlListener = null;
+ this._msgWindow = null;
+ this._authenticating = false;
+ this.verifyLogon = false;
+ this._idling = false;
+ if (this._idleTimer) {
+ clearTimeout(this._idleTimer);
+ this._idleTimer = null;
+ }
+ }
+
+ /**
+ * Initiate a connection to the server
+ */
+ connect() {
+ if (this.isOnline) {
+ // Reuse the connection.
+ this.onReady();
+ this._setSocketTimeout(this._prefs.tcpTimeout);
+ } else {
+ let hostname = this._server.hostName.toLowerCase();
+ this._logger.debug(`Connecting to ${hostname}:${this._server.port}`);
+ this._greeted = false;
+ this._capabilities = null;
+ this._secureTransport = this._server.socketType == Ci.nsMsgSocketType.SSL;
+ this._socket = new TCPSocket(hostname, this._server.port, {
+ binaryType: "arraybuffer",
+ useSecureTransport: this._secureTransport,
+ });
+ this._socket.onopen = this._onOpen;
+ this._socket.onerror = this._onError;
+ }
+ }
+
+ /**
+ * Set socket timeout in seconds.
+ *
+ * @param {number} timeout - The timeout in seconds.
+ */
+ _setSocketTimeout(timeout) {
+ this._socket.transport?.setTimeout(
+ Ci.nsISocketTransport.TIMEOUT_READ_WRITE,
+ timeout
+ );
+ }
+
+ /**
+ * Construct an nsIMsgMailNewsUrl instance, setup urlListener to notify when
+ * the current request is finished.
+ *
+ * @param {nsIUrlListener} urlListener - Callback for the request.
+ * @param {nsIMsgWindow} msgWindow - The associated msg window.
+ * @param {nsIMsgMailNewsUrl} [runningUri] - The url to run, if provided.
+ * @returns {nsIMsgMailNewsUrl}
+ */
+ startRunningUrl(urlListener, msgWindow, runningUri) {
+ this._urlListener = urlListener;
+ this._msgWindow = msgWindow;
+ this.runningUri = runningUri;
+ if (!this.runningUri) {
+ this.runningUri = Services.io.newURI(
+ `imap://${this._server.hostName}:${this._server.port}`
+ );
+ }
+ this._urlListener?.OnStartRunningUrl(this.runningUri, Cr.NS_OK);
+ this.runningUri
+ .QueryInterface(Ci.nsIMsgMailNewsUrl)
+ .SetUrlState(true, Cr.NS_OK);
+ return this.runningUri;
+ }
+
+ /**
+ * Discover all folders if the current server hasn't already discovered.
+ */
+ _discoverAllFoldersIfNecessary = () => {
+ if (this._server.hasDiscoveredFolders) {
+ this.onReady();
+ return;
+ }
+ this._actionAfterDiscoverAllFolders = this.onReady;
+ this.discoverAllFolders(this._server.rootFolder);
+ };
+
+ /**
+ * Discover all folders.
+ *
+ * @param {nsIMsgFolder} folder - The associated folder.
+ */
+ discoverAllFolders(folder) {
+ this._logger.debug("discoverAllFolders", folder.URI);
+
+ let handleListResponse = res => {
+ this._hasTrash = res.mailboxes.some(
+ mailbox => mailbox.flags & ImapUtils.FLAG_IMAP_TRASH
+ );
+ if (!this._hasTrash) {
+ let trashFolderName = this._server.trashFolderName.toLowerCase();
+ let trashMailbox = res.mailboxes.find(
+ mailbox => mailbox.name.toLowerCase() == trashFolderName
+ );
+ if (trashMailbox) {
+ this._hasTrash = true;
+ trashMailbox.flags |= ImapUtils.FLAG_IMAP_TRASH;
+ }
+ }
+ for (let mailbox of res.mailboxes) {
+ this._serverSink.possibleImapMailbox(
+ mailbox.name.replaceAll(mailbox.delimiter, "/"),
+ mailbox.delimiter,
+ mailbox.flags
+ );
+ }
+ };
+
+ if (this._capabilities.includes("LIST-EXTENDED")) {
+ this._nextAction = res => {
+ handleListResponse(res);
+ this._actionFinishFolderDiscovery();
+ };
+ let command = 'LIST (SUBSCRIBED) "" "*"';
+ if (this._capabilities.includes("SPECIAL-USE")) {
+ command += " RETURN (SPECIAL-USE)"; // rfc6154
+ }
+ this._sendTagged(command);
+ return;
+ }
+
+ this._nextAction = res => {
+ this._nextAction = res2 => {
+ // Per rfc3501#section-6.3.9, if LSUB returns different flags from LIST,
+ // use the LIST responses.
+ for (let mailbox of res2.mailboxes) {
+ let mailboxFromList = res.mailboxes.find(x => x.name == mailbox.name);
+ if (
+ mailboxFromList?.flags &&
+ mailboxFromList?.flags != mailbox.flags
+ ) {
+ mailbox.flags = mailboxFromList.flags;
+ }
+ }
+ handleListResponse(res2);
+ this._actionFinishFolderDiscovery();
+ };
+ this._sendTagged('LSUB "" "*"');
+ };
+ let command = 'LIST "" "*"';
+ if (this._capabilities.includes("SPECIAL-USE")) {
+ command += " RETURN (SPECIAL-USE)"; // rfc6154
+ }
+ this._sendTagged(command);
+ }
+
+ /**
+ * Discover all folders for the subscribe dialog.
+ *
+ * @param {nsIMsgFolder} folder - The associated folder.
+ */
+ discoverAllAndSubscribedFolders(folder) {
+ this._logger.debug("discoverAllAndSubscribedFolders", folder.URI);
+ let handleListResponse = res => {
+ for (let mailbox of res.mailboxes) {
+ this._serverSink.possibleImapMailbox(
+ mailbox.name.replaceAll(mailbox.delimiter, "/"),
+ mailbox.delimiter,
+ mailbox.flags
+ );
+ }
+ };
+
+ this._nextAction = res => {
+ handleListResponse(res);
+ this._server.doingLsub = false;
+ this._nextAction = res2 => {
+ // Per rfc3501#section-6.3.9, if LSUB returns different flags from LIST,
+ // use the LIST responses.
+ for (let mailbox of res2.mailboxes) {
+ let mailboxFromList = res.mailboxes.find(x => x.name == mailbox.name);
+ if (
+ mailboxFromList?.flags &&
+ mailboxFromList?.flags != mailbox.flags
+ ) {
+ mailbox.flags = mailboxFromList.flags;
+ }
+ }
+ handleListResponse(res2);
+ this._actionDone();
+ };
+ this._sendTagged('LIST "" "*"');
+ };
+ this._sendTagged('LSUB "" "*"');
+ this._server.doingLsub = true;
+ }
+
+ /**
+ * Select a folder.
+ *
+ * @param {nsIMsgFolder} folder - The folder to select.
+ */
+ selectFolder(folder) {
+ this._logger.debug("selectFolder", folder.URI);
+ if (this.folder == folder) {
+ this._actionNoop();
+ return;
+ }
+ this._actionAfterSelectFolder = this._actionUidFetch;
+ this._nextAction = this._actionSelectResponse(folder);
+ this._sendTagged(`SELECT "${this._getServerFolderName(folder)}"`);
+ }
+
+ /**
+ * Rename a folder.
+ *
+ * @param {nsIMsgFolder} folder - The folder to rename.
+ * @param {string} newName - The new folder name.
+ */
+ renameFolder(folder, newName) {
+ this._logger.debug("renameFolder", folder.URI, newName);
+ let delimiter =
+ folder.QueryInterface(Ci.nsIMsgImapMailFolder).hierarchyDelimiter || "/";
+ let names = this._getAncestorFolderNames(folder);
+ let oldName = this._getServerFolderName(folder);
+ newName = this._encodeMailboxName([...names, newName].join(delimiter));
+
+ this._nextAction = this._actionRenameResponse(oldName, newName);
+ this._sendTagged(`RENAME "${oldName}" "${newName}"`);
+ }
+
+ /**
+ * Move a source folder to be a child of another folder.
+ *
+ * @param {nsIMsgFolder} srcFolder - The source folder to move.
+ * @param {nsIMsgFolder} dstFolder - The target parent folder.
+ */
+ moveFolder(srcFolder, dstFolder) {
+ this._logger.debug("moveFolder", srcFolder.URI, dstFolder.URI);
+ let oldName = this._getServerFolderName(srcFolder);
+ let newName = this._getServerSubFolderName(dstFolder, srcFolder.name);
+ this._nextAction = this._actionRenameResponse(oldName, newName, true);
+ this._sendTagged(`RENAME "${oldName}" "${newName}"`);
+ }
+
+ /**
+ * Send LIST command for a folder.
+ *
+ * @param {nsIMsgFolder} folder - The folder to list.
+ */
+ listFolder(folder) {
+ this._logger.debug("listFolder", folder.URI);
+ this._actionList(this._getServerFolderName(folder), () => {
+ this._actionDone();
+ });
+ }
+
+ /**
+ * Send DELETE command for a folder and all subfolders.
+ *
+ * @param {nsIMsgFolder} folder - The folder to delete.
+ */
+ deleteFolder(folder) {
+ this._logger.debug("deleteFolder", folder.URI);
+ this._nextAction = res => {
+ // Leaves have longer names than parent mailbox, sort them by the name
+ // length, so that leaf mailbox will be deleted first.
+ let mailboxes = res.mailboxes.sort(
+ (x, y) => y.name.length - x.name.length
+ );
+ let selfName = this._getServerFolderName(folder);
+ let selfIncluded = false;
+ this._nextAction = () => {
+ let mailbox = mailboxes.shift();
+ if (mailbox) {
+ this._sendTagged(`DELETE "${mailbox.name}"`);
+ if (!selfIncluded && selfName == mailbox.name) {
+ selfIncluded = true;
+ }
+ } else if (!selfIncluded) {
+ this._nextAction = () => this._actionDone();
+ this._sendTagged(`DELETE "${this._getServerFolderName(folder)}"`);
+ } else {
+ this._actionDone();
+ }
+ };
+ this._nextAction();
+ };
+ this._sendTagged(`LIST "" "${this._getServerFolderName(folder)}"`);
+ }
+
+ /**
+ * Ensure a folder exists on the server. Create one if not already exists.
+ *
+ * @param {nsIMsgFolder} parent - The parent folder to check.
+ * @param {string} folderName - The folder name.
+ */
+ ensureFolderExists(parent, folderName) {
+ this._logger.debug("ensureFolderExists", parent.URI, folderName);
+ let mailboxName = this._getServerSubFolderName(parent, folderName);
+ this._nextAction = res => {
+ if (res.mailboxes.length) {
+ // Already exists.
+ this._actionDone();
+ return;
+ }
+ // Create one and subscribe to it.
+ this._actionCreateAndSubscribe(mailboxName, res => {
+ this._actionList(mailboxName, () => this._actionDone());
+ });
+ };
+ this._sendTagged(`LIST "" "${mailboxName}"`);
+ }
+
+ /**
+ * Create a folder on the server.
+ *
+ * @param {nsIMsgFolder} parent - The parent folder to check.
+ * @param {string} folderName - The folder name.
+ */
+ createFolder(parent, folderName) {
+ this._logger.debug("createFolder", parent.URI, folderName);
+ let mailboxName = this._getServerSubFolderName(parent, folderName);
+ this._actionCreateAndSubscribe(mailboxName, res => {
+ this._actionList(mailboxName, () => this._actionDone());
+ });
+ }
+
+ /**
+ * Subscribe a folder.
+ *
+ * @param {nsIMsgFolder} folder - The folder to subscribe.
+ * @param {string} folderName - The folder name.
+ */
+ subscribeFolder(folder, folderName) {
+ this._logger.debug("subscribeFolder", folder.URI, folderName);
+ this._nextAction = () => this._server.performExpand();
+ this._sendTagged(`SUBSCRIBE "${folderName}"`);
+ }
+
+ /**
+ * Unsubscribe a folder.
+ *
+ * @param {nsIMsgFolder} folder - The folder to unsubscribe.
+ * @param {string} folderName - The folder name.
+ */
+ unsubscribeFolder(folder, folderName) {
+ this._logger.debug("unsubscribeFolder", folder.URI, folderName);
+ this._nextAction = () => this._server.performExpand();
+ this._sendTagged(`UNSUBSCRIBE "${folderName}"`);
+ }
+
+ /**
+ * Fetch the attribute of messages.
+ *
+ * @param {nsIMsgFolder} folder - The folder to check.
+ * @param {string} uids - The message uids.
+ * @param {string} attribute - The message attribute to fetch
+ */
+ fetchMsgAttribute(folder, uids, attribute) {
+ this._logger.debug("fetchMsgAttribute", folder.URI, uids, attribute);
+ this._nextAction = res => {
+ if (res.done) {
+ let resultAttributes = res.messages
+ .map(m => m.customAttributes[attribute])
+ .flat();
+ this.runningUri.QueryInterface(Ci.nsIImapUrl).customAttributeResult =
+ resultAttributes.length > 1
+ ? `(${resultAttributes.join(" ")})`
+ : resultAttributes[0];
+ this._actionDone();
+ }
+ };
+ this._sendTagged(`UID FETCH ${uids} (${attribute})`);
+ }
+
+ /**
+ * Delete all the messages in a folder.
+ *
+ * @param {nsIMsgFolder} folder - The folder to delete messages.
+ */
+ deleteAllMessages(folder) {
+ this._logger.debug("deleteAllMessages", folder.URI);
+ this._actionInFolder(folder, () => {
+ if (!this._messages.size) {
+ this._actionDone();
+ return;
+ }
+
+ this._nextAction = () => this.expunge(folder);
+ this._sendTagged("UID STORE 1:* +FLAGS.SILENT (\\Deleted)");
+ });
+ }
+
+ /**
+ * Search in a folder.
+ *
+ * @param {nsIMsgFolder} folder - The folder to delete messages.
+ * @param {string} searchCommand - The SEARCH command together with the search
+ * criteria.
+ */
+ search(folder, searchCommand) {
+ this._logger.debug("search", folder.URI);
+ this._actionInFolder(folder, () => {
+ this._nextAction = res => {
+ this.onData(res.search);
+ this._actionDone();
+ };
+ this._sendTagged(`UID ${searchCommand}`);
+ });
+ }
+
+ /**
+ * Get the names of all ancestor folders. For example,
+ * folder a/b/c will return ['a', 'b'].
+ *
+ * @param {nsIMsgFolder} folder - The input folder.
+ * @returns {string[]}
+ */
+ _getAncestorFolderNames(folder) {
+ let matches = /imap:\/\/[^/]+\/(.+)/.exec(folder.URI);
+ return matches[1].split("/").slice(0, -1);
+ }
+
+ /**
+ * When UTF8 is enabled, use the name directly. Otherwise, encode to mUTF-7.
+ *
+ * @param {string} name - The mailbox name.
+ */
+ _encodeMailboxName(name) {
+ return this._utf8Enabled ? name : this._charsetManager.unicodeToMutf7(name);
+ }
+
+ /**
+ * Get the server name of a msg folder.
+ *
+ * @param {nsIMsgFolder} folder - The input folder.
+ * @returns {string}
+ */
+ _getServerFolderName(folder) {
+ if (folder.isServer) {
+ return "";
+ }
+
+ if (folder.onlineName) {
+ return folder.onlineName.replaceAll('"', '\\"');
+ }
+ let delimiter =
+ folder.QueryInterface(Ci.nsIMsgImapMailFolder).hierarchyDelimiter || "/";
+ let names = this._getAncestorFolderNames(folder);
+ return this._encodeMailboxName(
+ [...names, folder.name].join(delimiter)
+ ).replaceAll('"', '\\"');
+ }
+
+ /**
+ * Get the server name of a sub folder. The sub folder may or may not exist on
+ * the server.
+ *
+ * @param {nsIMsgFolder} parent - The parent folder.
+ * @param {string} folderName - The sub folder name.
+ * @returns {string}
+ */
+ _getServerSubFolderName(parent, folderName) {
+ folderName = this._encodeMailboxName(folderName);
+ let mailboxName = this._getServerFolderName(parent);
+ if (mailboxName) {
+ let delimiter = parent.QueryInterface(
+ Ci.nsIMsgImapMailFolder
+ ).hierarchyDelimiter;
+ // @see nsImapCore.h.
+ const ONLINE_HIERARCHY_SEPARATOR_UNKNOWN = "^";
+ if (!delimiter || delimiter == ONLINE_HIERARCHY_SEPARATOR_UNKNOWN) {
+ delimiter = "/";
+ }
+ return mailboxName + delimiter + folderName;
+ }
+ return folderName;
+ }
+
+ /**
+ * Fetch the full content of a message by UID.
+ *
+ * @param {nsIMsgFolder} folder - The associated folder.
+ * @param {number} uid - The message uid.
+ * @param {number} [size] - The body size to fetch.
+ */
+ fetchMessage(folder, uid, size) {
+ this._logger.debug(`fetchMessage folder=${folder.name} uid=${uid}`);
+ if (folder.hasMsgOffline(uid, null, 10)) {
+ this.onDone = () => {};
+ this.channel?.readFromLocalCache();
+ this._actionDone();
+ return;
+ }
+ this._actionInFolder(folder, () => {
+ this._nextAction = this._actionUidFetchBodyResponse;
+ let command;
+ if (size) {
+ command = `UID FETCH ${uid} (UID RFC822.SIZE FLAGS BODY.PEEK[HEADER.FIELDS (Content-Type Content-Transfer-Encoding)] BODY.PEEK[TEXT]<0.${size}>)`;
+ } else {
+ command = `UID FETCH ${uid} (UID RFC822.SIZE FLAGS BODY.PEEK[])`;
+ }
+ this._sendTagged(command);
+ });
+ }
+
+ /**
+ * Add, remove or replace flags of specified messages.
+ *
+ * @param {string} action - "+" means add, "-" means remove, "" means replace.
+ * @param {nsIMsgFolder} folder - The target folder.
+ * @param {string} messageIds - Message UIDs, e.g. "23,30:33".
+ * @param {number} flags - The internal flags number to update.
+ */
+ updateMessageFlags(action, folder, messageIds, flags) {
+ this._actionInFolder(folder, () => {
+ this._nextAction = () => this._actionDone();
+ // _supportedFlags is available after _actionSelectResponse.
+ let flagsStr = ImapUtils.flagsToString(flags, this._supportedFlags);
+ this._sendTagged(`UID STORE ${messageIds} ${action}FLAGS (${flagsStr})`);
+ });
+ }
+
+ /**
+ * Send EXPUNGE command to a folder.
+ *
+ * @param {nsIMsgFolder} folder - The associated folder.
+ */
+ expunge(folder) {
+ this._actionInFolder(folder, () => {
+ this._nextAction = () => this._actionDone();
+ this._sendTagged("EXPUNGE");
+ });
+ }
+
+ /**
+ * Move or copy messages from a folder to another folder.
+ *
+ * @param {nsIMsgFolder} folder - The source folder.
+ * @param {nsIMsgFolder} folder - The target folder.
+ * @param {string} messageIds - The message identifiers.
+ * @param {boolean} idsAreUids - If true messageIds are UIDs, otherwise,
+ * messageIds are sequences.
+ * @param {boolean} isMove - If true, use MOVE command when supported.
+ */
+ copy(folder, dstFolder, messageIds, idsAreUids, isMove) {
+ let command = idsAreUids ? "UID " : "";
+ command +=
+ isMove && this._capabilities.includes("MOVE")
+ ? "MOVE " // rfc6851
+ : "COPY ";
+ command += messageIds + ` "${this._getServerFolderName(dstFolder)}"`;
+ this._actionInFolder(folder, () => {
+ this._nextAction = this._actionNoopResponse;
+ this._sendTagged(command);
+ });
+ }
+
+ /**
+ * Upload a message file to a folder.
+ *
+ * @param {nsIFile} file - The message file to upload.
+ * @param {nsIMsgFolder} dstFolder - The target folder.
+ * @param {nsImapMailCopyState} copyState - A state used by nsImapMailFolder.
+ * @param {boolean} isDraft - Is the uploaded file a draft.
+ */
+ async uploadMessageFromFile(file, dstFolder, copyState, isDraft) {
+ this._logger.debug("uploadMessageFromFile", file.path, dstFolder.URI);
+ let mailbox = this._getServerFolderName(dstFolder);
+ let content = MailStringUtils.uint8ArrayToByteString(
+ await IOUtils.read(file.path)
+ );
+ this._nextAction = res => {
+ if (res.tag != "+") {
+ this._actionDone(Cr.NS_ERROR_FAILURE);
+ return;
+ }
+ this._nextAction = res => {
+ this._folderSink = dstFolder.QueryInterface(Ci.nsIImapMailFolderSink);
+ if (
+ // See rfc4315.
+ this._capabilities.includes("UIDPLUS") &&
+ res.attributes.appenduid
+ ) {
+ // The response is like `<tag> OK [APPENDUID <uidvalidity> <uid>]`.
+ this._folderSink.setAppendMsgUid(
+ res.attributes.appenduid[1],
+ this.runningUri
+ );
+ }
+ this._actionDone();
+ if (res.exists) {
+ // FIXME: _actionNoopResponse should be enough here, but it breaks
+ // test_imapAttachmentSaves.js.
+ this.folder = null;
+ }
+ try {
+ this._folderSink.copyNextStreamMessage(true, copyState);
+ } catch (e) {
+ this._logger.warn("copyNextStreamMessage failed", e);
+ }
+ };
+ this._send(content + (this._utf8Enabled ? ")" : ""));
+ };
+ let outKeywords = {};
+ let flags = dstFolder
+ .QueryInterface(Ci.nsIImapMessageSink)
+ .getCurMoveCopyMessageInfo(this.runningUri, {}, outKeywords);
+ let flagString = ImapUtils.flagsToString(flags, this._supportedFlags);
+ if (isDraft && !/\b\Draft\b/.test(flagString)) {
+ flagString += " \\Draft";
+ }
+ if (outKeywords.value) {
+ flagString += " " + outKeywords.value;
+ }
+ let open = this._utf8Enabled ? "UTF8 (~{" : "{";
+ let command = `APPEND "${mailbox}" (${flagString.trim()}) ${open}${
+ content.length
+ }}`;
+ this._sendTagged(command);
+ }
+
+ /**
+ * Check the status of a folder.
+ *
+ * @param {nsIMsgFolder} folder - The folder to check.
+ */
+ updateFolderStatus(folder) {
+ this._logger.debug("updateFolderStatus", folder.URI);
+ if (this._folder == folder) {
+ // According to rfc3501, "the STATUS command SHOULD NOT be used on the
+ // currently selected mailbox", so use NOOP instead.
+ this._actionNoop();
+ return;
+ }
+
+ this._nextAction = res => {
+ if (res.status == "OK") {
+ folder
+ .QueryInterface(Ci.nsIImapMailFolderSink)
+ .UpdateImapMailboxStatus(this, {
+ QueryInterface: ChromeUtils.generateQI(["nsIMailboxSpec"]),
+ nextUID: res.attributes.uidnext,
+ numMessages: res.attributes.messages.length,
+ numUnseenMessages: res.attributes.unseen,
+ });
+ folder.msgDatabase = null;
+ }
+ this._actionDone();
+ };
+ this._sendTagged(
+ `STATUS "${this._getServerFolderName(folder)}" (UIDNEXT MESSAGES UNSEEN)`
+ );
+ }
+
+ /**
+ * Update message flags.
+ *
+ * @param {nsIMsgFolder} folder - The associated folder.
+ * @param {string} flagsToAdd - The flags to add.
+ * @param {string} flagsToSubtract - The flags to subtract.
+ * @param {string} uids - The message uids.
+ */
+ storeCustomKeywords(folder, flagsToAdd, flagsToSubtract, uids) {
+ this._logger.debug(
+ "storeCustomKeywords",
+ folder.URI,
+ flagsToAdd,
+ flagsToSubtract,
+ uids
+ );
+ let subtractFlags = () => {
+ if (flagsToSubtract) {
+ this._nextAction = () => {
+ this._actionDone();
+ };
+ this._sendTagged(`UID STORE ${uids} -FLAGS (${flagsToSubtract})`);
+ } else {
+ this._actionDone();
+ }
+ };
+ this._actionInFolder(folder, () => {
+ if (flagsToAdd) {
+ this._nextAction = () => {
+ subtractFlags();
+ };
+ this._sendTagged(`UID STORE ${uids} +FLAGS (${flagsToAdd})`);
+ } else {
+ subtractFlags();
+ }
+ });
+ }
+
+ /**
+ * Get message headers by the specified uids.
+ *
+ * @param {nsIMsgFolder} folder - The folder of the messages.
+ * @param {string[]} uids - The message uids.
+ */
+ getHeaders(folder, uids) {
+ this._logger.debug("getHeaders", folder.URI, uids);
+ this._actionInFolder(folder, () => {
+ this._nextAction = this._actionUidFetchHeaderResponse;
+ let extraItems = "";
+ if (this._server.isGMailServer) {
+ extraItems += "X-GM-MSGID X-GM-THRID X-GM-LABELS ";
+ }
+ this._sendTagged(
+ `UID FETCH ${uids} (UID ${extraItems}RFC822.SIZE FLAGS BODY.PEEK[HEADER])`
+ );
+ });
+ }
+
+ /**
+ * Send IDLE command to the server.
+ */
+ idle() {
+ if (!this.folder) {
+ this._actionDone();
+ return;
+ }
+ this._nextAction = res => {
+ if (res.tag == "*") {
+ this.folder.performingBiff = true;
+ this._actionNoopResponse(res);
+ }
+ };
+ this._sendTagged("IDLE");
+ this._setSocketTimeout(PR_UINT32_MAX);
+ this._idling = true;
+ this._idleTimer = setTimeout(() => {
+ this.endIdle(() => {
+ this._actionNoop();
+ });
+ // Per rfc2177, should terminate the IDLE and re-issue it at least every
+ // 29 minutes. But in practice many servers timeout before that. A noop
+ // every 5min is better than timeout.
+ }, 5 * 60 * 1000);
+ this._logger.debug(`Idling in ${this.folder.URI}`);
+ }
+
+ /**
+ * Send DONE to end the IDLE command.
+ *
+ * @param {Function} nextAction - Callback function after IDLE is ended.
+ */
+ endIdle(nextAction) {
+ this._nextAction = res => {
+ if (res.status == "OK") {
+ nextAction();
+ }
+ };
+ this._send("DONE");
+ this._idling = false;
+ this.busy = true;
+ clearTimeout(this._idleTimer);
+ this._idleTimer = null;
+ }
+
+ /**
+ * Send LOGOUT and close the socket.
+ */
+ logout() {
+ this._sendTagged("LOGOUT");
+ this._socket.close();
+ }
+
+ /**
+ * The open event handler.
+ */
+ _onOpen = () => {
+ this._logger.debug("Connected");
+ this._socket.ondata = this._onData;
+ this._socket.onclose = this._onClose;
+ this._nextAction = res => {
+ this._greeted = true;
+ this._actionCapabilityResponse(res);
+ };
+
+ this._setSocketTimeout(this._prefs.tcpTimeout);
+ };
+
+ /**
+ * The data event handler.
+ *
+ * @param {TCPSocketEvent} event - The data event.
+ */
+ _onData = async event => {
+ // Without this, some tests are blocked waiting for response from Maild.jsm.
+ // Don't know the real cause, but possibly because ImapClient and Maild runs
+ // on the same process. We also have this in Pop3Client.
+ await new Promise(resolve => setTimeout(resolve));
+
+ let stringPayload = this._utf8Enabled
+ ? new TextDecoder().decode(event.data)
+ : MailStringUtils.uint8ArrayToByteString(new Uint8Array(event.data));
+ this._logger.debug(`S: ${stringPayload}`);
+ if (!this._response || this._idling || this._response.done) {
+ this._response = new ImapResponse();
+ this._response.onMessage = this._onMessage;
+ }
+ this._response.parse(stringPayload);
+ if (
+ !this._authenticating &&
+ this._response.done &&
+ this._response.status &&
+ this._response.tag != "+" &&
+ !["OK", "+"].includes(this._response.status)
+ ) {
+ this._actionDone(ImapUtils.NS_MSG_ERROR_IMAP_COMMAND_FAILED);
+ return;
+ }
+ if (!this._greeted || this._idling || this._response.done) {
+ this._nextAction?.(this._response);
+ }
+ };
+
+ /**
+ * The error event handler.
+ *
+ * @param {TCPSocketErrorEvent} event - The error event.
+ */
+ _onError = async event => {
+ this._logger.error(`${event.name}: a ${event.message} error occurred`);
+ if (event.errorCode == Cr.NS_ERROR_NET_TIMEOUT) {
+ this._actionError("imapNetTimeoutError");
+ this._actionDone(event.errorCode);
+ return;
+ }
+
+ let secInfo =
+ await event.target.transport?.tlsSocketControl?.asyncGetSecurityInfo();
+ if (secInfo) {
+ this._logger.error(`SecurityError info: ${secInfo.errorCodeString}`);
+ if (secInfo.failedCertChain.length) {
+ let chain = secInfo.failedCertChain.map(c => {
+ return c.commonName + "; serial# " + c.serialNumber;
+ });
+ this._logger.error(`SecurityError cert chain: ${chain.join(" <- ")}`);
+ }
+ this.runningUri.failedSecInfo = secInfo;
+ this._server.closeCachedConnections();
+ } else {
+ this.logout();
+ }
+
+ this._actionDone(event.errorCode);
+ };
+
+ /**
+ * The close event handler.
+ */
+ _onClose = () => {
+ this._logger.debug("Connection closed.");
+ this.folder = null;
+ };
+
+ /**
+ * Send a command to the server.
+ *
+ * @param {string} str - The command string to send.
+ * @param {boolean} [suppressLogging=false] - Whether to suppress logging the str.
+ */
+ _send(str, suppressLogging) {
+ if (suppressLogging && AppConstants.MOZ_UPDATE_CHANNEL != "default") {
+ this._logger.debug(
+ "C: Logging suppressed (it probably contained auth information)"
+ );
+ } else {
+ // Do not suppress for non-release builds, so that debugging auth problems
+ // is easier.
+ this._logger.debug(`C: ${str}`);
+ }
+
+ if (!this.isOnline) {
+ if (!str.includes("LOGOUT")) {
+ this._logger.warn(
+ `Failed to send because socket state is ${this._socket?.readyState}`
+ );
+ }
+ return;
+ }
+
+ let encode = this._utf8Enabled
+ ? x => new TextEncoder().encode(x)
+ : MailStringUtils.byteStringToUint8Array;
+ this._socket.send(encode(str + "\r\n").buffer);
+ }
+
+ /**
+ * Same as _send, but prepend a tag to the command.
+ */
+ _sendTagged(str, suppressLogging) {
+ if (this._idling) {
+ let nextAction = this._nextAction;
+ this.endIdle(() => {
+ this._nextAction = nextAction;
+ this._sendTagged(str, suppressLogging);
+ });
+ } else {
+ this._send(`${this._getNextTag()} ${str}`, suppressLogging);
+ }
+ }
+
+ /**
+ * Get the next command tag.
+ *
+ * @returns {number}
+ */
+ _getNextTag() {
+ this._tag = (this._tag + 1) % 100;
+ return this._tag;
+ }
+
+ /**
+ * Send CAPABILITY command to the server.
+ */
+ _actionCapability() {
+ this._nextAction = this._actionCapabilityResponse;
+ this._sendTagged("CAPABILITY");
+ }
+
+ /**
+ * Handle the capability response.
+ *
+ * @param {ImapResponse} res - Response received from the server.
+ */
+ _actionCapabilityResponse = res => {
+ if (res.capabilities) {
+ this._capabilities = res.capabilities;
+ this._server.wrappedJSObject.capabilities = res.capabilities;
+ if (this._capabilities.includes("X-GM-EXT-1")) {
+ this._server.isGMailServer = true;
+ }
+
+ this._supportedAuthMethods = res.authMethods;
+ this._actionChooseFirstAuthMethod();
+ } else {
+ this._actionCapability();
+ }
+ };
+
+ /**
+ * Decide the first auth method to try.
+ */
+ _actionChooseFirstAuthMethod = () => {
+ if (
+ [
+ Ci.nsMsgSocketType.trySTARTTLS,
+ Ci.nsMsgSocketType.alwaysSTARTTLS,
+ ].includes(this._server.socketType) &&
+ !this._secureTransport
+ ) {
+ if (this._capabilities.includes("STARTTLS")) {
+ // Init STARTTLS negotiation if required by user pref and supported.
+ this._nextAction = this._actionStarttlsResponse;
+ this._sendTagged("STARTTLS");
+ } else {
+ // Abort if not supported.
+ this._logger.error("Server doesn't support STARTTLS. Aborting.");
+ this._actionError("imapServerDisconnected");
+ this._actionDone(Cr.NS_ERROR_FAILURE);
+ }
+ return;
+ }
+
+ this._possibleAuthMethods = this._preferredAuthMethods.filter(x =>
+ this._supportedAuthMethods.includes(x)
+ );
+ if (
+ !this._possibleAuthMethods.length &&
+ this._server.authMethod == Ci.nsMsgAuthMethod.passwordCleartext &&
+ !this._capabilities.includes("LOGINDISABLED")
+ ) {
+ this._possibleAuthMethods = ["OLDLOGIN"];
+ }
+ this._logger.debug(`Possible auth methods: ${this._possibleAuthMethods}`);
+ this._nextAuthMethod = this._possibleAuthMethods[0];
+ if (this._capabilities.includes("CLIENTID") && this._server.clientid) {
+ this._nextAction = res => {
+ if (res.status == "OK") {
+ this._actionAuth();
+ } else {
+ this._actionDone(Cr.NS_ERROR_FAILURE);
+ }
+ };
+ this._sendTagged(`CLIENTID UUID ${this._server.clientid}`);
+ } else {
+ this._actionAuth();
+ }
+ };
+
+ /**
+ * Handle the STARTTLS response.
+ *
+ * @param {ImapResponse} res - The server response.
+ */
+ _actionStarttlsResponse(res) {
+ if (!res.status == "OK") {
+ this._actionDone(Cr.NS_ERROR_FAILURE);
+ return;
+ }
+ this._socket.upgradeToSecure();
+ this._secureTransport = true;
+ this._actionCapability();
+ }
+
+ /**
+ * Init authentication depending on server capabilities and user prefs.
+ */
+ _actionAuth = async () => {
+ if (!this._nextAuthMethod) {
+ this._socket.close();
+ this._actionDone(Cr.NS_ERROR_FAILURE);
+ return;
+ }
+
+ this._authenticating = true;
+
+ this._currentAuthMethod = this._nextAuthMethod;
+ this._nextAuthMethod =
+ this._possibleAuthMethods[
+ this._possibleAuthMethods.indexOf(this._currentAuthMethod) + 1
+ ];
+
+ switch (this._currentAuthMethod) {
+ case "OLDLOGIN":
+ this._nextAction = this._actionAuthResponse;
+ let password = await this._getPassword();
+ this._sendTagged(
+ `LOGIN ${this._authenticator.username} ${password}`,
+ true
+ );
+ break;
+ case "PLAIN":
+ this._nextAction = this._actionAuthPlain;
+ this._sendTagged("AUTHENTICATE PLAIN");
+ break;
+ case "LOGIN":
+ this._nextAction = this._actionAuthLoginUser;
+ this._sendTagged("AUTHENTICATE LOGIN");
+ break;
+ case "CRAM-MD5":
+ this._nextAction = this._actionAuthCramMd5;
+ this._sendTagged("AUTHENTICATE CRAM-MD5");
+ break;
+ case "GSSAPI": {
+ this._nextAction = this._actionAuthGssapi;
+ this._authenticator.initGssapiAuth("imap");
+ let token;
+ try {
+ token = this._authenticator.getNextGssapiToken("");
+ } catch (e) {
+ this._logger.error(e);
+ this._actionDone(Cr.NS_ERROR_FAILURE);
+ return;
+ }
+ this._sendTagged(`AUTHENTICATE GSSAPI ${token}`, true);
+ break;
+ }
+ case "NTLM": {
+ this._nextAction = this._actionAuthNtlm;
+ this._authenticator.initNtlmAuth("imap");
+ let token;
+ try {
+ token = this._authenticator.getNextNtlmToken("");
+ } catch (e) {
+ this._logger.error(e);
+ this._actionDone(Cr.NS_ERROR_FAILURE);
+ return;
+ }
+ this._sendTagged(`AUTHENTICATE NTLM ${token}`, true);
+ break;
+ }
+ case "XOAUTH2":
+ this._nextAction = this._actionAuthResponse;
+ let token = await this._authenticator.getOAuthToken();
+ this._sendTagged(`AUTHENTICATE XOAUTH2 ${token}`, true);
+ break;
+ case "EXTERNAL":
+ this._nextAction = this._actionAuthResponse;
+ this._sendTagged(
+ `AUTHENTICATE EXTERNAL ${this._authenticator.username}`
+ );
+ break;
+ default:
+ this._actionDone();
+ }
+ };
+
+ /**
+ * @param {ImapResponse} res - Response received from the server.
+ */
+ _actionAuthResponse = res => {
+ this._authenticating = false;
+
+ if (this.verifyLogon) {
+ this._actionDone(res.status == "OK" ? Cr.NS_OK : Cr.NS_ERROR_FAILURE);
+ return;
+ }
+ if (res.status == "OK") {
+ this._serverSink.userAuthenticated = true;
+ if (res.capabilities) {
+ this._capabilities = res.capabilities;
+ this._server.wrappedJSObject.capabilities = res.capabilities;
+ this._actionId();
+ } else {
+ this._nextAction = res => {
+ this._capabilities = res.capabilities;
+ this._server.wrappedJSObject.capabilities = res.capabilities;
+ this._actionId();
+ };
+ this._sendTagged("CAPABILITY");
+ }
+ return;
+ }
+ if (
+ ["OLDLOGIN", "PLAIN", "LOGIN", "CRAM-MD5"].includes(
+ this._currentAuthMethod
+ )
+ ) {
+ // Ask user what to do.
+ let action = this._authenticator.promptAuthFailed();
+ if (action == 1) {
+ // Cancel button pressed.
+ this._socket.close();
+ this._actionDone(Cr.NS_ERROR_FAILURE);
+ return;
+ }
+ if (action == 2) {
+ // 'New password' button pressed.
+ this._authenticator.forgetPassword();
+ }
+
+ // Retry.
+ this._nextAuthMethod = this._possibleAuthMethods[0];
+ this._actionAuth();
+ return;
+ }
+ this._logger.error("Authentication failed.");
+ this._actionDone(Cr.NS_ERROR_FAILURE);
+ };
+
+ /**
+ * Returns the saved/cached server password, or show a password dialog. If the
+ * user cancels the dialog, stop the process.
+ *
+ * @returns {string} The server password.
+ */
+ async _getPassword() {
+ try {
+ let password = await this._authenticator.getPassword();
+ return password;
+ } catch (e) {
+ if (e.result == Cr.NS_ERROR_ABORT) {
+ this._actionDone(e.result);
+ }
+ throw e;
+ }
+ }
+
+ /**
+ * The second step of PLAIN auth. Send the auth token to the server.
+ *
+ * @param {ImapResponse} res - Response received from the server.
+ */
+ _actionAuthPlain = async res => {
+ this._nextAction = this._actionAuthResponse;
+ this._send(await this._authenticator.getPlainToken(), true);
+ };
+
+ /**
+ * The second step of LOGIN auth. Send the username to the server.
+ *
+ * @param {ImapResponse} res - The server response.
+ */
+ _actionAuthLoginUser = res => {
+ this._nextAction = this._actionAuthLoginPass;
+ this._send(btoa(this._authenticator.username), true);
+ };
+
+ /**
+ * The third step of LOGIN auth. Send the password to the server.
+ *
+ * @param {ImapResponse} res - The server response.
+ */
+ _actionAuthLoginPass = async res => {
+ this._nextAction = this._actionAuthResponse;
+ let password = MailStringUtils.stringToByteString(
+ await this._getPassword()
+ );
+ this._send(btoa(password), true);
+ };
+
+ /**
+ * The second step of CRAM-MD5 auth, send a HMAC-MD5 signature to the server.
+ *
+ * @param {ImapResponse} res - The server response.
+ */
+ _actionAuthCramMd5 = async res => {
+ this._nextAction = this._actionAuthResponse;
+ let password = await this._getPassword();
+ this._send(
+ this._authenticator.getCramMd5Token(password, res.statusText),
+ true
+ );
+ };
+
+ /**
+ * The second and next step of GSSAPI auth.
+ *
+ * @param {ImapResponse} res - The server response.
+ */
+ _actionAuthGssapi = res => {
+ if (res.tag != "+") {
+ this._actionAuthResponse(res);
+ return;
+ }
+
+ // Server returns a challenge, we send a new token. Can happen multiple times.
+ let token;
+ try {
+ token = this._authenticator.getNextGssapiToken(res.statusText);
+ } catch (e) {
+ this._logger.error(e);
+ this._actionAuthResponse(res);
+ return;
+ }
+ this._send(token, true);
+ };
+
+ /**
+ * The second and next step of NTLM auth.
+ *
+ * @param {ImapResponse} res - The server response.
+ */
+ _actionAuthNtlm = res => {
+ if (res.tag != "+") {
+ this._actionAuthResponse(res);
+ return;
+ }
+
+ // Server returns a challenge, we send a new token. Can happen multiple times.
+ let token;
+ try {
+ token = this._authenticator.getNextNtlmToken(res.statusText);
+ } catch (e) {
+ this._logger.error(e);
+ this._actionAuthResponse(res);
+ return;
+ }
+ this._send(token, true);
+ };
+
+ /**
+ * Send ID command to the server.
+ *
+ * @param {Function} [actionAfter] - A callback after processing ID command.
+ */
+ _actionId = (actionAfter = this._actionEnableUtf8) => {
+ if (this._capabilities.includes("ID") && Services.appinfo.name) {
+ this._nextAction = res => {
+ this._server.serverIDPref = res.id;
+ actionAfter();
+ };
+ this._sendTagged(
+ `ID ("name" "${Services.appinfo.name}" "version" "${Services.appinfo.version}")`
+ );
+ } else {
+ actionAfter();
+ }
+ };
+
+ /**
+ * Enable UTF8 if supported by the server.
+ *
+ * @param {Function} [actionAfter] - A callback after processing ENABLE UTF8.
+ */
+ _actionEnableUtf8 = (actionAfter = this._discoverAllFoldersIfNecessary) => {
+ if (
+ this._server.allowUTF8Accept &&
+ (this._capabilities.includes("UTF8=ACCEPT") ||
+ this._capabilities.includes("UTF8=ONLY"))
+ ) {
+ this._nextAction = res => {
+ this._utf8Enabled = res.status == "OK";
+ this._server.utf8AcceptEnabled = this._utf8Enabled;
+ actionAfter();
+ };
+ this._sendTagged("ENABLE UTF8=ACCEPT");
+ } else {
+ this._utf8Enabled = false;
+ actionAfter();
+ }
+ };
+
+ /**
+ * Execute an action with a folder selected.
+ *
+ * @param {nsIMsgFolder} folder - The folder to select.
+ * @param {Function} actionInFolder - The action to execute.
+ */
+ _actionInFolder(folder, actionInFolder) {
+ if (this.folder == folder) {
+ // If already in the folder, execute the action now.
+ actionInFolder();
+ } else {
+ // Send the SELECT command and queue the action.
+ this._actionAfterSelectFolder = actionInFolder;
+ this._nextAction = this._actionSelectResponse(folder);
+ this._sendTagged(`SELECT "${this._getServerFolderName(folder)}"`);
+ }
+ }
+
+ /**
+ * Send LSUB or LIST command depending on the server capabilities.
+ *
+ * @param {string} [mailbox="*"] - The mailbox to list, default to list all.
+ */
+ _actionListOrLsub(mailbox = "*") {
+ this._nextAction = this._actionListResponse();
+ let command = this._capabilities.includes("LIST-EXTENDED")
+ ? "LIST (SUBSCRIBED)" // rfc5258
+ : "LSUB";
+ command += ` "" "${mailbox}"`;
+ if (this._capabilities.includes("SPECIAL-USE")) {
+ command += " RETURN (SPECIAL-USE)"; // rfc6154
+ }
+ this._sendTagged(command);
+ this._listInboxSent = false;
+ }
+
+ /**
+ * Handle LIST response.
+ *
+ * @param {Function} actionAfterResponse - A callback after handling the response.
+ * @param {ImapResponse} res - Response received from the server.
+ */
+ _actionListResponse =
+ (actionAfterResponse = this._actionFinishFolderDiscovery) =>
+ res => {
+ if (!this._hasInbox) {
+ this._hasInbox = res.mailboxes.some(
+ mailbox => mailbox.flags & ImapUtils.FLAG_IMAP_INBOX
+ );
+ }
+ for (let mailbox of res.mailboxes) {
+ this._serverSink.possibleImapMailbox(
+ mailbox.name.replaceAll(mailbox.delimiter, "/"),
+ mailbox.delimiter,
+ mailbox.flags
+ );
+ }
+
+ actionAfterResponse(res);
+ };
+
+ /**
+ * Send LIST command.
+ *
+ * @param {string} folderName - The name of the folder to list.
+ * @param {Function} actionAfterResponse - A callback after handling the response.
+ */
+ _actionList(folderName, actionAfterResponse) {
+ this._nextAction = this._actionListResponse(actionAfterResponse);
+ this._sendTagged(`LIST "" "${folderName}"`);
+ }
+
+ /**
+ * Finish folder discovery after checking Inbox and Trash folders.
+ */
+ _actionFinishFolderDiscovery = () => {
+ if (!this._hasInbox && !this._listInboxSent) {
+ this._actionList("INBOX");
+ this._listInboxSent = true;
+ return;
+ }
+ if (!this._hasTrash && !this._listTrashSent) {
+ this._actionCreateTrashFolderIfNeeded();
+ return;
+ }
+ this._serverSink.discoveryDone();
+ this._actionAfterDiscoverAllFolders
+ ? this._actionAfterDiscoverAllFolders()
+ : this._actionDone();
+ };
+
+ /**
+ * If Trash folder is not found on server, create one and subscribe to it.
+ */
+ _actionCreateTrashFolderIfNeeded() {
+ let trashFolderName = this._server.trashFolderName;
+ this._actionList(trashFolderName, res => {
+ this._hasTrash = res.mailboxes.length > 0;
+ if (this._hasTrash) {
+ // Trash folder exists.
+ this._actionFinishFolderDiscovery();
+ } else {
+ // Trash folder doesn't exist, create one and subscribe to it.
+ this._nextAction = res => {
+ this._actionList(trashFolderName, () => {
+ // After subscribing, finish folder discovery.
+ this._nextAction = this._actionFinishFolderDiscovery;
+ this._sendTagged(`SUBSCRIBE "${trashFolderName}"`);
+ });
+ };
+ this._sendTagged(`CREATE "${trashFolderName}"`);
+ }
+ });
+ this._listTrashSent = true;
+ }
+
+ /**
+ * Create and subscribe to a folder.
+ *
+ * @param {string} folderName - The folder name.
+ * @param {Function} callbackAfterSubscribe - The action after the subscribe
+ * command.
+ */
+ _actionCreateAndSubscribe(folderName, callbackAfterSubscribe) {
+ this._nextAction = res => {
+ this._nextAction = callbackAfterSubscribe;
+ this._sendTagged(`SUBSCRIBE "${folderName}"`);
+ };
+ this._sendTagged(`CREATE "${folderName}"`);
+ }
+
+ /**
+ * Handle SELECT response.
+ */
+ _actionSelectResponse = folder => res => {
+ if (folder) {
+ this.folder = folder;
+ }
+ this._supportedFlags = res.permanentflags || res.flags;
+ this._folderState = res;
+ if (this._capabilities.includes("QUOTA")) {
+ this._actionGetQuotaData();
+ } else {
+ this._actionAfterSelectFolder();
+ }
+ };
+
+ /**
+ * Send GETQUOTAROOT command and handle the response.
+ */
+ _actionGetQuotaData() {
+ this._folderSink = this.folder.QueryInterface(Ci.nsIImapMailFolderSink);
+ this._nextAction = res => {
+ const INVALIDATE_QUOTA = 0;
+ const STORE_QUOTA = 1;
+ const VALIDATE_QUOTA = 2;
+ for (let root of res.quotaRoots || []) {
+ this._folderSink.setFolderQuotaData(INVALIDATE_QUOTA, root, 0, 0);
+ }
+ for (let [mailbox, resource, usage, limit] of res.quotas || []) {
+ this._folderSink.setFolderQuotaData(
+ STORE_QUOTA,
+ mailbox ? `${mailbox} / ${resource}` : resource,
+ usage,
+ limit
+ );
+ }
+ this._folderSink.setFolderQuotaData(VALIDATE_QUOTA, "", 0, 0);
+ this._actionAfterSelectFolder();
+ };
+ this._sendTagged(
+ `GETQUOTAROOT "${this._getServerFolderName(this.folder)}"`
+ );
+ this._folderSink.folderQuotaCommandIssued = true;
+ }
+
+ /**
+ * Handle RENAME response. Three steps are involved.
+ *
+ * @param {string} oldName - The old folder name.
+ * @param {string} newName - The new folder name.
+ * @param {boolean} [isMove] - Is it response to MOVE command.
+ * @param {ImapResponse} res - The server response.
+ */
+ _actionRenameResponse = (oldName, newName, isMove) => res => {
+ // Step 3: Rename the local folder and send LIST command to re-sync folders.
+ let actionAfterUnsubscribe = () => {
+ this._serverSink.onlineFolderRename(this._msgWindow, oldName, newName);
+ if (isMove) {
+ this._actionDone();
+ } else {
+ this._actionListOrLsub(newName);
+ }
+ };
+ // Step 2: unsubscribe to the oldName.
+ this._nextAction = () => {
+ this._nextAction = actionAfterUnsubscribe;
+ this._sendTagged(`UNSUBSCRIBE "${oldName}"`);
+ };
+ // Step 1: subscribe to the newName.
+ this._sendTagged(`SUBSCRIBE "${newName}"`);
+ };
+
+ /**
+ * Send UID FETCH request to the server.
+ */
+ _actionUidFetch() {
+ if (this.runningUri.imapAction == Ci.nsIImapUrl.nsImapLiteSelectFolder) {
+ this._nextAction = () => this._actionDone();
+ } else {
+ this._nextAction = this._actionUidFetchResponse;
+ }
+ this._sendTagged("UID FETCH 1:* (FLAGS)");
+ }
+
+ /**
+ * Handle UID FETCH response.
+ *
+ * @param {ImapResponse} res - Response received from the server.
+ */
+ _actionUidFetchResponse(res) {
+ let outFolderInfo = {};
+ this.folder.getDBFolderInfoAndDB(outFolderInfo);
+ let highestUid = outFolderInfo.value.getUint32Property(
+ "highestRecordedUID",
+ 0
+ );
+ this._folderSink = this.folder.QueryInterface(Ci.nsIImapMailFolderSink);
+ this._folderSink.UpdateImapMailboxInfo(this, this._getMailboxSpec());
+ let latestUid = this._messageUids.at(-1);
+ if (latestUid > highestUid) {
+ let extraItems = "";
+ if (this._server.isGMailServer) {
+ extraItems += "X-GM-MSGID X-GM-THRID X-GM-LABELS ";
+ }
+ this._nextAction = this._actionUidFetchHeaderResponse;
+ this._sendTagged(
+ `UID FETCH ${
+ highestUid + 1
+ }:${latestUid} (UID ${extraItems}RFC822.SIZE FLAGS BODY.PEEK[HEADER])`
+ );
+ } else {
+ this._folderSink.headerFetchCompleted(this);
+ if (this._bodysToDownload.length) {
+ let uids = this._bodysToDownload.join(",");
+ this._nextAction = this._actionUidFetchBodyResponse;
+ this._sendTagged(
+ `UID FETCH ${uids} (UID RFC822.SIZE FLAGS BODY.PEEK[])`
+ );
+ return;
+ }
+ this._actionDone();
+ }
+ }
+
+ /**
+ * Make an nsIMailboxSpec instance to interact with nsIImapMailFolderSink.
+ *
+ * @returns {nsIMailboxSpec}
+ */
+ _getMailboxSpec() {
+ return {
+ QueryInterface: ChromeUtils.generateQI(["nsIMailboxSpec"]),
+ folder_UIDVALIDITY: this._folderState.uidvalidity,
+ box_flags: this._folderState.flags,
+ supportedUserFlags: this._folderState.supportedUserFlags,
+ nextUID: this._folderState.attributes.uidnext,
+ numMessages: this._messages.size,
+ numUnseenMessages: this._folderState.attributes.unseen,
+ flagState: this.flagAndUidState,
+ };
+ }
+
+ /**
+ * Handle UID FETCH BODY.PEEK[HEADER] response.
+ *
+ * @param {ImapResponse} res - Response received from the server.
+ */
+ _actionUidFetchHeaderResponse(res) {
+ this.folder
+ .QueryInterface(Ci.nsIImapMailFolderSink)
+ .headerFetchCompleted(this);
+ if (this._bodysToDownload.length) {
+ // nsImapMailFolder decides to fetch the full body by calling
+ // NotifyBodysToDownload.
+ let uids = this._bodysToDownload.join(",");
+ this._nextAction = this._actionUidFetchBodyResponse;
+ this._sendTagged(`UID FETCH ${uids} (UID RFC822.SIZE FLAGS BODY.PEEK[])`);
+ return;
+ }
+ this._actionDone();
+ }
+
+ /**
+ * Handle UID FETCH BODY response.
+ *
+ * @param {ImapResponse} res - Response received from the server.
+ */
+ _actionUidFetchBodyResponse(res) {
+ this._actionDone();
+ }
+
+ /**
+ * Handle a single message data response.
+ *
+ * @param {MessageData} msg - Message data parsed in ImapResponse.
+ */
+ _onMessage = msg => {
+ this._msgSink = this.folder.QueryInterface(Ci.nsIImapMessageSink);
+ this._folderSink = this.folder.QueryInterface(Ci.nsIImapMailFolderSink);
+
+ // Handle message flags.
+ if ((msg.uid || msg.sequence) && msg.flags != undefined) {
+ let uid = msg.uid;
+ if (uid && msg.sequence) {
+ this._messageUids[msg.sequence] = uid;
+ this._messages.set(uid, msg);
+ } else if (msg.sequence) {
+ uid = this._messageUids[msg.sequence];
+ }
+ if (uid) {
+ this.folder
+ .QueryInterface(Ci.nsIImapMessageSink)
+ .notifyMessageFlags(
+ msg.flags,
+ msg.keywords,
+ uid,
+ this._folderState.highestmodseq
+ );
+ }
+ }
+
+ if (msg.body) {
+ if (!msg.body.endsWith("\r\n")) {
+ msg.body += "\r\n";
+ }
+ if (msg.bodySection.length == 1 && msg.bodySection[0] == "HEADER") {
+ // Handle message headers.
+ this._messageUids[msg.sequence] = msg.uid;
+ this._messages.set(msg.uid, msg);
+ this._folderSink.StartMessage(this.runningUri);
+ let hdrXferInfo = {
+ numHeaders: 1,
+ getHeader() {
+ return {
+ msgUid: msg.uid,
+ msgSize: msg.size,
+ get msgHdrs() {
+ let sepIndex = msg.body.indexOf("\r\n\r\n");
+ return sepIndex == -1
+ ? msg.body + "\r\n"
+ : msg.body.slice(0, sepIndex + 2);
+ },
+ };
+ },
+ };
+ this._folderSink.parseMsgHdrs(this, hdrXferInfo);
+ } else {
+ // Handle message body.
+ let shouldStoreMsgOffline = false;
+ try {
+ shouldStoreMsgOffline = this.folder.shouldStoreMsgOffline(msg.uid);
+ } catch (e) {}
+ if (
+ (shouldStoreMsgOffline ||
+ this.runningUri.QueryInterface(Ci.nsIImapUrl)
+ .storeResultsOffline) &&
+ msg.body
+ ) {
+ this._folderSink.StartMessage(this.runningUri);
+ this._msgSink.parseAdoptedMsgLine(msg.body, msg.uid, this.runningUri);
+ this._msgSink.normalEndMsgWriteStream(
+ msg.uid,
+ true,
+ this.runningUri,
+ msg.body.length
+ );
+ this._folderSink.EndMessage(this.runningUri, msg.uid);
+ }
+
+ this.onData?.(msg.body);
+ // Release some memory.
+ msg.body = "";
+ }
+ }
+ };
+
+ /**
+ * Send NOOP command.
+ */
+ _actionNoop() {
+ this._nextAction = this._actionNoopResponse;
+ this._sendTagged("NOOP");
+ }
+
+ /**
+ * Handle NOOP response.
+ *
+ * @param {ImapResponse} res - Response received from the server.
+ */
+ _actionNoopResponse(res) {
+ if (
+ (res.exists && res.exists != this._folderState.exists) ||
+ res.expunged.length
+ ) {
+ // Handle messages number changes, re-sync the folder.
+ this._folderState.exists = res.exists;
+ this._actionAfterSelectFolder = this._actionUidFetch;
+ this._nextAction = this._actionSelectResponse();
+ if (res.expunged.length) {
+ this._messageUids = [];
+ this._messages.clear();
+ }
+ let folder = this.folder;
+ this.folder = null;
+ this.selectFolder(folder);
+ } else if (res.messages.length || res.exists) {
+ let outFolderInfo = {};
+ this.folder.getDBFolderInfoAndDB(outFolderInfo);
+ let highestUid = outFolderInfo.value.getUint32Property(
+ "highestRecordedUID",
+ 0
+ );
+ this._nextAction = this._actionUidFetchResponse;
+ this._sendTagged(`UID FETCH ${highestUid + 1}:* (FLAGS)`);
+ } else {
+ if (res.exists == 0) {
+ this._messageUids = [];
+ this._messages.clear();
+ this.folder
+ .QueryInterface(Ci.nsIImapMailFolderSink)
+ .UpdateImapMailboxInfo(this, this._getMailboxSpec());
+ }
+ if (!this._idling) {
+ this._actionDone();
+ }
+ }
+ }
+
+ /**
+ * Show an error prompt.
+ *
+ * @param {string} errorName - An error name corresponds to an entry of
+ * imapMsgs.properties.
+ */
+ _actionError(errorName) {
+ if (!this._msgWindow) {
+ return;
+ }
+ let bundle = Services.strings.createBundle(
+ "chrome://messenger/locale/imapMsgs.properties"
+ );
+ let errorMsg = bundle.formatStringFromName(errorName, [
+ this._server.hostName,
+ ]);
+ Services.prompt.alert(this._msgWindow.domWindow, null, errorMsg);
+ }
+
+ /**
+ * Finish a request and do necessary cleanup.
+ */
+ _actionDone = (status = Cr.NS_OK) => {
+ this._logger.debug(`Done with status=${status}`);
+ this._nextAction = null;
+ this._urlListener?.OnStopRunningUrl(this.runningUri, status);
+ this.runningUri.SetUrlState(false, status);
+ this.onDone?.(status);
+ this._reset();
+ // Tell ImapIncomingServer this client can be reused now.
+ this.onFree?.();
+ };
+
+ /** @see nsIImapProtocol */
+ NotifyBodysToDownload(keys) {
+ this._logger.debug("NotifyBodysToDownload", keys);
+ this._bodysToDownload = keys;
+ }
+
+ GetRunningUrl() {
+ this._logger.debug("GetRunningUrl");
+ }
+
+ get flagAndUidState() {
+ // The server sequence is 1 based, nsIImapFlagAndUidState sequence is 0 based.
+ let getUidOfMessage = index => this._messageUids[index + 1];
+ let getMessageFlagsByUid = uid => this._messages.get(uid)?.flags;
+
+ return {
+ QueryInterface: ChromeUtils.generateQI(["nsIImapFlagAndUidState"]),
+ numberOfMessages: this._messages.size,
+ getUidOfMessage,
+ getMessageFlags: index => getMessageFlagsByUid(getUidOfMessage(index)),
+ hasMessage: uid => this._messages.has(uid),
+ getMessageFlagsByUid,
+ getCustomFlags: uid => this._messages.get(uid)?.keywords,
+ getCustomAttribute: (uid, name) => {
+ let value = this._messages.get(uid)?.customAttributes[name];
+ return Array.isArray(value) ? value.join(" ") : value;
+ },
+ };
+ }
+}
diff --git a/comm/mailnews/imap/src/ImapFolderContentHandler.sys.mjs b/comm/mailnews/imap/src/ImapFolderContentHandler.sys.mjs
new file mode 100644
index 0000000000..da33c08829
--- /dev/null
+++ b/comm/mailnews/imap/src/ImapFolderContentHandler.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/. */
+
+import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
+
+const { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+const lazy = {};
+XPCOMUtils.defineLazyModuleGetters(lazy, {
+ MailUtils: "resource:///modules/MailUtils.jsm",
+});
+
+/**
+ * A service for handling content type x-application-imapfolder;
+ * that is, opening IMAP folder URLs.
+ *
+ * Set mailnews.imap.jsmodule to true to use this module.
+ *
+ * @implements {nsIContentHandler}
+ */
+export class ImapFolderContentHandler {
+ QueryInterface = ChromeUtils.generateQI(["nsIContentHandler"]);
+
+ /**
+ * @param contentType - The content type of request.
+ * @param windowContest - Window context, used to get things like the current
+ * nsIDOMWindow for this request.
+ * @param request - A request whose content type is already known.
+ * @see {nsIContentHandler}
+ */
+ handleContent(contentType, windowContext, request) {
+ if (contentType != "x-application-imapfolder") {
+ throw Components.Exception(
+ `Won't handle ${contentType}`,
+ Cr.NS_ERROR_WONT_HANDLE_CONTENT
+ );
+ }
+ request = request.QueryInterface(Ci.nsIChannel);
+
+ let imapFolderURL = Services.io.unescapeString(
+ request.URI.spec,
+ Ci.nsINetUtil.ESCAPE_URL_PATH
+ );
+
+ if (Services.wm.getMostRecentWindow("mail:3pane")) {
+ // Clicked IMAP folder URL in the window.
+ let folder = MailServices.folderLookup.getFolderForURL(imapFolderURL);
+ if (folder) {
+ lazy.MailUtils.displayFolderIn3Pane(folder.URI);
+ } else {
+ folder =
+ MailServices.folderLookup.getOrCreateFolderForURL(imapFolderURL);
+ // TODO: ask and maybe subscribe, like
+ // https://searchfox.org/comm-central/rev/1dd06be9d6c1178a34e6c28db03161e07e97d98c/mailnews/imap/src/nsImapService.cpp#2471-2534
+ dump(`Maybe subscribe to folder ${folder.URI}\n`);
+ }
+ } else {
+ // Got IMAP folder URL from command line (most likely).
+ Cc["@mozilla.org/messenger/windowservice;1"]
+ .getService(Ci.nsIMessengerWindowService)
+ .openMessengerWindowWithUri("mail:3pane", imapFolderURL, -1);
+ }
+ }
+}
+
+ImapFolderContentHandler.prototype.classID = Components.ID(
+ "{d927a82f-2d15-4972-ab88-6d84601aae68}"
+);
diff --git a/comm/mailnews/imap/src/ImapIncomingServer.jsm b/comm/mailnews/imap/src/ImapIncomingServer.jsm
new file mode 100644
index 0000000000..c9cf86bfdc
--- /dev/null
+++ b/comm/mailnews/imap/src/ImapIncomingServer.jsm
@@ -0,0 +1,783 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 = ["ImapIncomingServer"];
+
+const { XPCOMUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/XPCOMUtils.sys.mjs"
+);
+const { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+const { MsgIncomingServer } = ChromeUtils.import(
+ "resource:///modules/MsgIncomingServer.jsm"
+);
+
+const lazy = {};
+
+XPCOMUtils.defineLazyModuleGetters(lazy, {
+ ImapClient: "resource:///modules/ImapClient.jsm",
+ ImapCapFlags: "resource:///modules/ImapUtils.jsm",
+ ImapUtils: "resource:///modules/ImapUtils.jsm",
+ MailUtils: "resource:///modules/MailUtils.jsm",
+});
+
+/**
+ * @implements {nsIImapServerSink}
+ * @implements {nsIImapIncomingServer}
+ * @implements {nsIMsgIncomingServer}
+ * @implements {nsIUrlListener}
+ * @implements {nsISupportsWeakReference}
+ * @implements {nsISubscribableServer}
+ */
+class ImapIncomingServer extends MsgIncomingServer {
+ QueryInterface = ChromeUtils.generateQI([
+ "nsIImapServerSink",
+ "nsIImapIncomingServer",
+ "nsIMsgIncomingServer",
+ "nsIUrlListener",
+ "nsISupportsWeakReference",
+ "nsISubscribableServer",
+ ]);
+
+ _logger = lazy.ImapUtils.logger;
+
+ constructor() {
+ super();
+
+ this._userAuthenticated = false;
+
+ // nsIMsgIncomingServer attributes.
+ this.localStoreType = "imap";
+ this.localDatabaseType = "imap";
+ this.canBeDefaultServer = true;
+ this.canSearchMessages = true;
+
+ // nsISubscribableServer attributes.
+ this.supportsSubscribeSearch = false;
+
+ // nsIImapIncomingServer attributes that map directly to pref values.
+ this._mapAttrsToPrefs([
+ ["Bool", "allowUTF8Accept", "allow_utf8_accept"],
+ ["Bool", "autoSyncOfflineStores", "autosync_offline_stores"],
+ ["Bool", "checkAllFoldersForNew", "check_all_folders_for_new"],
+ ["Bool", "cleanupInboxOnExit", "cleanup_inbox_on_exit"],
+ ["Bool", "downloadBodiesOnGetNewMail", "download_bodies_on_get_new_mail"],
+ ["Bool", "dualUseFolders", "dual_use_folders"],
+ ["Bool", "fetchByChunks", "fetch_by_chunks"],
+ ["Bool", "forceSelect", "force_select_imap"],
+ ["Bool", "isGMailServer", "is_gmail"],
+ ["Bool", "offlineDownload", "offline_download"],
+ ["Bool", "sendID", "send_client_info"],
+ ["Bool", "useCompressDeflate", "use_compress_deflate"],
+ ["Bool", "useCondStore", "use_condstore"],
+ ["Bool", "useIdle", "use_idle"],
+ ["Char", "adminUrl", "admin_url"],
+ ["Char", "otherUsersNamespace", "namespace.other_users"],
+ ["Char", "personalNamespace", "namespace.personal"],
+ ["Char", "publicNamespace", "namespace.public"],
+ ["Char", "serverIDPref", "serverIDResponse"],
+ ["Int", "autoSyncMaxAgeDays", "autosync_max_age_days"],
+ ["Int", "timeOutLimits", "timeout"],
+ ]);
+ }
+
+ /**
+ * Most of nsISubscribableServer interfaces are delegated to
+ * this._subscribable.
+ */
+ get _subscribable() {
+ if (!this._subscribableServer) {
+ this._subscribableServer = Cc[
+ "@mozilla.org/messenger/subscribableserver;1"
+ ].createInstance(Ci.nsISubscribableServer);
+ this._subscribableServer.setIncomingServer(this);
+ }
+ return this._subscribableServer;
+ }
+
+ /** @see nsISubscribableServer */
+ get folderView() {
+ return this._subscribable.folderView;
+ }
+
+ get subscribeListener() {
+ return this._subscribable.subscribeListener;
+ }
+
+ set subscribeListener(value) {
+ this._subscribable.subscribeListener = value;
+ }
+
+ set delimiter(value) {
+ this._subscribable.delimiter = value;
+ }
+
+ subscribeCleanup() {
+ this._subscribableServer = null;
+ }
+
+ startPopulating(msgWindow, forceToServer, getOnlyNew) {
+ this._loadingInSubscribeDialog = true;
+ this._subscribable.startPopulating(msgWindow, forceToServer, getOnlyNew);
+ this.delimiter = "/";
+ this.setShowFullName(false);
+ MailServices.imap.getListOfFoldersOnServer(this, msgWindow);
+ }
+
+ stopPopulating(msgWindow) {
+ this._loadingInSubscribeDialog = false;
+ this._subscribable.stopPopulating(msgWindow);
+ }
+
+ addTo(name, addAsSubscribed, subscribable, changeIfExists) {
+ this._subscribable.addTo(
+ name,
+ addAsSubscribed,
+ subscribable,
+ changeIfExists
+ );
+ }
+
+ subscribe(name) {
+ this.subscribeToFolder(name, true);
+ }
+
+ unsubscribe(name) {
+ this.subscribeToFolder(name, false);
+ }
+
+ commitSubscribeChanges() {
+ this.performExpand();
+ }
+
+ setAsSubscribed(path) {
+ this._subscribable.setAsSubscribed(path);
+ }
+
+ updateSubscribed() {}
+
+ setState(path, state) {
+ return this._subscribable.setState(path, state);
+ }
+
+ setShowFullName(showFullName) {
+ this._subscribable.setShowFullName(showFullName);
+ }
+
+ hasChildren(path) {
+ return this._subscribable.hasChildren(path);
+ }
+
+ isSubscribed(path) {
+ return this._subscribable.isSubscribed(path);
+ }
+
+ isSubscribable(path) {
+ return this._subscribable.isSubscribable(path);
+ }
+
+ getLeafName(path) {
+ return this._subscribable.getLeafName(path);
+ }
+
+ getFirstChildURI(path) {
+ return this._subscribable.getFirstChildURI(path);
+ }
+
+ getChildURIs(path) {
+ return this._subscribable.getChildURIs(path);
+ }
+
+ /** @see nsIUrlListener */
+ OnStartRunningUrl() {}
+
+ OnStopRunningUrl(url, exitCode) {
+ switch (url.QueryInterface(Ci.nsIImapUrl).imapAction) {
+ case Ci.nsIImapUrl.nsImapDiscoverAllAndSubscribedBoxesUrl:
+ this.stopPopulating();
+ break;
+ }
+ }
+
+ /** @see nsIMsgIncomingServer */
+ get serverRequiresPasswordForBiff() {
+ return !this._userAuthenticated;
+ }
+
+ get offlineSupportLevel() {
+ const OFFLINE_SUPPORT_LEVEL_UNDEFINED = -1;
+ const OFFLINE_SUPPORT_LEVEL_REGULAR = 10;
+ let level = this.getIntValue("offline_support_level");
+ return level != OFFLINE_SUPPORT_LEVEL_UNDEFINED
+ ? level
+ : OFFLINE_SUPPORT_LEVEL_REGULAR;
+ }
+
+ get constructedPrettyName() {
+ let identity = MailServices.accounts.getFirstIdentityForServer(this);
+ let email;
+ if (identity) {
+ email = identity.email;
+ } else {
+ email = `${this.username}`;
+ if (this.hostName) {
+ email += `@${this.hostName}`;
+ }
+ }
+ let bundle = Services.strings.createBundle(
+ "chrome://messenger/locale/imapMsgs.properties"
+ );
+ return bundle.formatStringFromName("imapDefaultAccountName", [email]);
+ }
+
+ performBiff(msgWindow) {
+ this.performExpand(msgWindow);
+ }
+
+ performExpand(msgWindow) {
+ this._setFolderToUnverified();
+ this.hasDiscoveredFolders = false;
+ MailServices.imap.discoverAllFolders(this.rootFolder, this, msgWindow);
+ }
+
+ /**
+ * Recursively set a folder and its subFolders to unverified state.
+ *
+ * @param {nsIMsgFolder} folder - The folder to operate on.
+ */
+ _setFolderToUnverified(folder) {
+ if (!folder) {
+ this._setFolderToUnverified(this.rootFolder);
+ return;
+ }
+
+ folder.QueryInterface(
+ Ci.nsIMsgImapMailFolder
+ ).verifiedAsOnlineFolder = false;
+ for (let child of folder.subFolders) {
+ this._setFolderToUnverified(child);
+ }
+ }
+
+ closeCachedConnections() {
+ // Close all connections.
+ for (let client of this._connections) {
+ client.logout();
+ }
+ // Cancel all waitings in queue.
+ for (let resolve of this._connectionWaitingQueue) {
+ resolve(false);
+ }
+ this._connections = [];
+ }
+
+ verifyLogon(urlListener, msgWindow) {
+ return MailServices.imap.verifyLogon(
+ this.rootFolder,
+ urlListener,
+ msgWindow
+ );
+ }
+
+ subscribeToFolder(name, subscribe) {
+ let folder = this.rootMsgFolder.findSubFolder(name);
+ if (subscribe) {
+ return MailServices.imap.subscribeFolder(folder, name, null);
+ }
+ return MailServices.imap.unsubscribeFolder(folder, name, null);
+ }
+
+ /** @see nsIImapServerSink */
+
+ /** @type {boolean} - User has authenticated with the server. */
+ get userAuthenticated() {
+ return this._userAuthenticated;
+ }
+
+ set userAuthenticated(value) {
+ this._userAuthenticated = value;
+ if (value) {
+ MailServices.accounts.userNeedsToAuthenticate = false;
+ }
+ }
+
+ possibleImapMailbox(folderPath, delimiter, boxFlags) {
+ let explicitlyVerify = false;
+
+ if (folderPath.endsWith("/")) {
+ folderPath = folderPath.slice(0, -1);
+ if (!folderPath) {
+ throw Components.Exception(
+ "Empty folder path",
+ Cr.NS_ERROR_INVALID_ARG
+ );
+ }
+ explicitlyVerify = !(boxFlags & lazy.ImapUtils.FLAG_NAMESPACE);
+ }
+
+ if (this.hasDiscoveredFolders && this._loadingInSubscribeDialog) {
+ // Populate the subscribe dialog.
+ let noSelect = boxFlags & lazy.ImapUtils.FLAG_NO_SELECT;
+ this.addTo(
+ folderPath,
+ this.doingLsub && !noSelect,
+ !noSelect,
+ this.doingLsub
+ );
+ return false;
+ }
+
+ let slashIndex = folderPath.indexOf("/");
+ let token = folderPath;
+ let rest = "";
+ if (slashIndex > 0) {
+ token = folderPath.slice(0, slashIndex);
+ rest = folderPath.slice(slashIndex);
+ }
+
+ folderPath = (/^inbox/i.test(token) ? "INBOX" : token) + rest;
+
+ let uri = this.rootFolder.URI;
+ let parentName = folderPath;
+ let parentUri = uri;
+ let hasParent = false;
+ let lastSlashIndex = folderPath.lastIndexOf("/");
+ if (lastSlashIndex > 0) {
+ parentName = parentName.slice(0, lastSlashIndex);
+ hasParent = true;
+ parentUri += "/" + parentName;
+ }
+
+ if (/^inbox/i.test(folderPath) && delimiter == "|") {
+ delimiter = "/";
+ this.rootFolder.QueryInterface(
+ Ci.nsIMsgImapMailFolder
+ ).hierarchyDelimiter = delimiter;
+ }
+
+ uri += "/" + folderPath;
+ let child = this.rootFolder.getChildWithURI(
+ uri,
+ true,
+ /^inbox/i.test(folderPath)
+ );
+
+ let isNewFolder = !child;
+ if (isNewFolder) {
+ if (hasParent) {
+ let parent = this.rootFolder.getChildWithURI(
+ parentUri,
+ true,
+ /^inbox/i.test(parentName)
+ );
+
+ if (!parent) {
+ this.possibleImapMailbox(
+ parentName,
+ delimiter,
+ lazy.ImapUtils.FLAG_NO_SELECT |
+ (boxFlags &
+ (lazy.ImapUtils.FLAG_PUBLIC_MAILBOX |
+ lazy.ImapUtils.FLAG_OTHER_USERS_MAILBOX |
+ lazy.ImapUtils.FLAG_PERSONAL_MAILBOX))
+ );
+ }
+ }
+ this.rootFolder
+ .QueryInterface(Ci.nsIMsgImapMailFolder)
+ .createClientSubfolderInfo(folderPath, delimiter, boxFlags, false);
+ child = this.rootFolder.getChildWithURI(
+ uri,
+ true,
+ /^inbox/i.test(folderPath)
+ );
+ }
+ if (child) {
+ let imapFolder = child.QueryInterface(Ci.nsIMsgImapMailFolder);
+ imapFolder.verifiedAsOnlineFolder = true;
+ imapFolder.hierarchyDelimiter = delimiter;
+ if (boxFlags & lazy.ImapUtils.FLAG_IMAP_TRASH) {
+ if (this.deleteModel == Ci.nsMsgImapDeleteModels.MoveToTrash) {
+ child.setFlag(Ci.nsMsgFolderFlags.Trash);
+ }
+ }
+ imapFolder.boxFlags = boxFlags;
+ imapFolder.explicitlyVerify = explicitlyVerify;
+ let onlineName = imapFolder.onlineName;
+ folderPath = folderPath.replaceAll("/", delimiter);
+ if (delimiter != "/") {
+ folderPath = decodeURIComponent(folderPath);
+ }
+
+ if (boxFlags & lazy.ImapUtils.FLAG_IMAP_INBOX) {
+ // GMail gives us a localized name for the inbox but doesn't let
+ // us select that localized name.
+ imapFolder.onlineName = "INBOX";
+ } else if (!onlineName || onlineName != folderPath) {
+ imapFolder.onlineName = folderPath;
+ }
+
+ child.prettyName = imapFolder.name;
+ if (isNewFolder) {
+ // Close the db so we don't hold open all the .msf files for new folders.
+ child.msgDatabase = null;
+ }
+ }
+
+ return isNewFolder;
+ }
+
+ discoveryDone() {
+ this.hasDiscoveredFolders = true;
+ // No need to verify the root.
+ this.rootFolder.QueryInterface(
+ Ci.nsIMsgImapMailFolder
+ ).verifiedAsOnlineFolder = true;
+ let unverified = this._getUnverifiedFolders(this.rootFolder);
+ this._logger.debug(
+ `discoveryDone, unverified folders count=${unverified.length}.`
+ );
+ for (let folder of unverified) {
+ if (folder.flags & Ci.nsMsgFolderFlags.Virtual) {
+ // Do not remove virtual folders.
+ continue;
+ }
+ let imapFolder = folder.QueryInterface(Ci.nsIMsgImapMailFolder);
+ if (
+ !this.usingSubscription ||
+ imapFolder.explicitlyVerify ||
+ (folder.hasSubFolders && this._noDescendentsAreVerified(folder))
+ ) {
+ imapFolder.explicitlyVerify = false;
+ imapFolder.list();
+ } else if (folder.parent) {
+ imapFolder.removeLocalSelf();
+ this._logger.debug(`Removed unverified folder name=${folder.name}`);
+ }
+ }
+ }
+
+ /**
+ * Find local folders that do not exist on the server.
+ *
+ * @param {nsIMsgFolder} parentFolder - The folder to check.
+ * @returns {nsIMsgFolder[]}
+ */
+ _getUnverifiedFolders(parentFolder) {
+ let folders = [];
+ let imapFolder = parentFolder.QueryInterface(Ci.nsIMsgImapMailFolder);
+ if (!imapFolder.verifiedAsOnlineFolder || imapFolder.explicitlyVerify) {
+ folders.push(imapFolder);
+ }
+ for (let folder of parentFolder.subFolders) {
+ folders.push(...this._getUnverifiedFolders(folder));
+ }
+ return folders;
+ }
+
+ /**
+ * Returns true if all sub folders are unverified.
+ *
+ * @param {nsIMsgFolder} parentFolder - The folder to check.
+ * @returns {nsIMsgFolder[]}
+ */
+ _noDescendentsAreVerified(parentFolder) {
+ for (let folder of parentFolder.subFolders) {
+ let imapFolder = folder.QueryInterface(Ci.nsIMsgImapMailFolder);
+ if (
+ imapFolder.verifiedAsOnlineFolder ||
+ !this._noDescendentsAreVerified(folder)
+ ) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ onlineFolderRename(msgWindow, oldName, newName) {
+ let folder = this._getFolder(oldName).QueryInterface(
+ Ci.nsIMsgImapMailFolder
+ );
+ let index = newName.lastIndexOf("/");
+ let parent =
+ index > 0 ? this._getFolder(newName.slice(0, index)) : this.rootFolder;
+ folder.renameLocal(newName, parent);
+ if (parent instanceof Ci.nsIMsgImapMailFolder) {
+ try {
+ parent.renameClient(msgWindow, folder, oldName, newName);
+ } catch (e) {
+ this._logger.error("renameClient failed", e);
+ }
+ }
+
+ this._getFolder(newName).NotifyFolderEvent("RenameCompleted");
+ }
+
+ /**
+ * Given a canonical folder name, returns the corresponding msg folder.
+ *
+ * @param {string} name - The canonical folder name, e.g. a/b/c.
+ * @returns {nsIMsgFolder} The corresponding msg folder.
+ */
+ _getFolder(name) {
+ return lazy.MailUtils.getOrCreateFolder(this.rootFolder.URI + "/" + name);
+ }
+
+ abortQueuedUrls() {}
+
+ setCapability(capabilityFlags) {
+ this._capabilityFlags = capabilityFlags;
+ if (capabilityFlags & lazy.ImapCapFlags.Gmail) {
+ this.isGMailServer = true;
+ }
+ }
+
+ /** @see nsIImapIncomingServer */
+ getCapability() {
+ return this._capabilityFlags;
+ }
+
+ get deleteModel() {
+ return this.getIntValue("delete_model");
+ }
+
+ set deleteModel(value) {
+ this.setIntValue("delete_model", value);
+ let trashFolder = this._getFolder(this.trashFolderName);
+ if (trashFolder) {
+ if (value == Ci.nsMsgImapDeleteModels.MoveToTrash) {
+ trashFolder.setFlag(Ci.nsMsgFolderFlags.Trash);
+ } else {
+ trashFolder.clearFlag(Ci.nsMsgFolderFlags.Trash);
+ }
+ }
+ }
+
+ get usingSubscription() {
+ return this.getBoolValue("using_subscription");
+ }
+
+ get trashFolderName() {
+ return this.getUnicharValue("trash_folder_name") || "Trash";
+ }
+
+ get maximumConnectionsNumber() {
+ let maxConnections = this.getIntValue("max_cached_connections", 0);
+ if (maxConnections > 0) {
+ return maxConnections;
+ }
+ // The default is 5 connections, if the pref value is 0, we use the default.
+ // If it's negative, treat it as 1.
+ maxConnections = maxConnections == 0 ? 5 : 1;
+ this.maximumConnectionsNumber = maxConnections;
+ return maxConnections;
+ }
+
+ set maximumConnectionsNumber(value) {
+ this.setIntValue("max_cached_connections", value);
+ }
+
+ GetNewMessagesForNonInboxFolders(
+ folder,
+ msgWindow,
+ forceAllFolders,
+ performingBiff
+ ) {
+ let flags = folder.flags;
+
+ if (
+ folder.QueryInterface(Ci.nsIMsgImapMailFolder).canOpenFolder &&
+ ((forceAllFolders &&
+ !(
+ flags &
+ (Ci.nsMsgFolderFlags.Inbox |
+ Ci.nsMsgFolderFlags.Trash |
+ Ci.nsMsgFolderFlags.Junk |
+ Ci.nsMsgFolderFlags.Virtual)
+ )) ||
+ flags & Ci.nsMsgFolderFlags.CheckNew)
+ ) {
+ folder.gettingNewMessages = true;
+ if (performingBiff) {
+ folder.performingBiff = true;
+ }
+ }
+
+ if (
+ Services.prefs.getBoolPref("mail.imap.use_status_for_biff", false) &&
+ !MailServices.mailSession.IsFolderOpenInWindow(folder)
+ ) {
+ folder.updateStatus(this, msgWindow);
+ } else {
+ folder.updateFolder(msgWindow);
+ }
+
+ for (let subFolder of folder.subFolders) {
+ this.GetNewMessagesForNonInboxFolders(
+ subFolder,
+ msgWindow,
+ forceAllFolders,
+ performingBiff
+ );
+ }
+ }
+
+ CloseConnectionForFolder(folder) {
+ for (let client of this._connections) {
+ if (client.folder == folder) {
+ client.logout();
+ }
+ }
+ }
+
+ _capabilities = [];
+
+ set capabilities(value) {
+ this._capabilities = value;
+ this.setCapability(lazy.ImapCapFlags.stringsToFlags(value));
+ }
+
+ // @type {ImapClient[]} - An array of connections.
+ _connections = [];
+ // @type {Function[]} - An array of Promise.resolve functions.
+ _connectionWaitingQueue = [];
+
+ /**
+ * Wait for a free connection.
+ *
+ * @param {nsIMsgFolder} folder - The folder to operate on.
+ * @returns {ImapClient}
+ */
+ async _waitForNextClient(folder) {
+ // Wait until a connection is available. canGetNext is false when
+ // closeCachedConnections is called.
+ let canGetNext = await new Promise(resolve =>
+ this._connectionWaitingQueue.push(resolve)
+ );
+ if (canGetNext) {
+ return this._getNextClient(folder);
+ }
+ return null;
+ }
+
+ /**
+ * Check if INBOX folder is selected in a connection.
+ *
+ * @param {ImapClient} client - The client to check.
+ * @returns {boolean}
+ */
+ _isInboxConnection(client) {
+ return client.folder?.onlineName.toUpperCase() == "INBOX";
+ }
+
+ /**
+ * Get a free connection that can be used.
+ *
+ * @param {nsIMsgFolder} folder - The folder to operate on.
+ * @returns {ImapClient}
+ */
+ async _getNextClient(folder) {
+ let client;
+
+ for (client of this._connections) {
+ if (folder && client.folder == folder) {
+ if (client.busy) {
+ // Prevent operating on the same folder in two connections.
+ return this._waitForNextClient(folder);
+ }
+ // If we're idling in the target folder, reuse it.
+ client.busy = true;
+ return client;
+ }
+ }
+
+ // Create a new client if the pool is not full.
+ if (this._connections.length < this.maximumConnectionsNumber) {
+ client = new lazy.ImapClient(this);
+ this._connections.push(client);
+ client.busy = true;
+ return client;
+ }
+
+ let freeConnections = this._connections.filter(c => !c.busy);
+
+ // Wait if no free connection.
+ if (!freeConnections.length) {
+ return this._waitForNextClient(folder);
+ }
+
+ // Reuse any free connection if only have one connection or IDLE not used.
+ if (
+ this.maximumConnectionsNumber <= 1 ||
+ !this.useIdle ||
+ !this._capabilities.includes("IDLE")
+ ) {
+ freeConnections[0].busy = true;
+ return freeConnections[0];
+ }
+
+ // Reuse non-inbox free connection.
+ client = freeConnections.find(c => !this._isInboxConnection(c));
+ if (client) {
+ client.busy = true;
+ return client;
+ }
+ return this._waitForNextClient(folder);
+ }
+
+ /**
+ * Do some actions with a connection.
+ *
+ * @param {nsIMsgFolder} folder - The folder to operate on.
+ * @param {Function} handler - A callback function to take a ImapClient
+ * instance, and do some actions.
+ */
+ async withClient(folder, handler) {
+ let client = await this._getNextClient(folder);
+ if (!client) {
+ return;
+ }
+ let startIdle = async () => {
+ if (!this.useIdle || !this._capabilities.includes("IDLE")) {
+ return;
+ }
+
+ // IDLE is configed and supported, use IDLE to receive server pushes.
+ let hasInboxConnection = this._connections.some(c =>
+ this._isInboxConnection(c)
+ );
+ let alreadyIdling =
+ client.folder &&
+ this._connections.find(
+ c => c != client && !c.busy && c.folder == client.folder
+ );
+ if (!hasInboxConnection) {
+ client.selectFolder(
+ this.rootFolder.getFolderWithFlags(Ci.nsMsgFolderFlags.Inbox)
+ );
+ } else if (client.folder && !alreadyIdling) {
+ client.idle();
+ } else if (alreadyIdling) {
+ client.folder = null;
+ }
+ };
+ client.onFree = () => {
+ client.busy = false;
+ let resolve = this._connectionWaitingQueue.shift();
+ if (resolve) {
+ // Resolve the first waiting in queue.
+ resolve(true);
+ } else if (client.isOnline) {
+ startIdle();
+ }
+ };
+ handler(client);
+ client.connect();
+ }
+}
+
+ImapIncomingServer.prototype.classID = Components.ID(
+ "{b02a4e1c-0d9e-498c-8b9d-18917ba9f65b}"
+);
diff --git a/comm/mailnews/imap/src/ImapMessageService.jsm b/comm/mailnews/imap/src/ImapMessageService.jsm
new file mode 100644
index 0000000000..e88e0f8090
--- /dev/null
+++ b/comm/mailnews/imap/src/ImapMessageService.jsm
@@ -0,0 +1,292 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 = ["ImapMessageService", "ImapMessageMessageService"];
+
+const { XPCOMUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/XPCOMUtils.sys.mjs"
+);
+const { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+const { ImapUtils } = ChromeUtils.import("resource:///modules/ImapUtils.jsm");
+
+const lazy = {};
+
+XPCOMUtils.defineLazyModuleGetters(lazy, {
+ MailUtils: "resource:///modules/MailUtils.jsm",
+});
+
+/**
+ * @implements {nsIMsgMessageService}
+ */
+class BaseMessageService {
+ QueryInterface = ChromeUtils.generateQI(["nsIMsgMessageService"]);
+
+ _logger = ImapUtils.logger;
+
+ copyMessage(messageUri, copyListener, moveMessage, urlListener, msgWindow) {
+ this._logger.debug("copyMessage", messageUri, moveMessage);
+ let { serverURI, folder, folderName, key } =
+ this._decomposeMessageUri(messageUri);
+ let imapUrl = Services.io
+ .newURI(`${serverURI}/fetch>UID>/${folderName}>${key}`)
+ .QueryInterface(Ci.nsIImapUrl);
+
+ if (urlListener) {
+ imapUrl
+ .QueryInterface(Ci.nsIMsgMailNewsUrl)
+ .RegisterListener(urlListener);
+ }
+
+ return MailServices.imap.fetchMessage(
+ imapUrl,
+ moveMessage
+ ? Ci.nsIImapUrl.nsImapOnlineToOfflineMove
+ : Ci.nsIImapUrl.nsImapOnlineToOfflineCopy,
+ folder,
+ folder.QueryInterface(Ci.nsIImapMessageSink),
+ msgWindow,
+ copyListener,
+ key,
+ false,
+ {}
+ );
+ }
+
+ loadMessage(
+ messageUri,
+ displayConsumer,
+ msgWindow,
+ urlListener,
+ autodetectCharset
+ ) {
+ this._logger.debug("loadMessage", messageUri);
+ let { serverURI, folder, folderName, key } =
+ this._decomposeMessageUri(messageUri);
+ let imapUrl = Services.io
+ .newURI(`${serverURI}/fetch>UID>/${folderName}>${key}`)
+ .QueryInterface(Ci.nsIImapUrl);
+
+ let mailnewsUrl = imapUrl.QueryInterface(Ci.nsIMsgMailNewsUrl);
+ if (urlListener) {
+ mailnewsUrl.RegisterListener(urlListener);
+ }
+
+ return MailServices.imap.fetchMessage(
+ imapUrl,
+ Ci.nsIImapUrl.nsImapMsgFetch,
+ folder,
+ folder.QueryInterface(Ci.nsIImapMessageSink),
+ msgWindow,
+ displayConsumer,
+ key,
+ false,
+ {}
+ );
+ }
+
+ SaveMessageToDisk(
+ messageUri,
+ file,
+ addDummyEnvelope,
+ urlListener,
+ outUrl,
+ canonicalLineEnding,
+ msgWindow
+ ) {
+ this._logger.debug("SaveMessageToDisk", messageUri);
+ let { serverURI, folder, folderName, key } =
+ this._decomposeMessageUri(messageUri);
+ let imapUrl = Services.io
+ .newURI(`${serverURI}/fetch>UID>/${folderName}>${key}`)
+ .QueryInterface(Ci.nsIImapUrl);
+
+ let msgUrl = imapUrl.QueryInterface(Ci.nsIMsgMessageUrl);
+ msgUrl.messageFile = file;
+ msgUrl.AddDummyEnvelope = addDummyEnvelope;
+ msgUrl.canonicalLineEnding = canonicalLineEnding;
+ let mailnewsUrl = imapUrl.QueryInterface(Ci.nsIMsgMailNewsUrl);
+ mailnewsUrl.RegisterListener(urlListener);
+ mailnewsUrl.msgIsInLocalCache = folder.hasMsgOffline(key, null, 10);
+
+ return MailServices.imap.fetchMessage(
+ imapUrl,
+ Ci.nsIImapUrl.nsImapSaveMessageToDisk,
+ folder,
+ folder.QueryInterface(Ci.nsIImapMessageSink),
+ msgWindow,
+ mailnewsUrl.getSaveAsListener(addDummyEnvelope, file),
+ key,
+ false,
+ {}
+ );
+ }
+
+ getUrlForUri(messageUri, msgWindow) {
+ if (messageUri.includes("&type=application/x-message-display")) {
+ return Services.io.newURI(messageUri);
+ }
+
+ let { serverURI, folder, folderName, key } =
+ this._decomposeMessageUri(messageUri);
+ let delimiter =
+ folder.QueryInterface(Ci.nsIMsgImapMailFolder).hierarchyDelimiter || "/";
+ let imapUrl = Services.io
+ .newURI(
+ `${serverURI}:${folder.server.port}/fetch>UID>${delimiter}${folderName}>${key}`
+ )
+ .QueryInterface(Ci.nsIImapUrl);
+
+ return imapUrl;
+ }
+
+ streamMessage(
+ messageUri,
+ consumer,
+ msgWindow,
+ urlListener,
+ convertData,
+ additionalHeader,
+ localOnly
+ ) {
+ this._logger.debug("streamMessage", messageUri);
+ let { serverURI, folder, folderName, key } =
+ this._decomposeMessageUri(messageUri);
+ let url = `${serverURI}/fetch>UID>/${folderName}>${key}`;
+ if (additionalHeader) {
+ url += `?header=${additionalHeader}`;
+ }
+ let imapUrl = Services.io.newURI(url).QueryInterface(Ci.nsIImapUrl);
+ imapUrl.localFetchOnly = localOnly;
+
+ let mailnewsUrl = imapUrl.QueryInterface(Ci.nsIMsgMailNewsUrl);
+ mailnewsUrl.folder = folder;
+ mailnewsUrl.msgWindow = msgWindow;
+ mailnewsUrl.msgIsInLocalCache = folder.hasMsgOffline(key);
+ if (urlListener) {
+ mailnewsUrl.RegisterListener(urlListener);
+ }
+
+ return MailServices.imap.fetchMessage(
+ imapUrl,
+ Ci.nsIImapUrl.nsImapMsgFetchPeek,
+ folder,
+ folder.QueryInterface(Ci.nsIImapMessageSink),
+ msgWindow,
+ consumer,
+ key,
+ convertData,
+ {}
+ );
+ }
+
+ streamHeaders(messageUri, consumer, urlListener, localOnly) {
+ this._logger.debug("streamHeaders", messageUri);
+ let { folder, key } = this._decomposeMessageUri(messageUri);
+
+ let hasMsgOffline = folder.hasMsgOffline(key);
+ if (!hasMsgOffline) {
+ return;
+ }
+
+ let localMsgStream = folder.getLocalMsgStream(folder.GetMessageHeader(key));
+ let sstream = Cc["@mozilla.org/scriptableinputstream;1"].createInstance(
+ Ci.nsIScriptableInputStream
+ );
+ sstream.init(localMsgStream);
+ let headers = "";
+ let str = "";
+ do {
+ str = sstream.read(4096);
+ let index = str.indexOf("\r\n\r\n");
+ if (index != -1) {
+ headers += str.slice(0, index) + "\r\n";
+ break;
+ } else {
+ headers += str;
+ }
+ } while (str.length);
+
+ let headersStream = Cc[
+ "@mozilla.org/io/string-input-stream;1"
+ ].createInstance(Ci.nsIStringInputStream);
+ headersStream.setData(headers, headers.length);
+ let pump = Cc["@mozilla.org/network/input-stream-pump;1"].createInstance(
+ Ci.nsIInputStreamPump
+ );
+ pump.init(headersStream, 0, 0, true);
+ pump.asyncRead(consumer);
+ }
+
+ /**
+ * Go from message uri to go nsIMsgDBHdr.
+ *
+ * @param {string} uri - A message uri to get the nsIMsgDBHdr for.
+ * @returns {?nsIMsgDBHdr} Hdr for the uri, or or null if failed.
+ */
+ messageURIToMsgHdr(uri) {
+ try {
+ let { folder, key } = this._decomposeMessageUri(uri);
+ return folder.GetMessageHeader(key);
+ } catch (e) {
+ return null;
+ }
+ }
+
+ Search(searchSession, msgWindow, folder, searchUri) {
+ let server = folder.server.QueryInterface(Ci.nsIMsgIncomingServer);
+ server.wrappedJSObject.withClient(folder, client => {
+ client.startRunningUrl(
+ searchSession.QueryInterface(Ci.nsIUrlListener),
+ msgWindow
+ );
+ client.onReady = () => {
+ client.search(folder, searchUri);
+ };
+ client.onData = uids => {
+ for (let uid of uids) {
+ let msgHdr = folder.msgDatabase.getMsgHdrForKey(uid);
+ searchSession.runningAdapter.AddResultElement(msgHdr);
+ }
+ };
+ });
+ }
+
+ /**
+ * Parse a message uri to hostname, folder and message key.
+ *
+ * @param {string} uri - The imap-message:// url to parse.
+ * @returns {serverURI: string, folder: nsIMsgFolder, folderName: string, key: string}
+ */
+ _decomposeMessageUri(messageUri) {
+ let matches = /imap-message:\/\/([^:/]+)\/(.+)#(\d+)/.exec(messageUri);
+ if (!matches) {
+ throw new Error(`Unexpected IMAP URL: ${messageUri}`);
+ }
+ let [, host, folderName, key] = matches;
+ let folder = lazy.MailUtils.getOrCreateFolder(
+ `imap://${host}/${folderName}`
+ );
+ return { serverURI: folder.server.serverURI, folder, folderName, key };
+ }
+}
+
+/**
+ * A message service for imap://.
+ */
+class ImapMessageService extends BaseMessageService {}
+
+ImapMessageService.prototype.classID = Components.ID(
+ "{d63af753-c2f3-4f1d-b650-9d12229de8ad}"
+);
+
+/**
+ * A message service for imap-message://.
+ */
+class ImapMessageMessageService extends BaseMessageService {}
+
+ImapMessageMessageService.prototype.classID = Components.ID(
+ "{2532ae4f-a852-4c96-be45-1308ba23d62e}"
+);
diff --git a/comm/mailnews/imap/src/ImapModuleLoader.jsm b/comm/mailnews/imap/src/ImapModuleLoader.jsm
new file mode 100644
index 0000000000..427fe2a3f2
--- /dev/null
+++ b/comm/mailnews/imap/src/ImapModuleLoader.jsm
@@ -0,0 +1,131 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 = ["ImapModuleLoader"];
+
+/**
+ * Determine whether to use nsImapService.cpp or ImapService.jsm. When
+ * `mailnews.imap.jsmodule` is `true`, use ImapService.jsm.
+ */
+function ImapModuleLoader() {
+ try {
+ this.loadModule();
+ } catch (e) {
+ console.error(e);
+ }
+}
+
+var imapJSModules = [
+ // moduleName, interfaceId, contractId
+ [
+ "ImapIncomingServer",
+ "{b02a4e1c-0d9e-498c-8b9d-18917ba9f65b}",
+ "@mozilla.org/messenger/server;1?type=imap",
+ ],
+ [
+ "ImapService",
+ "{2ea8fbe6-029b-4bff-ae05-b794cf955afb}",
+ "@mozilla.org/messenger/imapservice;1",
+ ],
+ [
+ "ImapMessageService",
+ "{d63af753-c2f3-4f1d-b650-9d12229de8ad}",
+ "@mozilla.org/messenger/messageservice;1?type=imap",
+ "ImapMessageService",
+ ],
+ [
+ "ImapFolderContentHandler",
+ "{d927a82f-2d15-4972-ab88-6d84601aae68}",
+ "@mozilla.org/uriloader/content-handler;1?type=x-application-imapfolder",
+ ],
+ [
+ "ImapMessageMessageService",
+ "{2532ae4f-a852-4c96-be45-1308ba23d62e}",
+ "@mozilla.org/messenger/messageservice;1?type=imap-message",
+ "ImapMessageService",
+ ],
+ [
+ "ImapProtocolHandler",
+ "{ebb06c58-6ccd-4bde-9087-40663e0388ae}",
+ "@mozilla.org/network/protocol;1?name=imap",
+ ],
+ [
+ "ImapProtocolInfo",
+ "{1d9473bc-423a-4632-ad5d-802154e80f6f}",
+ "@mozilla.org/messenger/protocol/info;1?type=imap",
+ ],
+];
+
+ImapModuleLoader.prototype = {
+ QueryInterface: ChromeUtils.generateQI(["nsIObserver"]),
+
+ observe() {
+ // Nothing to do here, just need the entry so this is instantiated.
+ },
+
+ loadModule() {
+ if (Services.prefs.getBoolPref("mailnews.imap.jsmodule", false)) {
+ let registrar = Components.manager.QueryInterface(
+ Ci.nsIComponentRegistrar
+ );
+
+ for (let [
+ moduleName,
+ interfaceId,
+ contractId,
+ fileName,
+ ] of imapJSModules) {
+ // Register a module.
+ let classId = Components.ID(interfaceId);
+ registrar.registerFactory(
+ classId,
+ "",
+ contractId,
+ lazyFactoryFor(fileName || moduleName, moduleName)
+ );
+ }
+
+ dump("[ImapModuleLoader] Using ImapService.jsm\n");
+
+ const { ImapProtocolHandler } = ChromeUtils.import(
+ `resource:///modules/ImapProtocolHandler.jsm`
+ );
+ let protocolFlags =
+ Ci.nsIProtocolHandler.URI_NORELATIVE |
+ Ci.nsIProtocolHandler.URI_FORBIDS_AUTOMATIC_DOCUMENT_REPLACEMENT |
+ Ci.nsIProtocolHandler.URI_DANGEROUS_TO_LOAD |
+ Ci.nsIProtocolHandler.ALLOWS_PROXY |
+ Ci.nsIProtocolHandler.URI_FORBIDS_COOKIE_ACCESS |
+ Ci.nsIProtocolHandler.ORIGIN_IS_FULL_SPEC;
+
+ Services.io.registerProtocolHandler(
+ "imap",
+ new ImapProtocolHandler(),
+ protocolFlags,
+ Ci.nsIImapUrl.DEFAULT_IMAP_PORT
+ );
+ } else {
+ dump("[ImapModuleLoader] Using nsImapService.cpp\n");
+ // Ensure the imap protocol is actually registered.
+ Cc["@mozilla.org/network/protocol;1?name=imap"].getService(
+ Ci.nsIImapService
+ );
+ }
+ },
+};
+
+function lazyFactoryFor(fileName, constructorName) {
+ let factory = {
+ get scope() {
+ delete this.scope;
+ this.scope = ChromeUtils.import(`resource:///modules/${fileName}.jsm`);
+ return this.scope;
+ },
+ createInstance(interfaceID) {
+ let componentConstructor = this.scope[constructorName];
+ return new componentConstructor().QueryInterface(interfaceID);
+ },
+ };
+ return factory;
+}
diff --git a/comm/mailnews/imap/src/ImapProtocolHandler.jsm b/comm/mailnews/imap/src/ImapProtocolHandler.jsm
new file mode 100644
index 0000000000..949dca9632
--- /dev/null
+++ b/comm/mailnews/imap/src/ImapProtocolHandler.jsm
@@ -0,0 +1,40 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 = ["ImapProtocolHandler"];
+
+var { ImapChannel } = ChromeUtils.import("resource:///modules/ImapChannel.jsm");
+
+/**
+ * @implements {nsIProtocolHandler}
+ */
+class ImapProtocolHandler {
+ QueryInterface = ChromeUtils.generateQI(["nsIProtocolHandler"]);
+
+ scheme = "imap";
+
+ newChannel(uri, loadInfo) {
+ let channel = new ImapChannel(uri, loadInfo);
+ let spec = uri.spec;
+ if (
+ spec.includes("part=") &&
+ !spec.includes("type=message/rfc822") &&
+ !spec.includes("type=application/x-message-display") &&
+ !spec.includes("type=application/pdf")
+ ) {
+ channel.contentDisposition = Ci.nsIChannel.DISPOSITION_ATTACHMENT;
+ } else {
+ channel.contentDisposition = Ci.nsIChannel.DISPOSITION_INLINE;
+ }
+ return channel;
+ }
+
+ allowPort(port, scheme) {
+ return true;
+ }
+}
+
+ImapProtocolHandler.prototype.classID = Components.ID(
+ "{ebb06c58-6ccd-4bde-9087-40663e0388ae}"
+);
diff --git a/comm/mailnews/imap/src/ImapProtocolInfo.jsm b/comm/mailnews/imap/src/ImapProtocolInfo.jsm
new file mode 100644
index 0000000000..9d12175791
--- /dev/null
+++ b/comm/mailnews/imap/src/ImapProtocolInfo.jsm
@@ -0,0 +1,44 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const EXPORTED_SYMBOLS = ["ImapProtocolInfo"];
+
+var { MsgProtocolInfo } = ChromeUtils.importESModule(
+ "resource:///modules/MsgProtocolInfo.sys.mjs"
+);
+
+/**
+ * @implements {nsIMsgProtocolInfo}
+ */
+class ImapProtocolInfo extends MsgProtocolInfo {
+ QueryInterface = ChromeUtils.generateQI(["nsIMsgProtocolInfo"]);
+
+ serverIID = Components.ID("{b02a4e1c-0d9e-498c-8b9d-18917ba9f65b}");
+
+ requiresUsername = true;
+ preflightPrettyNameWithEmailAddress = true;
+ canDelete = true;
+ canLoginAtStartUp = true;
+ canDuplicate = true;
+ canGetMessages = true;
+ canGetIncomingMessages = true;
+ defaultDoBiff = true;
+ showComposeMsgLink = true;
+ foldersCreatedAsync = true;
+
+ getDefaultServerPort(isSecure) {
+ return isSecure
+ ? Ci.nsIImapUrl.DEFAULT_IMAPS_PORT
+ : Ci.nsIImapUrl.DEFAULT_IMAP_PORT;
+ }
+
+ // @see MsgProtocolInfo.sys.mjs
+ RELATIVE_PREF = "mail.root.imap-rel";
+ ABSOLUTE_PREF = "mail.root.imap";
+ DIR_SERVICE_PROP = "IMapMD";
+}
+
+ImapProtocolInfo.prototype.classID = Components.ID(
+ "{1d9473bc-423a-4632-ad5d-802154e80f6f}"
+);
diff --git a/comm/mailnews/imap/src/ImapResponse.jsm b/comm/mailnews/imap/src/ImapResponse.jsm
new file mode 100644
index 0000000000..08d1c25916
--- /dev/null
+++ b/comm/mailnews/imap/src/ImapResponse.jsm
@@ -0,0 +1,479 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 = ["ImapResponse"];
+
+var { ImapUtils } = ChromeUtils.import("resource:///modules/ImapUtils.jsm");
+
+/**
+ * A structure to represent a server response.
+ */
+class ImapResponse {
+ constructor() {
+ // @type {MailboxData[]} The mailbox-data in this response.
+ this.mailboxes = [];
+ // @type {MessageData[]} The message-data in this response.
+ this.messages = [];
+ // A holder for attributes.
+ this.attributes = {};
+ // Expunged message sequences.
+ this.expunged = [];
+
+ // The remaining string to parse.
+ this._response = "";
+
+ this.onMessage = () => {};
+ }
+
+ /**
+ * A server response can span multiple chunks, this function parses one chunk.
+ *
+ * @param {string} str - A chunk of server response.
+ */
+ parse(str) {
+ this._response += str;
+ if (this._pendingMessage) {
+ // We have an unfinished message in the last chunk.
+ let remaining =
+ this._pendingMessage.bodySize - this._pendingMessage.body.length;
+ if (remaining + ")\r\n".length <= this._response.length) {
+ // Consume the message together with the ending ")\r\n".
+ this._pendingMessage.body += this._response.slice(0, remaining);
+ this.onMessage(this._pendingMessage);
+ this._pendingMessage = null;
+ this._advance(remaining + ")\r\n".length);
+ } else {
+ this.done = false;
+ return;
+ }
+ }
+ this._parse();
+ }
+
+ /**
+ * Drop n characters from _response.
+ *
+ * @param {number} n - The number of characters to drop.
+ */
+ _advance(n) {
+ this._response = this._response.slice(n);
+ }
+
+ /**
+ * Parse the response line by line. Because a single response can contain
+ * multiple types of data, update the corresponding properties after parsing
+ * a line, e.g. this.capabilities, this.flags, this.messages.
+ */
+ _parse() {
+ if (!this._response && this.tag != "*") {
+ // Nothing more to parse.
+ this.done = true;
+ return;
+ }
+ let index = this._response.indexOf("\r\n");
+ if (index == -1) {
+ // Expect more string in the next chunk.
+ this.done = false;
+ return;
+ }
+
+ let line = this._response.slice(0, index);
+ this._advance(index + 2); // Consume the line and "\r\n".
+ let tokens = this._parseLine(line);
+ this.tag = tokens[0];
+ this.status = tokens[1];
+ if (this.tag == "+") {
+ this.statusText = tokens.slice(1).join(" ");
+ if (!this._response) {
+ this.done = true;
+ return;
+ }
+ }
+
+ let parsed;
+
+ if (this.tag == "*") {
+ parsed = true;
+ switch (tokens[1].toUpperCase()) {
+ case "CAPABILITY":
+ // * CAPABILITY IMAP4rev1 IDLE STARTTLS AUTH=LOGIN AUTH=PLAIN
+ let { capabilities, authMethods } = new CapabilityData(
+ tokens.slice(2)
+ );
+ this.capabilities = capabilities;
+ this.authMethods = authMethods;
+ break;
+ case "FLAGS":
+ // * FLAGS (\Seen \Draft $Forwarded)
+ this.flags = ImapUtils.stringsToFlags(tokens[2]);
+ if (tokens[2].includes("\\*")) {
+ this.supportedUserFlags =
+ ImapUtils.FLAG_LABEL |
+ ImapUtils.FLAG_MDN_SENT |
+ ImapUtils.FLAG_SUPPORT_FORWARDED_FLAG |
+ ImapUtils.FLAG_SUPPORT_USER_FLAG;
+ }
+ break;
+ case "ID":
+ // * ID ("name" "imap" "vendor" "Example, Inc.")
+ this.id = line.slice("* ID ".length);
+ break;
+ case "LIST":
+ case "LSUB":
+ // * LIST (\Subscribed \NoInferiors \UnMarked \Sent) "/" Sent
+ this.mailboxes.push(new MailboxData(tokens));
+ break;
+ case "QUOTAROOT":
+ // * QUOTAROOT Sent INBOX
+ this.quotaRoots = tokens.slice(3);
+ break;
+ case "QUOTA":
+ // S: * QUOTA INBOX (STORAGE 95295 97656832)
+ if (!this.quotas) {
+ this.quotas = [];
+ }
+ this.quotas.push([tokens[2], ...tokens[3]]);
+ break;
+ case "SEARCH":
+ // * SEARCH 1 4 9
+ this.search = tokens.slice(2).map(x => Number(x));
+ break;
+ case "STATUS":
+ // * STATUS \"folder 2\" (UIDNEXT 2 MESSAGES 1 UNSEEN 1)
+ this.attributes = new StatusData(tokens).attributes;
+ break;
+ default:
+ if (Number.isInteger(+tokens[1])) {
+ this._parseNumbered(tokens);
+ } else {
+ parsed = false;
+ }
+ break;
+ }
+ }
+ if (!parsed && Array.isArray(tokens[2])) {
+ let type = tokens[2][0].toUpperCase();
+ let data = tokens[2].slice(1);
+ switch (type) {
+ case "CAPABILITY":
+ // 32 OK [CAPABILITY IMAP4rev1 IDLE STARTTLS AUTH=LOGIN AUTH=PLAIN]
+ let { capabilities, authMethods } = new CapabilityData(data);
+ this.capabilities = capabilities;
+ this.authMethods = authMethods;
+ break;
+ case "PERMANENTFLAGS":
+ // * OK [PERMANENTFLAGS (\\Seen \\Draft $Forwarded \\*)]
+ this.permanentflags = ImapUtils.stringsToFlags(tokens[2][1]);
+ if (tokens[2][1].includes("\\*")) {
+ this.supportedUserFlags =
+ ImapUtils.FLAG_LABEL |
+ ImapUtils.FLAG_MDN_SENT |
+ ImapUtils.FLAG_SUPPORT_FORWARDED_FLAG |
+ ImapUtils.FLAG_SUPPORT_USER_FLAG;
+ }
+ break;
+ default:
+ let field = type.toLowerCase();
+ if (tokens[2].length == 1) {
+ // A boolean attribute, e.g. 12 OK [READ-WRITE]
+ this[field] = true;
+ } else if (tokens[2].length == 2) {
+ // An attribute/value pair, e.g. 12 OK [UIDNEXT 600]
+ this[field] = tokens[2][1];
+ } else {
+ // Hold other attributes.
+ this.attributes[field] = data;
+ }
+ }
+ }
+ this._parse();
+ }
+
+ /**
+ * Handle the tokens of a line in the form of "* NUM TYPE".
+ *
+ * @params {Array<string|string[]>} tokens - The tokens of the line.
+ */
+ _parseNumbered(tokens) {
+ let intValue = +tokens[1];
+ let type = tokens[2].toUpperCase();
+ switch (type) {
+ case "FETCH":
+ // * 1 FETCH (UID 5 FLAGS (\SEEN) BODY[HEADER.FIELDS (FROM TO)] {12}
+ let message = new MessageData(intValue, tokens[3]);
+ this.messages.push(message);
+ if (message.bodySize) {
+ if (message.bodySize + ")\r\n".length <= this._response.length) {
+ // Consume the message together with the ending ")\r\n".
+ message.body = this._response.slice(0, message.bodySize);
+ this.onMessage(message);
+ } else {
+ message.body = this._response;
+ this._pendingMessage = message;
+ this.done = false;
+ }
+ this._advance(message.bodySize + ")\r\n".length);
+ } else {
+ this.onMessage(message);
+ }
+ break;
+ case "EXISTS":
+ // * 6 EXISTS
+ this.exists = intValue;
+ break;
+ case "EXPUNGE":
+ // * 2 EXPUNGE
+ this.expunged.push(intValue);
+ break;
+ case "RECENT":
+ // Deprecated in rfc9051.
+ break;
+ default:
+ throw Components.Exception(
+ `Unrecognized response: ${tokens.join(" ")}`,
+ Cr.NS_ERROR_ILLEGAL_VALUE
+ );
+ }
+ }
+
+ /**
+ * Break a line into flat tokens array. For example,
+ * "(UID 24 FLAGS (NonJunk))" will be tokenized to
+ * ["(", "UID", "24", "FLAGS", "(", "NonJunk", ")", ")"].
+ *
+ * @param {string} line - A single line of string.
+ * @returns {string[]}
+ */
+ _tokenize(line) {
+ const SEPARATORS = /[()\[\]" ]/;
+ let tokens = [];
+ while (line) {
+ // Find the first separator.
+ let index = line.search(SEPARATORS);
+ if (index == -1) {
+ tokens.push(line);
+ break;
+ }
+ let sep = line[index];
+ let token = line.slice(0, index);
+ if (token) {
+ tokens.push(token);
+ }
+ if (sep == '"') {
+ // Parse the whole string as a token.
+ line = line.slice(index + 1);
+ let str = sep;
+ while (true) {
+ index = line.indexOf('"');
+ if (line[index - 1] == "\\") {
+ // Not the ending quote.
+ str += line.slice(0, index + 1);
+ line = line.slice(index + 1);
+ continue;
+ } else {
+ // The ending quote.
+ str += line.slice(0, index + 1);
+ tokens.push(str);
+ line = line.slice(index + 1);
+ break;
+ }
+ }
+ continue;
+ } else if (sep != " ") {
+ tokens.push(sep);
+ }
+ line = line.slice(index + 1);
+ }
+ return tokens;
+ }
+
+ /**
+ * Parse a line into nested tokens array. For example,
+ * "(UID 24 FLAGS (NonJunk))" will be parsed to
+ * ["UID", "24", "FLAGS", ["NonJunk"]].
+ *
+ * @param {string} line - A single line of string.
+ * @returns {Array<string|string[]>}
+ */
+ _parseLine(line) {
+ let tokens = [];
+ let arrayDepth = 0;
+
+ for (let token of this._tokenize(line)) {
+ let depth = arrayDepth;
+ let arr = tokens;
+ while (depth-- > 0) {
+ arr = arr.at(-1);
+ }
+ switch (token) {
+ case "(":
+ case "[":
+ arr.push([]);
+ arrayDepth++;
+ break;
+ case ")":
+ case "]":
+ arrayDepth--;
+ break;
+ default:
+ arr.push(token);
+ }
+ }
+
+ return tokens;
+ }
+}
+
+/**
+ * A structure to represent capability-data.
+ */
+class CapabilityData {
+ /**
+ * @param {string[]} tokens - An array like: ["IMAP4rev1", "IDLE", "STARTTLS",
+ * "AUTH=LOGIN", "AUTH=PLAIN"].
+ */
+ constructor(tokens) {
+ this.capabilities = [];
+ this.authMethods = [];
+ for (let cap of tokens) {
+ cap = cap.toUpperCase();
+ if (cap.startsWith("AUTH=")) {
+ this.authMethods.push(cap.slice(5));
+ } else {
+ this.capabilities.push(cap);
+ }
+ }
+ }
+}
+
+/**
+ * A structure to represent message-data.
+ */
+class MessageData {
+ /**
+ * @param {number} sequence - The sequence number of this message.
+ * @param {string[]} tokens - An array like: ["UID", "24", "FLAGS", ["\Seen"]].
+ */
+ constructor(sequence, tokens) {
+ this.sequence = sequence;
+ this.customAttributes = {};
+ for (let i = 0; i < tokens.length; i += 2) {
+ let name = tokens[i].toUpperCase();
+ switch (name) {
+ case "UID":
+ this.uid = +tokens[i + 1];
+ break;
+ case "FLAGS":
+ this.flags = ImapUtils.stringsToFlags(tokens[i + 1]);
+ this.keywords = tokens[i + 1]
+ .filter(x => !x.startsWith("\\"))
+ .join(" ");
+ break;
+ case "BODY": {
+ // bodySection is the part between [ and ].
+ this.bodySection = tokens[i + 1];
+ i++;
+ // {123} means the following 123 bytes are the body.
+ let matches = tokens[i + 1].match(/{(\d+)}/);
+ if (matches) {
+ this.bodySize = +matches[1];
+ this.body = "";
+ }
+ break;
+ }
+ case "RFC822.SIZE": {
+ this.size = +tokens[i + 1];
+ break;
+ }
+ default:
+ this.customAttributes[tokens[i]] = tokens[i + 1];
+ break;
+ }
+ }
+ }
+}
+
+/**
+ * A structure to represent mailbox-data.
+ */
+class MailboxData {
+ constructor(tokens) {
+ let [, , attributes, delimiter, name] = tokens;
+ this.flags = this._stringsToFlags(attributes);
+ this.delimiter = unwrapString(delimiter);
+ this.name = unwrapString(name);
+ }
+
+ /**
+ * Convert an array of flag string to an internal flag number.
+ *
+ * @param {string[]} arr - An array of flag string.
+ * @returns {number} An internal flag number.
+ */
+ _stringsToFlags(arr) {
+ let stringToFlag = {
+ "\\MARKED": ImapUtils.FLAG_MARKED,
+ "\\UNMARKED": ImapUtils.FLAG_UNMARKED,
+ "\\NOINFERIORS":
+ // RFC 5258 \NoInferiors implies \HasNoChildren
+ ImapUtils.FLAG_NO_INFERIORS | ImapUtils.FLAG_HAS_NO_CHILDREN,
+ "\\NOSELECT": ImapUtils.FLAG_NO_SELECT,
+ "\\TRASH": ImapUtils.FLAG_IMAP_TRASH | ImapUtils.FLAG_IMAP_XLIST_TRASH,
+ "\\SENT": ImapUtils.FLAG_IMAP_SENT,
+ "\\DRAFTS": ImapUtils.FLAG_IMAP_DRAFTS,
+ "\\SPAM": ImapUtils.FLAG_IMAP_SPAM,
+ "\\JUNK": ImapUtils.FLAG_IMAP_SPAM,
+ "\\ARCHIVE": ImapUtils.FLAG_IMAP_ARCHIVE,
+ "\\ALL": ImapUtils.FLAG_IMAP_ALL_MAIL,
+ "\\ALLMAIL": ImapUtils.FLAG_IMAP_ALL_MAIL,
+ "\\INBOX": ImapUtils.FLAG_IMAP_INBOX,
+ "\\NONEXISTENT":
+ // RFC 5258 \NonExistent implies \NoSelect
+ ImapUtils.FLAG_NON_EXISTENT | ImapUtils.FLAG_NO_SELECT,
+ "\\SUBSCRIBED": ImapUtils.FLAG_SUBSCRIBED,
+ "\\REMOTE": ImapUtils.FLAG_REMOTE,
+ "\\HASCHILDREN": ImapUtils.FLAG_HAS_CHILDREN,
+ "\\HASNOCHILDREN": ImapUtils.FLAG_HAS_NO_CHILDREN,
+ };
+ let flags = 0;
+ for (let str of arr) {
+ flags |= stringToFlag[str.toUpperCase()] || 0;
+ }
+ return flags;
+ }
+}
+
+/**
+ * A structure to represent STATUS data.
+ * STATUS \"folder 2\" (UIDNEXT 2 MESSAGES 1 UNSEEN 1)
+ */
+class StatusData {
+ /**
+ * @params {Array<string|string[]>} tokens - The tokens of the line.
+ */
+ constructor(tokens) {
+ this.attributes = {};
+
+ // The first two tokens are ["*", "STATUS"], the last token is the attribute
+ // list, the middle part is the mailbox name.
+ this.attributes.mailbox = unwrapString(tokens[2]);
+
+ let attributes = tokens.at(-1);
+ for (let i = 0; i < attributes.length; i += 2) {
+ let type = attributes[i].toLowerCase();
+ this.attributes[type] = attributes[i + 1];
+ }
+ }
+}
+
+/**
+ * Following rfc3501 section-5.1 and section-9, this function does two things:
+ * 1. Remove the wrapping DQUOTE.
+ * 2. Unesacpe QUOTED-CHAR.
+ *
+ * @params {string} name - E.g. `"a \"b\" c"` will become `a "b" c`.
+ */
+function unwrapString(name) {
+ return name.replace(/(^"|"$)/g, "").replaceAll('\\"', '"');
+}
diff --git a/comm/mailnews/imap/src/ImapService.jsm b/comm/mailnews/imap/src/ImapService.jsm
new file mode 100644
index 0000000000..abc4036300
--- /dev/null
+++ b/comm/mailnews/imap/src/ImapService.jsm
@@ -0,0 +1,518 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 = ["ImapService"];
+
+const { XPCOMUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/XPCOMUtils.sys.mjs"
+);
+
+const lazy = {};
+
+XPCOMUtils.defineLazyModuleGetters(lazy, {
+ ImapChannel: "resource:///modules/ImapChannel.jsm",
+ MailStringUtils: "resource:///modules/MailStringUtils.jsm",
+});
+
+/**
+ * Set mailnews.imap.jsmodule to true to use this module.
+ *
+ * @implements {nsIImapService}
+ */
+class ImapService {
+ QueryInterface = ChromeUtils.generateQI(["nsIImapService"]);
+
+ constructor() {
+ // Initialize nsIAutoSyncManager.
+ Cc["@mozilla.org/imap/autosyncmgr;1"].getService(Ci.nsIAutoSyncManager);
+ }
+
+ get cacheStorage() {
+ if (!this._cacheStorage) {
+ this._cacheStorage = Services.cache2.memoryCacheStorage(
+ Services.loadContextInfo.custom(false, {})
+ );
+ }
+ return this._cacheStorage;
+ }
+
+ selectFolder(folder, urlListener, msgWindow) {
+ return this._withClient(folder, (client, runningUrl) => {
+ client.startRunningUrl(
+ urlListener || folder.QueryInterface(Ci.nsIUrlListener),
+ msgWindow,
+ runningUrl
+ );
+ runningUrl.updatingFolder = true;
+ client.onReady = () => {
+ client.selectFolder(folder);
+ };
+ });
+ }
+
+ liteSelectFolder(folder, urlListener, msgWindow) {
+ return this._withClient(folder, (client, runningUrl) => {
+ client.startRunningUrl(
+ urlListener || folder.QueryInterface(Ci.nsIUrlListener),
+ msgWindow,
+ runningUrl
+ );
+ runningUrl.QueryInterface(Ci.nsIImapUrl).imapAction =
+ Ci.nsIImapUrl.nsImapLiteSelectFolder;
+ client.onReady = () => {
+ client.selectFolder(folder);
+ };
+ });
+ }
+
+ discoverAllFolders(folder, urlListener, msgWindow) {
+ let server = folder.QueryInterface(
+ Ci.nsIMsgImapMailFolder
+ ).imapIncomingServer;
+ if (server.wrappedJSObject.hasDiscoveredFolders) {
+ return;
+ }
+ server.wrappedJSObject.hasDiscoveredFolders = true;
+ this._withClient(folder, client => {
+ client.startRunningUrl(urlListener, msgWindow);
+ client.onReady = () => {
+ client.discoverAllFolders(folder);
+ };
+ });
+ }
+
+ discoverAllAndSubscribedFolders(folder, urlListener, msgWindow) {
+ this._withClient(folder, client => {
+ let runningUrl = client.startRunningUrl(urlListener, msgWindow);
+ runningUrl.QueryInterface(Ci.nsIImapUrl).imapAction =
+ Ci.nsIImapUrl.nsImapDiscoverAllAndSubscribedBoxesUrl;
+ client.onReady = () => {
+ client.discoverAllAndSubscribedFolders(folder);
+ };
+ });
+ }
+
+ getListOfFoldersOnServer(server, msgWindow) {
+ this.discoverAllAndSubscribedFolders(
+ server.rootMsgFolder,
+ server.QueryInterface(Ci.nsIUrlListener),
+ msgWindow
+ );
+ }
+
+ subscribeFolder(folder, name, urlListener) {
+ return this._withClient(folder, client => {
+ client.startRunningUrl(urlListener);
+ client.onReady = () => {
+ client.subscribeFolder(folder, name);
+ };
+ });
+ }
+
+ unsubscribeFolder(folder, name, urlListener) {
+ return this._withClient(folder, client => {
+ client.startRunningUrl(urlListener);
+ client.onReady = () => {
+ client.unsubscribeFolder(folder, name);
+ };
+ });
+ }
+
+ addMessageFlags(folder, urlListener, messageIds, flags, messageIdsAreUID) {
+ this._updateMessageFlags("+", folder, urlListener, messageIds, flags);
+ }
+
+ subtractMessageFlags(
+ folder,
+ urlListener,
+ messageIds,
+ flags,
+ messageIdsAreUID
+ ) {
+ this._updateMessageFlags("-", folder, urlListener, messageIds, flags);
+ }
+
+ setMessageFlags(
+ folder,
+ urlListener,
+ outURL,
+ messageIds,
+ flags,
+ messageIdsAreUID
+ ) {
+ outURL.value = this._updateMessageFlags(
+ "",
+ folder,
+ urlListener,
+ messageIds,
+ flags
+ );
+ }
+
+ _updateMessageFlags(action, folder, urlListener, messageIds, flags) {
+ return this._withClient(folder, (client, runningUrl) => {
+ client.startRunningUrl(urlListener, null, runningUrl);
+ client.onReady = () => {
+ client.updateMessageFlags(action, folder, messageIds, flags);
+ };
+ });
+ }
+
+ renameLeaf(folder, newName, urlListener, msgWindow) {
+ this._withClient(folder, client => {
+ client.startRunningUrl(urlListener, msgWindow);
+ client.onReady = () => {
+ client.renameFolder(folder, newName);
+ };
+ });
+ }
+
+ fetchMessage(
+ imapUrl,
+ imapAction,
+ folder,
+ msgSink,
+ msgWindow,
+ displayConsumer,
+ msgIds,
+ convertDataToText
+ ) {
+ imapUrl.imapAction = imapAction;
+ imapUrl.QueryInterface(Ci.nsIMsgMailNewsUrl).msgWindow = msgWindow;
+ if (displayConsumer instanceof Ci.nsIDocShell) {
+ imapUrl
+ .QueryInterface(Ci.nsIMsgMailNewsUrl)
+ .loadURI(
+ displayConsumer.QueryInterface(Ci.nsIDocShell),
+ Ci.nsIWebNavigation.LOAD_FLAGS_NONE
+ );
+ } else {
+ let streamListener = displayConsumer.QueryInterface(Ci.nsIStreamListener);
+ let channel = new lazy.ImapChannel(imapUrl, {
+ QueryInterface: ChromeUtils.generateQI(["nsILoadInfo"]),
+ loadingPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
+ securityFlags:
+ Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ internalContentPolicy: Ci.nsIContentPolicy.TYPE_OTHER,
+ });
+ let listener = streamListener;
+ if (convertDataToText) {
+ let converter = Cc["@mozilla.org/streamConverters;1"].getService(
+ Ci.nsIStreamConverterService
+ );
+ listener = converter.asyncConvertData(
+ "message/rfc822",
+ "*/*",
+ streamListener,
+ channel
+ );
+ }
+ channel.asyncOpen(listener);
+ }
+ }
+
+ fetchCustomMsgAttribute(folder, msgWindow, attribute, uids) {
+ return this._withClient(folder, (client, runningUrl) => {
+ client.startRunningUrl(null, msgWindow, runningUrl);
+ client.onReady = () => {
+ client.fetchMsgAttribute(folder, uids, attribute);
+ };
+ });
+ }
+
+ expunge(folder, urlListener, msgWindow) {
+ this._withClient(folder, client => {
+ client.startRunningUrl(urlListener, msgWindow);
+ client.onReady = () => {
+ client.expunge(folder);
+ };
+ });
+ }
+
+ onlineMessageCopy(
+ folder,
+ messageIds,
+ dstFolder,
+ idsAreUids,
+ isMove,
+ urlListener,
+ outURL,
+ copyState,
+ msgWindow
+ ) {
+ this._withClient(folder, client => {
+ let runningUrl = client.startRunningUrl(urlListener, msgWindow);
+ runningUrl.QueryInterface(Ci.nsIImapUrl).imapAction = isMove
+ ? Ci.nsIImapUrl.nsImapOnlineMove
+ : Ci.nsIImapUrl.nsImapOnlineCopy;
+ client.onReady = () => {
+ client.copy(folder, dstFolder, messageIds, idsAreUids, isMove);
+ };
+ });
+ }
+
+ appendMessageFromFile(
+ file,
+ dstFolder,
+ messageId,
+ idsAreUids,
+ inSelectedState,
+ urlListener,
+ copyState,
+ msgWindow
+ ) {
+ let server = dstFolder.server;
+ let imapUrl = Services.io
+ .newURI(
+ `imap://${server.hostName}:${server.port}/fetch>UID>/${dstFolder.name}>${messageId}`
+ )
+ .QueryInterface(Ci.nsIImapUrl);
+ imapUrl.QueryInterface(Ci.nsIImapUrl).imapAction =
+ Ci.nsIImapUrl.nsImapAppendMsgFromFile;
+ imapUrl.copyState = copyState;
+ if (Services.io.offline) {
+ this._offlineAppendMessageFile(file, imapUrl, dstFolder, urlListener);
+ return;
+ }
+ this._withClient(dstFolder, client => {
+ client.startRunningUrl(urlListener, msgWindow, imapUrl);
+ client.onReady = () => {
+ client.uploadMessageFromFile(
+ file,
+ dstFolder,
+ copyState,
+ inSelectedState
+ );
+ };
+ });
+ }
+
+ /**
+ * Append a message file to a folder locally.
+ *
+ * @param {nsIFile} file - The message file to append.
+ * @param {nsIURI} url - The imap url to run.
+ * @param {nsIMsgFolder} dstFolder - The target message folder.
+ * @param {nsIUrlListener} urlListener - Callback for the request.
+ */
+ async _offlineAppendMessageFile(file, url, dstFolder, urlListener) {
+ if (dstFolder.locked) {
+ const NS_MSG_FOLDER_BUSY = 2153054218;
+ throw Components.Exception(
+ "Destination folder locked",
+ NS_MSG_FOLDER_BUSY
+ );
+ }
+
+ let db = dstFolder.msgDatabase.QueryInterface(Ci.nsIMsgOfflineOpsDatabase);
+ let fakeKey = db.nextFakeOfflineMsgKey;
+ let op = db.getOfflineOpForKey(fakeKey, true);
+ op.operation = Ci.nsIMsgOfflineImapOperation.kAppendDraft;
+ op.destinationFolderURI = dstFolder.URI;
+ // Release op eagerly, to make test_offlineDraftDataloss happy in debug build.
+ op = null;
+ Cu.forceGC();
+
+ let server = dstFolder.server;
+ let newMsgHdr = db.createNewHdr(fakeKey);
+ let outputStream = dstFolder.getOfflineStoreOutputStream(newMsgHdr);
+ let content = lazy.MailStringUtils.uint8ArrayToByteString(
+ await IOUtils.read(file.path)
+ );
+
+ let msgParser = Cc[
+ "@mozilla.org/messenger/messagestateparser;1"
+ ].createInstance(Ci.nsIMsgParseMailMsgState);
+ msgParser.SetMailDB(db);
+ msgParser.state = Ci.nsIMsgParseMailMsgState.ParseHeadersState;
+ msgParser.newMsgHdr = newMsgHdr;
+ msgParser.setNewKey(fakeKey);
+
+ for (let line of content.split("\r\n")) {
+ line += "\r\n";
+ msgParser.ParseAFolderLine(line, line.length);
+ outputStream.write(line, line.length);
+ }
+ msgParser.FinishHeader();
+
+ newMsgHdr.orFlags(Ci.nsMsgMessageFlags.Offline | Ci.nsMsgMessageFlags.Read);
+ newMsgHdr.offlineMessageSize = content.length;
+ db.addNewHdrToDB(newMsgHdr, true);
+ dstFolder.setFlag(Ci.nsMsgFolderFlags.OfflineEvents);
+ if (server.msgStore) {
+ server.msgStore.finishNewMessage(outputStream, newMsgHdr);
+ }
+
+ urlListener.OnStopRunningUrl(url, Cr.NS_OK);
+ outputStream.close();
+ db.close(true);
+ }
+
+ ensureFolderExists(parent, folderName, msgWindow, urlListener) {
+ this._withClient(parent, client => {
+ let runningUrl = client.startRunningUrl(urlListener, msgWindow);
+ runningUrl.QueryInterface(Ci.nsIImapUrl).imapAction =
+ Ci.nsIImapUrl.nsImapEnsureExistsFolder;
+ client.onReady = () => {
+ client.ensureFolderExists(parent, folderName);
+ };
+ });
+ }
+
+ updateFolderStatus(folder, urlListener) {
+ this._withClient(folder, client => {
+ let runningUrl = client.startRunningUrl(urlListener);
+ runningUrl.QueryInterface(Ci.nsIImapUrl).imapAction =
+ Ci.nsIImapUrl.nsImapFolderStatus;
+ client.onReady = () => {
+ client.updateFolderStatus(folder);
+ };
+ });
+ }
+
+ createFolder(parent, folderName, urlListener) {
+ return this._withClient(parent, (client, runningUrl) => {
+ client.startRunningUrl(urlListener, null, runningUrl);
+ runningUrl.QueryInterface(Ci.nsIImapUrl).imapAction =
+ Ci.nsIImapUrl.nsImapCreateFolder;
+ client.onReady = () => {
+ client.createFolder(parent, folderName);
+ };
+ });
+ }
+
+ moveFolder(srcFolder, dstFolder, urlListener, msgWindow) {
+ this._withClient(srcFolder, client => {
+ let runningUrl = client.startRunningUrl(urlListener, msgWindow);
+ runningUrl.QueryInterface(Ci.nsIImapUrl).imapAction =
+ Ci.nsIImapUrl.nsImapMoveFolderHierarchy;
+ client.onReady = () => {
+ client.moveFolder(srcFolder, dstFolder);
+ };
+ });
+ }
+
+ listFolder(folder, urlListener) {
+ this._withClient(folder, client => {
+ let runningUrl = client.startRunningUrl(urlListener);
+ runningUrl.QueryInterface(Ci.nsIImapUrl).imapAction =
+ Ci.nsIImapUrl.nsImapListFolder;
+ client.onReady = () => {
+ client.listFolder(folder);
+ };
+ });
+ }
+
+ deleteFolder(folder, urlListener, msgWindow) {
+ this._withClient(folder, client => {
+ client.startRunningUrl(urlListener, msgWindow);
+ client.onReady = () => {
+ client.deleteFolder(folder);
+ };
+ });
+ }
+
+ storeCustomKeywords(folder, msgWindow, flagsToAdd, flagsToSubtract, uids) {
+ return this._withClient(folder, (client, runningUrl) => {
+ client.startRunningUrl(null, msgWindow, runningUrl);
+ runningUrl.QueryInterface(Ci.nsIImapUrl).imapAction =
+ Ci.nsIImapUrl.nsImapMsgStoreCustomKeywords;
+ client.onReady = () => {
+ client.storeCustomKeywords(folder, flagsToAdd, flagsToSubtract, uids);
+ };
+ });
+ }
+
+ downloadMessagesForOffline(messageIds, folder, urlListener, msgWindow) {
+ let server = folder.QueryInterface(
+ Ci.nsIMsgImapMailFolder
+ ).imapIncomingServer;
+ let imapUrl = Services.io
+ .newURI(
+ `imap://${server.hostName}:${server.port}/fetch>UID>/${folder.name}>${messageIds}`
+ )
+ .QueryInterface(Ci.nsIImapUrl);
+ imapUrl.storeResultsOffline = true;
+ if (urlListener) {
+ imapUrl
+ .QueryInterface(Ci.nsIMsgMailNewsUrl)
+ .RegisterListener(urlListener);
+ }
+ this._withClient(folder, client => {
+ client.startRunningUrl(urlListener, msgWindow, imapUrl);
+ client.onReady = () => {
+ client.fetchMessage(folder, messageIds);
+ };
+ });
+ }
+
+ playbackAllOfflineOperations(msgWindow, urlListener) {
+ let offlineSync = Cc["@mozilla.org/imap/offlinesync;1"].createInstance(
+ Ci.nsIImapOfflineSync
+ );
+ offlineSync.init(msgWindow, urlListener, null, false);
+ offlineSync.processNextOperation();
+ }
+
+ getHeaders(folder, urlListener, outURL, messageIds, messageIdsAreUID) {
+ return this._withClient(folder, (client, runningUrl) => {
+ client.startRunningUrl(urlListener, null, runningUrl);
+ runningUrl.QueryInterface(Ci.nsIImapUrl).imapAction =
+ Ci.nsIImapUrl.nsImapMsgFetch;
+ client.onReady = () => {
+ client.getHeaders(folder, messageIds);
+ };
+ });
+ }
+
+ getBodyStart(folder, urlListener, messageIds, numBytes) {
+ return this._withClient(folder, (client, runningUrl) => {
+ runningUrl.QueryInterface(Ci.nsIImapUrl).imapAction =
+ Ci.nsIImapUrl.nsImapMsgPreview;
+ client.startRunningUrl(urlListener, null, runningUrl);
+ client.onReady = () => {
+ client.fetchMessage(folder, messageIds, numBytes);
+ };
+ });
+ }
+
+ deleteAllMessages(folder, urlListener) {
+ this._withClient(folder, client => {
+ client.startRunningUrl(urlListener);
+ client.onReady = () => {
+ client.deleteAllMessages(folder);
+ };
+ });
+ }
+
+ verifyLogon(folder, urlListener, msgWindow) {
+ return this._withClient(folder, (client, runningUrl) => {
+ client.verifyLogon = true;
+ client.startRunningUrl(urlListener, msgWindow, runningUrl);
+ client.onReady = () => {};
+ });
+ }
+
+ /**
+ * Do some actions with a connection.
+ *
+ * @param {nsIMsgFolder} folder - The associated folder.
+ * @param {Function} handler - A callback function to take a ImapClient
+ * instance, and do some actions.
+ */
+ _withClient(folder, handler) {
+ let server = folder.server.QueryInterface(Ci.nsIMsgIncomingServer);
+ let runningUrl = Services.io
+ .newURI(`imap://${server.hostName}:${server.port}`)
+ .QueryInterface(Ci.nsIMsgMailNewsUrl);
+ server.wrappedJSObject.withClient(folder, client =>
+ handler(client, runningUrl)
+ );
+ return runningUrl;
+ }
+}
+
+ImapService.prototype.classID = Components.ID(
+ "{2ea8fbe6-029b-4bff-ae05-b794cf955afb}"
+);
diff --git a/comm/mailnews/imap/src/ImapUtils.jsm b/comm/mailnews/imap/src/ImapUtils.jsm
new file mode 100644
index 0000000000..468d0c98a5
--- /dev/null
+++ b/comm/mailnews/imap/src/ImapUtils.jsm
@@ -0,0 +1,197 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 = ["ImapCapFlags", "ImapUtils"];
+
+/**
+ * The purpose here is not to convert all capabilities to flag number, but to
+ * interact with nsImapMailFolder through nsIImapIncomingServer.getCapability
+ * interface.
+ *
+ * @see nsImapCore.h
+ */
+var ImapCapFlags = {
+ Undefined: 0x00000000,
+ Defined: 0x00000001,
+ AuthLogin: 0x00000002, // AUTH LOGIN
+ XSender: 0x00000008,
+ IMAP4: 0x00000010, // RFC1734
+ IMAP4rev1: 0x00000020, // RFC2060
+ NoHierarchyRename: 0x00000080, // no hierarchy rename
+ ACL: 0x00000100, // ACL extension
+ Namespace: 0x00000200, // IMAP4 Namespace Extension
+ ID: 0x00000400, // client user agent id extension
+ XServerInfo: 0x00000800, // XSERVERINFO extension for admin urls
+ AuthPlain: 0x00001000, // new form of auth plain base64 login
+ Uidplus: 0x00002000, // RFC 2359 UIDPLUS extension
+ LiteralPlus: 0x00004000, // RFC 2088 LITERAL+ extension
+ AOLImap: 0x00008000, // aol imap extensions
+ Language: 0x00010000, // language extensions
+ CRAM: 0x00020000, // CRAM auth extension
+ Quota: 0x00040000, // RFC 2087 quota extension
+ Idle: 0x00080000, // RFC 2177 idle extension
+ AuthNTLM: 0x00100000, // AUTH NTLM extension
+ AuthMSN: 0x00200000, // AUTH MSN extension
+ StartTLS: 0x00400000, // STARTTLS support
+ AuthNone: 0x00800000, // needs no login
+ AuthGssApi: 0x01000000, // GSSAPI AUTH
+ CondStore: 0x02000000, // RFC 3551 CondStore extension
+ Enable: 0x04000000, // RFC 5161 ENABLE extension
+ XList: 0x08000000, // XLIST extension
+ CompressDeflate: 0x10000000, // RFC 4978 COMPRESS extension
+ AuthExternal: 0x20000000, // RFC 2222 SASL AUTH EXTERNAL
+ Move: 0x40000000, // Proposed MOVE RFC
+ HighestModSeq: 0x80000000, // Subset of RFC 3551
+ ListExtended: 0x100000000, // RFC 5258
+ SpecialUse: 0x200000000, // RFC 6154: Sent, Draft etc. folders
+ Gmail: 0x400000000, // X-GM-EXT-1 capability extension for gmail
+ XOAuth2: 0x800000000, // AUTH XOAUTH2 extension
+ ClientID: 0x1000000000, // ClientID capability
+ UTF8Accept: 0x2000000000, // RFC 6855: UTF8:ACCEPT
+
+ /**
+ * Convert an array of capability string to an internal flag number, for example,
+ * ["QUOTA", "X-GM-EXT-1"] will become 0x400040000.
+ *
+ * @param {string[]} arr - An array of flag string.
+ * @returns {number} An internal flag number.
+ */
+ stringsToFlags(arr) {
+ let flags = 0;
+ for (let str of arr) {
+ switch (str) {
+ case "QUOTA":
+ flags |= this.Quota;
+ break;
+ case "X-GM-EXT-1":
+ flags |= this.Gmail;
+ break;
+ default:
+ break;
+ }
+ }
+ return flags;
+ },
+};
+
+/**
+ * Collection of helper functions for IMAP.
+ */
+var ImapUtils = {
+ NS_MSG_ERROR_IMAP_COMMAND_FAILED: 0x80550021,
+
+ /** @see nsImapCore.h */
+ FLAG_NONE: 0x0000,
+ /** mailbox flags */
+ FLAG_MARKED: 0x01,
+ FLAG_UNMARKED: 0x02,
+ FLAG_NO_INFERIORS: 0x04,
+ FLAG_NO_SELECT: 0x08,
+ FLAG_IMAP_TRASH: 0x10,
+ FLAG_JUST_EXPUNGED: 0x20,
+ FLAG_PERSONAL_MAILBOX: 0x40,
+ FLAG_PUBLIC_MAILBOX: 0x80,
+ FLAG_OTHER_USERS_MAILBOX: 0x100,
+ FLAG_NAMESPACE: 0x200,
+ FLAG_NEWLY_CREATED_FOLDER: 0x400,
+ FLAG_IMAP_DRAFTS: 0x800,
+ FLAG_IMAP_SPAM: 0x1000,
+ FLAG_IMAP_SENT: 0x2000,
+ FLAG_IMAP_INBOX: 0x4000,
+ FLAG_IMAP_ALL_MAIL: 0x8000,
+ FLAG_IMAP_XLIST_TRASH: 0x10000,
+ FLAG_NON_EXISTENT: 0x20000,
+ FLAG_SUBSCRIBED: 0x40000,
+ FLAG_REMOTE: 0x80000,
+ FLAG_HAS_CHILDREN: 0x100000,
+ FLAG_HAS_NO_CHILDREN: 0x200000,
+ FLAG_IMAP_ARCHIVE: 0x400000,
+
+ /** message flags */
+ FLAG_SEEN: 0x0001,
+ FLAG_ANSWERED: 0x0002,
+ FLAG_FLAGGED: 0x0004,
+ FLAG_DELETED: 0x0008,
+ FLAG_DRAFT: 0x0010,
+ FLAG_FORWARDED: 0x0040,
+ FLAG_MDN_SENT: 0x0080,
+ FLAG_CUSTOM_KEYWORD: 0x0100,
+ FLAG_LABEL: 0x0e00,
+ FLAG_SUPPORT_FORWARDED_FLAG: 0x4000,
+ FLAG_SUPPORT_USER_FLAG: 0x8000,
+
+ logger: console.createInstance({
+ prefix: "mailnews.imap",
+ maxLogLevel: "Warn",
+ maxLogLevelPref: "mailnews.imap.loglevel",
+ }),
+
+ /**
+ * Convert internal flag number to flag string, for example,
+ * 0x3 will become "\\Seen \\Answered".
+ *
+ * @param {number} flags - Internal flag number.
+ * @param {number} supportedFlags - Server supported flags.
+ * @returns {string} Flags string that can be sent to the server.
+ */
+ flagsToString(flags, supportedFlags) {
+ let arr = [];
+ let strFlags = [
+ ["\\Seen", this.FLAG_SEEN],
+ ["\\Answered", this.FLAG_ANSWERED],
+ ["\\Flagged", this.FLAG_FLAGGED],
+ ["\\Deleted", this.FLAG_DELETED],
+ ["\\Draft", this.FLAG_DRAFT],
+ ["$Forwarded", this.FLAG_FORWARDED],
+ ["$MDNSent", this.FLAG_MDN_SENT],
+ ];
+ for (let [str, flag] of strFlags) {
+ if (flags & flag && supportedFlags & flag) {
+ arr.push(str);
+ }
+ }
+ return arr.join(" ");
+ },
+
+ /**
+ * Convert a flag string to an internal flag number, for example,
+ * "\\Seen" will become 0x1.
+ *
+ * @param {string} str - A single flag string.
+ * @returns {number} An internal flag number.
+ */
+ stringToFlag(str) {
+ return (
+ {
+ "\\SEEN": this.FLAG_SEEN,
+ "\\ANSWERED": this.FLAG_ANSWERED,
+ "\\FLAGGED": this.FLAG_FLAGGED,
+ "\\DELETED": this.FLAG_DELETED,
+ "\\DRAFT": this.FLAG_DRAFT,
+ "\\*":
+ this.FLAG_LABEL |
+ this.FLAG_MDN_SENT |
+ this.FLAG_FORWARDED |
+ this.FLAG_SUPPORT_USER_FLAG,
+ $MDNSENT: this.FLAG_MDN_SENT,
+ $FORWARDED: this.FLAG_FORWARDED,
+ }[str.toUpperCase()] || this.FLAG_NONE
+ );
+ },
+
+ /**
+ * Convert an array of flag string to an internal flag number, for example,
+ * ["\\Seen", "\\Answered"] will become 0x3.
+ *
+ * @param {string[]} arr - An array of flag string.
+ * @returns {number} An internal flag number.
+ */
+ stringsToFlags(arr) {
+ let flags = 0;
+ for (let str of arr) {
+ flags |= this.stringToFlag(str);
+ }
+ return flags;
+ },
+};
diff --git a/comm/mailnews/imap/src/components.conf b/comm/mailnews/imap/src/components.conf
new file mode 100644
index 0000000000..c3c0f692cf
--- /dev/null
+++ b/comm/mailnews/imap/src/components.conf
@@ -0,0 +1,76 @@
+# -*- 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": "{26b5aa4d-742d-4112-b834-d6c4b1276878}",
+ "contract_ids": ["@mozilla.org/messenger/imap-module-loader;1"],
+ "jsm": "resource:///modules/ImapModuleLoader.jsm",
+ "constructor": "ImapModuleLoader",
+ "categories": {"profile-after-change": "ImapModuleLoader"},
+ },
+ {
+ "cid": "{21a89611-dc0d-11d2-806c-006008128c4e}",
+ "type": "nsImapUrl",
+ "headers": ["/comm/mailnews/imap/src/nsImapUrl.h"],
+ },
+ {
+ "cid": "{8c0c40d1-e173-11d2-806e-006008128c4e}",
+ "type": "nsImapProtocol",
+ "headers": ["/comm/mailnews/imap/src/nsImapProtocol.h"],
+ },
+ {
+ "cid": "{4eca51df-6734-11d3-989a-001083010e9b}",
+ "type": "nsImapMockChannel",
+ "headers": ["/comm/mailnews/imap/src/nsImapProtocol.h"],
+ },
+ {
+ "cid": "{479ce8fc-e725-11d2-a505-0060b0fc04b7}",
+ "type": "nsImapHostSessionList",
+ "init_method": "Init",
+ "headers": ["/comm/mailnews/imap/src/nsImapHostSessionList.h"],
+ },
+ {
+ "cid": "{8d3675e0-ed46-11d2-8077-006008128c4e}",
+ "contract_ids": ["@mozilla.org/messenger/server;1?type=imap"],
+ "type": "nsImapIncomingServer",
+ "init_method": "Init",
+ "headers": ["/comm/mailnews/imap/src/nsImapIncomingServer.h"],
+ },
+ {
+ "cid": "{fa32d000-f6a0-11d2-af8d-001083002da8}",
+ "contract_ids": ["@mozilla.org/mail/folder-factory;1?name=imap"],
+ "type": "nsImapMailFolder",
+ "headers": ["/comm/mailnews/imap/src/nsImapMailFolder.h"],
+ },
+ {
+ "cid": "{c5852b22-ebe2-11d2-95ad-000064657374}",
+ "contract_ids": [
+ "@mozilla.org/messenger/messageservice;1?type=imap-message",
+ "@mozilla.org/messenger/messageservice;1?type=imap",
+ "@mozilla.org/messenger/imapservice;1",
+ "@mozilla.org/network/protocol;1?name=imap",
+ "@mozilla.org/messenger/protocol/info;1?type=imap",
+ "@mozilla.org/uriloader/content-handler;1?type=x-application-imapfolder",
+ ],
+ "type": "nsImapService",
+ "headers": ["/comm/mailnews/imap/src/nsImapService.h"],
+ "name": "Imap",
+ "interfaces": ["nsIImapService"],
+ },
+ {
+ "cid": "{c358c568-47b2-42b2-8146-3c0f8d1fad6e}",
+ "contract_ids": ["@mozilla.org/imap/autosyncmgr;1"],
+ "type": "nsAutoSyncManager",
+ "headers": ["/comm/mailnews/imap/src/nsAutoSyncManager.h"],
+ },
+ {
+ "cid": "{64fa0a31-a494-4f4b-ac4d-b29910d6ccd6}",
+ "contract_ids": ["@mozilla.org/imap/offlinesync;1"],
+ "type": "nsImapOfflineSync",
+ "headers": ["/comm/mailnews/imap/src/nsImapOfflineSync.h"],
+ },
+]
diff --git a/comm/mailnews/imap/src/moz.build b/comm/mailnews/imap/src/moz.build
new file mode 100644
index 0000000000..0d1f2150de
--- /dev/null
+++ b/comm/mailnews/imap/src/moz.build
@@ -0,0 +1,56 @@
+# 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 += [
+ "nsImapCore.h",
+]
+
+SOURCES += [
+ "nsAutoSyncManager.cpp",
+ "nsAutoSyncState.cpp",
+ "nsImapFlagAndUidState.cpp",
+ "nsImapGenericParser.cpp",
+ "nsImapHostSessionList.cpp",
+ "nsImapIncomingServer.cpp",
+ "nsImapMailFolder.cpp",
+ "nsImapNamespace.cpp",
+ "nsImapOfflineSync.cpp",
+ "nsImapProtocol.cpp",
+ "nsImapSearchResults.cpp",
+ "nsImapServerResponseParser.cpp",
+ "nsImapService.cpp",
+ "nsImapStringBundle.cpp",
+ "nsImapUndoTxn.cpp",
+ "nsImapUrl.cpp",
+ "nsImapUtils.cpp",
+ "nsSyncRunnableHelpers.cpp",
+]
+
+FINAL_LIBRARY = "mail"
+
+LOCAL_INCLUDES += [
+ # for nsImapProtocol.cpp
+ "!/ipc/ipdl/_ipdlheaders",
+ "/ipc/chromium/src",
+ "/netwerk/base",
+]
+
+EXTRA_JS_MODULES += [
+ "ImapChannel.jsm",
+ "ImapClient.jsm",
+ "ImapFolderContentHandler.sys.mjs",
+ "ImapIncomingServer.jsm",
+ "ImapMessageService.jsm",
+ "ImapModuleLoader.jsm",
+ "ImapProtocolHandler.jsm",
+ "ImapProtocolInfo.jsm",
+ "ImapResponse.jsm",
+ "ImapService.jsm",
+ "ImapUtils.jsm",
+]
+
+XPCOM_MANIFESTS += [
+ "components.conf",
+]
diff --git a/comm/mailnews/imap/src/nsAutoSyncManager.cpp b/comm/mailnews/imap/src/nsAutoSyncManager.cpp
new file mode 100644
index 0000000000..e0b9611dc0
--- /dev/null
+++ b/comm/mailnews/imap/src/nsAutoSyncManager.cpp
@@ -0,0 +1,1372 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "nsAutoSyncManager.h"
+#include "nsAutoSyncState.h"
+#include "nsIMsgImapMailFolder.h"
+#include "nsIMsgHdr.h"
+#include "nsIObserverService.h"
+#include "nsIMsgAccountManager.h"
+#include "nsIMsgIncomingServer.h"
+#include "nsIImapIncomingServer.h"
+#include "nsIMsgMailSession.h"
+#include "nsMsgFolderFlags.h"
+#include "nsMsgMessageFlags.h"
+#include "nsMsgUtils.h"
+#include "nsIIOService.h"
+#include "nsITimer.h"
+#include "nsComponentManagerUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "mozilla/Services.h"
+#include "mozilla/Logging.h"
+
+using namespace mozilla;
+
+NS_IMPL_ISUPPORTS(nsDefaultAutoSyncMsgStrategy, nsIAutoSyncMsgStrategy)
+
+const char* kAppIdleNotification = "mail:appIdle";
+const char* kStartupDoneNotification = "mail-startup-done";
+LazyLogModule gAutoSyncLog("IMAPAutoSync");
+
+// recommended size of each group of messages per download
+static const uint32_t kDefaultGroupSize = 50U * 1024U /* 50K */;
+
+nsDefaultAutoSyncMsgStrategy::nsDefaultAutoSyncMsgStrategy() {}
+
+nsDefaultAutoSyncMsgStrategy::~nsDefaultAutoSyncMsgStrategy() {}
+
+NS_IMETHODIMP nsDefaultAutoSyncMsgStrategy::Sort(
+ nsIMsgFolder* aFolder, nsIMsgDBHdr* aMsgHdr1, nsIMsgDBHdr* aMsgHdr2,
+ nsAutoSyncStrategyDecisionType* aDecision) {
+ NS_ENSURE_ARG_POINTER(aDecision);
+
+ uint32_t msgSize1 = 0, msgSize2 = 0;
+ PRTime msgDate1 = 0, msgDate2 = 0;
+
+ if (!aMsgHdr1 || !aMsgHdr2) {
+ *aDecision = nsAutoSyncStrategyDecisions::Same;
+ return NS_OK;
+ }
+
+ aMsgHdr1->GetMessageSize(&msgSize1);
+ aMsgHdr1->GetDate(&msgDate1);
+
+ aMsgHdr2->GetMessageSize(&msgSize2);
+ aMsgHdr2->GetDate(&msgDate2);
+
+ // Special case: if message size is larger than a
+ // certain size, then place it to the bottom of the q
+ if (msgSize2 > kFirstPassMessageSize && msgSize1 > kFirstPassMessageSize)
+ *aDecision = msgSize2 > msgSize1 ? nsAutoSyncStrategyDecisions::Lower
+ : nsAutoSyncStrategyDecisions::Higher;
+ else if (msgSize2 > kFirstPassMessageSize)
+ *aDecision = nsAutoSyncStrategyDecisions::Lower;
+ else if (msgSize1 > kFirstPassMessageSize)
+ *aDecision = nsAutoSyncStrategyDecisions::Higher;
+ else {
+ // Most recent and smallest first
+ if (msgDate1 < msgDate2)
+ *aDecision = nsAutoSyncStrategyDecisions::Higher;
+ else if (msgDate1 > msgDate2)
+ *aDecision = nsAutoSyncStrategyDecisions::Lower;
+ else {
+ if (msgSize1 > msgSize2)
+ *aDecision = nsAutoSyncStrategyDecisions::Higher;
+ else if (msgSize1 < msgSize2)
+ *aDecision = nsAutoSyncStrategyDecisions::Lower;
+ else
+ *aDecision = nsAutoSyncStrategyDecisions::Same;
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsDefaultAutoSyncMsgStrategy::IsExcluded(nsIMsgFolder* aFolder,
+ nsIMsgDBHdr* aMsgHdr,
+ bool* aDecision) {
+ NS_ENSURE_ARG_POINTER(aDecision);
+ NS_ENSURE_ARG_POINTER(aMsgHdr);
+ NS_ENSURE_ARG_POINTER(aFolder);
+ nsCOMPtr<nsIMsgIncomingServer> server;
+
+ nsresult rv = aFolder->GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIImapIncomingServer> imapServer(do_QueryInterface(server, &rv));
+ int32_t offlineMsgAgeLimit = -1;
+ imapServer->GetAutoSyncMaxAgeDays(&offlineMsgAgeLimit);
+ NS_ENSURE_SUCCESS(rv, rv);
+ PRTime msgDate;
+ aMsgHdr->GetDate(&msgDate);
+ *aDecision = offlineMsgAgeLimit > 0 &&
+ msgDate < MsgConvertAgeInDaysToCutoffDate(offlineMsgAgeLimit);
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(nsDefaultAutoSyncFolderStrategy, nsIAutoSyncFolderStrategy)
+
+nsDefaultAutoSyncFolderStrategy::nsDefaultAutoSyncFolderStrategy() {}
+
+nsDefaultAutoSyncFolderStrategy::~nsDefaultAutoSyncFolderStrategy() {}
+
+NS_IMETHODIMP nsDefaultAutoSyncFolderStrategy::Sort(
+ nsIMsgFolder* aFolderA, nsIMsgFolder* aFolderB,
+ nsAutoSyncStrategyDecisionType* aDecision) {
+ NS_ENSURE_ARG_POINTER(aDecision);
+
+ if (!aFolderA || !aFolderB) {
+ *aDecision = nsAutoSyncStrategyDecisions::Same;
+ return NS_OK;
+ }
+
+ bool isInbox1, isInbox2, isDrafts1, isDrafts2, isTrash1, isTrash2;
+ aFolderA->GetFlag(nsMsgFolderFlags::Inbox, &isInbox1);
+ aFolderB->GetFlag(nsMsgFolderFlags::Inbox, &isInbox2);
+ //
+ aFolderA->GetFlag(nsMsgFolderFlags::Drafts, &isDrafts1);
+ aFolderB->GetFlag(nsMsgFolderFlags::Drafts, &isDrafts2);
+ //
+ aFolderA->GetFlag(nsMsgFolderFlags::Trash, &isTrash1);
+ aFolderB->GetFlag(nsMsgFolderFlags::Trash, &isTrash2);
+
+ // Follow this order;
+ // INBOX > DRAFTS > SUBFOLDERS > TRASH
+
+ // test whether the folder is opened by the user.
+ // we give high priority to the folders explicitly opened by
+ // the user.
+ nsresult rv;
+ bool folderAOpen = false;
+ bool folderBOpen = false;
+ nsCOMPtr<nsIMsgMailSession> session =
+ do_GetService("@mozilla.org/messenger/services/session;1", &rv);
+ if (NS_SUCCEEDED(rv) && session) {
+ session->IsFolderOpenInWindow(aFolderA, &folderAOpen);
+ session->IsFolderOpenInWindow(aFolderB, &folderBOpen);
+ }
+
+ if (folderAOpen == folderBOpen) {
+ // if both of them or none of them are opened by the user
+ // make your decision based on the folder type
+ if (isInbox2 || (isDrafts2 && !isInbox1) || isTrash1)
+ *aDecision = nsAutoSyncStrategyDecisions::Higher;
+ else if (isInbox1 || (isDrafts1 && !isDrafts2) || isTrash2)
+ *aDecision = nsAutoSyncStrategyDecisions::Lower;
+ else
+ *aDecision = nsAutoSyncStrategyDecisions::Same;
+ } else {
+ // otherwise give higher priority to opened one
+ *aDecision = folderBOpen ? nsAutoSyncStrategyDecisions::Higher
+ : nsAutoSyncStrategyDecisions::Lower;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDefaultAutoSyncFolderStrategy::IsExcluded(nsIMsgFolder* aFolder,
+ bool* aDecision) {
+ NS_ENSURE_ARG_POINTER(aDecision);
+ NS_ENSURE_ARG_POINTER(aFolder);
+ uint32_t folderFlags;
+ aFolder->GetFlags(&folderFlags);
+ // exclude saved search
+ *aDecision = (folderFlags & nsMsgFolderFlags::Virtual);
+ if (!*aDecision) {
+ // Exclude orphans
+ nsCOMPtr<nsIMsgFolder> parent;
+ aFolder->GetParent(getter_AddRefs(parent));
+ if (!parent) *aDecision = true;
+ }
+ return NS_OK;
+}
+
+#define NOTIFY_LISTENERS_STATIC(obj_, propertyfunc_, params_) \
+ PR_BEGIN_MACRO \
+ nsTObserverArray<nsCOMPtr<nsIAutoSyncMgrListener>>::ForwardIterator iter( \
+ obj_->mListeners); \
+ nsCOMPtr<nsIAutoSyncMgrListener> listener; \
+ while (iter.HasMore()) { \
+ listener = iter.GetNext(); \
+ listener->propertyfunc_ params_; \
+ } \
+ PR_END_MACRO
+
+#define NOTIFY_LISTENERS(propertyfunc_, params_) \
+ NOTIFY_LISTENERS_STATIC(this, propertyfunc_, params_)
+
+nsAutoSyncManager::nsAutoSyncManager() {
+ mGroupSize = kDefaultGroupSize;
+
+ mIdleState = notIdle;
+ mStartupDone = false;
+ mDownloadModel = dmChained;
+ mUpdateInProgress = false;
+ mPaused = false;
+
+ nsresult rv;
+ mIdleService = do_GetService("@mozilla.org/widget/useridleservice;1", &rv);
+ if (mIdleService) mIdleService->AddIdleObserver(this, kIdleTimeInSec);
+
+ // Observe xpcom-shutdown event and app-idle changes
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+
+ rv = observerService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
+ observerService->AddObserver(this, kAppIdleNotification, false);
+ observerService->AddObserver(this, NS_IOSERVICE_OFFLINE_STATUS_TOPIC, false);
+ observerService->AddObserver(this, NS_IOSERVICE_GOING_OFFLINE_TOPIC, false);
+ observerService->AddObserver(this, kStartupDoneNotification, false);
+}
+
+nsAutoSyncManager::~nsAutoSyncManager() {}
+
+void nsAutoSyncManager::InitTimer() {
+ if (!mTimer) {
+ nsresult rv = NS_NewTimerWithFuncCallback(
+ getter_AddRefs(mTimer), TimerCallback, (void*)this, kTimerIntervalInMs,
+ nsITimer::TYPE_REPEATING_SLACK, "nsAutoSyncManager::TimerCallback",
+ nullptr);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Could not start nsAutoSyncManager timer");
+ }
+ }
+}
+
+void nsAutoSyncManager::StopTimer() {
+ if (mTimer) {
+ mTimer->Cancel();
+ mTimer = nullptr;
+ }
+}
+
+void nsAutoSyncManager::StartTimerIfNeeded() {
+ if ((mUpdateQ.Count() > 0 || mDiscoveryQ.Count() > 0) && !mTimer) InitTimer();
+}
+
+void nsAutoSyncManager::TimerCallback(nsITimer* aTimer, void* aClosure) {
+ if (!aClosure) return;
+
+ nsAutoSyncManager* autoSyncMgr = static_cast<nsAutoSyncManager*>(aClosure);
+ if (autoSyncMgr->GetIdleState() == notIdle ||
+ (autoSyncMgr->mDiscoveryQ.Count() <= 0 &&
+ autoSyncMgr->mUpdateQ.Count() <= 0)) {
+ // Idle will create a new timer automatically if discovery Q or update Q is
+ // not empty
+ autoSyncMgr->StopTimer();
+ }
+
+ // process a folder in the discovery queue
+ if (autoSyncMgr->mDiscoveryQ.Count() > 0) {
+ nsCOMPtr<nsIAutoSyncState> autoSyncStateObj(autoSyncMgr->mDiscoveryQ[0]);
+ if (autoSyncStateObj) {
+ uint32_t leftToProcess;
+ nsresult rv = autoSyncStateObj->ProcessExistingHeaders(
+ kNumberOfHeadersToProcess, &leftToProcess);
+
+ nsCOMPtr<nsIMsgFolder> folder;
+ autoSyncStateObj->GetOwnerFolder(getter_AddRefs(folder));
+ if (folder)
+ NOTIFY_LISTENERS_STATIC(
+ autoSyncMgr, OnDiscoveryQProcessed,
+ (folder, kNumberOfHeadersToProcess, leftToProcess));
+
+ if (NS_SUCCEEDED(rv) && 0 == leftToProcess) {
+ autoSyncMgr->mDiscoveryQ.RemoveObjectAt(0);
+ if (folder)
+ NOTIFY_LISTENERS_STATIC(
+ autoSyncMgr, OnFolderRemovedFromQ,
+ (nsIAutoSyncMgrListener::DiscoveryQueue, folder));
+ }
+ if (MOZ_LOG_TEST(gAutoSyncLog, LogLevel::Debug)) {
+ nsCString folderName;
+ folder->GetURI(folderName);
+ MOZ_LOG(gAutoSyncLog, LogLevel::Debug,
+ ("%s: processed discovery q for folder=%s, "
+ "msgs left to process in folder=%d",
+ __func__, folderName.get(), leftToProcess));
+ }
+ }
+ }
+
+ if (autoSyncMgr->mUpdateQ.Count() > 0) {
+ if (!autoSyncMgr->mUpdateInProgress) // Avoids possible overlap of updates
+ {
+ nsCOMPtr<nsIAutoSyncState> autoSyncStateObj(autoSyncMgr->mUpdateQ[0]);
+ if (autoSyncStateObj) {
+ int32_t state;
+ nsresult rv = autoSyncStateObj->GetState(&state);
+ if (NS_SUCCEEDED(rv) && (state == nsAutoSyncState::stCompletedIdle ||
+ state == nsAutoSyncState::stUpdateNeeded)) {
+ nsCOMPtr<nsIMsgFolder> folder;
+ autoSyncStateObj->GetOwnerFolder(getter_AddRefs(folder));
+ if (folder) {
+ nsCOMPtr<nsIMsgImapMailFolder> imapFolder =
+ do_QueryInterface(folder, &rv);
+ NS_ENSURE_SUCCESS_VOID(rv);
+ rv = imapFolder->InitiateAutoSync(autoSyncMgr);
+ if (NS_SUCCEEDED(rv)) {
+ autoSyncMgr->mUpdateInProgress = true;
+ NOTIFY_LISTENERS_STATIC(autoSyncMgr, OnAutoSyncInitiated,
+ (folder));
+ }
+ }
+ if (MOZ_LOG_TEST(gAutoSyncLog, LogLevel::Debug)) {
+ nsCString folderName;
+ folder->GetURI(folderName);
+ MOZ_LOG(gAutoSyncLog, LogLevel::Debug,
+ ("%s: process update q for folder=%s", __func__,
+ folderName.get()));
+ }
+ }
+ }
+ }
+ // if initiation is not successful for some reason, or
+ // if there is an on going download for this folder,
+ // remove it from q and continue with the next one
+ if (!autoSyncMgr->mUpdateInProgress) {
+ nsCOMPtr<nsIMsgFolder> folder;
+ autoSyncMgr->mUpdateQ[0]->GetOwnerFolder(getter_AddRefs(folder));
+
+ autoSyncMgr->mUpdateQ.RemoveObjectAt(0);
+
+ if (folder)
+ NOTIFY_LISTENERS_STATIC(autoSyncMgr, OnFolderRemovedFromQ,
+ (nsIAutoSyncMgrListener::UpdateQueue, folder));
+ if (MOZ_LOG_TEST(gAutoSyncLog, LogLevel::Error)) {
+ nsCString folderName;
+ folder->GetURI(folderName);
+ MOZ_LOG(gAutoSyncLog, LogLevel::Error,
+ ("%s: update q init failed for folder=%s", __func__,
+ folderName.get()));
+ }
+ }
+
+ } // endif
+}
+
+/**
+ * Populates aChainedQ with the auto-sync state objects that are not owned by
+ * the same imap server.
+ * Assumes that aChainedQ initially empty.
+ */
+void nsAutoSyncManager::ChainFoldersInQ(
+ const nsCOMArray<nsIAutoSyncState>& aQueue,
+ nsCOMArray<nsIAutoSyncState>& aChainedQ) {
+ if (aQueue.Count() > 0) aChainedQ.AppendObject(aQueue[0]);
+
+ int32_t pqElemCount = aQueue.Count();
+ for (int32_t pqidx = 1; pqidx < pqElemCount; pqidx++) {
+ bool chained = false;
+ int32_t needToBeReplacedWith = -1;
+ int32_t elemCount = aChainedQ.Count();
+ for (int32_t idx = 0; idx < elemCount; idx++) {
+ bool isSibling;
+ nsresult rv = aChainedQ[idx]->IsSibling(aQueue[pqidx], &isSibling);
+
+ if (NS_SUCCEEDED(rv) && isSibling) {
+ // this prevent us to overwrite a lower priority sibling in
+ // download-in-progress state with a higher priority one.
+ // we have to wait until its download is completed before
+ // switching to new one.
+ int32_t state;
+ aQueue[pqidx]->GetState(&state);
+ if (aQueue[pqidx] != aChainedQ[idx] &&
+ state == nsAutoSyncState::stDownloadInProgress)
+ needToBeReplacedWith = idx;
+ else
+ chained = true;
+
+ break;
+ }
+ } // endfor
+
+ if (needToBeReplacedWith > -1)
+ aChainedQ.ReplaceObjectAt(aQueue[pqidx], needToBeReplacedWith);
+ else if (!chained)
+ aChainedQ.AppendObject(aQueue[pqidx]);
+
+ } // endfor
+}
+
+/**
+ * Searches the given queue for another folder owned by the same imap server.
+ */
+nsIAutoSyncState* nsAutoSyncManager::SearchQForSibling(
+ const nsCOMArray<nsIAutoSyncState>& aQueue,
+ nsIAutoSyncState* aAutoSyncStateObj, int32_t aStartIdx, int32_t* aIndex) {
+ if (aIndex) *aIndex = -1;
+
+ if (aAutoSyncStateObj) {
+ bool isSibling;
+ int32_t elemCount = aQueue.Count();
+ for (int32_t idx = aStartIdx; idx < elemCount; idx++) {
+ nsresult rv = aAutoSyncStateObj->IsSibling(aQueue[idx], &isSibling);
+
+ if (NS_SUCCEEDED(rv) && isSibling && aAutoSyncStateObj != aQueue[idx]) {
+ if (aIndex) *aIndex = idx;
+
+ return aQueue[idx];
+ }
+ }
+ }
+ return nullptr;
+}
+
+/**
+ * Searches for the next folder owned by the same imap server in the given
+ * queue, starting from the index of the given folder.
+ */
+nsIAutoSyncState* nsAutoSyncManager::GetNextSibling(
+ const nsCOMArray<nsIAutoSyncState>& aQueue,
+ nsIAutoSyncState* aAutoSyncStateObj, int32_t* aIndex) {
+ if (aIndex) *aIndex = -1;
+
+ if (aAutoSyncStateObj) {
+ bool located = false;
+ bool isSibling;
+ int32_t elemCount = aQueue.Count();
+ for (int32_t idx = 0; idx < elemCount; idx++) {
+ if (!located) {
+ located = (aAutoSyncStateObj == aQueue[idx]);
+ continue;
+ }
+
+ nsresult rv = aAutoSyncStateObj->IsSibling(aQueue[idx], &isSibling);
+ if (NS_SUCCEEDED(rv) && isSibling) {
+ if (aIndex) *aIndex = idx;
+
+ return aQueue[idx];
+ }
+ }
+ }
+ return nullptr;
+}
+
+/**
+ * Checks whether there is another folder in the given q that is owned
+ * by the same imap server or not.
+ *
+ * @param aQueue the queue that will be searched for a sibling
+ * @param aAutoSyncStateObj the auto-sync state object that we are looking
+ * a sibling for
+ * @param aState the state of the sibling. -1 means "any state"
+ * @param aIndex [out] the index of the found sibling, if it is provided by the
+ * caller (not null)
+ * @return true if found, false otherwise
+ */
+bool nsAutoSyncManager::DoesQContainAnySiblingOf(
+ const nsCOMArray<nsIAutoSyncState>& aQueue,
+ nsIAutoSyncState* aAutoSyncStateObj, const int32_t aState,
+ int32_t* aIndex) {
+ if (aState == -1)
+ return (nullptr != SearchQForSibling(aQueue, aAutoSyncStateObj, 0, aIndex));
+
+ int32_t offset = 0;
+ nsIAutoSyncState* autoSyncState;
+ while ((autoSyncState =
+ SearchQForSibling(aQueue, aAutoSyncStateObj, offset, &offset))) {
+ int32_t state;
+ nsresult rv = autoSyncState->GetState(&state);
+ if (NS_SUCCEEDED(rv) && aState == state) break;
+
+ offset++;
+ }
+ if (aIndex) *aIndex = offset;
+
+ return (nullptr != autoSyncState);
+}
+
+/**
+ * Searches the given queue for the highest priority folder owned by the
+ * same imap server.
+ */
+nsIAutoSyncState* nsAutoSyncManager::GetHighestPrioSibling(
+ const nsCOMArray<nsIAutoSyncState>& aQueue,
+ nsIAutoSyncState* aAutoSyncStateObj, int32_t* aIndex) {
+ return SearchQForSibling(aQueue, aAutoSyncStateObj, 0, aIndex);
+}
+
+// to chain update folder actions
+NS_IMETHODIMP nsAutoSyncManager::OnStartRunningUrl(nsIURI* aUrl) {
+ return NS_OK;
+}
+
+/**
+ * This is called when an update folder URL finishes. It is also called by
+ * nsAutoSyncState::OnStopRunningUrl when a folder status URL finishes.
+ */
+NS_IMETHODIMP nsAutoSyncManager::OnStopRunningUrl(nsIURI* aUrl,
+ nsresult aExitCode) {
+ if (MOZ_LOG_TEST(gAutoSyncLog, LogLevel::Debug)) {
+ nsCString uri;
+ if (aUrl) uri = aUrl->GetSpecOrDefault();
+ MOZ_LOG(gAutoSyncLog, LogLevel::Debug,
+ ("nsAutoSyncManager::%s, count=%d, url=%s", __func__,
+ mUpdateQ.Count(), uri.get()));
+ }
+ mUpdateInProgress = false; // Set false to allow next folder to update
+ if (mUpdateQ.Count() > 0) mUpdateQ.RemoveObjectAt(0);
+
+ return aExitCode;
+}
+
+/**
+ * This occurs on system sleep, hibernate or when TB is set offline or shutdown.
+ */
+NS_IMETHODIMP nsAutoSyncManager::Pause() {
+ StopTimer();
+ mPaused = true;
+ MOZ_LOG(gAutoSyncLog, LogLevel::Debug, ("autosync paused"));
+ return NS_OK;
+}
+
+/**
+ * This occurs on wakeup from sleep or hibernate and when TB is returned online.
+ */
+NS_IMETHODIMP nsAutoSyncManager::Resume() {
+ mPaused = false;
+ StartTimerIfNeeded();
+ // If mUpdateInProgress was true on resume it needs to be reset back to false
+ // to avoid inhibiting autosync until a restart. OnStopRunningUrl(), where it
+ // is normally reset, may not occur depending on timing and autosync will
+ // never be initiated in TimerCallback() for any folder.
+ MOZ_LOG(gAutoSyncLog, LogLevel::Debug,
+ ("autosync resumed, mUpdateInProgress=%d(bool)", mUpdateInProgress));
+ mUpdateInProgress = false; // May already be false, that's OK
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAutoSyncManager::Observe(nsISupports*, const char* aTopic,
+ const char16_t* aSomeData) {
+ if (!PL_strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (observerService) {
+ observerService->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
+ observerService->RemoveObserver(this, kAppIdleNotification);
+ observerService->RemoveObserver(this, NS_IOSERVICE_OFFLINE_STATUS_TOPIC);
+ observerService->RemoveObserver(this, NS_IOSERVICE_GOING_OFFLINE_TOPIC);
+ observerService->RemoveObserver(this, kStartupDoneNotification);
+ }
+
+ // cancel and release the timer
+ if (mTimer) {
+ mTimer->Cancel();
+ mTimer = nullptr;
+ }
+ // unsubscribe from idle service
+ if (mIdleService) mIdleService->RemoveIdleObserver(this, kIdleTimeInSec);
+
+ return NS_OK;
+ }
+
+ if (!PL_strcmp(aTopic, kStartupDoneNotification)) {
+ mStartupDone = true;
+ } else if (!PL_strcmp(aTopic, kAppIdleNotification)) {
+ if (nsDependentString(aSomeData).EqualsLiteral("idle")) {
+ IdleState prevIdleState = GetIdleState();
+
+ // we were already idle (either system or app), so
+ // just remember that we're app idle and return.
+ SetIdleState(appIdle);
+ if (prevIdleState != notIdle) return NS_OK;
+
+ MOZ_LOG(gAutoSyncLog, LogLevel::Debug, ("%s: in app idle", __func__));
+ return StartIdleProcessing();
+ }
+
+ // we're back from appIdle - if already notIdle, just return;
+ if (GetIdleState() == notIdle) return NS_OK;
+ MOZ_LOG(gAutoSyncLog, LogLevel::Debug, ("%s: out of app idle", __func__));
+
+ SetIdleState(notIdle);
+ NOTIFY_LISTENERS(OnStateChanged, (false));
+ return NS_OK;
+ } else if (!PL_strcmp(aTopic, NS_IOSERVICE_OFFLINE_STATUS_TOPIC)) {
+ if (nsDependentString(aSomeData).EqualsLiteral(NS_IOSERVICE_ONLINE))
+ Resume();
+ } else if (!PL_strcmp(aTopic, NS_IOSERVICE_GOING_OFFLINE_TOPIC)) {
+ Pause();
+ }
+ // we're back from system idle
+ else if (!PL_strcmp(aTopic, "back")) {
+ // if we're app idle when we get back from system idle, we ignore
+ // it, since we'll keep doing our idle stuff.
+ if (GetIdleState() != appIdle) {
+ SetIdleState(notIdle);
+ NOTIFY_LISTENERS(OnStateChanged, (false));
+ MOZ_LOG(gAutoSyncLog, LogLevel::Debug, ("%s: out of idle", __func__));
+ }
+ return NS_OK;
+ } else // we've gone system idle
+ {
+ // Check if we were already idle. We may have gotten
+ // multiple system idle notificatons. In that case,
+ // just remember that we're systemIdle and return;
+ if (GetIdleState() != notIdle) return NS_OK;
+
+ // we might want to remember if we were app idle, because
+ // coming back from system idle while app idle shouldn't stop
+ // app indexing. But I think it's OK for now just leave ourselves
+ // in appIdle state.
+ if (GetIdleState() != appIdle) SetIdleState(systemIdle);
+ if (WeAreOffline()) return NS_OK;
+ MOZ_LOG(gAutoSyncLog, LogLevel::Debug, ("%s: in sys idle", __func__));
+ return StartIdleProcessing();
+ }
+ return NS_OK;
+}
+
+nsresult nsAutoSyncManager::StartIdleProcessing() {
+ if (mPaused) return NS_OK;
+
+ MOZ_LOG(gAutoSyncLog, LogLevel::Debug, ("enter %s", __func__));
+ StartTimerIfNeeded();
+
+ // Ignore idle events sent during the startup
+ if (!mStartupDone) return NS_OK;
+
+ // notify listeners that auto-sync is running
+ NOTIFY_LISTENERS(OnStateChanged, (true));
+
+ nsCOMArray<nsIAutoSyncState> chainedQ;
+ nsCOMArray<nsIAutoSyncState>* queue = &mPriorityQ;
+ if (mDownloadModel == dmChained) {
+ ChainFoldersInQ(mPriorityQ, chainedQ);
+ queue = &chainedQ;
+ }
+
+ // to store the folders that should be removed from the priority
+ // queue at the end of the iteration.
+ nsCOMArray<nsIAutoSyncState> foldersToBeRemoved;
+
+ // process folders in the priority queue
+ int32_t elemCount = queue->Count();
+ for (int32_t idx = 0; idx < elemCount; idx++) {
+ nsCOMPtr<nsIAutoSyncState> autoSyncStateObj((*queue)[idx]);
+ if (!autoSyncStateObj) continue;
+
+ int32_t state;
+ autoSyncStateObj->GetState(&state);
+
+ // TODO: Test cached-connection availability in parallel mode
+ // and do not exceed (cached-connection count - 1)
+
+ if (state != nsAutoSyncState::stReadyToDownload) continue;
+
+ nsresult rv = DownloadMessagesForOffline(autoSyncStateObj);
+ if (NS_FAILED(rv)) {
+ // special case: this folder does not have any message to download
+ // (see bug 457342), remove it explicitly from the queue when iteration
+ // is over.
+ // Note that in normal execution flow, folders are removed from priority
+ // queue only in OnDownloadCompleted when all messages are downloaded
+ // successfully. This is the only place we change this flow.
+ if (NS_ERROR_NOT_AVAILABLE == rv)
+ foldersToBeRemoved.AppendObject(autoSyncStateObj);
+
+ HandleDownloadErrorFor(autoSyncStateObj, rv);
+ } // endif
+ } // endfor
+
+ // remove folders with no pending messages from the priority queue
+ elemCount = foldersToBeRemoved.Count();
+ for (int32_t idx = 0; idx < elemCount; idx++) {
+ nsCOMPtr<nsIAutoSyncState> autoSyncStateObj(foldersToBeRemoved[idx]);
+ if (!autoSyncStateObj) continue;
+
+ nsCOMPtr<nsIMsgFolder> folder;
+ autoSyncStateObj->GetOwnerFolder(getter_AddRefs(folder));
+ if (folder) NOTIFY_LISTENERS(OnDownloadCompleted, (folder));
+
+ if (MOZ_LOG_TEST(gAutoSyncLog, LogLevel::Debug)) {
+ nsCString folderName;
+ folder->GetURI(folderName);
+ MOZ_LOG(gAutoSyncLog, LogLevel::Debug,
+ ("%s: folder=%s has no pending msgs, "
+ "remove from priority q",
+ __func__, folderName.get()));
+ }
+ autoSyncStateObj->SetState(nsAutoSyncState::stCompletedIdle);
+
+ if (mPriorityQ.RemoveObject(autoSyncStateObj))
+ NOTIFY_LISTENERS(OnFolderRemovedFromQ,
+ (nsIAutoSyncMgrListener::PriorityQueue, folder));
+ }
+
+ return AutoUpdateFolders();
+}
+
+/**
+ * Updates offline imap folders that are not synchronized recently. This is
+ * called whenever we're idle.
+ */
+nsresult nsAutoSyncManager::AutoUpdateFolders() {
+ nsresult rv;
+
+ // iterate through each imap account and update offline folders automatically
+ MOZ_LOG(gAutoSyncLog, LogLevel::Debug, ("enter %s", __func__));
+
+ nsCOMPtr<nsIMsgAccountManager> accountManager =
+ do_GetService("@mozilla.org/messenger/account-manager;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsTArray<RefPtr<nsIMsgAccount>> accounts;
+ rv = accountManager->GetAccounts(accounts);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (auto account : accounts) {
+ if (!account) continue;
+
+ nsCOMPtr<nsIMsgIncomingServer> incomingServer;
+ rv = account->GetIncomingServer(getter_AddRefs(incomingServer));
+ if (!incomingServer) continue;
+
+ nsCString type;
+ rv = incomingServer->GetType(type);
+
+ if (!type.EqualsLiteral("imap")) continue;
+
+ // If we haven't logged onto this server yet during this session or if the
+ // password has been removed from cache (see
+ // nsImapIncomingServer::ForgetSessionPassword) then skip autosync for
+ // this account.
+ bool notLoggedIn;
+ incomingServer->GetServerRequiresPasswordForBiff(&notLoggedIn);
+ if (notLoggedIn) {
+ if (MOZ_LOG_TEST(gAutoSyncLog, LogLevel::Debug)) {
+ nsCString serverName;
+ incomingServer->GetHostName(serverName);
+ MOZ_LOG(gAutoSyncLog, LogLevel::Debug,
+ ("%s: server |%s| don't autosync; not yet logged in", __func__,
+ serverName.get()));
+ }
+ continue;
+ }
+
+ nsCOMPtr<nsIMsgFolder> rootFolder;
+
+ rv = incomingServer->GetRootFolder(getter_AddRefs(rootFolder));
+ if (rootFolder) {
+ if (NS_FAILED(rv)) continue;
+
+ nsTArray<RefPtr<nsIMsgFolder>> allDescendants;
+ rv = rootFolder->GetDescendants(allDescendants);
+
+ // Get the update time in minutes for each folder of this account/server.
+ // It will be the user configured biff time for server even if user has
+ // disabled "Check for new messages every X minutes" for the account.
+ // Update time will default to 10 minutes if an invalid value is set or
+ // if there are errors obtaining it.
+ // Specifically, the value used here is mail.server.serverX.check_time
+ // or the default mail.server.default.check_time.
+ int32_t updateMinutes = -1;
+ rv = incomingServer->GetBiffMinutes(&updateMinutes);
+ if (NS_FAILED(rv) || updateMinutes < 1)
+ updateMinutes = kDefaultUpdateInterval;
+ PRTime span = updateMinutes * (PR_USEC_PER_SEC * 60UL);
+ if (MOZ_LOG_TEST(gAutoSyncLog, LogLevel::Debug)) {
+ nsCString serverName;
+ incomingServer->GetHostName(serverName);
+ MOZ_LOG(gAutoSyncLog, LogLevel::Debug,
+ ("%s: Update time set to |%d| minutes for "
+ "folders in account |%s|",
+ __func__, updateMinutes, serverName.get()));
+ }
+
+ for (auto folder : allDescendants) {
+ uint32_t folderFlags;
+ rv = folder->GetFlags(&folderFlags);
+ // Skip this folder if not offline or is a saved search or is no select.
+ if (NS_FAILED(rv) || !(folderFlags & nsMsgFolderFlags::Offline) ||
+ folderFlags &
+ (nsMsgFolderFlags::Virtual | nsMsgFolderFlags::ImapNoselect))
+ continue;
+
+ nsCOMPtr<nsIMsgImapMailFolder> imapFolder =
+ do_QueryInterface(folder, &rv);
+ if (NS_FAILED(rv)) continue;
+
+ nsCOMPtr<nsIImapIncomingServer> imapServer;
+ rv = imapFolder->GetImapIncomingServer(getter_AddRefs(imapServer));
+ if (imapServer) {
+ bool autoSyncOfflineStores = false;
+ rv = imapServer->GetAutoSyncOfflineStores(&autoSyncOfflineStores);
+
+ // skip if AutoSyncOfflineStores pref is not set for this folder
+ if (NS_FAILED(rv) || !autoSyncOfflineStores) continue;
+ }
+
+ nsCOMPtr<nsIAutoSyncState> autoSyncState;
+ rv = imapFolder->GetAutoSyncStateObj(getter_AddRefs(autoSyncState));
+ NS_ASSERTION(
+ autoSyncState,
+ "*** nsAutoSyncState shouldn't be NULL, check owner folder");
+
+ // shouldn't happen but let's be defensive here
+ if (!autoSyncState) continue;
+
+ int32_t state;
+ rv = autoSyncState->GetState(&state);
+ nsCString folderName;
+ if (MOZ_LOG_TEST(gAutoSyncLog, LogLevel::Debug)) {
+ folder->GetURI(folderName);
+ MOZ_LOG(
+ gAutoSyncLog, LogLevel::Debug,
+ ("%s: folder=%s, state=%d", __func__, folderName.get(), state));
+ }
+ if (state == nsAutoSyncState::stCompletedIdle ||
+ state == nsAutoSyncState::stUpdateNeeded ||
+ state == nsAutoSyncState::stUpdateIssued) {
+ // Ensure that we wait for at least the "span" time set above before
+ // queuing an update of the same folder.
+ PRTime lastUpdateTime;
+ rv = autoSyncState->GetLastUpdateTime(&lastUpdateTime);
+ if (NS_SUCCEEDED(rv) && ((lastUpdateTime + span) < PR_Now())) {
+ int32_t idx = mUpdateQ.IndexOf(autoSyncState);
+ if (state == nsAutoSyncState::stUpdateIssued) {
+ // Handle the case where an update is triggered but nothing is
+ // found to download. This can happen after messages are copied
+ // or moved between offline folders of the same server or if imap
+ // "folderstatus" URL triggers an update but no new messages
+ // are detected.
+ bool downloadQEmpty;
+ autoSyncState->IsDownloadQEmpty(&downloadQEmpty);
+ if (downloadQEmpty) {
+ MOZ_LOG(gAutoSyncLog, LogLevel::Debug,
+ ("%s: nothing to download for folder %s, "
+ "set state to stCompletedIdle, updateQ idx=%d",
+ __func__, folderName.get(), idx));
+ autoSyncState->SetState(nsAutoSyncState::stCompletedIdle);
+
+ // This should already be done by
+ // nsAutoSyncManager::OnStopRunningUrl() but set update state to
+ // completed and remove folder state object from update queue in
+ // case OnStopRunningUrl never occurred.
+ mUpdateInProgress = false;
+ if (idx > -1) {
+ mUpdateQ.RemoveObjectAt(idx);
+ idx = -1; // re-q below
+ }
+ } else {
+ MOZ_LOG(gAutoSyncLog, LogLevel::Debug,
+ ("%s: downloadQ not empty. Why? updateQ idx=%d",
+ __func__, idx));
+ if (idx > -1) {
+ // Download q not empty and folder still on update q, maybe it
+ // just needs more time so leave update q as it is to update
+ // on next "span" interval. (Never seen this happen.)
+ idx = 0;
+ }
+ }
+ }
+ // Now q or re-q the update for this folder unless it's still q'd.
+ if (idx < 0) {
+ mUpdateQ.AppendObject(autoSyncState);
+ MOZ_LOG(gAutoSyncLog, LogLevel::Debug,
+ ("%s: folder=%s added to update q", __func__,
+ folderName.get()));
+ if (folder)
+ NOTIFY_LISTENERS(OnFolderAddedIntoQ,
+ (nsIAutoSyncMgrListener::UpdateQueue, folder));
+ }
+ }
+ }
+
+ // Check if time to add folder to discovery q on kAutoSyncFreq (1 hour)
+ // time base.
+ PRTime lastSyncTime;
+ rv = autoSyncState->GetLastSyncTime(&lastSyncTime);
+ if (NS_SUCCEEDED(rv) && ((lastSyncTime + kAutoSyncFreq) < PR_Now())) {
+ // add this folder into discovery queue to process existing headers
+ // and discover messages not downloaded yet
+ if (mDiscoveryQ.IndexOf(autoSyncState) == -1) {
+ mDiscoveryQ.AppendObject(autoSyncState);
+ MOZ_LOG(gAutoSyncLog, LogLevel::Debug,
+ ("%s: folder=%s added to discovery q", __func__,
+ folderName.get()));
+ if (folder)
+ NOTIFY_LISTENERS(
+ OnFolderAddedIntoQ,
+ (nsIAutoSyncMgrListener::DiscoveryQueue, folder));
+ }
+ }
+ } // endfor
+ } // endif
+ } // endfor
+
+ // lazily create the timer if there is something to process in the queue
+ // when timer is done, it will self destruct
+ StartTimerIfNeeded();
+
+ return rv;
+}
+
+/**
+ * Places the given folder into the priority queue based on active
+ * strategy function.
+ */
+void nsAutoSyncManager::ScheduleFolderForOfflineDownload(
+ nsIAutoSyncState* aAutoSyncStateObj) {
+ if (aAutoSyncStateObj && (mPriorityQ.IndexOf(aAutoSyncStateObj) == -1)) {
+ nsCOMPtr<nsIAutoSyncFolderStrategy> folStrategy;
+ GetFolderStrategy(getter_AddRefs(folStrategy));
+
+ if (mPriorityQ.Count() <= 0) {
+ // make sure that we don't insert a folder excluded by the given strategy
+ nsCOMPtr<nsIMsgFolder> folder;
+ aAutoSyncStateObj->GetOwnerFolder(getter_AddRefs(folder));
+ if (folder) {
+ bool excluded = false;
+ if (folStrategy) folStrategy->IsExcluded(folder, &excluded);
+
+ if (!excluded) {
+ mPriorityQ.AppendObject(
+ aAutoSyncStateObj); // insert into the first spot
+ NOTIFY_LISTENERS(OnFolderAddedIntoQ,
+ (nsIAutoSyncMgrListener::PriorityQueue, folder));
+ }
+ }
+ } else {
+ // find the right spot for the given folder
+ uint32_t qidx = mPriorityQ.Count();
+ while (qidx > 0) {
+ --qidx;
+
+ nsCOMPtr<nsIMsgFolder> folderA, folderB;
+ mPriorityQ[qidx]->GetOwnerFolder(getter_AddRefs(folderA));
+ aAutoSyncStateObj->GetOwnerFolder(getter_AddRefs(folderB));
+
+ bool excluded = false;
+ if (folderB && folStrategy) folStrategy->IsExcluded(folderB, &excluded);
+
+ if (excluded) break;
+
+ nsAutoSyncStrategyDecisionType decision =
+ nsAutoSyncStrategyDecisions::Same;
+ if (folderA && folderB && folStrategy)
+ folStrategy->Sort(folderA, folderB, &decision);
+
+ if (decision == nsAutoSyncStrategyDecisions::Higher && 0 == qidx)
+ mPriorityQ.InsertObjectAt(aAutoSyncStateObj, 0);
+ else if (decision == nsAutoSyncStrategyDecisions::Higher)
+ continue;
+ else if (decision == nsAutoSyncStrategyDecisions::Lower)
+ mPriorityQ.InsertObjectAt(aAutoSyncStateObj, qidx + 1);
+ else // decision == nsAutoSyncStrategyDecisions::Same
+ mPriorityQ.InsertObjectAt(aAutoSyncStateObj, qidx);
+
+ NOTIFY_LISTENERS(OnFolderAddedIntoQ,
+ (nsIAutoSyncMgrListener::PriorityQueue, folderB));
+ break;
+ } // end while
+ }
+ } // endif
+}
+
+/**
+ * Zero aSizeLimit means no limit
+ */
+nsresult nsAutoSyncManager::DownloadMessagesForOffline(
+ nsIAutoSyncState* aAutoSyncStateObj, uint32_t aSizeLimit) {
+ if (!aAutoSyncStateObj) return NS_ERROR_INVALID_ARG;
+
+ int32_t count;
+ nsresult rv = aAutoSyncStateObj->GetPendingMessageCount(&count);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // special case: no more message to download for this folder:
+ // see HandleDownloadErrorFor for recovery policy
+ if (!count) return NS_ERROR_NOT_AVAILABLE;
+
+ nsTArray<RefPtr<nsIMsgDBHdr>> messagesToDownload;
+ uint32_t totalSize = 0;
+ rv = aAutoSyncStateObj->GetNextGroupOfMessages(mGroupSize, &totalSize,
+ messagesToDownload);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // there are pending messages but the cumulative size is zero:
+ // treat as special case.
+ // Note that although it shouldn't happen, we know that sometimes
+ // imap servers manifest messages as zero length. By returning
+ // NS_ERROR_NOT_AVAILABLE we cause this folder to be removed from
+ // the priority queue temporarily (until the next idle or next update)
+ // in an effort to prevent it blocking other folders of the same account
+ // being synced.
+ if (!totalSize) return NS_ERROR_NOT_AVAILABLE;
+
+ // ensure that we don't exceed the given size limit for this particular group
+ if (aSizeLimit && aSizeLimit < totalSize) return NS_ERROR_FAILURE;
+
+ if (!messagesToDownload.IsEmpty()) {
+ rv = aAutoSyncStateObj->DownloadMessagesForOffline(messagesToDownload);
+
+ int32_t totalCount;
+ (void)aAutoSyncStateObj->GetTotalMessageCount(&totalCount);
+
+ nsCOMPtr<nsIMsgFolder> folder;
+ aAutoSyncStateObj->GetOwnerFolder(getter_AddRefs(folder));
+ if (NS_SUCCEEDED(rv) && folder)
+ NOTIFY_LISTENERS(OnDownloadStarted,
+ (folder, messagesToDownload.Length(), totalCount));
+ }
+
+ return rv;
+}
+
+// clang-format off
+/**
+ * Assuming that the download operation on the given folder has been failed at
+ * least once, execute these steps:
+ * - put the auto-sync state into ready-to-download mode
+ * - rollback the message offset so we can try the same group again (unless the
+ * retry count is reached to the given limit)
+ * - if parallel model is active, wait to be resumed by the next idle
+ * - if chained model is active, search the priority queue to find a sibling to
+ * continue with.
+ */
+// clang-format on
+nsresult nsAutoSyncManager::HandleDownloadErrorFor(
+ nsIAutoSyncState* aAutoSyncStateObj, const nsresult error) {
+ if (!aAutoSyncStateObj) return NS_ERROR_INVALID_ARG;
+
+ // ensure that an error occurred
+ if (NS_SUCCEEDED(error)) return NS_OK;
+
+ // NS_ERROR_NOT_AVAILABLE is a special case/error happens when the queued
+ // folder doesn't have any message to download (see bug 457342). In such case
+ // we shouldn't retry the current message group, nor notify listeners. Simply
+ // continuing with the next sibling in the priority queue would suffice.
+
+ if (NS_ERROR_NOT_AVAILABLE != error) {
+ // force the auto-sync state to try downloading the same group at least
+ // kGroupRetryCount times before it moves to the next one
+ aAutoSyncStateObj->TryCurrentGroupAgain(kGroupRetryCount);
+
+ nsCOMPtr<nsIMsgFolder> folder;
+ aAutoSyncStateObj->GetOwnerFolder(getter_AddRefs(folder));
+ if (folder) NOTIFY_LISTENERS(OnDownloadError, (folder));
+ }
+
+ // if parallel model, don't do anything else
+
+ if (mDownloadModel == dmChained) {
+ // switch to the next folder in the chain and continue downloading
+ nsIAutoSyncState* autoSyncStateObj = aAutoSyncStateObj;
+ nsIAutoSyncState* nextAutoSyncStateObj = nullptr;
+ while (
+ (nextAutoSyncStateObj = GetNextSibling(mPriorityQ, autoSyncStateObj))) {
+ autoSyncStateObj = nextAutoSyncStateObj;
+ nsresult rv = DownloadMessagesForOffline(autoSyncStateObj);
+ if (NS_SUCCEEDED(rv)) break;
+ if (rv == NS_ERROR_NOT_AVAILABLE)
+ // next folder in the chain also doesn't have any message to download
+ // switch to next one if any
+ continue;
+ autoSyncStateObj->TryCurrentGroupAgain(kGroupRetryCount);
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAutoSyncManager::GetGroupSize(uint32_t* aGroupSize) {
+ NS_ENSURE_ARG_POINTER(aGroupSize);
+ *aGroupSize = mGroupSize;
+ return NS_OK;
+}
+NS_IMETHODIMP nsAutoSyncManager::SetGroupSize(uint32_t aGroupSize) {
+ mGroupSize = aGroupSize ? aGroupSize : kDefaultGroupSize;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAutoSyncManager::GetMsgStrategy(
+ nsIAutoSyncMsgStrategy** aMsgStrategy) {
+ NS_ENSURE_ARG_POINTER(aMsgStrategy);
+
+ // lazily create if it is not done already
+ if (!mMsgStrategyImpl) {
+ mMsgStrategyImpl = new nsDefaultAutoSyncMsgStrategy;
+ if (!mMsgStrategyImpl) return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ NS_IF_ADDREF(*aMsgStrategy = mMsgStrategyImpl);
+ return NS_OK;
+}
+NS_IMETHODIMP nsAutoSyncManager::SetMsgStrategy(
+ nsIAutoSyncMsgStrategy* aMsgStrategy) {
+ mMsgStrategyImpl = aMsgStrategy;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAutoSyncManager::GetFolderStrategy(
+ nsIAutoSyncFolderStrategy** aFolderStrategy) {
+ NS_ENSURE_ARG_POINTER(aFolderStrategy);
+
+ // lazily create if it is not done already
+ if (!mFolderStrategyImpl) {
+ mFolderStrategyImpl = new nsDefaultAutoSyncFolderStrategy;
+ if (!mFolderStrategyImpl) return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ NS_IF_ADDREF(*aFolderStrategy = mFolderStrategyImpl);
+ return NS_OK;
+}
+NS_IMETHODIMP nsAutoSyncManager::SetFolderStrategy(
+ nsIAutoSyncFolderStrategy* aFolderStrategy) {
+ mFolderStrategyImpl = aFolderStrategy;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAutoSyncManager::DoesMsgFitDownloadCriteria(nsIMsgDBHdr* aMsgHdr,
+ bool* aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ uint32_t msgFlags = 0;
+ aMsgHdr->GetFlags(&msgFlags);
+
+ // check whether this message is marked imap deleted or not
+ *aResult = !(msgFlags & nsMsgMessageFlags::IMAPDeleted);
+ if (!(*aResult)) return NS_OK;
+
+ bool shouldStoreMsgOffline = true;
+ nsCOMPtr<nsIMsgFolder> folder;
+ aMsgHdr->GetFolder(getter_AddRefs(folder));
+ if (folder) {
+ nsMsgKey msgKey;
+ nsresult rv = aMsgHdr->GetMessageKey(&msgKey);
+ // a cheap way to get the size limit for this folder and make
+ // sure that we don't have this message offline already
+ if (NS_SUCCEEDED(rv))
+ folder->ShouldStoreMsgOffline(msgKey, &shouldStoreMsgOffline);
+ }
+
+ *aResult &= shouldStoreMsgOffline;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAutoSyncManager::OnDownloadQChanged(
+ nsIAutoSyncState* aAutoSyncStateObj) {
+ nsCOMPtr<nsIAutoSyncState> autoSyncStateObj(aAutoSyncStateObj);
+ if (!autoSyncStateObj) return NS_ERROR_INVALID_ARG;
+
+ if (mPaused) return NS_OK;
+ // We want to start downloading immediately unless the folder is excluded.
+ bool excluded = false;
+ nsCOMPtr<nsIAutoSyncFolderStrategy> folStrategy;
+ nsCOMPtr<nsIMsgFolder> folder;
+
+ GetFolderStrategy(getter_AddRefs(folStrategy));
+ autoSyncStateObj->GetOwnerFolder(getter_AddRefs(folder));
+
+ if (folder && folStrategy) folStrategy->IsExcluded(folder, &excluded);
+
+ nsresult rv = NS_OK;
+
+ if (!excluded) {
+ // Add this folder into the priority queue.
+ autoSyncStateObj->SetState(nsAutoSyncState::stReadyToDownload);
+ ScheduleFolderForOfflineDownload(autoSyncStateObj);
+
+ // If we operate in parallel mode or if there is no sibling downloading
+ // messages at the moment, we can download the first group of the messages
+ // for this folder
+ if (mDownloadModel == dmParallel ||
+ !DoesQContainAnySiblingOf(mPriorityQ, autoSyncStateObj,
+ nsAutoSyncState::stDownloadInProgress)) {
+ // this will download the first group of messages immediately;
+ // to ensure that we don't end up downloading a large single message in
+ // not-idle time, we enforce a limit. If there is no message fits into
+ // this limit we postpone the download until the next idle.
+ if (GetIdleState() == notIdle)
+ rv = DownloadMessagesForOffline(autoSyncStateObj, kFirstGroupSizeLimit);
+ else
+ rv = DownloadMessagesForOffline(autoSyncStateObj);
+
+ if (NS_FAILED(rv))
+ autoSyncStateObj->TryCurrentGroupAgain(kGroupRetryCount);
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsAutoSyncManager::OnDownloadStarted(nsIAutoSyncState* aAutoSyncStateObj,
+ nsresult aStartCode) {
+ nsCOMPtr<nsIAutoSyncState> autoSyncStateObj(aAutoSyncStateObj);
+ if (!autoSyncStateObj) return NS_ERROR_INVALID_ARG;
+
+ // resume downloads during next idle time
+ if (NS_FAILED(aStartCode))
+ autoSyncStateObj->SetState(nsAutoSyncState::stReadyToDownload);
+
+ return aStartCode;
+}
+
+NS_IMETHODIMP
+nsAutoSyncManager::OnDownloadCompleted(nsIAutoSyncState* aAutoSyncStateObj,
+ nsresult aExitCode) {
+ nsCOMPtr<nsIAutoSyncState> autoSyncStateObj(aAutoSyncStateObj);
+ if (!autoSyncStateObj) return NS_ERROR_INVALID_ARG;
+
+ nsresult rv = aExitCode;
+
+ if (NS_FAILED(aExitCode)) {
+ // retry the same group kGroupRetryCount times
+ // try again if TB still idle, otherwise wait for the next idle time
+ autoSyncStateObj->TryCurrentGroupAgain(kGroupRetryCount);
+ if (GetIdleState() != notIdle) {
+ rv = DownloadMessagesForOffline(autoSyncStateObj);
+ if (NS_FAILED(rv)) rv = HandleDownloadErrorFor(autoSyncStateObj, rv);
+ }
+ return rv;
+ }
+
+ // download is successful, reset the retry counter of the folder
+ autoSyncStateObj->ResetRetryCounter();
+
+ nsCOMPtr<nsIMsgFolder> folder;
+ aAutoSyncStateObj->GetOwnerFolder(getter_AddRefs(folder));
+ if (folder) NOTIFY_LISTENERS(OnDownloadCompleted, (folder));
+
+ int32_t count;
+ rv = autoSyncStateObj->GetPendingMessageCount(&count);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsIAutoSyncState* nextFolderToDownload = nullptr;
+ if (count > 0) {
+ autoSyncStateObj->SetState(nsAutoSyncState::stReadyToDownload);
+
+ // in parallel model, we continue downloading the same folder as long as it
+ // has more pending messages
+ nextFolderToDownload = autoSyncStateObj;
+
+ // in chained model, ensure that we are always downloading the highest
+ // priority folder first
+ if (mDownloadModel == dmChained) {
+ // switch to higher priority folder and continue to download,
+ // if any added recently
+ int32_t myIndex = mPriorityQ.IndexOf(autoSyncStateObj);
+
+ int32_t siblingIndex;
+ nsIAutoSyncState* sibling =
+ GetHighestPrioSibling(mPriorityQ, autoSyncStateObj, &siblingIndex);
+
+ // lesser index = higher priority
+ if (sibling && myIndex > -1 && siblingIndex < myIndex)
+ nextFolderToDownload = sibling;
+ }
+ } else {
+ autoSyncStateObj->SetState(nsAutoSyncState::stCompletedIdle);
+
+ nsCOMPtr<nsIMsgFolder> folder;
+ nsresult rv = autoSyncStateObj->GetOwnerFolder(getter_AddRefs(folder));
+
+ if (NS_SUCCEEDED(rv) && mPriorityQ.RemoveObject(autoSyncStateObj))
+ NOTIFY_LISTENERS(OnFolderRemovedFromQ,
+ (nsIAutoSyncMgrListener::PriorityQueue, folder));
+
+ // find the next folder owned by the same server in the queue and continue
+ // downloading
+ if (mDownloadModel == dmChained)
+ nextFolderToDownload =
+ GetHighestPrioSibling(mPriorityQ, autoSyncStateObj);
+
+ } // endif
+
+ // continue downloading if TB is still in idle state
+ if (nextFolderToDownload && GetIdleState() != notIdle) {
+ rv = DownloadMessagesForOffline(nextFolderToDownload);
+ if (NS_FAILED(rv)) rv = HandleDownloadErrorFor(nextFolderToDownload, rv);
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP nsAutoSyncManager::GetDownloadModel(int32_t* aDownloadModel) {
+ NS_ENSURE_ARG_POINTER(aDownloadModel);
+ *aDownloadModel = mDownloadModel;
+ return NS_OK;
+}
+NS_IMETHODIMP nsAutoSyncManager::SetDownloadModel(int32_t aDownloadModel) {
+ mDownloadModel = aDownloadModel;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAutoSyncManager::AddListener(
+ nsIAutoSyncMgrListener* aListener) {
+ NS_ENSURE_ARG_POINTER(aListener);
+ mListeners.AppendElementUnlessExists(aListener);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAutoSyncManager::RemoveListener(
+ nsIAutoSyncMgrListener* aListener) {
+ NS_ENSURE_ARG_POINTER(aListener);
+ mListeners.RemoveElement(aListener);
+ return NS_OK;
+}
+
+/* readonly attribute unsigned long discoveryQLength; */
+NS_IMETHODIMP nsAutoSyncManager::GetDiscoveryQLength(
+ uint32_t* aDiscoveryQLength) {
+ NS_ENSURE_ARG_POINTER(aDiscoveryQLength);
+ *aDiscoveryQLength = mDiscoveryQ.Count();
+ return NS_OK;
+}
+
+/* readonly attribute unsigned long uploadQLength; */
+NS_IMETHODIMP nsAutoSyncManager::GetUpdateQLength(uint32_t* aUpdateQLength) {
+ NS_ENSURE_ARG_POINTER(aUpdateQLength);
+ *aUpdateQLength = mUpdateQ.Count();
+ return NS_OK;
+}
+
+/* readonly attribute unsigned long downloadQLength; */
+NS_IMETHODIMP nsAutoSyncManager::GetDownloadQLength(
+ uint32_t* aDownloadQLength) {
+ NS_ENSURE_ARG_POINTER(aDownloadQLength);
+ *aDownloadQLength = mPriorityQ.Count();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAutoSyncManager::OnFolderHasPendingMsgs(nsIAutoSyncState* aAutoSyncStateObj) {
+ NS_ENSURE_ARG_POINTER(aAutoSyncStateObj);
+ if (mUpdateQ.IndexOf(aAutoSyncStateObj) == -1) {
+ nsCOMPtr<nsIMsgFolder> folder;
+ aAutoSyncStateObj->GetOwnerFolder(getter_AddRefs(folder));
+ // If this folder isn't the trash, add it to the update q.
+ if (folder) {
+ bool isTrash;
+ folder->GetFlag(nsMsgFolderFlags::Trash, &isTrash);
+ if (!isTrash) {
+ bool isSentOrArchive;
+ folder->IsSpecialFolder(
+ nsMsgFolderFlags::SentMail | nsMsgFolderFlags::Archive, true,
+ &isSentOrArchive);
+ // Sent or archive folders go to the q front, the rest to the end.
+ if (isSentOrArchive)
+ mUpdateQ.InsertObjectAt(aAutoSyncStateObj, 0);
+ else
+ mUpdateQ.AppendObject(aAutoSyncStateObj);
+ aAutoSyncStateObj->SetState(nsAutoSyncState::stUpdateNeeded);
+ NOTIFY_LISTENERS(OnFolderAddedIntoQ,
+ (nsIAutoSyncMgrListener::UpdateQueue, folder));
+ }
+ }
+ }
+ return NS_OK;
+}
+
+void nsAutoSyncManager::SetIdleState(IdleState st) { mIdleState = st; }
+
+nsAutoSyncManager::IdleState nsAutoSyncManager::GetIdleState() const {
+ return mIdleState;
+}
+
+NS_IMPL_ISUPPORTS(nsAutoSyncManager, nsIObserver, nsIUrlListener,
+ nsIAutoSyncManager)
diff --git a/comm/mailnews/imap/src/nsAutoSyncManager.h b/comm/mailnews/imap/src/nsAutoSyncManager.h
new file mode 100644
index 0000000000..fb77b66a77
--- /dev/null
+++ b/comm/mailnews/imap/src/nsAutoSyncManager.h
@@ -0,0 +1,265 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 nsAutoSyncManager_h__
+# define nsAutoSyncManager_h__
+
+# include "nsString.h"
+# include "nsCOMArray.h"
+# include "nsIObserver.h"
+# include "nsIUrlListener.h"
+# include "nsITimer.h"
+# include "nsTObserverArray.h"
+# include "nsIAutoSyncManager.h"
+# include "nsIAutoSyncMsgStrategy.h"
+# include "nsIAutoSyncFolderStrategy.h"
+# include "nsIUserIdleService.h"
+# include "prtime.h"
+
+// clang-format off
+/* Auto-Sync
+ *
+ * Background:
+ * it works only with offline imap folders. "autosync_offline_stores" pref
+ * enables/disables auto-sync mechanism. Note that setting "autosync_offline_stores"
+ * to false, or setting folder to not-offline doesn't stop synchronization
+ * process for already queued folders.
+ *
+ * Auto-Sync policy:
+ * o It kicks in during system idle time, and tries to download as much messages
+ * as possible based on given folder and message prioritization strategies/rules.
+ * Default folder prioritization strategy dictates to sort the folders based on the
+ * following order: INBOX > DRAFTS > SUBFOLDERS > TRASH.
+ * Similarly, default message prioritization strategy dictates to download the most
+ * recent and smallest message first. Also, by sorting the messages by size in the
+ * queue, it tries to maximize the number of messages downloaded.
+ * o It downloads the messages in groups. Default groups size is defined by |kDefaultGroupSize|.
+ * o It downloads the messages larger than the group size one-by-one.
+ * o If new messages arrive when not idle, it downloads the messages that do fit into
+ * |kFirstGroupSizeLimit| size limit immediately, without waiting for idle time, unless there is
+ * a sibling (a folder owned by the same imap server) in stDownloadInProgress state in the q
+ * o If new messages arrive when idle, it downloads all the messages without any restriction.
+ * o If new messages arrive into a folder while auto-sync is downloading other messages of the
+ * same folder, it simply puts the new messages into the folder's download queue, and
+ * re-prioritize the messages. That behavior makes sure that the high priority
+ * (defined by the message strategy) get downloaded first always.
+ * o If new messages arrive into a folder while auto-sync is downloading messages of a lower
+ * priority folder, auto-sync switches the folders in the queue and starts downloading the
+ * messages of the higher priority folder next time it downloads a message group.
+ * o Currently there is no way to stop/pause/cancel a message download. The smallest
+ * granularity is the message group size.
+ * o Auto-Sync manager periodically (kAutoSyncFreq) checks folder for existing messages
+ * w/o bodies. It persists the last time the folder is checked in the local database of the
+ * folder. We call this process 'Discovery'. This process is asynchronous and processes
+ * |kNumberOfHeadersToProcess| number of headers at each cycle. Since it works on local data,
+ * it doesn't consume lots of system resources, it does its job fast.
+ * o Discovery is necessary especially when the user makes a transition from not-offline
+ * to offline mode.
+ * o Update frequency is defined by nsMsgIncomingServer::BiffMinutes.
+ *
+ * Error Handling:
+ * o if the user moves/deletes/filters all messages of a folder already queued, auto-sync
+ * deals with that situation by skipping the folder in question, and continuing with the
+ * next in chain.
+ * o If the message size is zero, auto-sync ignores the message.
+ * o If the download of the message group fails for some reason, auto-sync tries to
+ * download the same group |kGroupRetryCount| times. If it still fails, continues with the
+ * next group of messages.
+ *
+ * Download Model:
+ * Parallel model should be used with the imap servers that do not have any "max number of sessions
+ * per IP" limit, and when the bandwidth is significantly large.
+ *
+ * How it really works:
+ * The AutoSyncManager gets an idle notification. First it processes any
+ * folders in the discovery queue (which means it schedules message download
+ * for any messages it previously determined it should download). Then it sets
+ * a timer, and in the timer callback, it processes the update q, by calling
+ * InitiateAutoSync on the first folder in the update q.
+ *
+ * See additional info near the bottom of this file.
+ */
+// clang-format on
+
+/**
+ * Default strategy implementation to prioritize messages in the download queue.
+ */
+class nsDefaultAutoSyncMsgStrategy final : public nsIAutoSyncMsgStrategy {
+ static const uint32_t kFirstPassMessageSize = 60U * 1024U; // 60K
+
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIAUTOSYNCMSGSTRATEGY
+
+ nsDefaultAutoSyncMsgStrategy();
+
+ private:
+ ~nsDefaultAutoSyncMsgStrategy();
+};
+
+/**
+ * Default strategy implementation to prioritize folders in the download queue.
+ */
+class nsDefaultAutoSyncFolderStrategy final : public nsIAutoSyncFolderStrategy {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIAUTOSYNCFOLDERSTRATEGY
+
+ nsDefaultAutoSyncFolderStrategy();
+
+ private:
+ ~nsDefaultAutoSyncFolderStrategy();
+};
+
+// see the end of the page for auto-sync internals
+
+/**
+ * Manages background message download operations for offline imap folders.
+ */
+class nsAutoSyncManager final : public nsIObserver,
+ public nsIUrlListener,
+ public nsIAutoSyncManager {
+ static const PRTime kAutoSyncFreq = 60UL * (PR_USEC_PER_SEC * 60UL); // 1hr
+ static const uint32_t kDefaultUpdateInterval = 10UL; // 10min
+ static const int32_t kTimerIntervalInMs = 400;
+ static const uint32_t kNumberOfHeadersToProcess = 250U;
+ // enforced size of the first group that will be downloaded before idle time
+ static const uint32_t kFirstGroupSizeLimit = 60U * 1024U /* 60K */;
+ static const int32_t kIdleTimeInSec = 10;
+ static const uint32_t kGroupRetryCount = 3;
+
+ enum IdleState { systemIdle, appIdle, notIdle };
+
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+ NS_DECL_NSIURLLISTENER
+ NS_DECL_NSIAUTOSYNCMANAGER
+
+ nsAutoSyncManager();
+
+ private:
+ ~nsAutoSyncManager();
+
+ void SetIdleState(IdleState st);
+ IdleState GetIdleState() const;
+ nsresult StartIdleProcessing();
+ nsresult AutoUpdateFolders();
+ void ScheduleFolderForOfflineDownload(nsIAutoSyncState* aAutoSyncStateObj);
+ nsresult DownloadMessagesForOffline(nsIAutoSyncState* aAutoSyncStateObj,
+ uint32_t aSizeLimit = 0);
+ nsresult HandleDownloadErrorFor(nsIAutoSyncState* aAutoSyncStateObj,
+ const nsresult error);
+
+ // Helper methods for priority Q operations
+ static void ChainFoldersInQ(const nsCOMArray<nsIAutoSyncState>& aQueue,
+ nsCOMArray<nsIAutoSyncState>& aChainedQ);
+ static nsIAutoSyncState* SearchQForSibling(
+ const nsCOMArray<nsIAutoSyncState>& aQueue,
+ nsIAutoSyncState* aAutoSyncStateObj, int32_t aStartIdx,
+ int32_t* aIndex = nullptr);
+ static bool DoesQContainAnySiblingOf(
+ const nsCOMArray<nsIAutoSyncState>& aQueue,
+ nsIAutoSyncState* aAutoSyncStateObj, const int32_t aState,
+ int32_t* aIndex = nullptr);
+ static nsIAutoSyncState* GetNextSibling(
+ const nsCOMArray<nsIAutoSyncState>& aQueue,
+ nsIAutoSyncState* aAutoSyncStateObj, int32_t* aIndex = nullptr);
+ static nsIAutoSyncState* GetHighestPrioSibling(
+ const nsCOMArray<nsIAutoSyncState>& aQueue,
+ nsIAutoSyncState* aAutoSyncStateObj, int32_t* aIndex = nullptr);
+
+ /// timer to process existing keys and updates
+ void InitTimer();
+ static void TimerCallback(nsITimer* aTimer, void* aClosure);
+ void StopTimer();
+ void StartTimerIfNeeded();
+
+ protected:
+ nsCOMPtr<nsIAutoSyncMsgStrategy> mMsgStrategyImpl;
+ nsCOMPtr<nsIAutoSyncFolderStrategy> mFolderStrategyImpl;
+ // contains the folders that will be downloaded on background
+ nsCOMArray<nsIAutoSyncState> mPriorityQ;
+ // contains the folders that will be examined for existing headers and
+ // adds the headers we don't have offline into the autosyncState
+ // object's download queue.
+ nsCOMArray<nsIAutoSyncState> mDiscoveryQ;
+ // contains the folders that will be checked for new messages with STATUS,
+ // and if there are any, we'll call UpdateFolder on them.
+ nsCOMArray<nsIAutoSyncState> mUpdateQ;
+ // this is set true when autosync is initiated for a single folder. Its
+ // purpose is ensure that update for a folder finishes before the next one
+ // starts.
+ bool mUpdateInProgress;
+
+ // This is set if auto sync has been completely paused.
+ bool mPaused;
+ // This is set if we've finished startup and should start
+ // paying attention to idle notifications.
+ bool mStartupDone;
+
+ private:
+ uint32_t mGroupSize;
+ IdleState mIdleState;
+ int32_t mDownloadModel;
+ nsCOMPtr<nsIUserIdleService> mIdleService;
+ nsCOMPtr<nsITimer> mTimer;
+ nsTObserverArray<nsCOMPtr<nsIAutoSyncMgrListener> > mListeners;
+};
+
+#endif
+
+/*
+How queues inter-relate:
+
+nsAutoSyncState has an internal priority queue to store messages waiting to be
+downloaded. nsAutoSyncMsgStrategy object determines the order in this queue,
+nsAutoSyncManager uses this queue to manage downloads. Two events cause a
+change in this queue:
+
+1) nsImapMailFolder::HeaderFetchCompleted: is triggered when TB notices that
+there are pending messages on the server -- via IDLE command from the server,
+via explicit select from the user, or via automatic Update during idle time. If
+it turns out that there are pending messages on the server, it adds them into
+nsAutoSyncState's download queue.
+
+2) nsAutoSyncState::ProcessExistingHeaders: is triggered for every imap folder
+every hour or so (see kAutoSyncFreq). nsAutoSyncManager uses an internal queue
+called Discovery queue to keep track of this task. The purpose of
+ProcessExistingHeaders() method is to check existing headers of a given folder
+in batches and discover the messages without bodies, in asynchronous fashion.
+This process is sequential, one and only one folder at any given time, very
+similar to indexing. Again, if it turns out that the folder in hand has messages
+w/o bodies, ProcessExistingHeaders adds them into nsAutoSyncState's download
+queue.
+
+Any change in nsAutoSyncState's download queue, notifies nsAutoSyncManager and
+nsAutoSyncManager puts the requesting nsAutoSyncState into its internal
+priority queue (called mPriorityQ) -- if the folder is not already there.
+nsAutoSyncFolderStrategy object determines the order in this queue. This queue
+is processed in two modes: chained and parallel.
+
+i) Chained: One folder per imap server any given time. Folders owned by
+different imap servers are simultaneous.
+
+ii) Parallel: All folders at the same time, using all cached-connections -
+a.k.a 'Folders gone wild' mode.
+
+Note: The "Chained" mode is currently in use: mDownloadModel = dmChained;
+
+The order the folders are added into the mPriorityQ doesn't matter since every
+time a batch completed for an imap server, nsAutoSyncManager adjusts the order.
+So, lets say that updating a sub-folder starts downloading message immediately,
+when an higher priority folder is added into the queue, nsAutoSyncManager
+switches to this higher priority folder instead of processing the next group of
+messages of the lower priority one. Setting group size too high might delay
+this switch at worst.
+
+And finally, Update queue helps nsAutoSyncManager to keep track of folders
+waiting to be updated. With the latest change, we update one and only one
+folder at any given time. Default frequency of updating is 10 min
+(kDefaultUpdateInterval). We add folders into the update queue during idle time,
+if they are not in mPriorityQ already.
+
+*/
diff --git a/comm/mailnews/imap/src/nsAutoSyncState.cpp b/comm/mailnews/imap/src/nsAutoSyncState.cpp
new file mode 100644
index 0000000000..db3216419a
--- /dev/null
+++ b/comm/mailnews/imap/src/nsAutoSyncState.cpp
@@ -0,0 +1,782 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "nsAutoSyncState.h"
+#include "nsImapMailFolder.h"
+#include "nsIImapService.h"
+#include "nsIMsgMailNewsUrl.h"
+#include "nsIMsgMailSession.h"
+#include "nsMsgFolderFlags.h"
+#include "nsIAutoSyncManager.h"
+#include "nsIAutoSyncMsgStrategy.h"
+#include "nsServiceManagerUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "mozilla/Logging.h"
+
+using namespace mozilla;
+
+extern LazyLogModule gAutoSyncLog; // defined in nsAutoSyncManager.cpp
+
+MsgStrategyComparatorAdaptor::MsgStrategyComparatorAdaptor(
+ nsIAutoSyncMsgStrategy* aStrategy, nsIMsgFolder* aFolder,
+ nsIMsgDatabase* aDatabase)
+ : mStrategy(aStrategy), mFolder(aFolder), mDatabase(aDatabase) {}
+
+/** @return True if the elements are equals; false otherwise. */
+bool MsgStrategyComparatorAdaptor::Equals(const nsMsgKey& a,
+ const nsMsgKey& b) const {
+ nsCOMPtr<nsIMsgDBHdr> hdrA;
+ nsCOMPtr<nsIMsgDBHdr> hdrB;
+
+ mDatabase->GetMsgHdrForKey(a, getter_AddRefs(hdrA));
+ mDatabase->GetMsgHdrForKey(b, getter_AddRefs(hdrB));
+
+ if (hdrA && hdrB) {
+ nsresult rv = NS_OK;
+ nsAutoSyncStrategyDecisionType decision = nsAutoSyncStrategyDecisions::Same;
+
+ if (mStrategy) rv = mStrategy->Sort(mFolder, hdrA, hdrB, &decision);
+
+ if (NS_SUCCEEDED(rv))
+ return (decision == nsAutoSyncStrategyDecisions::Same);
+ }
+
+ return false;
+}
+
+/** @return True if (a < b); false otherwise. */
+bool MsgStrategyComparatorAdaptor::LessThan(const nsMsgKey& a,
+ const nsMsgKey& b) const {
+ nsCOMPtr<nsIMsgDBHdr> hdrA;
+ nsCOMPtr<nsIMsgDBHdr> hdrB;
+
+ mDatabase->GetMsgHdrForKey(a, getter_AddRefs(hdrA));
+ mDatabase->GetMsgHdrForKey(b, getter_AddRefs(hdrB));
+
+ if (hdrA && hdrB) {
+ nsresult rv = NS_OK;
+ nsAutoSyncStrategyDecisionType decision = nsAutoSyncStrategyDecisions::Same;
+
+ if (mStrategy) rv = mStrategy->Sort(mFolder, hdrA, hdrB, &decision);
+
+ if (NS_SUCCEEDED(rv))
+ return (decision == nsAutoSyncStrategyDecisions::Lower);
+ }
+
+ return false;
+}
+
+nsAutoSyncState::nsAutoSyncState(nsImapMailFolder* aOwnerFolder,
+ PRTime aLastSyncTime)
+ : mSyncState(stCompletedIdle),
+ mOffset(0U),
+ mLastOffset(0U),
+ mLastServerTotal(0),
+ mLastServerRecent(0),
+ mLastServerUnseen(0),
+ mLastNextUID(0),
+ mLastSyncTime(aLastSyncTime),
+ mLastUpdateTime(0UL),
+ mProcessPointer(0U),
+ mIsDownloadQChanged(false),
+ mRetryCounter(0U) {
+ mOwnerFolder =
+ do_GetWeakReference(static_cast<nsIMsgImapMailFolder*>(aOwnerFolder));
+ mHaveAStatusResponse = false;
+}
+
+nsAutoSyncState::~nsAutoSyncState() {}
+
+// TODO:XXXemre should be implemented when we start
+// doing space management
+nsresult nsAutoSyncState::ManageStorageSpace() { return NS_OK; }
+
+nsresult nsAutoSyncState::PlaceIntoDownloadQ(
+ const nsTArray<nsMsgKey>& aMsgKeyList) {
+ nsresult rv = NS_OK;
+ if (!aMsgKeyList.IsEmpty()) {
+ nsCOMPtr<nsIMsgFolder> folder = do_QueryReferent(mOwnerFolder, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgDatabase> database;
+ rv = folder->GetMsgDatabase(getter_AddRefs(database));
+ if (!database) return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIAutoSyncManager> autoSyncMgr =
+ do_GetService(NS_AUTOSYNCMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAutoSyncMsgStrategy> msgStrategy;
+ autoSyncMgr->GetMsgStrategy(getter_AddRefs(msgStrategy));
+
+ // increase the array size
+ mDownloadQ.SetCapacity(mDownloadQ.Length() + aMsgKeyList.Length());
+
+ // remove excluded messages
+ int32_t elemCount = aMsgKeyList.Length();
+ for (int32_t idx = 0; idx < elemCount; idx++) {
+ nsCOMPtr<nsIMsgDBHdr> hdr;
+ bool containsKey;
+ database->ContainsKey(aMsgKeyList[idx], &containsKey);
+ if (!containsKey) continue;
+ rv = database->GetMsgHdrForKey(aMsgKeyList[idx], getter_AddRefs(hdr));
+ if (!hdr)
+ continue; // can't get message header, continue with the next one
+
+ bool doesFit = true;
+ rv = autoSyncMgr->DoesMsgFitDownloadCriteria(hdr, &doesFit);
+ if (NS_SUCCEEDED(rv) && !mDownloadSet.Contains(aMsgKeyList[idx]) &&
+ doesFit) {
+ bool excluded = false;
+ if (msgStrategy) {
+ rv = msgStrategy->IsExcluded(folder, hdr, &excluded);
+
+ if (NS_SUCCEEDED(rv) && !excluded) {
+ mIsDownloadQChanged = true;
+ mDownloadSet.PutEntry(aMsgKeyList[idx]);
+ mDownloadQ.AppendElement(aMsgKeyList[idx]);
+ }
+ }
+ }
+ } // endfor
+
+ if (mIsDownloadQChanged) {
+ LogOwnerFolderName("Download Q is created for ");
+ LogQWithSize(mDownloadQ, 0);
+ rv = autoSyncMgr->OnDownloadQChanged(this);
+ }
+ }
+ return rv;
+}
+
+nsresult nsAutoSyncState::SortQueueBasedOnStrategy(nsTArray<nsMsgKey>& aQueue) {
+ nsresult rv;
+ nsCOMPtr<nsIMsgFolder> folder = do_QueryReferent(mOwnerFolder, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgDatabase> database;
+ rv = folder->GetMsgDatabase(getter_AddRefs(database));
+ if (!database) return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIAutoSyncManager> autoSyncMgr =
+ do_GetService(NS_AUTOSYNCMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAutoSyncMsgStrategy> msgStrategy;
+ rv = autoSyncMgr->GetMsgStrategy(getter_AddRefs(msgStrategy));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ MsgStrategyComparatorAdaptor strategyComp(msgStrategy, folder, database);
+ aQueue.Sort(strategyComp);
+
+ return rv;
+}
+
+// This method is a hack to prioritize newly inserted messages,
+// without changing the size of the queue. It is required since
+// we cannot sort ranges in nsTArray.
+nsresult nsAutoSyncState::SortSubQueueBasedOnStrategy(
+ nsTArray<nsMsgKey>& aQueue, uint32_t aStartingOffset) {
+ NS_ASSERTION(aStartingOffset < aQueue.Length(),
+ "*** Starting offset is out of range");
+
+ // Copy already downloaded messages into a temporary queue,
+ // we want to exclude them from the sort.
+ nsTArray<nsMsgKey> tmpQ;
+ tmpQ.AppendElements(aQueue.Elements(), aStartingOffset);
+
+ // Remove already downloaded messages and sort the resulting queue
+ aQueue.RemoveElementsAt(0, aStartingOffset);
+
+ nsresult rv = SortQueueBasedOnStrategy(aQueue);
+
+ // copy excluded messages back
+ aQueue.InsertElementsAt(0, tmpQ);
+
+ return rv;
+}
+
+NS_IMETHODIMP nsAutoSyncState::GetNextGroupOfMessages(
+ uint32_t aSuggestedGroupSizeLimit, uint32_t* aActualGroupSize,
+ nsTArray<RefPtr<nsIMsgDBHdr>>& aMessages) {
+ NS_ENSURE_ARG_POINTER(aActualGroupSize);
+
+ aMessages.Clear();
+ *aActualGroupSize = 0;
+
+ nsresult rv;
+ nsCOMPtr<nsIMsgFolder> folder = do_QueryReferent(mOwnerFolder, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgDatabase> database;
+ folder->GetMsgDatabase(getter_AddRefs(database));
+
+ if (database) {
+ if (!mDownloadQ.IsEmpty()) {
+ // sort the download queue if new items are added since the last time
+ if (mIsDownloadQChanged) {
+ // we want to sort only pending messages. mOffset is
+ // the position of the first pending message in the download queue
+ rv = (mOffset > 0) ? SortSubQueueBasedOnStrategy(mDownloadQ, mOffset)
+ : SortQueueBasedOnStrategy(mDownloadQ);
+
+ if (NS_SUCCEEDED(rv)) mIsDownloadQChanged = false;
+ }
+
+ nsCOMPtr<nsIAutoSyncManager> autoSyncMgr =
+ do_GetService(NS_AUTOSYNCMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t msgCount = mDownloadQ.Length();
+ uint32_t idx = mOffset;
+
+ nsCOMPtr<nsIAutoSyncMsgStrategy> msgStrategy;
+ autoSyncMgr->GetMsgStrategy(getter_AddRefs(msgStrategy));
+
+ for (; idx < msgCount; idx++) {
+ bool containsKey = false;
+ database->ContainsKey(mDownloadQ[idx], &containsKey);
+ if (!containsKey) {
+ mDownloadSet.RemoveEntry(mDownloadQ[idx]);
+ mDownloadQ.RemoveElementAt(idx--);
+ msgCount--;
+ continue;
+ }
+ nsCOMPtr<nsIMsgDBHdr> qhdr;
+ database->GetMsgHdrForKey(mDownloadQ[idx], getter_AddRefs(qhdr));
+ if (!qhdr) continue; // maybe deleted, skip it!
+
+ // ensure that we don't have this message body offline already,
+ // possible if the user explicitly selects this message prior
+ // to auto-sync kicks in
+ bool hasMessageOffline;
+ folder->HasMsgOffline(mDownloadQ[idx], &hasMessageOffline);
+ if (hasMessageOffline) continue;
+
+ // this check point allows msg strategy function
+ // to do last minute decisions based on the current
+ // state of TB such as the size of the message store etc.
+ if (msgStrategy) {
+ bool excluded = false;
+ if (NS_SUCCEEDED(msgStrategy->IsExcluded(folder, qhdr, &excluded)) &&
+ excluded)
+ continue;
+ }
+
+ uint32_t msgSize;
+ qhdr->GetMessageSize(&msgSize);
+ // ignore 0 byte messages; the imap parser asserts when we try
+ // to download them, and there's no point anyway.
+ if (!msgSize) continue;
+
+ if (!*aActualGroupSize && msgSize >= aSuggestedGroupSizeLimit) {
+ *aActualGroupSize = msgSize;
+ aMessages.AppendElement(qhdr);
+ idx++;
+ break;
+ }
+ if ((*aActualGroupSize) + msgSize > aSuggestedGroupSizeLimit) break;
+
+ aMessages.AppendElement(qhdr);
+ *aActualGroupSize += msgSize;
+ } // endfor
+
+ mLastOffset = mOffset;
+ mOffset = idx;
+ }
+
+ LogOwnerFolderName("Next group of messages to be downloaded.");
+ LogQWithSize(aMessages, 0);
+ } // endif
+
+ return NS_OK;
+}
+
+/**
+ * Called by nsAutoSyncManager::TimerCallback to process message headers for a
+ * folder in the discovery queue. The queue is created on the kAutoSyncFreq
+ * time base (1 hour). Headers lacking offline store are placed in download
+ * queue.
+ */
+NS_IMETHODIMP nsAutoSyncState::ProcessExistingHeaders(
+ uint32_t aNumOfHdrsToProcess, uint32_t* aLeftToProcess) {
+ NS_ENSURE_ARG_POINTER(aLeftToProcess);
+
+ nsresult rv;
+ nsCOMPtr<nsIMsgFolder> folder = do_QueryReferent(mOwnerFolder, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgDatabase> database;
+ rv = folder->GetMsgDatabase(getter_AddRefs(database));
+ if (!database) return NS_ERROR_FAILURE;
+
+ // create a queue to process existing headers for the first time
+ if (mExistingHeadersQ.IsEmpty()) {
+ nsTArray<nsMsgKey> keys;
+ rv = database->ListAllKeys(keys);
+ NS_ENSURE_SUCCESS(rv, rv);
+ keys.Sort();
+ mExistingHeadersQ.AppendElements(keys);
+ mProcessPointer = 0;
+ }
+
+ // process the existing headers and find the messages not downloaded yet
+ uint32_t lastIdx = mProcessPointer;
+ nsTArray<nsMsgKey> msgKeys;
+ uint32_t keyCount = mExistingHeadersQ.Length();
+ for (; mProcessPointer < (lastIdx + aNumOfHdrsToProcess) &&
+ mProcessPointer < keyCount;
+ mProcessPointer++) {
+ bool hasMessageOffline;
+ folder->HasMsgOffline(mExistingHeadersQ[mProcessPointer],
+ &hasMessageOffline);
+ if (!hasMessageOffline)
+ msgKeys.AppendElement(mExistingHeadersQ[mProcessPointer]);
+ }
+ if (!msgKeys.IsEmpty()) {
+ nsCString folderName;
+ folder->GetURI(folderName);
+ MOZ_LOG(
+ gAutoSyncLog, LogLevel::Debug,
+ ("%s: %zu messages will be added into the download q of folder %s\n",
+ __func__, msgKeys.Length(), folderName.get()));
+
+ rv = PlaceIntoDownloadQ(msgKeys);
+ if (NS_FAILED(rv)) mProcessPointer = lastIdx;
+ }
+
+ *aLeftToProcess = keyCount - mProcessPointer;
+
+ // cleanup if we are done processing
+ if (0 == *aLeftToProcess) {
+ mLastSyncTime = PR_Now();
+ mExistingHeadersQ.Clear();
+ mProcessPointer = 0;
+ folder->SetMsgDatabase(nullptr);
+ }
+
+ return rv;
+}
+
+void nsAutoSyncState::OnNewHeaderFetchCompleted(
+ const nsTArray<nsMsgKey>& aMsgKeyList) {
+ SetLastUpdateTime(PR_Now());
+ if (!aMsgKeyList.IsEmpty()) PlaceIntoDownloadQ(aMsgKeyList);
+ MOZ_LOG(
+ gAutoSyncLog, LogLevel::Debug,
+ ("%s: %zu msg keys put into download q", __func__, aMsgKeyList.Length()));
+}
+
+NS_IMETHODIMP nsAutoSyncState::UpdateFolder() {
+ nsresult rv;
+ nsCOMPtr<nsIAutoSyncManager> autoSyncMgr =
+ do_GetService(NS_AUTOSYNCMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIUrlListener> autoSyncMgrListener =
+ do_QueryInterface(autoSyncMgr, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIMsgImapMailFolder> imapFolder =
+ do_QueryReferent(mOwnerFolder, &rv);
+ SetState(nsAutoSyncState::stUpdateIssued);
+ return imapFolder->UpdateFolderWithListener(nullptr, autoSyncMgrListener);
+}
+
+NS_IMETHODIMP nsAutoSyncState::OnStartRunningUrl(nsIURI* aUrl) {
+ nsresult rv = NS_OK;
+
+ // if there is a problem to start the download, set rv with the
+ // corresponding error code. In that case, AutoSyncManager is going to
+ // set the autosync state to nsAutoSyncState::stReadyToDownload
+ // to resume downloading another time
+
+ // TODO: is there a way to make sure that download started without
+ // problem through nsIURI interface?
+
+ nsCOMPtr<nsIAutoSyncManager> autoSyncMgr =
+ do_GetService(NS_AUTOSYNCMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return autoSyncMgr->OnDownloadStarted(this, rv);
+}
+
+/**
+ * This is called when a folder status URL finishes. It is also called when
+ * needed message downloads (imap fetch) for a folder completes.
+ */
+NS_IMETHODIMP nsAutoSyncState::OnStopRunningUrl(nsIURI* aUrl,
+ nsresult aExitCode) {
+ nsresult rv;
+ nsCOMPtr<nsIMsgFolder> ownerFolder = do_QueryReferent(mOwnerFolder, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIAutoSyncManager> autoSyncMgr =
+ do_GetService(NS_AUTOSYNCMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIUrlListener> autoSyncMgrListener =
+ do_QueryInterface(autoSyncMgr, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (mSyncState == stStatusIssued) {
+ nsCOMPtr<nsIMsgImapMailFolder> imapFolder =
+ do_QueryReferent(mOwnerFolder, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ int32_t serverTotal, serverUnseen, serverRecent, serverNextUID;
+ imapFolder->GetServerTotal(&serverTotal);
+ imapFolder->GetServerUnseen(&serverUnseen);
+ imapFolder->GetServerRecent(&serverRecent);
+ imapFolder->GetServerNextUID(&serverNextUID);
+ // Note: UNSEEN often shows a change when nothing else changes. This is
+ // because UNSEEN produced by SELECT is not the number of unseen messages.
+ // So ignore change to UNSEEN to avoid spurious folder updates. Commented
+ // out below.
+ MOZ_LOG(gAutoSyncLog, LogLevel::Debug,
+ ("%s: serverUnseen=%d lastServerUnseen=%d", __func__, serverUnseen,
+ mLastServerUnseen));
+ if (serverNextUID != mLastNextUID || serverTotal != mLastServerTotal ||
+ serverRecent != mLastServerRecent //||
+ /*(serverUnseen != mLastServerUnseen)*/) {
+ if (MOZ_LOG_TEST(gAutoSyncLog, LogLevel::Debug)) {
+ nsCString folderName;
+ ownerFolder->GetURI(folderName);
+ MOZ_LOG(gAutoSyncLog, LogLevel::Debug,
+ ("%s: folder %s status changed serverNextUID=%d lastNextUID=%d",
+ __func__, folderName.get(), serverNextUID, mLastNextUID));
+ MOZ_LOG(gAutoSyncLog, LogLevel::Debug,
+ ("%s: serverTotal = %d lastServerTotal = %d serverRecent = %d "
+ "lastServerRecent = %d\n",
+ __func__, serverTotal, mLastServerTotal, serverRecent,
+ mLastServerRecent));
+ }
+ SetServerCounts(serverTotal, serverRecent, serverUnseen, serverNextUID);
+ SetState(nsAutoSyncState::stUpdateIssued);
+ rv = imapFolder->UpdateFolderWithListener(nullptr, autoSyncMgrListener);
+ } else // folderstatus detected no change
+ {
+ if (MOZ_LOG_TEST(gAutoSyncLog, LogLevel::Debug)) {
+ nsCString folderName;
+ ownerFolder->GetURI(folderName);
+ MOZ_LOG(gAutoSyncLog, LogLevel::Debug,
+ ("%s: folder %s status or noop issued, no change", __func__,
+ folderName.get()));
+ }
+ // Status detected no change. This may be due to an previously deleted and
+ // now empty database so change compares above could be invalid. If so,
+ // force an update which will re-populate the database (.msf) and download
+ // all the message to mbox/maildir store. This check is only done on the
+ // first imap STATUS response after start-up and if the server response
+ // reports that the folder is not empty.
+ if (!mHaveAStatusResponse && serverTotal != 0) {
+ nsCOMPtr<nsIMsgDatabase> database;
+ ownerFolder->GetMsgDatabase(getter_AddRefs(database));
+ bool hasHeader = false;
+ if (database) {
+ nsCOMPtr<nsIMsgEnumerator> hdrs;
+ database->EnumerateMessages(getter_AddRefs(hdrs));
+ if (hdrs) hdrs->HasMoreElements(&hasHeader);
+ }
+ if (!hasHeader) {
+ if (MOZ_LOG_TEST(gAutoSyncLog, LogLevel::Debug)) {
+ nsCString folderName;
+ ownerFolder->GetURI(folderName);
+ MOZ_LOG(gAutoSyncLog, LogLevel::Debug,
+ ("%s: folder %s has empty DB, force an update", __func__,
+ folderName.get()));
+ }
+ SetServerCounts(serverTotal, serverRecent, serverUnseen,
+ serverNextUID);
+ SetState(nsAutoSyncState::stUpdateIssued);
+ rv = imapFolder->UpdateFolderWithListener(nullptr,
+ autoSyncMgrListener);
+ }
+ }
+ if (mSyncState == stStatusIssued) {
+ // Didn't force an update above so transition back to stCompletedIdle
+ ownerFolder->SetMsgDatabase(nullptr);
+ // nothing more to do.
+ SetState(nsAutoSyncState::stCompletedIdle);
+ // autoSyncMgr needs this notification, so manufacture it.
+ rv = autoSyncMgrListener->OnStopRunningUrl(aUrl, NS_OK);
+ }
+ } // end no change detected
+ mHaveAStatusResponse = true;
+ } else // URL not folderstatus but FETCH of message body
+ {
+ // XXXemre how we recover from this error?
+ rv = ownerFolder->ReleaseSemaphore(ownerFolder);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "*** Cannot release folder semaphore");
+
+ nsCOMPtr<nsIMsgMailNewsUrl> mailUrl = do_QueryInterface(aUrl);
+ if (mailUrl) rv = mailUrl->UnRegisterListener(this);
+
+ if (MOZ_LOG_TEST(gAutoSyncLog, LogLevel::Debug)) {
+ nsCString folderName;
+ ownerFolder->GetURI(folderName);
+ MOZ_LOG(gAutoSyncLog, LogLevel::Debug,
+ ("%s: URL for FETCH of msg body/bodies complete, folder %s",
+ __func__, folderName.get()));
+ }
+ rv = autoSyncMgr->OnDownloadCompleted(this, aExitCode);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsAutoSyncState::GetState(int32_t* aState) {
+ NS_ENSURE_ARG_POINTER(aState);
+ *aState = mSyncState;
+ return NS_OK;
+}
+
+// clang-format off
+const char* stateStrings[] = {
+ "stCompletedIdle:0", // Initial state
+ "stStatusIssued:1", // Imap STATUS or NOOP to occur to detect new msgs
+ "stUpdateNeeded:2", // Imap SELECT to occur due to "pending" msgs
+ "stUpdateIssued:3", // Imap SELECT to occur then fetch new headers
+ "stReadyToDownload:4", // Ready to download a group of new messages
+ "stDownloadInProgress:5" // Download, go to 4 if more msgs then 0 when all done
+};
+// clang-format on
+
+NS_IMETHODIMP nsAutoSyncState::SetState(int32_t aState) {
+ mSyncState = aState;
+ if (aState == stCompletedIdle) {
+ ResetDownloadQ();
+ // tell folder to let go of its cached msg db pointer
+ nsresult rv;
+ nsCOMPtr<nsIMsgMailSession> session =
+ do_GetService("@mozilla.org/messenger/services/session;1", &rv);
+ if (NS_SUCCEEDED(rv) && session) {
+ nsCOMPtr<nsIMsgFolder> ownerFolder = do_QueryReferent(mOwnerFolder, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool folderOpen;
+ uint32_t folderFlags;
+ ownerFolder->GetFlags(&folderFlags);
+ session->IsFolderOpenInWindow(ownerFolder, &folderOpen);
+ if (!folderOpen && !(folderFlags & nsMsgFolderFlags::Inbox))
+ ownerFolder->SetMsgDatabase(nullptr);
+ }
+ }
+ nsCString logStr("Sync State set to |");
+ logStr.Append(stateStrings[aState]);
+ logStr.AppendLiteral("| for ");
+ LogOwnerFolderName(logStr.get());
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAutoSyncState::TryCurrentGroupAgain(uint32_t aRetryCount) {
+ SetState(stReadyToDownload);
+
+ nsresult rv;
+ if (++mRetryCounter > aRetryCount) {
+ ResetRetryCounter();
+ rv = NS_ERROR_FAILURE;
+ } else
+ rv = Rollback();
+
+ return rv;
+}
+
+NS_IMETHODIMP nsAutoSyncState::ResetRetryCounter() {
+ mRetryCounter = 0;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAutoSyncState::GetPendingMessageCount(int32_t* aMsgCount) {
+ NS_ENSURE_ARG_POINTER(aMsgCount);
+ *aMsgCount = mDownloadQ.Length() - mOffset;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAutoSyncState::GetTotalMessageCount(int32_t* aMsgCount) {
+ NS_ENSURE_ARG_POINTER(aMsgCount);
+ *aMsgCount = mDownloadQ.Length();
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAutoSyncState::GetOwnerFolder(nsIMsgFolder** aFolder) {
+ NS_ENSURE_ARG_POINTER(aFolder);
+
+ nsresult rv;
+ nsCOMPtr<nsIMsgFolder> ownerFolder = do_QueryReferent(mOwnerFolder, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ ownerFolder.forget(aFolder);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAutoSyncState::Rollback() {
+ mOffset = mLastOffset;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAutoSyncState::ResetDownloadQ() {
+ mOffset = mLastOffset = 0;
+ mDownloadSet.Clear();
+ mDownloadQ.Clear();
+ mDownloadQ.Compact();
+
+ return NS_OK;
+}
+
+/**
+ * Tests whether the given folder is owned by the same imap server
+ * or not.
+ */
+NS_IMETHODIMP nsAutoSyncState::IsSibling(nsIAutoSyncState* aAnotherStateObj,
+ bool* aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = false;
+
+ nsresult rv;
+ nsCOMPtr<nsIMsgFolder> folderA, folderB;
+
+ rv = GetOwnerFolder(getter_AddRefs(folderA));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aAnotherStateObj->GetOwnerFolder(getter_AddRefs(folderB));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgIncomingServer> serverA, serverB;
+ rv = folderA->GetServer(getter_AddRefs(serverA));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = folderB->GetServer(getter_AddRefs(serverB));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool isSibling;
+ rv = serverA->Equals(serverB, &isSibling);
+
+ if (NS_SUCCEEDED(rv)) *aResult = isSibling;
+
+ return rv;
+}
+
+/**
+ * Test whether the download queue is empty.
+ */
+NS_IMETHODIMP nsAutoSyncState::IsDownloadQEmpty(bool* aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = mDownloadQ.IsEmpty();
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAutoSyncState::DownloadMessagesForOffline(
+ nsTArray<RefPtr<nsIMsgDBHdr>> const& messages) {
+ nsresult rv;
+ nsCOMPtr<nsIImapService> imapService =
+ do_GetService("@mozilla.org/messenger/imapservice;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString messageIds;
+ nsTArray<nsMsgKey> msgKeys;
+
+ rv = nsImapMailFolder::BuildIdsAndKeyArray(messages, messageIds, msgKeys);
+ if (NS_FAILED(rv) || messageIds.IsEmpty()) return rv;
+
+ // acquire semaphore for offline store. If it fails, we won't download
+ nsCOMPtr<nsIMsgFolder> folder = do_QueryReferent(mOwnerFolder, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = folder->AcquireSemaphore(folder);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (MOZ_LOG_TEST(gAutoSyncLog, LogLevel::Debug)) {
+ nsCString folderName;
+ folder->GetURI(folderName);
+ MOZ_LOG(gAutoSyncLog, LogLevel::Debug,
+ ("%s: downloading UIDs %s for folder %s", __func__,
+ messageIds.get(), folderName.get()));
+ }
+ // start downloading
+ rv = imapService->DownloadMessagesForOffline(messageIds, folder, this,
+ nullptr);
+ if (NS_SUCCEEDED(rv)) SetState(stDownloadInProgress);
+
+ return rv;
+}
+
+NS_IMETHODIMP nsAutoSyncState::GetLastSyncTime(PRTime* aLastSyncTime) {
+ NS_ENSURE_ARG_POINTER(aLastSyncTime);
+ *aLastSyncTime = mLastSyncTime;
+ return NS_OK;
+}
+
+void nsAutoSyncState::SetLastSyncTimeInSec(int32_t aLastSyncTime) {
+ mLastSyncTime = ((PRTime)aLastSyncTime * PR_USEC_PER_SEC);
+}
+
+NS_IMETHODIMP nsAutoSyncState::GetLastUpdateTime(PRTime* aLastUpdateTime) {
+ NS_ENSURE_ARG_POINTER(aLastUpdateTime);
+ *aLastUpdateTime = mLastUpdateTime;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAutoSyncState::SetLastUpdateTime(PRTime aLastUpdateTime) {
+ mLastUpdateTime = aLastUpdateTime;
+ return NS_OK;
+}
+
+void nsAutoSyncState::SetServerCounts(int32_t total, int32_t recent,
+ int32_t unseen, int32_t nextUID) {
+ mLastServerTotal = total;
+ mLastServerRecent = recent;
+ mLastServerUnseen = unseen;
+ mLastNextUID = nextUID;
+}
+
+NS_IMPL_ISUPPORTS(nsAutoSyncState, nsIAutoSyncState, nsIUrlListener)
+
+void nsAutoSyncState::LogQWithSize(nsTArray<nsMsgKey>& q, uint32_t toOffset) {
+ nsCOMPtr<nsIMsgFolder> ownerFolder = do_QueryReferent(mOwnerFolder);
+ if (ownerFolder) {
+ nsCOMPtr<nsIMsgDatabase> database;
+ ownerFolder->GetMsgDatabase(getter_AddRefs(database));
+
+ uint32_t x = q.Length();
+ while (x > toOffset && database) {
+ x--;
+ nsCOMPtr<nsIMsgDBHdr> h;
+ database->GetMsgHdrForKey(q[x], getter_AddRefs(h));
+ uint32_t s;
+ if (h) {
+ h->GetMessageSize(&s);
+ MOZ_LOG(gAutoSyncLog, LogLevel::Debug,
+ ("Elem #%d, size: %u bytes\n", x + 1, s));
+ } else
+ MOZ_LOG(gAutoSyncLog, LogLevel::Debug,
+ ("unable to get header for key %ul", q[x]));
+ }
+ }
+}
+
+void nsAutoSyncState::LogQWithSize(nsTArray<RefPtr<nsIMsgDBHdr>> const& q,
+ uint32_t toOffset) {
+ nsCOMPtr<nsIMsgFolder> ownerFolder = do_QueryReferent(mOwnerFolder);
+ if (ownerFolder) {
+ nsCOMPtr<nsIMsgDatabase> database;
+ ownerFolder->GetMsgDatabase(getter_AddRefs(database));
+
+ uint32_t x = q.Length();
+ while (x > toOffset && database) {
+ x--;
+ if (q[x]) {
+ uint32_t s;
+ q[x]->GetMessageSize(&s);
+ MOZ_LOG(gAutoSyncLog, LogLevel::Debug,
+ ("Elem #%d, size: %u bytes\n", x + 1, s));
+ } else
+ MOZ_LOG(gAutoSyncLog, LogLevel::Debug,
+ ("null header in q at index %ul", x));
+ }
+ }
+}
+
+void nsAutoSyncState::LogOwnerFolderName(const char* s) {
+ nsCOMPtr<nsIMsgFolder> ownerFolder = do_QueryReferent(mOwnerFolder);
+ if (ownerFolder) {
+ nsCString folderName;
+ ownerFolder->GetURI(folderName);
+ MOZ_LOG(gAutoSyncLog, LogLevel::Debug,
+ ("*** %s Folder: %s ***\n", s, folderName.get()));
+ }
+}
diff --git a/comm/mailnews/imap/src/nsAutoSyncState.h b/comm/mailnews/imap/src/nsAutoSyncState.h
new file mode 100644
index 0000000000..d04ad016c2
--- /dev/null
+++ b/comm/mailnews/imap/src/nsAutoSyncState.h
@@ -0,0 +1,106 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 nsAutoSyncState_h__
+#define nsAutoSyncState_h__
+
+#include "MailNewsTypes.h"
+#include "nsIAutoSyncState.h"
+#include "nsIUrlListener.h"
+#include "nsIWeakReferenceUtils.h"
+#include "nsTHashtable.h"
+#include "nsHashKeys.h"
+#include "nsTArray.h"
+#include "prlog.h"
+
+class nsImapMailFolder;
+class nsIAutoSyncMsgStrategy;
+class nsIMsgDatabase;
+
+/**
+ * An adaptor class to make msg strategy nsTArray.Sort()
+ * compatible.
+ */
+class MsgStrategyComparatorAdaptor {
+ public:
+ MsgStrategyComparatorAdaptor(nsIAutoSyncMsgStrategy* aStrategy,
+ nsIMsgFolder* aFolder,
+ nsIMsgDatabase* aDatabase);
+
+ /** @return True if the elements are equals; false otherwise. */
+ bool Equals(const nsMsgKey& a, const nsMsgKey& b) const;
+
+ /** @return True if (a < b); false otherwise. */
+ bool LessThan(const nsMsgKey& a, const nsMsgKey& b) const;
+
+ private:
+ MsgStrategyComparatorAdaptor();
+
+ private:
+ nsIAutoSyncMsgStrategy* mStrategy;
+ nsIMsgFolder* mFolder;
+ nsIMsgDatabase* mDatabase;
+};
+
+/**
+ * Facilitates auto-sync capabilities for imap folders.
+ */
+class nsAutoSyncState final : public nsIAutoSyncState, public nsIUrlListener {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIAUTOSYNCSTATE
+ NS_DECL_NSIURLLISTENER
+
+ explicit nsAutoSyncState(nsImapMailFolder* aOwnerFolder,
+ PRTime aLastSyncTime = 0UL);
+
+ /// Called by owner folder when new headers are fetched from the server
+ void OnNewHeaderFetchCompleted(const nsTArray<nsMsgKey>& aMsgKeyList);
+
+ /// Sets the last sync time in lower precision (seconds)
+ void SetLastSyncTimeInSec(int32_t aLastSyncTime);
+
+ /// Manages storage space for auto-sync operations
+ nsresult ManageStorageSpace();
+
+ void SetServerCounts(int32_t total, int32_t recent, int32_t unseen,
+ int32_t nextUID);
+
+ private:
+ ~nsAutoSyncState();
+
+ nsresult PlaceIntoDownloadQ(const nsTArray<nsMsgKey>& aMsgKeyList);
+ nsresult SortQueueBasedOnStrategy(nsTArray<nsMsgKey>& aQueue);
+ nsresult SortSubQueueBasedOnStrategy(nsTArray<nsMsgKey>& aQueue,
+ uint32_t aStartingOffset);
+
+ void LogOwnerFolderName(const char* s);
+ void LogQWithSize(nsTArray<nsMsgKey>& q, uint32_t toOffset = 0);
+ void LogQWithSize(nsTArray<RefPtr<nsIMsgDBHdr>> const& q,
+ uint32_t toOffset = 0);
+
+ private:
+ int32_t mSyncState;
+ nsWeakPtr mOwnerFolder;
+ uint32_t mOffset;
+ uint32_t mLastOffset;
+
+ // used to tell if the Server counts have changed.
+ int32_t mLastServerTotal;
+ int32_t mLastServerRecent;
+ int32_t mLastServerUnseen;
+ int32_t mLastNextUID;
+
+ PRTime mLastSyncTime;
+ PRTime mLastUpdateTime;
+ uint32_t mProcessPointer;
+ bool mIsDownloadQChanged;
+ uint32_t mRetryCounter;
+ nsTHashtable<nsUint32HashKey> mDownloadSet;
+ nsTArray<nsMsgKey> mDownloadQ;
+ nsTArray<nsMsgKey> mExistingHeadersQ;
+ bool mHaveAStatusResponse;
+};
+
+#endif
diff --git a/comm/mailnews/imap/src/nsImapBodyShell.cpp b/comm/mailnews/imap/src/nsImapBodyShell.cpp
new file mode 100644
index 0000000000..fecd4230b7
--- /dev/null
+++ b/comm/mailnews/imap/src/nsImapBodyShell.cpp
@@ -0,0 +1,1060 @@
+/* -*- 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 "nsImapBodyShell.h"
+#include "nsImapProtocol.h"
+#include "nsMimeTypes.h"
+#include "nsServiceManagerUtils.h"
+#include "mozilla/Logging.h"
+
+// need to talk to Rich about this...
+#define IMAP_EXTERNAL_CONTENT_HEADER "X-Mozilla-IMAP-Part"
+
+using namespace mozilla;
+extern LazyLogModule IMAPCache; // defined in nsImapProtocol.cpp
+
+// imapbody.cpp
+// Implementation of the nsImapBodyShell and associated classes
+// These are used to parse IMAP BODYSTRUCTURE responses, and intelligently (?)
+// figure out what parts we need to display inline.
+
+/*
+ Create a nsImapBodyShell from a full BODYSTRUCUTRE response from the
+ parser.
+
+ The body shell represents a single, top-level object, the message. The
+ message body might be treated as either a container or a leaf (just like any
+ arbitrary part).
+
+ Steps for creating a part:
+ 1. Pull out the paren grouping for the part
+ 2. Create a generic part object with that buffer
+ 3. The factory will return either a leaf or container, depending on what
+ it really is.
+ 4. It is responsible for parsing its children, if there are any
+*/
+
+///////////// nsImapBodyShell ////////////////////////////////////
+
+NS_IMPL_ISUPPORTS0(nsImapBodyShell)
+
+nsImapBodyShell::nsImapBodyShell(nsIMAPBodypartMessage* message, uint32_t UID,
+ uint32_t UIDValidity, const char* folderName,
+ bool showAttachmentsInline)
+ : m_message(message),
+ m_isValid(false),
+ m_folderName(folderName),
+ m_generatingPart(nullptr),
+ m_isBeingGenerated(false),
+ m_cached(false),
+ m_generatingWholeMessage(false),
+ m_contentModified(IMAP_ContentModifiedType::IMAP_CONTENT_NOT_MODIFIED) {
+ m_UID = "";
+ m_UID.AppendInt(UID);
+ m_UID_validity = m_UID;
+ m_UID_validity.AppendInt(UIDValidity);
+
+ m_contentModified = showAttachmentsInline
+ ? IMAP_CONTENT_MODIFIED_VIEW_INLINE
+ : IMAP_CONTENT_MODIFIED_VIEW_AS_LINKS;
+
+ SetIsValid(m_message != nullptr);
+}
+
+nsImapBodyShell::~nsImapBodyShell() {
+ delete m_message;
+ m_prefetchQueue.Clear();
+}
+
+void nsImapBodyShell::SetIsValid(bool valid) { m_isValid = valid; }
+
+// Fills in buffer (and adopts storage) for header object
+void nsImapBodyShell::AdoptMessageHeaders(char* headers, const char* partNum) {
+ if (!GetIsValid()) return;
+
+ if (!partNum) partNum = "0";
+
+ // we are going to say that a message header object only has
+ // part data, and no header data.
+ nsIMAPBodypart* foundPart = m_message->FindPartWithNumber(partNum);
+ if (foundPart) {
+ nsIMAPBodypartMessage* messageObj = foundPart->GetnsIMAPBodypartMessage();
+ if (messageObj) {
+ messageObj->AdoptMessageHeaders(headers);
+ if (!messageObj->GetIsValid()) SetIsValid(false);
+ } else {
+ // We were filling in message headers for a given part number.
+ // We looked up that part number, found an object, but it
+ // wasn't of type message/rfc822.
+ // Something's wrong.
+ NS_ASSERTION(false, "object not of type message rfc822");
+ }
+ } else
+ SetIsValid(false);
+}
+
+// Fills in buffer (and adopts storage) for MIME headers in appropriate object.
+// If object can't be found, sets isValid to false.
+void nsImapBodyShell::AdoptMimeHeader(const char* partNum, char* mimeHeader) {
+ if (!GetIsValid()) return;
+
+ NS_ASSERTION(partNum, "null partnum in body shell");
+
+ nsIMAPBodypart* foundPart = m_message->FindPartWithNumber(partNum);
+
+ if (foundPart) {
+ foundPart->AdoptHeaderDataBuffer(mimeHeader);
+ if (!foundPart->GetIsValid()) SetIsValid(false);
+ } else {
+ SetIsValid(false);
+ }
+}
+
+void nsImapBodyShell::AddPrefetchToQueue(nsIMAPeFetchFields fields,
+ const char* partNumber) {
+ nsIMAPMessagePartID newPart(fields, partNumber);
+ m_prefetchQueue.AppendElement(newPart);
+}
+
+// Requires that the shell is valid when called
+// Performs a preflight check on all message parts to see if they are all
+// inline. Returns true if all parts are inline, false otherwise.
+bool nsImapBodyShell::PreflightCheckAllInline() {
+ bool rv = m_message->PreflightCheckAllInline(this);
+ // if (rv)
+ // MOZ_LOG(IMAP, out, ("BODYSHELL: All parts inline. Reverting to whole
+ // message download."));
+ return rv;
+}
+
+// When partNum is NULL, Generates a whole message and intelligently
+// leaves out parts that are not inline.
+
+// When partNum is not NULL, Generates a MIME part that hasn't been downloaded
+// yet Ok, here's how we're going to do this. Essentially, this will be the
+// mirror image of the "normal" generation. All parts will be left out except a
+// single part which is explicitly specified. All relevant headers will be
+// included. Libmime will extract only the part of interest, so we don't have to
+// worry about the other parts. This also has the advantage that it looks like
+// it will be more workable for nested parts. For instance, if a user clicks on
+// a link to a forwarded message, then that forwarded message may be generated
+// along with any images that the forwarded message contains, for instance.
+
+int32_t nsImapBodyShell::Generate(nsImapProtocol* conn, char* partNum) {
+ // Hold the connection in existence for the duration.
+ RefPtr<nsImapProtocol> kungFuDeathGrip(conn);
+
+ m_isBeingGenerated = true;
+ m_generatingPart = partNum;
+ int32_t contentLength = 0;
+
+ if (!GetIsValid() || PreflightCheckAllInline()) {
+ // We don't have a valid shell, or all parts are going to be inline anyway.
+ // Fall back to fetching the whole message.
+#ifdef DEBUG_chrisf
+ NS_ASSERTION(GetIsValid());
+#endif
+ m_generatingWholeMessage = true;
+ uint32_t messageSize = conn->GetMessageSize(GetUID());
+ MOZ_LOG(IMAPCache, LogLevel::Debug,
+ ("Generate(): Set IMAP_CONTENT_NOT MODIFIED"));
+ // So that when we cache it, we know we have the whole message.
+ conn->SetContentModified(IMAP_CONTENT_NOT_MODIFIED);
+ if (!conn->DeathSignalReceived())
+ conn->FallbackToFetchWholeMsg(GetUID(), messageSize);
+ contentLength = (int32_t)messageSize; // ugh
+ } else {
+ // We have a valid shell.
+ bool streamCreated = false;
+ m_generatingWholeMessage = false;
+
+ ////// PASS 1 : PREFETCH ///////
+ // First, prefetch any additional headers/data that we need
+ if (!conn->GetPseudoInterrupted())
+ m_message->Generate(
+ this, conn, false,
+ true); // This queues up everything we need to prefetch
+ // Now, run a single pipelined prefetch (neato!)
+ conn->PipelinedFetchMessageParts(GetUID(), m_prefetchQueue);
+ m_prefetchQueue.Clear();
+
+ ////// PASS 2 : COMPUTE STREAM SIZE ///////
+ // Next, figure out the size from the parts that we're going to fill in,
+ // plus all of the MIME headers, plus the message header itself
+ if (!conn->GetPseudoInterrupted())
+ contentLength = m_message->Generate(this, conn, false, false);
+
+ // Setup the stream
+ if (!conn->GetPseudoInterrupted() && !conn->DeathSignalReceived()) {
+ nsresult rv = conn->BeginMessageDownLoad(contentLength, MESSAGE_RFC822);
+ if (NS_FAILED(rv)) {
+ m_generatingPart = nullptr;
+ conn->AbortMessageDownLoad();
+ return 0;
+ }
+ streamCreated = true;
+ }
+
+ ////// PASS 3 : GENERATE ///////
+ // Generate the message
+ if (!conn->GetPseudoInterrupted() && !conn->DeathSignalReceived())
+ m_message->Generate(this, conn, true, false);
+
+ // Close the stream here - normal. If pseudointerrupted, the connection
+ // will abort the download stream
+ if (!conn->GetPseudoInterrupted() && !conn->DeathSignalReceived())
+ conn->NormalMessageEndDownload();
+ else if (streamCreated)
+ conn->AbortMessageDownLoad();
+
+ m_generatingPart = NULL;
+ }
+
+ m_isBeingGenerated = false;
+ return contentLength;
+}
+
+///////////// nsIMAPBodypart ////////////////////////////////////
+
+nsIMAPBodypart::nsIMAPBodypart(char* partNumber, nsIMAPBodypart* parentPart) {
+ SetIsValid(true);
+ m_parentPart = parentPart;
+ m_partNumberString = partNumber; // storage adopted
+ m_partData = NULL;
+ m_headerData = NULL;
+ m_boundaryData = NULL; // initialize from parsed BODYSTRUCTURE
+ m_contentLength = 0;
+ m_partLength = 0;
+
+ m_contentType = NULL;
+ m_bodyType = NULL;
+ m_bodySubType = NULL;
+ m_bodyID = NULL;
+ m_bodyDescription = NULL;
+ m_bodyEncoding = NULL;
+}
+
+nsIMAPBodypart::~nsIMAPBodypart() {
+ PR_FREEIF(m_partNumberString);
+ PR_FREEIF(m_contentType);
+ PR_FREEIF(m_bodyType);
+ PR_FREEIF(m_bodySubType);
+ PR_FREEIF(m_bodyID);
+ PR_FREEIF(m_bodyDescription);
+ PR_FREEIF(m_bodyEncoding);
+ PR_FREEIF(m_partData);
+ PR_FREEIF(m_headerData);
+ PR_FREEIF(m_boundaryData);
+}
+
+void nsIMAPBodypart::SetIsValid(bool valid) {
+ m_isValid = valid;
+ if (!m_isValid) {
+ // MOZ_LOG(IMAP, out, ("BODYSHELL: Part is invalid. Part Number: %s
+ // Content-Type: %s", m_partNumberString, m_contentType));
+ }
+}
+
+// Adopts storage for part data buffer. If NULL, sets isValid to false.
+void nsIMAPBodypart::AdoptPartDataBuffer(char* buf) {
+ m_partData = buf;
+ if (!m_partData) {
+ SetIsValid(false);
+ }
+}
+
+// Adopts storage for header data buffer. If NULL, sets isValid to false.
+void nsIMAPBodypart::AdoptHeaderDataBuffer(char* buf) {
+ m_headerData = buf;
+ if (!m_headerData) {
+ SetIsValid(false);
+ }
+}
+
+// Finds the part with given part number
+// Returns a nsIMAPBodystructure of the matched part if it is this
+// or one of its children. Returns NULL otherwise.
+nsIMAPBodypart* nsIMAPBodypart::FindPartWithNumber(const char* partNum) {
+ // either brute force, or do it the smart way - look at the number.
+ // (the parts should be ordered, and hopefully indexed by their number)
+
+ if (m_partNumberString && !PL_strcasecmp(partNum, m_partNumberString))
+ return this;
+
+ // if (!m_partNumberString && !PL_strcasecmp(partNum, "1"))
+ // return this;
+
+ return NULL;
+}
+
+void nsIMAPBodypart::QueuePrefetchMIMEHeader(nsImapBodyShell* aShell) {
+ aShell->AddPrefetchToQueue(kMIMEHeader, m_partNumberString);
+}
+
+int32_t nsIMAPBodypart::GenerateMIMEHeader(nsImapBodyShell* aShell,
+ nsImapProtocol* conn, bool stream,
+ bool prefetch) {
+ if (prefetch && !m_headerData) {
+ QueuePrefetchMIMEHeader(aShell);
+ return 0;
+ }
+ if (m_headerData) {
+ int32_t mimeHeaderLength = 0;
+
+ if (!ShouldFetchInline(aShell)) {
+ // if this part isn't inline, add the X-Mozilla-IMAP-Part header
+ char* xPartHeader = PR_smprintf("%s: %s", IMAP_EXTERNAL_CONTENT_HEADER,
+ m_partNumberString);
+ if (xPartHeader) {
+ if (stream) {
+ conn->Log("SHELL", "GENERATE-XHeader", m_partNumberString);
+ conn->HandleMessageDownLoadLine(xPartHeader, false);
+ }
+ mimeHeaderLength += PL_strlen(xPartHeader);
+ PR_Free(xPartHeader);
+ }
+ }
+
+ mimeHeaderLength += PL_strlen(m_headerData);
+ if (stream) {
+ conn->Log("SHELL", "GENERATE-MIMEHeader", m_partNumberString);
+ conn->HandleMessageDownLoadLine(m_headerData,
+ false); // all one line? Can we do that?
+ }
+
+ return mimeHeaderLength;
+ }
+
+ SetIsValid(false); // prefetch didn't adopt a MIME header
+ return 0;
+}
+
+int32_t nsIMAPBodypart::GeneratePart(nsImapBodyShell* aShell,
+ nsImapProtocol* conn, bool stream,
+ bool prefetch) {
+ if (prefetch) return 0; // don't need to prefetch anything
+
+ if (m_partData) // we have prefetched the part data
+ {
+ if (stream) {
+ conn->Log("SHELL", "GENERATE-Part-Prefetched", m_partNumberString);
+ conn->HandleMessageDownLoadLine(m_partData, false);
+ }
+ return PL_strlen(m_partData);
+ }
+
+ // we are fetching and streaming this part's body as we go
+ if (stream && !conn->DeathSignalReceived()) {
+ char* generatingPart = aShell->GetGeneratingPart();
+ bool fetchingSpecificPart =
+ (generatingPart && !PL_strcmp(generatingPart, m_partNumberString));
+
+ conn->Log("SHELL", "GENERATE-Part-Inline", m_partNumberString);
+ MOZ_LOG(IMAPCache, LogLevel::Debug,
+ ("GeneratePart(): Call FetchTryChunking() part length=%" PRIi32
+ ", part number=%s",
+ m_partLength, m_partNumberString));
+ conn->FetchTryChunking(aShell->GetUID(), kMIMEPart, true,
+ m_partNumberString, m_partLength,
+ !fetchingSpecificPart);
+ }
+ return m_partLength; // the part length has been filled in from the
+ // BODYSTRUCTURE response
+}
+
+int32_t nsIMAPBodypart::GenerateBoundary(nsImapBodyShell* aShell,
+ nsImapProtocol* conn, bool stream,
+ bool prefetch, bool lastBoundary) {
+ if (prefetch) return 0; // don't need to prefetch anything
+
+ if (m_boundaryData) {
+ if (!lastBoundary) {
+ if (stream) {
+ conn->Log("SHELL", "GENERATE-Boundary", m_partNumberString);
+ conn->HandleMessageDownLoadLine(m_boundaryData, false);
+ }
+ return PL_strlen(m_boundaryData);
+ }
+
+ // the last boundary
+ char* lastBoundaryData = PR_smprintf("%s--", m_boundaryData);
+ if (lastBoundaryData) {
+ if (stream) {
+ conn->Log("SHELL", "GENERATE-Boundary-Last", m_partNumberString);
+ conn->HandleMessageDownLoadLine(lastBoundaryData, false);
+ }
+ int32_t rv = PL_strlen(lastBoundaryData);
+ PR_Free(lastBoundaryData);
+ return rv;
+ }
+ // HandleMemoryFailure();
+ return 0;
+ }
+ return 0;
+}
+
+int32_t nsIMAPBodypart::GenerateEmptyFilling(nsImapBodyShell* aShell,
+ nsImapProtocol* conn, bool stream,
+ bool prefetch) {
+ if (prefetch) return 0; // don't need to prefetch anything
+
+ const nsString& emptyString = conn->GetEmptyMimePartString();
+ if (!emptyString.IsEmpty()) {
+ if (stream) {
+ conn->Log("SHELL", "GENERATE-Filling", m_partNumberString);
+ conn->HandleMessageDownLoadLine(NS_ConvertUTF16toUTF8(emptyString).get(),
+ false);
+ }
+ return emptyString.Length();
+ }
+ return 0;
+}
+
+// Returns true if the prefs say that this content type should
+// explicitly be kept in when filling in the shell
+bool nsIMAPBodypart::ShouldExplicitlyFetchInline() { return false; }
+
+// Returns true if the prefs say that this content type should
+// explicitly be left out when filling in the shell
+bool nsIMAPBodypart::ShouldExplicitlyNotFetchInline() { return false; }
+
+///////////// nsIMAPBodypartLeaf /////////////////////////////
+
+nsIMAPBodypartLeaf::nsIMAPBodypartLeaf(char* partNum,
+ nsIMAPBodypart* parentPart,
+ char* bodyType, char* bodySubType,
+ char* bodyID, char* bodyDescription,
+ char* bodyEncoding, int32_t partLength,
+ bool preferPlainText)
+ : nsIMAPBodypart(partNum, parentPart), mPreferPlainText(preferPlainText) {
+ m_bodyType = bodyType;
+ m_bodySubType = bodySubType;
+ m_bodyID = bodyID;
+ m_bodyDescription = bodyDescription;
+ m_bodyEncoding = bodyEncoding;
+ m_partLength = partLength;
+ if (m_bodyType && m_bodySubType) {
+ m_contentType = PR_smprintf("%s/%s", m_bodyType, m_bodySubType);
+ }
+ SetIsValid(true);
+}
+
+nsIMAPBodypartType nsIMAPBodypartLeaf::GetType() { return IMAP_BODY_LEAF; }
+
+int32_t nsIMAPBodypartLeaf::Generate(nsImapBodyShell* aShell,
+ nsImapProtocol* conn, bool stream,
+ bool prefetch) {
+ int32_t len = 0;
+
+ if (GetIsValid()) {
+ if (stream && !prefetch)
+ conn->Log("SHELL", "GENERATE-Leaf", m_partNumberString);
+
+ // Stream out the MIME part boundary
+ // GenerateBoundary();
+ NS_ASSERTION(m_parentPart, "part has no parent");
+ // nsIMAPBodypartMessage *parentMessage = m_parentPart ?
+ // m_parentPart->GetnsIMAPBodypartMessage() : NULL;
+
+ // Stream out the MIME header of this part, if this isn't the only body part
+ // of a message
+ // if (parentMessage ? !parentMessage->GetIsTopLevelMessage() : true)
+ if ((m_parentPart->GetType() != IMAP_BODY_MESSAGE_RFC822) &&
+ !conn->GetPseudoInterrupted())
+ len += GenerateMIMEHeader(aShell, conn, stream, prefetch);
+
+ if (!conn->GetPseudoInterrupted()) {
+ if (ShouldFetchInline(aShell)) {
+ // Fetch and stream the content of this part
+ len += GeneratePart(aShell, conn, stream, prefetch);
+ } else {
+ // fill in the filling within the empty part
+ len += GenerateEmptyFilling(aShell, conn, stream, prefetch);
+ }
+ }
+ }
+ m_contentLength = len;
+ return m_contentLength;
+}
+
+// returns true if this part should be fetched inline for generation.
+bool nsIMAPBodypartLeaf::ShouldFetchInline(nsImapBodyShell* aShell) {
+ char* generatingPart = aShell->GetGeneratingPart();
+ if (generatingPart) {
+ // If we are generating a specific part
+ if (!PL_strcmp(generatingPart, m_partNumberString)) {
+ // This is the part we're generating
+ return true;
+ }
+
+ // If this is the only body part of a message, and that
+ // message is the part being generated, then this leaf should
+ // be inline as well.
+ if ((m_parentPart->GetType() == IMAP_BODY_MESSAGE_RFC822) &&
+ (!PL_strcmp(m_parentPart->GetPartNumberString(), generatingPart)))
+ return true;
+
+ // The parent of this part is a multipart
+ if (m_parentPart->GetType() == IMAP_BODY_MULTIPART) {
+ // This is the first text part of a forwarded message
+ // with a multipart body, and that message is being generated,
+ // then generate this part.
+ nsIMAPBodypart* grandParent = m_parentPart->GetParentPart();
+ // grandParent must exist, since multiparts need parents
+ NS_ASSERTION(grandParent, "grandparent doesn't exist for multi-part alt");
+ if (grandParent && (grandParent->GetType() == IMAP_BODY_MESSAGE_RFC822) &&
+ (!PL_strcmp(grandParent->GetPartNumberString(), generatingPart)) &&
+ (m_partNumberString[PL_strlen(m_partNumberString) - 1] == '1') &&
+ !PL_strcasecmp(m_bodyType, "text"))
+ return true; // we're downloading it inline
+
+ // This is a child of a multipart/appledouble attachment,
+ // and that multipart/appledouble attachment is being generated
+ if (!PL_strcasecmp(m_parentPart->GetBodySubType(), "appledouble") &&
+ !PL_strcmp(m_parentPart->GetPartNumberString(), generatingPart))
+ return true; // we're downloading it inline
+ }
+
+ // Leave out all other leaves if this isn't the one
+ // we're generating.
+ // Maybe change later to check parents, etc.
+ return false;
+ }
+
+ // We are generating the whole message, possibly (hopefully)
+ // leaving out non-inline parts
+ if (ShouldExplicitlyFetchInline()) return true;
+ if (ShouldExplicitlyNotFetchInline()) return false;
+
+ // If the parent is a message (this is the only body part of that
+ // message), and that message should be inline, then its body
+ // should inherit the inline characteristics of that message
+ if (m_parentPart->GetType() == IMAP_BODY_MESSAGE_RFC822)
+ return m_parentPart->ShouldFetchInline(aShell);
+
+ // View Attachments As Links is on.
+ if (!(aShell->GetContentModified() == IMAP_CONTENT_MODIFIED_VIEW_INLINE)) {
+ // The last text part is still displayed inline,
+ // even if View Attachments As Links is on.
+ nsIMAPBodypart* grandParentPart = m_parentPart->GetParentPart();
+ if ((mPreferPlainText ||
+ !PL_strcasecmp(m_parentPart->GetBodySubType(), "mixed")) &&
+ !PL_strcmp(m_partNumberString, "1") &&
+ !PL_strcasecmp(m_bodyType, "text"))
+ return true; // we're downloading it inline
+
+ if ((!PL_strcasecmp(m_parentPart->GetBodySubType(), "alternative") ||
+ (grandParentPart &&
+ !PL_strcasecmp(grandParentPart->GetBodySubType(), "alternative"))) &&
+ !PL_strcasecmp(m_bodyType, "text") &&
+ ((!PL_strcasecmp(m_bodySubType, "plain") && mPreferPlainText) ||
+ (!PL_strcasecmp(m_bodySubType, "html") && !mPreferPlainText)))
+ return true;
+
+ // This is the first text part of a top-level multipart.
+ // For instance, a message with multipart body, where the first
+ // part is multipart, and this is the first leaf of that first part.
+ if (m_parentPart->GetType() == IMAP_BODY_MULTIPART &&
+ (PL_strlen(m_partNumberString) >= 2) &&
+ !PL_strcmp(m_partNumberString + PL_strlen(m_partNumberString) - 2,
+ ".1") && // this is the first text type on this level
+ (!PL_strcmp(m_parentPart->GetPartNumberString(), "1") ||
+ !PL_strcmp(m_parentPart->GetPartNumberString(), "2")) &&
+ !PL_strcasecmp(m_bodyType, "text"))
+ return true;
+ // This is the first text part of a top-level multipart of the
+ // toplevelmessage This 'assumes' the text body is first leaf. This is not
+ // required for valid email. The only other way is to get
+ // content-disposition = attachment and exclude those text parts.
+ if (m_parentPart->GetType() == IMAP_BODY_MULTIPART &&
+ !PL_strcasecmp(m_bodyType, "text") &&
+ !PL_strcmp(m_parentPart->GetPartNumberString(), "0") &&
+ !PL_strcmp(m_partNumberString, "1"))
+ return true;
+
+ // we may have future problems needing tests here
+
+ return false; // we can leave it on the server
+ }
+#ifdef XP_MACOSX
+ // If it is either applesingle, or a resource fork for appledouble
+ if (!PL_strcasecmp(m_contentType, "application/applefile")) {
+ // if it is appledouble
+ if (m_parentPart->GetType() == IMAP_BODY_MULTIPART &&
+ !PL_strcasecmp(m_parentPart->GetBodySubType(), "appledouble")) {
+ // This is the resource fork of a multipart/appledouble.
+ // We inherit the inline attributes of the parent,
+ // which was derived from its OTHER child. (The data fork.)
+ return m_parentPart->ShouldFetchInline(aShell);
+ }
+ // it is applesingle
+ return false; // we can leave it on the server
+ }
+#endif // XP_MACOSX
+
+ // Fetch type APPLICATION now if the subtype is a signature or if it's an
+ // octet-stream. Otherwise, fetch on demand.
+ if (!PL_strcasecmp(m_bodyType, "APPLICATION") &&
+ PL_strncasecmp(m_bodySubType, "x-pkcs7", 7) &&
+ PL_strcasecmp(m_bodySubType, "octet-stream"))
+ return false; // we can leave it on the server
+ if (!PL_strcasecmp(m_bodyType, "AUDIO")) return false;
+ // Here's where we can add some more intelligence -- let's leave out
+ // any other parts that we know we can't display inline.
+ return true; // we're downloading it inline
+}
+
+bool nsIMAPBodypartMultipart::IsLastTextPart(const char* partNumberString) {
+ // iterate backwards over the parent's part list and if the part is
+ // text, compare it to the part number string
+ for (int i = m_partList.Length() - 1; i >= 0; i--) {
+ nsIMAPBodypart* part = m_partList[i];
+ if (!PL_strcasecmp(part->GetBodyType(), "text"))
+ return !PL_strcasecmp(part->GetPartNumberString(), partNumberString);
+ }
+ return false;
+}
+
+bool nsIMAPBodypartLeaf::PreflightCheckAllInline(nsImapBodyShell* aShell) {
+ // only need to check this part, since it has no children.
+ return ShouldFetchInline(aShell);
+}
+
+///////////// nsIMAPBodypartMessage ////////////////////////
+
+nsIMAPBodypartMessage::nsIMAPBodypartMessage(
+ char* partNum, nsIMAPBodypart* parentPart, bool topLevelMessage,
+ char* bodyType, char* bodySubType, char* bodyID, char* bodyDescription,
+ char* bodyEncoding, int32_t partLength, bool preferPlainText)
+ : nsIMAPBodypartLeaf(partNum, parentPart, bodyType, bodySubType, bodyID,
+ bodyDescription, bodyEncoding, partLength,
+ preferPlainText) {
+ m_topLevelMessage = topLevelMessage;
+ if (m_topLevelMessage) {
+ m_partNumberString = PR_smprintf("0");
+ if (!m_partNumberString) {
+ SetIsValid(false);
+ return;
+ }
+ }
+ m_body = NULL;
+ m_headers = new nsIMAPMessageHeaders(
+ m_partNumberString, this); // We always have a Headers object
+ if (!m_headers || !m_headers->GetIsValid()) {
+ SetIsValid(false);
+ return;
+ }
+ SetIsValid(true);
+}
+
+void nsIMAPBodypartMessage::SetBody(nsIMAPBodypart* body) {
+ if (m_body) delete m_body;
+ m_body = body;
+}
+
+nsIMAPBodypartType nsIMAPBodypartMessage::GetType() {
+ return IMAP_BODY_MESSAGE_RFC822;
+}
+
+nsIMAPBodypartMessage::~nsIMAPBodypartMessage() {
+ delete m_headers;
+ delete m_body;
+}
+
+int32_t nsIMAPBodypartMessage::Generate(nsImapBodyShell* aShell,
+ nsImapProtocol* conn, bool stream,
+ bool prefetch) {
+ if (!GetIsValid()) return 0;
+
+ m_contentLength = 0;
+
+ if (stream && !prefetch)
+ conn->Log("SHELL", "GENERATE-MessageRFC822", m_partNumberString);
+
+ if (!m_topLevelMessage &&
+ !conn->GetPseudoInterrupted()) // not the top-level message - we need
+ // the MIME header as well as the
+ // message header
+ {
+ // but we don't need the MIME headers of a message/rfc822 part if this
+ // content type is in (part of) the main msg header. In other words, we
+ // still need these MIME headers if this message/rfc822 body part is
+ // enclosed in the msg body (most likely as a body part of a multipart/mixed
+ // msg).
+ // Don't fetch (bug 128888) Do fetch (bug 168097)
+ // ---------------------------------- -----------------------------------
+ // message/rfc822 (parent part) message/rfc822
+ // message/rfc822 <<<--- multipart/mixed (parent part)
+ // multipart/mixed message/rfc822 <<<---
+ // text/html (body text) multipart/mixed
+ // text/plain (attachment) text/html (body text)
+ // application/msword (attachment) text/plain (attachment)
+ // application/msword (attachment)
+ // "<<<---" points to the part we're examining here.
+ if (PL_strcasecmp(m_bodyType, "message") ||
+ PL_strcasecmp(m_bodySubType, "rfc822") ||
+ PL_strcasecmp(m_parentPart->GetBodyType(), "message") ||
+ PL_strcasecmp(m_parentPart->GetBodySubType(), "rfc822"))
+ m_contentLength += GenerateMIMEHeader(aShell, conn, stream, prefetch);
+ }
+
+ if (!conn->GetPseudoInterrupted())
+ m_contentLength += m_headers->Generate(aShell, conn, stream, prefetch);
+ if (!conn->GetPseudoInterrupted())
+ m_contentLength += m_body->Generate(aShell, conn, stream, prefetch);
+
+ return m_contentLength;
+}
+
+bool nsIMAPBodypartMessage::ShouldFetchInline(nsImapBodyShell* aShell) {
+ if (m_topLevelMessage) // the main message should always be defined as
+ // "inline"
+ return true;
+
+ char* generatingPart = aShell->GetGeneratingPart();
+ if (generatingPart) {
+ // If we are generating a specific part
+ // Always generate containers (just don't fill them in)
+ // because it is low cost (everything is cached)
+ // and it gives the message its full MIME structure,
+ // to avoid any potential mishap.
+ return true;
+ }
+
+ // Generating whole message
+ if (ShouldExplicitlyFetchInline()) return true;
+ if (ShouldExplicitlyNotFetchInline()) return false;
+
+ // Message types are inline, by default.
+ return true;
+}
+
+bool nsIMAPBodypartMessage::PreflightCheckAllInline(nsImapBodyShell* aShell) {
+ if (!ShouldFetchInline(aShell)) return false;
+
+ return m_body->PreflightCheckAllInline(aShell);
+}
+
+// Fills in buffer (and adopts storage) for header object
+void nsIMAPBodypartMessage::AdoptMessageHeaders(char* headers) {
+ if (!GetIsValid()) return;
+
+ // we are going to say that the message headers only have
+ // part data, and no header data.
+ m_headers->AdoptPartDataBuffer(headers);
+ if (!m_headers->GetIsValid()) SetIsValid(false);
+}
+
+// Finds the part with given part number
+// Returns a nsIMAPBodystructure of the matched part if it is this
+// or one of its children. Returns NULL otherwise.
+nsIMAPBodypart* nsIMAPBodypartMessage::FindPartWithNumber(const char* partNum) {
+ // either brute force, or do it the smart way - look at the number.
+ // (the parts should be ordered, and hopefully indexed by their number)
+
+ if (!PL_strcasecmp(partNum, m_partNumberString)) return this;
+
+ return m_body->FindPartWithNumber(partNum);
+}
+
+///////////// nsIMAPBodypartMultipart ////////////////////////
+
+nsIMAPBodypartMultipart::nsIMAPBodypartMultipart(char* partNum,
+ nsIMAPBodypart* parentPart)
+ : nsIMAPBodypart(partNum, parentPart) {
+ if (!m_parentPart || (m_parentPart->GetType() == IMAP_BODY_MESSAGE_RFC822)) {
+ // the multipart (this) will inherit the part number of its parent
+ PR_FREEIF(m_partNumberString);
+ if (!m_parentPart) {
+ m_partNumberString = PR_smprintf("0");
+ } else
+ m_partNumberString = NS_xstrdup(m_parentPart->GetPartNumberString());
+ }
+ m_bodyType = NS_xstrdup("multipart");
+ if (m_parentPart && m_bodyType)
+ SetIsValid(true);
+ else
+ SetIsValid(false);
+}
+
+nsIMAPBodypartType nsIMAPBodypartMultipart::GetType() {
+ return IMAP_BODY_MULTIPART;
+}
+
+nsIMAPBodypartMultipart::~nsIMAPBodypartMultipart() {
+ for (int i = m_partList.Length() - 1; i >= 0; i--) {
+ delete m_partList[i];
+ }
+}
+
+void nsIMAPBodypartMultipart::SetBodySubType(char* bodySubType) {
+ PR_FREEIF(m_bodySubType);
+ PR_FREEIF(m_contentType);
+ m_bodySubType = bodySubType;
+ if (m_bodyType && m_bodySubType)
+ m_contentType = PR_smprintf("%s/%s", m_bodyType, m_bodySubType);
+}
+
+int32_t nsIMAPBodypartMultipart::Generate(nsImapBodyShell* aShell,
+ nsImapProtocol* conn, bool stream,
+ bool prefetch) {
+ int32_t len = 0;
+
+ if (GetIsValid()) {
+ if (stream && !prefetch)
+ conn->Log("SHELL", "GENERATE-Multipart", m_partNumberString);
+
+ // Stream out the MIME header of this part
+
+ bool parentIsMessageType =
+ GetParentPart()
+ ? (GetParentPart()->GetType() == IMAP_BODY_MESSAGE_RFC822)
+ : true;
+
+ // If this is multipart/signed, then we always want to generate the MIME
+ // headers of this multipart. Otherwise, we only want to do it if the parent
+ // is not of type "message"
+ bool needMIMEHeader =
+ !parentIsMessageType; // !PL_strcasecmp(m_bodySubType, "signed") ? true
+ // : !parentIsMessageType;
+ if (needMIMEHeader &&
+ !conn->GetPseudoInterrupted()) // not a message body's type
+ {
+ len += GenerateMIMEHeader(aShell, conn, stream, prefetch);
+ }
+
+ if (ShouldFetchInline(aShell)) {
+ for (auto part : m_partList) {
+ if (!conn->GetPseudoInterrupted())
+ len += GenerateBoundary(aShell, conn, stream, prefetch, false);
+ if (!conn->GetPseudoInterrupted())
+ len += part->Generate(aShell, conn, stream, prefetch);
+ }
+ if (!conn->GetPseudoInterrupted())
+ len += GenerateBoundary(aShell, conn, stream, prefetch, true);
+ } else {
+ // fill in the filling within the empty part
+ if (!conn->GetPseudoInterrupted())
+ len += GenerateEmptyFilling(aShell, conn, stream, prefetch);
+ }
+ }
+ m_contentLength = len;
+ return m_contentLength;
+}
+
+bool nsIMAPBodypartMultipart::ShouldFetchInline(nsImapBodyShell* aShell) {
+ char* generatingPart = aShell->GetGeneratingPart();
+ if (generatingPart) {
+ // If we are generating a specific part
+ // Always generate containers (just don't fill them in)
+ // because it is low cost (everything is cached)
+ // and it gives the message its full MIME structure,
+ // to avoid any potential mishap.
+ return true;
+ }
+
+ // Generating whole message
+ if (ShouldExplicitlyFetchInline()) return true;
+ if (ShouldExplicitlyNotFetchInline()) return false;
+
+ if (!PL_strcasecmp(m_bodySubType, "alternative")) return true;
+
+ nsIMAPBodypart* grandparentPart = m_parentPart->GetParentPart();
+
+ // if we're a multipart sub-part of multipart alternative, we need to
+ // be fetched because mime will always display us.
+ if (!PL_strcasecmp(m_parentPart->GetBodySubType(), "alternative") &&
+ GetType() == IMAP_BODY_MULTIPART)
+ return true;
+ // If "Show Attachments as Links" is on, and
+ // the parent of this multipart is not a message,
+ // then it's not inline.
+ if (!(aShell->GetContentModified() == IMAP_CONTENT_MODIFIED_VIEW_INLINE) &&
+ (m_parentPart->GetType() != IMAP_BODY_MESSAGE_RFC822) &&
+ (m_parentPart->GetType() == IMAP_BODY_MULTIPART
+ ? (grandparentPart
+ ? grandparentPart->GetType() != IMAP_BODY_MESSAGE_RFC822
+ : true)
+ : true))
+ return false;
+
+ // multiparts are always inline (even multipart/appledouble)
+ // (their children might not be, though)
+ return true;
+}
+
+bool nsIMAPBodypartMultipart::PreflightCheckAllInline(nsImapBodyShell* aShell) {
+ bool rv = ShouldFetchInline(aShell);
+
+ size_t i = 0;
+ while (rv && (i < m_partList.Length())) {
+ rv = m_partList[i]->PreflightCheckAllInline(aShell);
+ i++;
+ }
+
+ return rv;
+}
+
+nsIMAPBodypart* nsIMAPBodypartMultipart::FindPartWithNumber(
+ const char* partNum) {
+ NS_ASSERTION(partNum, "null part passed into FindPartWithNumber");
+
+ // check this
+ if (!PL_strcmp(partNum, m_partNumberString)) return this;
+
+ // check children
+ for (int i = m_partList.Length() - 1; i >= 0; i--) {
+ nsIMAPBodypart* foundPart = m_partList[i]->FindPartWithNumber(partNum);
+ if (foundPart) return foundPart;
+ }
+
+ // not this, or any of this's children
+ return NULL;
+}
+
+///////////// nsIMAPMessageHeaders ////////////////////////////////////
+
+nsIMAPMessageHeaders::nsIMAPMessageHeaders(char* partNum,
+ nsIMAPBodypart* parentPart)
+ : nsIMAPBodypart(partNum, parentPart) {
+ if (!partNum) {
+ SetIsValid(false);
+ return;
+ }
+ m_partNumberString = NS_xstrdup(partNum);
+ if (!m_partNumberString) {
+ SetIsValid(false);
+ return;
+ }
+ if (!m_parentPart || !m_parentPart->GetnsIMAPBodypartMessage()) {
+ // Message headers created without a valid Message parent
+ NS_ASSERTION(false, "creating message headers with invalid message parent");
+ SetIsValid(false);
+ }
+}
+
+nsIMAPBodypartType nsIMAPMessageHeaders::GetType() {
+ return IMAP_BODY_MESSAGE_HEADER;
+}
+
+void nsIMAPMessageHeaders::QueuePrefetchMessageHeaders(
+ nsImapBodyShell* aShell) {
+ if (!m_parentPart->GetnsIMAPBodypartMessage()
+ ->GetIsTopLevelMessage()) // not top-level headers
+ aShell->AddPrefetchToQueue(kRFC822HeadersOnly, m_partNumberString);
+ else
+ aShell->AddPrefetchToQueue(kRFC822HeadersOnly, NULL);
+}
+
+int32_t nsIMAPMessageHeaders::Generate(nsImapBodyShell* aShell,
+ nsImapProtocol* conn, bool stream,
+ bool prefetch) {
+ // prefetch the header
+ if (prefetch && !m_partData && !conn->DeathSignalReceived()) {
+ QueuePrefetchMessageHeaders(aShell);
+ }
+
+ if (stream && !prefetch)
+ conn->Log("SHELL", "GENERATE-MessageHeaders", m_partNumberString);
+
+ // stream out the part data
+ if (ShouldFetchInline(aShell)) {
+ if (!conn->GetPseudoInterrupted())
+ m_contentLength = GeneratePart(aShell, conn, stream, prefetch);
+ } else {
+ m_contentLength = 0; // don't fill in any filling for the headers
+ }
+ return m_contentLength;
+}
+
+bool nsIMAPMessageHeaders::ShouldFetchInline(nsImapBodyShell* aShell) {
+ return m_parentPart->ShouldFetchInline(aShell);
+}
+
+///////////// nsImapBodyShellCache ////////////////////////////////////
+
+nsImapBodyShellCache::nsImapBodyShellCache()
+ : m_shellList(kMaxEntries), m_shellHash(kMaxEntries) {}
+
+// We'll use an LRU scheme here.
+// We will add shells in numerical order, so the
+// least recently used one will be in slot 0.
+void nsImapBodyShellCache::EjectEntry() {
+ MOZ_ASSERT(!m_shellList.IsEmpty());
+
+ nsImapBodyShell* removedShell = m_shellList.ElementAt(0);
+
+ m_shellList.RemoveElementAt(0);
+ m_shellHash.Remove(removedShell->GetUID());
+}
+
+void nsImapBodyShellCache::Clear() {
+ m_shellList.ClearAndRetainStorage();
+ m_shellHash.Clear();
+}
+
+void nsImapBodyShellCache::AddShellToCache(nsImapBodyShell* shell) {
+ // If it's already in the cache, then just return.
+ // This has the side-effect of re-ordering the LRU list
+ // to put this at the top, which is good, because it's what we want.
+ if (FindShellForUID(shell->GetUID_validity(), shell->GetFolderName(),
+ shell->GetContentModified())) {
+ return;
+ }
+
+ // OK, so it's not in the cache currently.
+
+ // First, for safety sake, remove any entry with the given UID,
+ // just in case we have a collision between two messages in different
+ // folders with the same UID.
+ RefPtr<nsImapBodyShell> foundShell;
+ m_shellHash.Get(shell->GetUID_validity(), getter_AddRefs(foundShell));
+ if (foundShell) {
+ m_shellHash.Remove(foundShell->GetUID_validity());
+ m_shellList.RemoveElement(foundShell);
+ }
+
+ // Make sure there's enough room
+ while (m_shellList.Length() > (kMaxEntries - 1)) {
+ EjectEntry();
+ }
+
+ // Add the new one to the cache
+ m_shellList.AppendElement(shell);
+
+ m_shellHash.InsertOrUpdate(shell->GetUID_validity(), RefPtr{shell});
+ shell->SetIsCached(true);
+}
+
+nsImapBodyShell* nsImapBodyShellCache::FindShellForUID(
+ nsACString const& UID, nsACString const& mailboxName,
+ IMAP_ContentModifiedType modType) {
+ RefPtr<nsImapBodyShell> foundShell;
+ m_shellHash.Get(UID, getter_AddRefs(foundShell));
+ if (!foundShell) return nullptr;
+ // Make sure the content-modified types are compatible.
+ // This allows us to work seamlessly while people switch between
+ // View Attachments Inline and View Attachments As Links.
+ // Enforce the invariant that any cached shell we use
+ // match the current content-modified settings.
+ if (modType != foundShell->GetContentModified()) return nullptr;
+
+ // mailbox names must match also.
+ if (!mailboxName.Equals(foundShell->GetFolderName())) return nullptr;
+
+ // adjust the LRU stuff. This defeats the performance gain of the hash if
+ // it actually is found since this is linear.
+ m_shellList.RemoveElement(foundShell);
+ m_shellList.AppendElement(foundShell); // Adds to end
+
+ return foundShell;
+}
+
+///////////// nsIMAPMessagePartID ////////////////////////////////////
+
+nsIMAPMessagePartID::nsIMAPMessagePartID(nsIMAPeFetchFields fields,
+ const char* partNumberString)
+ : m_partNumberString(partNumberString), m_fields(fields) {}
diff --git a/comm/mailnews/imap/src/nsImapBodyShell.h b/comm/mailnews/imap/src/nsImapBodyShell.h
new file mode 100644
index 0000000000..c2ab8c5898
--- /dev/null
+++ b/comm/mailnews/imap/src/nsImapBodyShell.h
@@ -0,0 +1,357 @@
+/* -*- 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/. */
+
+/*
+nsImapBodyShell and associated classes
+*/
+
+#ifndef IMAPBODY_H
+#define IMAPBODY_H
+
+#include "mozilla/Attributes.h"
+#include "nsImapCore.h"
+#include "nsString.h"
+#include "nsRefPtrHashtable.h"
+#include "nsTArray.h"
+
+class nsImapProtocol;
+
+typedef enum _nsIMAPBodypartType {
+ IMAP_BODY_MESSAGE_RFC822,
+ IMAP_BODY_MESSAGE_HEADER,
+ IMAP_BODY_LEAF,
+ IMAP_BODY_MULTIPART
+} nsIMAPBodypartType;
+
+class nsImapBodyShell;
+class nsIMAPBodypartMessage;
+
+class nsIMAPBodypart {
+ public:
+ // Construction
+ virtual bool GetIsValid() { return m_isValid; }
+ virtual void SetIsValid(bool valid);
+ virtual nsIMAPBodypartType GetType() = 0;
+
+ // Generation
+ // Generates an HTML representation of this part. Returns content length
+ // generated, -1 if failed.
+ virtual int32_t Generate(nsImapBodyShell* aShell, nsImapProtocol* conn,
+ bool /*stream*/, bool /* prefetch */) {
+ return -1;
+ }
+ virtual void AdoptPartDataBuffer(
+ char* buf); // Adopts storage for part data buffer. If NULL, sets
+ // isValid to false.
+ virtual void AdoptHeaderDataBuffer(
+ char* buf); // Adopts storage for header data buffer. If NULL, sets
+ // isValid to false.
+ virtual bool ShouldFetchInline(nsImapBodyShell* aShell) {
+ return true;
+ } // returns true if this part should be fetched inline for generation.
+ virtual bool PreflightCheckAllInline(nsImapBodyShell* aShell) { return true; }
+
+ virtual bool ShouldExplicitlyFetchInline();
+ virtual bool ShouldExplicitlyNotFetchInline();
+ virtual bool IsLastTextPart(const char* partNumberString) { return true; }
+
+ protected:
+ // If stream is false, simply returns the content length that will be
+ // generated the body of the part itself
+ virtual int32_t GeneratePart(nsImapBodyShell* aShell, nsImapProtocol* conn,
+ bool stream, bool prefetch);
+ // the MIME headers of the part
+ virtual int32_t GenerateMIMEHeader(nsImapBodyShell* aShell,
+ nsImapProtocol* conn, bool stream,
+ bool prefetch);
+ // Generates the MIME boundary wrapper for this part.
+ virtual int32_t GenerateBoundary(nsImapBodyShell* aShell,
+ nsImapProtocol* conn, bool stream,
+ bool prefetch, bool lastBoundary);
+ // lastBoundary indicates whether or not this should be the boundary for the
+ // final MIME part of the multipart message.
+ // Generates (possibly empty) filling for a part that won't be filled in
+ // inline.
+ virtual int32_t GenerateEmptyFilling(nsImapBodyShell* aShell,
+ nsImapProtocol* conn, bool stream,
+ bool prefetch);
+
+ // Part Numbers / Hierarchy
+ public:
+ virtual char* GetPartNumberString() { return m_partNumberString; }
+ virtual nsIMAPBodypart* FindPartWithNumber(
+ const char* partNum); // Returns the part object with the given number
+ virtual nsIMAPBodypart* GetParentPart() {
+ return m_parentPart;
+ } // Returns the parent of this part.
+ // We will define a part of type message/rfc822 to be the
+ // parent of its body and header.
+ // A multipart is a parent of its child parts.
+ // All other leafs do not have children.
+
+ // Other / Helpers
+ public:
+ virtual ~nsIMAPBodypart();
+ virtual nsIMAPBodypartMessage* GetnsIMAPBodypartMessage() { return NULL; }
+
+ const char* GetBodyType() { return m_bodyType; }
+ const char* GetBodySubType() { return m_bodySubType; }
+ void SetBoundaryData(char* boundaryData) { m_boundaryData = boundaryData; }
+
+ protected:
+ virtual void QueuePrefetchMIMEHeader(nsImapBodyShell* aShell);
+ // virtual void PrefetchMIMEHeader(); // Initiates a prefetch for the MIME
+ // header of this part.
+ nsIMAPBodypart(char* partNumber, nsIMAPBodypart* parentPart);
+
+ protected:
+ bool m_isValid; // If this part is valid.
+ char* m_partNumberString; // string representation of this part's
+ // full-hierarchy number. Define 0 to be the
+ // top-level message
+ char* m_partData; // data for this part. NULL if not filled in yet.
+ char* m_headerData; // data for this part's MIME header. NULL if not filled
+ // in yet.
+ char* m_boundaryData; // MIME boundary for this part
+ int32_t m_partLength;
+ int32_t m_contentLength; // Total content length which will be Generate()'d.
+ // -1 if not filled in yet.
+ nsIMAPBodypart* m_parentPart; // Parent of this part
+
+ // Fields - Filled in from parsed BODYSTRUCTURE response (as well as others)
+ char* m_contentType; // constructed from m_bodyType and m_bodySubType
+ char* m_bodyType;
+ char* m_bodySubType;
+ char* m_bodyID;
+ char* m_bodyDescription;
+ char* m_bodyEncoding;
+ // we ignore extension data for now
+};
+
+// Message headers
+// A special type of nsIMAPBodypart
+// These may be headers for the top-level message,
+// or any body part of type message/rfc822.
+class nsIMAPMessageHeaders : public nsIMAPBodypart {
+ public:
+ nsIMAPMessageHeaders(char* partNum, nsIMAPBodypart* parentPart);
+ virtual nsIMAPBodypartType GetType() override;
+ // Generates an HTML representation of this part. Returns content length
+ // generated, -1 if failed.
+ virtual int32_t Generate(nsImapBodyShell* aShell, nsImapProtocol* conn,
+ bool stream, bool prefetch) override;
+ virtual bool ShouldFetchInline(nsImapBodyShell* aShell) override;
+ virtual void QueuePrefetchMessageHeaders(nsImapBodyShell* aShell);
+};
+
+class nsIMAPBodypartMultipart : public nsIMAPBodypart {
+ public:
+ nsIMAPBodypartMultipart(char* partNum, nsIMAPBodypart* parentPart);
+ virtual nsIMAPBodypartType GetType() override;
+ virtual ~nsIMAPBodypartMultipart();
+ virtual bool ShouldFetchInline(nsImapBodyShell* aShell) override;
+ virtual bool PreflightCheckAllInline(nsImapBodyShell* aShell) override;
+ // Generates an HTML representation of this part. Returns content length
+ // generated, -1 if failed.
+ virtual int32_t Generate(nsImapBodyShell* aShell, nsImapProtocol* conn,
+ bool stream, bool prefetch) override;
+ // Returns the part object with the given number
+ virtual nsIMAPBodypart* FindPartWithNumber(const char* partNum) override;
+ virtual bool IsLastTextPart(const char* partNumberString) override;
+ void AppendPart(nsIMAPBodypart* part) { m_partList.AppendElement(part); }
+ void SetBodySubType(char* bodySubType);
+
+ protected:
+ // An ordered list of top-level body parts for this shell
+ nsTArray<nsIMAPBodypart*> m_partList;
+};
+
+// The name "leaf" is somewhat misleading, since a part of type message/rfc822
+// is technically a leaf, even though it can contain other parts within it.
+class nsIMAPBodypartLeaf : public nsIMAPBodypart {
+ public:
+ nsIMAPBodypartLeaf(char* partNum, nsIMAPBodypart* parentPart, char* bodyType,
+ char* bodySubType, char* bodyID, char* bodyDescription,
+ char* bodyEncoding, int32_t partLength,
+ bool preferPlainText);
+ virtual nsIMAPBodypartType GetType() override;
+ // Generates an HTML representation of this part. Returns content length
+ // generated, -1 if failed.
+ virtual int32_t Generate(nsImapBodyShell* aShell, nsImapProtocol* conn,
+ bool stream, bool prefetch) override;
+ // returns true if this part should be fetched inline for generation.
+ virtual bool ShouldFetchInline(nsImapBodyShell* aShell) override;
+ virtual bool PreflightCheckAllInline(nsImapBodyShell* aShell) override;
+
+ private:
+ bool mPreferPlainText;
+};
+
+class nsIMAPBodypartMessage : public nsIMAPBodypartLeaf {
+ public:
+ nsIMAPBodypartMessage(char* partNum, nsIMAPBodypart* parentPart,
+ bool topLevelMessage, char* bodyType, char* bodySubType,
+ char* bodyID, char* bodyDescription, char* bodyEncoding,
+ int32_t partLength, bool preferPlainText);
+ void SetBody(nsIMAPBodypart* body);
+ virtual nsIMAPBodypartType GetType() override;
+ virtual ~nsIMAPBodypartMessage();
+ virtual int32_t Generate(nsImapBodyShell* aShell, nsImapProtocol* conn,
+ bool stream, bool prefetch) override;
+ virtual bool ShouldFetchInline(nsImapBodyShell* aShell) override;
+ virtual bool PreflightCheckAllInline(nsImapBodyShell* aShell) override;
+ // Returns the part object with the given number
+ virtual nsIMAPBodypart* FindPartWithNumber(const char* partNum) override;
+ void AdoptMessageHeaders(
+ char* headers); // Fills in buffer (and adopts storage) for header object
+ // partNum specifies the message part number to which the
+ // headers correspond. NULL indicates the top-level
+ // message
+ virtual nsIMAPBodypartMessage* GetnsIMAPBodypartMessage() override {
+ return this;
+ }
+ virtual bool GetIsTopLevelMessage() { return m_topLevelMessage; }
+
+ protected:
+ nsIMAPMessageHeaders* m_headers; // Every body shell should have headers
+ nsIMAPBodypart* m_body;
+ bool m_topLevelMessage; // Whether or not this is the top-level message
+};
+
+// MessagePartID and an array of them are used for pipelining prefetches.
+
+class nsIMAPMessagePartID {
+ public:
+ nsIMAPMessagePartID(nsIMAPeFetchFields fields, const char* partNumberString);
+ nsIMAPeFetchFields GetFields() { return m_fields; }
+ const char* GetPartNumberString() { return m_partNumberString; }
+
+ protected:
+ const char* m_partNumberString;
+ nsIMAPeFetchFields m_fields;
+};
+
+// We will refer to a Body "Shell" as a hierarchical object representation of a
+// parsed BODYSTRUCTURE response. A shell contains representations of Shell
+// "Parts." A Body Shell can undergo essentially two operations: Construction
+// and Generation. Shell Construction occurs from a parsed a BODYSTRUCTURE
+// response, split into empty parts. Shell Generation generates a "MIME Shell"
+// of the message and streams it to libmime for display. The MIME Shell has
+// selected (inline) parts filled in, and leaves all others for on-demand
+// retrieval through explicit part fetches.
+
+class nsImapBodyShell : public nsISupports {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ nsImapBodyShell(nsIMAPBodypartMessage* message, uint32_t UID,
+ uint32_t UIDValidity, const char* folderName,
+ bool showAttachmentsInline);
+ // To be used after a shell is uncached
+ bool GetIsValid() { return m_isValid; }
+ void SetIsValid(bool valid);
+
+ // Prefetch
+ // Adds a message body part to the queue to be prefetched
+ // in a single, pipelined command
+ void AddPrefetchToQueue(nsIMAPeFetchFields, const char* partNum);
+ // Fills in buffer (and adopts storage) for header object
+ // partNum specifies the message part number to which the
+ // headers correspond. NULL indicates the top-level message
+ void AdoptMessageHeaders(char* headers, const char* partNum);
+ // Fills in buffer (and adopts storage) for MIME headers in appropriate
+ // object. If object can't be found, sets isValid to false.
+ void AdoptMimeHeader(const char* partNum, char* mimeHeader);
+
+ // Generation
+ // Streams out an HTML representation of this IMAP message, going along and
+ // fetching parts it thinks it needs, and leaving empty shells for the parts
+ // it doesn't.
+ // Returns number of bytes generated, or -1 if invalid.
+ // If partNum is not NULL, then this works to generates a MIME part that
+ // hasn't been downloaded yet and leaves out all other parts. By default, to
+ // generate a normal message, partNum should be NULL.
+ int32_t Generate(nsImapProtocol* conn, char* partNum);
+
+ // Returns TRUE if the user has the pref "Show Attachments Inline" set.
+ // Returns FALSE if the setting is "Show Attachments as Links"
+ bool GetShowAttachmentsInline();
+ // Returns true if all parts are inline, false otherwise. Does not generate
+ // anything.
+ bool PreflightCheckAllInline();
+
+ // Helpers
+ nsCString& GetUID() { return m_UID; }
+ nsCString& GetUID_validity() { return m_UID_validity; }
+ nsCString const& GetFolderName() const { return m_folderName; }
+ char* GetGeneratingPart() { return m_generatingPart; }
+ // Returns true if this is in the process of being generated,
+ // so we don't re-enter
+ bool IsBeingGenerated() { return m_isBeingGenerated; }
+ bool IsShellCached() { return m_cached; }
+ void SetIsCached(bool isCached) { m_cached = isCached; }
+ bool GetGeneratingWholeMessage() { return m_generatingWholeMessage; }
+ IMAP_ContentModifiedType GetContentModified() { return m_contentModified; }
+
+ protected:
+ virtual ~nsImapBodyShell();
+
+ nsIMAPBodypartMessage* m_message;
+
+ // Array of pipelined part prefetches.
+ nsTArray<nsIMAPMessagePartID> m_prefetchQueue;
+
+ bool m_isValid;
+ nsCString m_UID; // UID of this message
+ nsCString m_UID_validity; // appended UID and UID-validity of this message
+ nsCString m_folderName; // folder that contains this message
+ char* m_generatingPart; // If a specific part is being generated, this is it.
+ // Otherwise, NULL.
+ bool m_isBeingGenerated; // true if this body shell is in the process of
+ // being generated
+ bool m_cached; // Whether or not this shell is cached
+ bool m_generatingWholeMessage; // whether or not we are generating the whole
+ // (non-MPOD) message Set to false if we are
+ // generating by parts
+ // under what conditions the content has been modified.
+ // Either IMAP_CONTENT_MODIFIED_VIEW_INLINE or
+ // IMAP_CONTENT_MODIFIED_VIEW_AS_LINKS
+ IMAP_ContentModifiedType m_contentModified;
+};
+
+// This class caches shells, so we don't have to always go and re-fetch them.
+// This does not cache any of the filled-in inline parts; those are cached
+// individually in the libnet memory cache. (ugh, how will we do that?) Since
+// we'll only be retrieving shells for messages over a given size, and since the
+// shells themselves won't be very large, this cache will not grow very big
+// (relatively) and should handle most common usage scenarios.
+
+// A body cache is associated with a given host, spanning folders, so
+// it uses both UID and UIDVALIDITY .
+
+class nsImapBodyShellCache {
+ public:
+ nsImapBodyShellCache();
+
+ // Adds shell to cache, possibly ejecting
+ // another entry based on scheme in EjectEntry().
+ void AddShellToCache(nsImapBodyShell* shell);
+ // Looks up a shell in the cache given the message's UID.
+ nsImapBodyShell* FindShellForUID(nsACString const& UID,
+ nsACString const& mailboxName,
+ IMAP_ContentModifiedType modType);
+ void Clear();
+
+ protected:
+ static constexpr int kMaxEntries = 20;
+ // Chooses an entry to eject; deletes that entry; and ejects it from the
+ // cache, clearing up a new space.
+ void EjectEntry();
+
+ nsTArray<nsImapBodyShell*> m_shellList;
+ // For quick lookup based on UID
+ nsRefPtrHashtable<nsCStringHashKey, nsImapBodyShell> m_shellHash;
+};
+
+#endif // IMAPBODY_H
diff --git a/comm/mailnews/imap/src/nsImapCore.h b/comm/mailnews/imap/src/nsImapCore.h
new file mode 100644
index 0000000000..4c4e213d45
--- /dev/null
+++ b/comm/mailnews/imap/src/nsImapCore.h
@@ -0,0 +1,191 @@
+/* -*- 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/. */
+
+// clang-format off
+#ifndef _nsImapCore_H_
+#define _nsImapCore_H_
+
+#include "MailNewsTypes.h"
+#include "nsString.h"
+
+/* imap message flags */
+typedef uint16_t imapMessageFlagsType;
+
+/* used for communication between imap thread and event sinks */
+#define kNoFlags 0x00 /* RFC flags */
+#define kMarked 0x01
+#define kUnmarked 0x02
+#define kNoinferiors 0x04
+#define kNoselect 0x08
+#define kImapTrash 0x10 /* Navigator flag */
+#define kJustExpunged 0x20 /* This update is a post expunge url update. */
+#define kPersonalMailbox 0x40 /* this mailbox is in the personal namespace */
+#define kPublicMailbox 0x80 /* this mailbox is in the public namespace */
+#define kOtherUsersMailbox 0x100 /* this mailbox is in the other users' namespace */
+#define kNameSpace 0x200 /* this mailbox IS a namespace */
+#define kNewlyCreatedFolder 0x400 /* this folder was just created */
+#define kImapDrafts 0x800 /* XLIST says this is the drafts folder */
+#define kImapSpam 0x1000 /* XLIST says this is the spam folder */
+#define kImapSent 0x2000 /* XLIST says this is the sent folder */
+#define kImapInbox 0x4000 /* XLIST says this is the INBOX folder */
+#define kImapAllMail 0x8000 /* XLIST says this is AllMail (GMail) */
+#define kImapXListTrash 0x10000 /* XLIST says this is the trash */
+#define kNonExistent 0x20000 /* RFC 5258, LIST-EXTENDED */
+#define kSubscribed 0x40000 /* RFC 5258, LIST-EXTENDED */
+#define kRemote 0x80000 /* RFC 5258, LIST-EXTENDED */
+#define kHasChildren 0x100000 /* RFC 5258, LIST-EXTENDED */
+#define kHasNoChildren 0x200000 /* RFC 5258, LIST-EXTENDED */
+#define kImapArchive 0x400000 /* RFC 5258, LIST-EXTENDED */
+
+/* flags for individual messages */
+/* currently the ui only offers \Seen and \Flagged */
+#define kNoImapMsgFlag 0x0000
+#define kImapMsgSeenFlag 0x0001
+#define kImapMsgAnsweredFlag 0x0002
+#define kImapMsgFlaggedFlag 0x0004
+#define kImapMsgDeletedFlag 0x0008
+#define kImapMsgDraftFlag 0x0010
+#define kImapMsgRecentFlag 0x0020
+#define kImapMsgForwardedFlag 0x0040 /* Not always supported, check mailbox folder */
+#define kImapMsgMDNSentFlag 0x0080 /* Not always supported. check mailbox folder */
+#define kImapMsgCustomKeywordFlag 0x0100 /* this msg has a custom keyword */
+#define kImapMsgSupportMDNSentFlag 0x2000
+#define kImapMsgSupportForwardedFlag 0x4000
+/**
+ * We use a separate xlist trash flag so we can prefer the GMail trash
+ * over an existing Trash folder we may have created.
+ */
+#define kImapMsgSupportUserFlag 0x8000
+/* This seems to be the most cost effective way of
+* piggying back the server support user flag info.
+*/
+
+/* if a url creator does not know the hierarchyDelimiter, use this */
+#define kOnlineHierarchySeparatorUnknown '^'
+#define kOnlineHierarchySeparatorNil '|'
+
+#define IMAP_URL_TOKEN_SEPARATOR ">"
+#define kUidUnknown -1
+// Special initial value meaning ACLs need to be loaded from DB.
+#define kAclInvalid ((uint32_t) -1)
+
+// this has to do with Mime Parts on Demand. It used to live in net.h
+// I'm not sure where this will live, but here is OK temporarily
+typedef enum {
+ IMAP_CONTENT_NOT_MODIFIED = 0,
+ IMAP_CONTENT_MODIFIED_VIEW_INLINE,
+ IMAP_CONTENT_MODIFIED_VIEW_AS_LINKS,
+ IMAP_CONTENT_FORCE_CONTENT_NOT_MODIFIED
+} IMAP_ContentModifiedType;
+
+// I think this should really go in an imap.h equivalent file
+typedef enum {
+ kPersonalNamespace = 0,
+ kOtherUsersNamespace,
+ kPublicNamespace,
+ kDefaultNamespace,
+ kUnknownNamespace
+} EIMAPNamespaceType;
+
+
+/**
+ * IMAP server feature, mostly CAPABILITY responses
+ *
+ * one of the cap flags below
+ */
+typedef uint64_t eIMAPCapabilityFlag;
+/**
+ * IMAP server features, mostly CAPABILITY responses
+ *
+ * any set of the cap flags below, i.e.
+ * i.e. 0, 1 or more |eIMAPCapabilityFlag|.
+ */
+typedef uint64_t eIMAPCapabilityFlags;
+
+const eIMAPCapabilityFlag kCapabilityUndefined = 0x00000000;
+const eIMAPCapabilityFlag kCapabilityDefined = 0x00000001;
+const eIMAPCapabilityFlag kHasAuthLoginCapability = 0x00000002; /* AUTH LOGIN (not the same as kHasAuthOldLoginCapability) */
+const eIMAPCapabilityFlag kHasAuthOldLoginCapability = 0x00000004; /* original IMAP login method */
+const eIMAPCapabilityFlag kHasXSenderCapability = 0x00000008;
+const eIMAPCapabilityFlag kIMAP4Capability = 0x00000010; /* RFC1734 */
+const eIMAPCapabilityFlag kIMAP4rev1Capability = 0x00000020; /* RFC2060 */
+const eIMAPCapabilityFlag kIMAP4other = 0x00000040; /* future rev?? */
+const eIMAPCapabilityFlag kNoHierarchyRename = 0x00000080; /* no hierarchy rename */
+const eIMAPCapabilityFlag kACLCapability = 0x00000100; /* ACL extension */
+const eIMAPCapabilityFlag kNamespaceCapability = 0x00000200; /* IMAP4 Namespace Extension */
+const eIMAPCapabilityFlag kHasIDCapability = 0x00000400; /* client user agent id extension */
+const eIMAPCapabilityFlag kXServerInfoCapability = 0x00000800; /* XSERVERINFO extension for admin urls */
+const eIMAPCapabilityFlag kHasAuthPlainCapability = 0x00001000; /* new form of auth plain base64 login */
+const eIMAPCapabilityFlag kUidplusCapability = 0x00002000; /* RFC 2359 UIDPLUS extension */
+const eIMAPCapabilityFlag kLiteralPlusCapability = 0x00004000; /* RFC 2088 LITERAL+ extension */
+const eIMAPCapabilityFlag kAOLImapCapability = 0x00008000; /* aol imap extensions */
+const eIMAPCapabilityFlag kHasLanguageCapability = 0x00010000; /* language extensions */
+const eIMAPCapabilityFlag kHasCRAMCapability = 0x00020000; /* CRAM auth extension */
+const eIMAPCapabilityFlag kQuotaCapability = 0x00040000; /* RFC 2087 quota extension */
+const eIMAPCapabilityFlag kHasIdleCapability = 0x00080000; /* RFC 2177 idle extension */
+const eIMAPCapabilityFlag kHasAuthNTLMCapability = 0x00100000; /* AUTH NTLM extension */
+const eIMAPCapabilityFlag kHasAuthMSNCapability = 0x00200000; /* AUTH MSN extension */
+const eIMAPCapabilityFlag kHasStartTLSCapability = 0x00400000; /* STARTTLS support */
+const eIMAPCapabilityFlag kHasAuthNoneCapability = 0x00800000; /* needs no login */
+const eIMAPCapabilityFlag kHasAuthGssApiCapability = 0x01000000; /* GSSAPI AUTH */
+const eIMAPCapabilityFlag kHasCondStoreCapability = 0x02000000; /* RFC 3551 CondStore extension */
+const eIMAPCapabilityFlag kHasEnableCapability = 0x04000000; /* RFC 5161 ENABLE extension */
+const eIMAPCapabilityFlag kHasXListCapability = 0x08000000; /* XLIST extension */
+const eIMAPCapabilityFlag kHasCompressDeflateCapability = 0x10000000; /* RFC 4978 COMPRESS extension */
+const eIMAPCapabilityFlag kHasAuthExternalCapability = 0x20000000; /* RFC 2222 SASL AUTH EXTERNAL */
+const eIMAPCapabilityFlag kHasMoveCapability = 0x40000000; /* Proposed MOVE RFC */
+const eIMAPCapabilityFlag kHasHighestModSeqCapability = 0x80000000; /* Subset of RFC 3551 */
+// above are 32bit; below start the uint64_t bits 33-64
+const eIMAPCapabilityFlag kHasListExtendedCapability = 0x100000000LL; /* RFC 5258 */
+const eIMAPCapabilityFlag kHasSpecialUseCapability = 0x200000000LL; /* RFC 6154: Sent, Draft etc. folders */
+const eIMAPCapabilityFlag kGmailImapCapability = 0x400000000LL; /* X-GM-EXT-1 capability extension for gmail */
+const eIMAPCapabilityFlag kHasXOAuth2Capability = 0x800000000LL; /* AUTH XOAUTH2 extension */
+const eIMAPCapabilityFlag kHasClientIDCapability = 0x1000000000LL; /* ClientID capability */
+const eIMAPCapabilityFlag kHasUTF8AcceptCapability = 0x2000000000LL; /* RFC 6855: UTF8=ACCEPT */
+
+
+// this used to be part of the connection object class - maybe we should move it into
+// something similar
+typedef enum {
+ kEveryThingRFC822,
+ kEveryThingRFC822Peek,
+ kHeadersRFC822andUid,
+ kUid,
+ kFlags,
+ kRFC822Size,
+ kRFC822HeadersOnly,
+ kMIMEPart,
+ kMIMEHeader,
+ kBodyStart
+} nsIMAPeFetchFields;
+
+typedef struct _utf_name_struct {
+ bool toUtf7Imap;
+ unsigned char *sourceString;
+ unsigned char *convertedString;
+} utf_name_struct;
+
+typedef struct _ProgressInfo {
+ char16_t *message;
+ int32_t currentProgress;
+ int32_t maxProgress;
+} ProgressInfo;
+
+typedef enum {
+ eContinue,
+ eContinueNew,
+ eListMyChildren,
+ eNewServerDirectory,
+ eCancelled
+} EMailboxDiscoverStatus;
+
+typedef enum {
+ kInvalidateQuota,
+ kStoreQuota,
+ kValidateQuota
+} nsImapQuotaAction;
+
+#endif
+// clang-format on
diff --git a/comm/mailnews/imap/src/nsImapFlagAndUidState.cpp b/comm/mailnews/imap/src/nsImapFlagAndUidState.cpp
new file mode 100644
index 0000000000..cc3217d780
--- /dev/null
+++ b/comm/mailnews/imap/src/nsImapFlagAndUidState.cpp
@@ -0,0 +1,315 @@
+/* -*- 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 "nsImapCore.h"
+#include "nsImapFlagAndUidState.h"
+#include "nsMsgUtils.h"
+#include "prcmon.h"
+#include "nspr.h"
+
+NS_IMPL_ISUPPORTS(nsImapFlagAndUidState, nsIImapFlagAndUidState)
+
+using namespace mozilla;
+
+NS_IMETHODIMP nsImapFlagAndUidState::GetNumberOfMessages(int32_t* result) {
+ if (!result) return NS_ERROR_NULL_POINTER;
+ *result = fUids.Length();
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapFlagAndUidState::GetUidOfMessage(int32_t zeroBasedIndex,
+ uint32_t* aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ PR_CEnterMonitor(this);
+ *aResult = fUids.SafeElementAt(zeroBasedIndex, nsMsgKey_None);
+ PR_CExitMonitor(this);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapFlagAndUidState::HasMessage(uint32_t uid, bool* result) {
+ NS_ENSURE_ARG_POINTER(result);
+ *result = fUids.Contains(uid);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapFlagAndUidState::GetMessageFlags(int32_t zeroBasedIndex,
+ uint16_t* aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = fFlags.SafeElementAt(zeroBasedIndex, kNoImapMsgFlag);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapFlagAndUidState::SetMessageFlags(int32_t zeroBasedIndex,
+ unsigned short flags) {
+ if (zeroBasedIndex < (int32_t)fUids.Length()) fFlags[zeroBasedIndex] = flags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapFlagAndUidState::GetNumberOfRecentMessages(
+ int32_t* result) {
+ if (!result) return NS_ERROR_NULL_POINTER;
+
+ PR_CEnterMonitor(this);
+ uint32_t counter = 0;
+ int32_t numUnseenMessages = 0;
+
+ for (counter = 0; counter < fUids.Length(); counter++) {
+ if (fFlags[counter] & kImapMsgRecentFlag) numUnseenMessages++;
+ }
+ PR_CExitMonitor(this);
+
+ *result = numUnseenMessages;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapFlagAndUidState::GetPartialUIDFetch(
+ bool* aPartialUIDFetch) {
+ NS_ENSURE_ARG_POINTER(aPartialUIDFetch);
+ *aPartialUIDFetch = fPartialUIDFetch;
+ return NS_OK;
+}
+
+/* amount to expand for imap entry flags when we need more */
+
+nsImapFlagAndUidState::nsImapFlagAndUidState(int32_t numberOfMessages)
+ : fUids(numberOfMessages),
+ fFlags(numberOfMessages),
+ m_customFlagsHash(10),
+ m_customAttributesHash(10),
+ mLock("nsImapFlagAndUidState.mLock") {
+ fSupportedUserFlags = 0;
+ fNumberDeleted = 0;
+ fPartialUIDFetch = true;
+ fStartCapture = false;
+ fNumAdded = 0;
+}
+
+nsImapFlagAndUidState::~nsImapFlagAndUidState() {}
+
+NS_IMETHODIMP
+nsImapFlagAndUidState::OrSupportedUserFlags(uint16_t flags) {
+ fSupportedUserFlags |= flags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapFlagAndUidState::GetSupportedUserFlags(uint16_t* aFlags) {
+ NS_ENSURE_ARG_POINTER(aFlags);
+ *aFlags = fSupportedUserFlags;
+ return NS_OK;
+}
+
+// we need to reset our flags, (re-read all) but chances are the memory
+// allocation needed will be very close to what we were already using
+
+NS_IMETHODIMP nsImapFlagAndUidState::Reset() {
+ PR_CEnterMonitor(this);
+ fNumberDeleted = 0;
+ m_customFlagsHash.Clear();
+ fUids.Clear();
+ fFlags.Clear();
+ fPartialUIDFetch = true;
+ fStartCapture = false;
+ fNumAdded = 0;
+ PR_CExitMonitor(this);
+ return NS_OK;
+}
+
+// Remove (expunge) a message from our array, since now it is gone for good
+
+NS_IMETHODIMP nsImapFlagAndUidState::ExpungeByIndex(uint32_t msgIndex) {
+ // protect ourselves in case the server gave us an index key of -1 or 0
+ if ((int32_t)msgIndex <= 0) return NS_ERROR_INVALID_ARG;
+
+ if ((uint32_t)fUids.Length() < msgIndex) return NS_ERROR_INVALID_ARG;
+
+ PR_CEnterMonitor(this);
+ msgIndex--; // msgIndex is 1-relative
+ if (fFlags[msgIndex] &
+ kImapMsgDeletedFlag) // see if we already had counted this one as deleted
+ fNumberDeleted--;
+ fUids.RemoveElementAt(msgIndex);
+ fFlags.RemoveElementAt(msgIndex);
+ PR_CExitMonitor(this);
+ return NS_OK;
+}
+
+// adds to sorted list, protects against duplicates and going past array bounds.
+NS_IMETHODIMP nsImapFlagAndUidState::AddUidFlagPair(uint32_t uid,
+ imapMessageFlagsType flags,
+ uint32_t zeroBasedIndex) {
+ if (uid == nsMsgKey_None) // ignore uid of -1
+ return NS_OK;
+ // check for potential overflow in buffer size for uid array
+ if (zeroBasedIndex > 0x3FFFFFFF) return NS_ERROR_INVALID_ARG;
+ PR_CEnterMonitor(this);
+ // make sure there is room for this pair
+ if (zeroBasedIndex >= fUids.Length()) {
+ int32_t sizeToGrowBy = zeroBasedIndex - fUids.Length() + 1;
+ fUids.InsertElementsAt(fUids.Length(), sizeToGrowBy, 0);
+ fFlags.InsertElementsAt(fFlags.Length(), sizeToGrowBy, 0);
+ if (fStartCapture) {
+ // A new partial (CONDSTORE/CHANGEDSINCE) fetch response is occurring
+ // so need to start the count of number of uid/flag combos added.
+ fNumAdded = 0;
+ fStartCapture = false;
+ }
+ fNumAdded++;
+ }
+
+ fUids[zeroBasedIndex] = uid;
+ fFlags[zeroBasedIndex] = flags;
+ if (flags & kImapMsgDeletedFlag) fNumberDeleted++;
+ PR_CExitMonitor(this);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapFlagAndUidState::GetNumberOfDeletedMessages(
+ int32_t* numDeletedMessages) {
+ NS_ENSURE_ARG_POINTER(numDeletedMessages);
+ *numDeletedMessages = NumberOfDeletedMessages();
+ return NS_OK;
+}
+
+int32_t nsImapFlagAndUidState::NumberOfDeletedMessages() {
+ return fNumberDeleted;
+}
+
+// since the uids are sorted, start from the back (rb)
+
+uint32_t nsImapFlagAndUidState::GetHighestNonDeletedUID() {
+ uint32_t msgIndex = fUids.Length();
+ do {
+ if (msgIndex <= 0) return (0);
+ msgIndex--;
+ if (fUids[msgIndex] && !(fFlags[msgIndex] & kImapMsgDeletedFlag))
+ return fUids[msgIndex];
+ } while (msgIndex > 0);
+ return 0;
+}
+
+// Has the user read the last message here ? Used when we first open the inbox
+// to see if there really is new mail there.
+
+bool nsImapFlagAndUidState::IsLastMessageUnseen() {
+ uint32_t msgIndex = fUids.Length();
+
+ if (msgIndex <= 0) return false;
+ msgIndex--;
+ // if last message is deleted, it was probably filtered the last time around
+ if (fUids[msgIndex] &&
+ (fFlags[msgIndex] & (kImapMsgSeenFlag | kImapMsgDeletedFlag)))
+ return false;
+ return true;
+}
+
+// find a message flag given a key with non-recursive binary search, since some
+// folders may have thousand of messages, once we find the key set its index, or
+// the index of where the key should be inserted
+
+imapMessageFlagsType nsImapFlagAndUidState::GetMessageFlagsFromUID(
+ uint32_t uid, bool* foundIt, int32_t* ndx) {
+ PR_CEnterMonitor(this);
+ *ndx = (int32_t)fUids.IndexOfFirstElementGt(uid) - 1;
+ *foundIt = *ndx >= 0 && fUids[*ndx] == uid;
+ imapMessageFlagsType retFlags = (*foundIt) ? fFlags[*ndx] : kNoImapMsgFlag;
+ PR_CExitMonitor(this);
+ return retFlags;
+}
+
+NS_IMETHODIMP
+nsImapFlagAndUidState::GetMessageFlagsByUid(uint32_t uid,
+ imapMessageFlagsType* retFlags) {
+ PR_CEnterMonitor(this);
+ int32_t ndx = (int32_t)fUids.IndexOf(uid);
+ bool foundIt = ndx >= 0;
+ *retFlags = foundIt ? fFlags[ndx] : kNoImapMsgFlag;
+ PR_CExitMonitor(this);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapFlagAndUidState::AddUidCustomFlagPair(
+ uint32_t uid, const char* customFlag) {
+ if (!customFlag) return NS_OK;
+
+ MutexAutoLock mon(mLock);
+ nsCString ourCustomFlags;
+ nsCString oldValue;
+ if (m_customFlagsHash.Get(uid, &oldValue)) {
+ // We'll store multiple keys as space-delimited since space is not
+ // a valid character in a keyword. First, we need to look for the
+ // customFlag in the existing flags;
+ nsDependentCString customFlagString(customFlag);
+ int32_t existingCustomFlagPos = oldValue.Find(customFlagString);
+ uint32_t customFlagLen = customFlagString.Length();
+ while (existingCustomFlagPos != kNotFound) {
+ // if existing flags ends with this exact flag, or flag + ' '
+ // and the flag is at the beginning of the string or there is ' ' + flag
+ // then we have this flag already;
+ if (((oldValue.Length() == existingCustomFlagPos + customFlagLen) ||
+ (oldValue.CharAt(existingCustomFlagPos + customFlagLen) == ' ')) &&
+ ((existingCustomFlagPos == 0) ||
+ (oldValue.CharAt(existingCustomFlagPos - 1) == ' ')))
+ return NS_OK;
+ // else, advance to next flag
+ existingCustomFlagPos = oldValue.Find(
+ customFlagString, existingCustomFlagPos + customFlagLen);
+ }
+ ourCustomFlags.Assign(oldValue);
+ ourCustomFlags.Append(' ');
+ ourCustomFlags.Append(customFlag);
+ m_customFlagsHash.Remove(uid);
+ } else {
+ ourCustomFlags.Assign(customFlag);
+ }
+ m_customFlagsHash.InsertOrUpdate(uid, ourCustomFlags);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapFlagAndUidState::GetCustomFlags(uint32_t uid,
+ char** customFlags) {
+ MutexAutoLock mon(mLock);
+ nsCString value;
+ if (m_customFlagsHash.Get(uid, &value)) {
+ *customFlags = NS_xstrdup(value.get());
+ return (*customFlags) ? NS_OK : NS_ERROR_FAILURE;
+ }
+ *customFlags = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapFlagAndUidState::ClearCustomFlags(uint32_t uid) {
+ MutexAutoLock mon(mLock);
+ m_customFlagsHash.Remove(uid);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapFlagAndUidState::SetCustomAttribute(
+ uint32_t aUid, const nsACString& aCustomAttributeName,
+ const nsACString& aCustomAttributeValue) {
+ nsCString key;
+ key.AppendInt((int64_t)aUid);
+ key.Append(aCustomAttributeName);
+ nsCString value;
+ value.Assign(aCustomAttributeValue);
+ m_customAttributesHash.InsertOrUpdate(key, value);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapFlagAndUidState::GetCustomAttribute(
+ uint32_t aUid, const nsACString& aCustomAttributeName,
+ nsACString& aCustomAttributeValue) {
+ nsCString key;
+ key.AppendInt((int64_t)aUid);
+ key.Append(aCustomAttributeName);
+ nsCString val = m_customAttributesHash.Get(key);
+ aCustomAttributeValue.Assign(val);
+ return NS_OK;
+}
diff --git a/comm/mailnews/imap/src/nsImapFlagAndUidState.h b/comm/mailnews/imap/src/nsImapFlagAndUidState.h
new file mode 100644
index 0000000000..b8d2fa2b45
--- /dev/null
+++ b/comm/mailnews/imap/src/nsImapFlagAndUidState.h
@@ -0,0 +1,56 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsImapFlagAndUidState_h___
+#define nsImapFlagAndUidState_h___
+
+#include "MailNewsTypes.h"
+#include "nsIImapFlagAndUidState.h"
+#include "nsImapCore.h"
+#include "nsTArray.h"
+#include "mozilla/Mutex.h"
+
+const int32_t kImapFlagAndUidStateSize = 100;
+
+#include "nsTHashMap.h"
+
+class nsImapFlagAndUidState : public nsIImapFlagAndUidState {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ explicit nsImapFlagAndUidState(int numberOfMessages);
+
+ NS_DECL_NSIIMAPFLAGANDUIDSTATE
+
+ int32_t NumberOfDeletedMessages();
+
+ imapMessageFlagsType GetMessageFlagsFromUID(uint32_t uid, bool* foundIt,
+ int32_t* ndx);
+
+ bool IsLastMessageUnseen(void);
+ bool GetPartialUIDFetch() { return fPartialUIDFetch; }
+ void SetPartialUIDFetch(bool isPartial) { fPartialUIDFetch = isPartial; }
+ uint32_t GetHighestNonDeletedUID();
+ uint16_t GetSupportedUserFlags() { return fSupportedUserFlags; }
+ void StartCapture() { fStartCapture = true; }
+ uint32_t GetNumAdded() { return fNumAdded; }
+
+ private:
+ virtual ~nsImapFlagAndUidState();
+
+ nsTArray<nsMsgKey> fUids;
+ nsTArray<imapMessageFlagsType> fFlags;
+ // Hash table, mapping uids to extra flags
+ nsTHashMap<nsUint32HashKey, nsCString> m_customFlagsHash;
+ // Hash table, mapping UID+customAttributeName to customAttributeValue.
+ nsTHashMap<nsCStringHashKey, nsCString> m_customAttributesHash;
+ uint16_t fSupportedUserFlags;
+ int32_t fNumberDeleted;
+ bool fPartialUIDFetch;
+ uint32_t fNumAdded;
+ bool fStartCapture;
+ mozilla::Mutex mLock;
+};
+
+#endif
diff --git a/comm/mailnews/imap/src/nsImapGenericParser.cpp b/comm/mailnews/imap/src/nsImapGenericParser.cpp
new file mode 100644
index 0000000000..009c7c1e5a
--- /dev/null
+++ b/comm/mailnews/imap/src/nsImapGenericParser.cpp
@@ -0,0 +1,407 @@
+/* -*- 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 "nsImapGenericParser.h"
+#include "nsString.h"
+
+////////////////// nsImapGenericParser /////////////////////////
+
+nsImapGenericParser::nsImapGenericParser()
+ : fNextToken(nullptr),
+ fCurrentLine(nullptr),
+ fLineOfTokens(nullptr),
+ fStartOfLineOfTokens(nullptr),
+ fCurrentTokenPlaceHolder(nullptr),
+ fAtEndOfLine(false),
+ fParserState(stateOK) {}
+
+nsImapGenericParser::~nsImapGenericParser() {
+ PR_FREEIF(fCurrentLine);
+ PR_FREEIF(fStartOfLineOfTokens);
+}
+
+void nsImapGenericParser::HandleMemoryFailure() { SetConnected(false); }
+
+void nsImapGenericParser::ResetLexAnalyzer() {
+ PR_FREEIF(fCurrentLine);
+ PR_FREEIF(fStartOfLineOfTokens);
+
+ fNextToken = fCurrentLine = fLineOfTokens = fStartOfLineOfTokens =
+ fCurrentTokenPlaceHolder = nullptr;
+ fAtEndOfLine = false;
+}
+
+bool nsImapGenericParser::LastCommandSuccessful() {
+ return fParserState == stateOK;
+}
+
+void nsImapGenericParser::SetSyntaxError(bool error, const char* msg) {
+ if (error)
+ fParserState |= stateSyntaxErrorFlag;
+ else
+ fParserState &= ~stateSyntaxErrorFlag;
+ NS_ASSERTION(!error, "syntax error in generic parser");
+}
+
+void nsImapGenericParser::SetConnected(bool connected) {
+ if (connected)
+ fParserState &= ~stateDisconnectedFlag;
+ else
+ fParserState |= stateDisconnectedFlag;
+}
+
+void nsImapGenericParser::skip_to_CRLF() {
+ while (Connected() && !fAtEndOfLine) AdvanceToNextToken();
+}
+
+// fNextToken initially should point to
+// a string after the initial open paren ("(")
+// After this call, fNextToken points to the
+// first character after the matching close
+// paren. Only call AdvanceToNextToken() to get the NEXT
+// token after the one returned in fNextToken.
+void nsImapGenericParser::skip_to_close_paren() {
+ int numberOfCloseParensNeeded = 1;
+ while (ContinueParse()) {
+ // go through fNextToken, account for nested parens
+ const char* loc;
+ for (loc = fNextToken; loc && *loc; loc++) {
+ if (*loc == '(')
+ numberOfCloseParensNeeded++;
+ else if (*loc == ')') {
+ numberOfCloseParensNeeded--;
+ if (numberOfCloseParensNeeded == 0) {
+ fNextToken = loc + 1;
+ if (!fNextToken || !*fNextToken) AdvanceToNextToken();
+ return;
+ }
+ } else if (*loc == '{' || *loc == '"') {
+ // quoted or literal
+ fNextToken = loc;
+ char* a = CreateString();
+ PR_FREEIF(a);
+ break; // move to next token
+ }
+ }
+ if (ContinueParse()) AdvanceToNextToken();
+ }
+}
+
+void nsImapGenericParser::AdvanceToNextToken() {
+ if (!fCurrentLine || fAtEndOfLine) AdvanceToNextLine();
+ if (Connected()) {
+ if (!fStartOfLineOfTokens) {
+ // this is the first token of the line; setup tokenizer now
+ fStartOfLineOfTokens = PL_strdup(fCurrentLine);
+ if (!fStartOfLineOfTokens) {
+ HandleMemoryFailure();
+ return;
+ }
+ fLineOfTokens = fStartOfLineOfTokens;
+ fCurrentTokenPlaceHolder = fStartOfLineOfTokens;
+ }
+ fNextToken = NS_strtok(WHITESPACE, &fCurrentTokenPlaceHolder);
+ if (!fNextToken) {
+ fAtEndOfLine = true;
+ fNextToken = CRLF;
+ }
+ }
+}
+
+void nsImapGenericParser::AdvanceToNextLine() {
+ PR_FREEIF(fCurrentLine);
+ PR_FREEIF(fStartOfLineOfTokens);
+
+ bool ok = GetNextLineForParser(&fCurrentLine);
+ if (!ok) {
+ SetConnected(false);
+ fStartOfLineOfTokens = nullptr;
+ fLineOfTokens = nullptr;
+ fCurrentTokenPlaceHolder = nullptr;
+ fAtEndOfLine = true;
+ fNextToken = CRLF;
+ } else if (!fCurrentLine) {
+ HandleMemoryFailure();
+ } else {
+ fNextToken = nullptr;
+ // determine if there are any tokens (without calling AdvanceToNextToken);
+ // otherwise we are already at end of line
+ NS_ASSERTION(strlen(WHITESPACE) == 3, "assume 3 chars of whitespace");
+ char* firstToken = fCurrentLine;
+ while (*firstToken &&
+ (*firstToken == WHITESPACE[0] || *firstToken == WHITESPACE[1] ||
+ *firstToken == WHITESPACE[2]))
+ firstToken++;
+ fAtEndOfLine = (*firstToken == '\0');
+ }
+}
+
+// advances |fLineOfTokens| by |bytesToAdvance| bytes
+void nsImapGenericParser::AdvanceTokenizerStartingPoint(
+ int32_t bytesToAdvance) {
+ NS_ASSERTION(bytesToAdvance >= 0, "bytesToAdvance must not be negative");
+ if (!fStartOfLineOfTokens) {
+ AdvanceToNextToken(); // the tokenizer was not yet initialized, do it now
+ if (!fStartOfLineOfTokens) return;
+ }
+
+ if (!fStartOfLineOfTokens) return;
+ // The last call to AdvanceToNextToken() cleared the token separator to '\0'
+ // iff |fCurrentTokenPlaceHolder|. We must recover this token separator now.
+ if (fCurrentTokenPlaceHolder) {
+ int endTokenOffset = fCurrentTokenPlaceHolder - fStartOfLineOfTokens - 1;
+ if (endTokenOffset >= 0)
+ fStartOfLineOfTokens[endTokenOffset] = fCurrentLine[endTokenOffset];
+ }
+
+ NS_ASSERTION(bytesToAdvance + (fLineOfTokens - fStartOfLineOfTokens) <=
+ (int32_t)strlen(fCurrentLine),
+ "cannot advance beyond end of fLineOfTokens");
+ fLineOfTokens += bytesToAdvance;
+ fCurrentTokenPlaceHolder = fLineOfTokens;
+}
+
+// RFC3501: astring = 1*ASTRING-CHAR / string
+// string = quoted / literal
+// This function leaves us off with fCurrentTokenPlaceHolder immediately after
+// the end of the Astring. Call AdvanceToNextToken() to get the token after it.
+char* nsImapGenericParser::CreateAstring() {
+ if (*fNextToken == '{') return CreateLiteral(); // literal
+ if (*fNextToken == '"') return CreateQuoted(); // quoted
+ return CreateAtom(true); // atom
+}
+
+// Create an atom
+// This function does not advance the parser.
+// Call AdvanceToNextToken() to get the next token after the atom.
+// RFC3501: atom = 1*ATOM-CHAR
+// ASTRING-CHAR = ATOM-CHAR / resp-specials
+// ATOM-CHAR = <any CHAR except atom-specials>
+// atom-specials = "(" / ")" / "{" / SP / CTL / list-wildcards /
+// quoted-specials / resp-specials
+// list-wildcards = "%" / "*"
+// quoted-specials = DQUOTE / "\"
+// resp-specials = "]"
+// "Characters are 7-bit US-ASCII unless otherwise specified." [RFC3501, 1.2.]
+char* nsImapGenericParser::CreateAtom(bool isAstring) {
+ char* rv = PL_strdup(fNextToken);
+ if (!rv) {
+ HandleMemoryFailure();
+ return nullptr;
+ }
+ // We wish to stop at the following characters (in decimal ascii)
+ // 1-31 (CTL), 32 (SP), 34 '"', 37 '%', 40-42 "()*", 92 '\\', 123 '{'
+ // also, ']' is only allowed in astrings
+ char* last = rv;
+ char c = *last;
+ while ((c > 42 || c == 33 || c == 35 || c == 36 || c == 38 || c == 39) &&
+ c != '\\' && c != '{' && (isAstring || c != ']'))
+ c = *++last;
+ if (rv == last) {
+ SetSyntaxError(true, "no atom characters found");
+ PL_strfree(rv);
+ return nullptr;
+ }
+ if (*last) {
+ // not the whole token was consumed
+ *last = '\0';
+ AdvanceTokenizerStartingPoint((fNextToken - fLineOfTokens) + (last - rv));
+ }
+ return rv;
+}
+
+// CreateNilString return either NULL (for "NIL") or a string
+// Call with fNextToken pointing to the thing which we think is the nilstring.
+// This function leaves us off with fCurrentTokenPlaceHolder immediately after
+// the end of the string.
+// Regardless of type, call AdvanceToNextToken() to get the token after it.
+// RFC3501: nstring = string / nil
+// nil = "NIL"
+char* nsImapGenericParser::CreateNilString() {
+ if (!PL_strncasecmp(fNextToken, "NIL", 3)) {
+ // check if there is text after "NIL" in fNextToken,
+ // equivalent handling as in CreateQuoted
+ if (fNextToken[3])
+ AdvanceTokenizerStartingPoint((fNextToken - fLineOfTokens) + 3);
+ return NULL;
+ }
+ return CreateString();
+}
+
+// Create a string, which can either be quoted or literal,
+// but not an atom.
+// This function leaves us off with fCurrentTokenPlaceHolder immediately after
+// the end of the String. Call AdvanceToNextToken() to get the token after it.
+char* nsImapGenericParser::CreateString() {
+ if (*fNextToken == '{') {
+ char* rv = CreateLiteral(); // literal
+ return (rv);
+ }
+ if (*fNextToken == '"') {
+ char* rv = CreateQuoted(); // quoted
+ return (rv);
+ }
+ SetSyntaxError(true, "string does not start with '{' or '\"'");
+ return NULL;
+}
+
+// This function sets fCurrentTokenPlaceHolder immediately after the end of the
+// closing quote. Call AdvanceToNextToken() to get the token after it.
+// QUOTED_CHAR ::= <any TEXT_CHAR except quoted_specials> /
+// "\" quoted_specials
+// TEXT_CHAR ::= <any CHAR except CR and LF>
+// quoted_specials ::= <"> / "\"
+// Note that according to RFC 1064 and RFC 2060, CRs and LFs are not allowed
+// inside a quoted string. It is sufficient to read from the current line only.
+char* nsImapGenericParser::CreateQuoted(bool /*skipToEnd*/) {
+ // one char past opening '"'
+ char* currentChar = fCurrentLine + (fNextToken - fStartOfLineOfTokens) + 1;
+
+ int escapeCharsCut = 0;
+ nsCString returnString(currentChar);
+ int charIndex;
+ for (charIndex = 0; returnString.CharAt(charIndex) != '"'; charIndex++) {
+ if (!returnString.CharAt(charIndex)) {
+ SetSyntaxError(true, "no closing '\"' found in quoted");
+ return nullptr;
+ }
+ if (returnString.CharAt(charIndex) == '\\') {
+ // eat the escape character, but keep the escaped character
+ returnString.Cut(charIndex, 1);
+ escapeCharsCut++;
+ }
+ }
+ // +2 because of the start and end quotes
+ AdvanceTokenizerStartingPoint((fNextToken - fLineOfTokens) + charIndex +
+ escapeCharsCut + 2);
+
+ returnString.SetLength(charIndex);
+ return ToNewCString(returnString);
+}
+
+// This function leaves us off with fCurrentTokenPlaceHolder immediately after
+// the end of the literal string. Call AdvanceToNextToken() to get the token
+// after the literal string.
+// RFC3501: literal = "{" number "}" CRLF *CHAR8
+// ; Number represents the number of CHAR8s
+// CHAR8 = %x01-ff
+// ; any OCTET except NUL, %x00
+char* nsImapGenericParser::CreateLiteral() {
+ int32_t numberOfCharsInMessage = atoi(fNextToken + 1);
+ uint32_t numBytes = numberOfCharsInMessage + 1;
+ NS_ASSERTION(numBytes, "overflow!");
+ if (!numBytes) return nullptr;
+ char* returnString = (char*)PR_Malloc(numBytes);
+ if (!returnString) {
+ HandleMemoryFailure();
+ return nullptr;
+ }
+
+ int32_t currentLineLength = 0;
+ int32_t charsReadSoFar = 0;
+ int32_t bytesToCopy = 0;
+ while (charsReadSoFar < numberOfCharsInMessage) {
+ AdvanceToNextLine();
+ if (!ContinueParse()) break;
+
+ currentLineLength = strlen(fCurrentLine);
+ bytesToCopy = (currentLineLength > numberOfCharsInMessage - charsReadSoFar
+ ? numberOfCharsInMessage - charsReadSoFar
+ : currentLineLength);
+ NS_ASSERTION(bytesToCopy, "zero-length line?");
+ memcpy(returnString + charsReadSoFar, fCurrentLine, bytesToCopy);
+ charsReadSoFar += bytesToCopy;
+ }
+
+ if (ContinueParse()) {
+ if (currentLineLength == bytesToCopy) {
+ // We have consumed the entire line.
+ // Consider the input "{4}\r\n" "L1\r\n" " A2\r\n" which is read
+ // line-by-line. Reading an Astring, this should result in "L1\r\n".
+ // Note that the second line is "L1\r\n", where the "\r\n" is part of
+ // the literal. Hence, we now read the next line to ensure that the
+ // next call to AdvanceToNextToken() leads to fNextToken=="A2" in our
+ // example.
+ AdvanceToNextLine();
+ } else
+ AdvanceTokenizerStartingPoint(bytesToCopy);
+ }
+
+ returnString[charsReadSoFar] = 0;
+ return returnString;
+}
+
+// Call this to create a buffer containing all characters within
+// a given set of parentheses.
+// Call this with fNextToken[0]=='(', that is, the open paren
+// of the group.
+// It will allocate and return all characters up to and including the
+// corresponding closing paren, and leave the parser in the right place
+// afterwards.
+char* nsImapGenericParser::CreateParenGroup() {
+ NS_ASSERTION(fNextToken[0] == '(', "we don't have a paren group!");
+
+ int numOpenParens = 0;
+ AdvanceTokenizerStartingPoint(fNextToken - fLineOfTokens);
+
+ // Build up a buffer containing the paren group.
+ nsCString returnString;
+ char* parenGroupStart = fCurrentTokenPlaceHolder;
+ NS_ASSERTION(parenGroupStart[0] == '(', "we don't have a paren group (2)!");
+ while (*fCurrentTokenPlaceHolder) {
+ if (*fCurrentTokenPlaceHolder == '{') // literal
+ {
+ // Ensure it is a properly formatted literal.
+ NS_ASSERTION(!strcmp("}\r\n", fCurrentTokenPlaceHolder +
+ strlen(fCurrentTokenPlaceHolder) - 3),
+ "not a literal");
+
+ // Append previous characters and the "{xx}\r\n" to buffer.
+ returnString.Append(parenGroupStart);
+
+ // Append literal itself.
+ AdvanceToNextToken();
+ if (!ContinueParse()) break;
+ char* lit = CreateLiteral();
+ NS_ASSERTION(lit, "syntax error or out of memory");
+ if (!lit) break;
+ returnString.Append(lit);
+ PR_Free(lit);
+ if (!ContinueParse()) break;
+ parenGroupStart = fCurrentTokenPlaceHolder;
+ } else if (*fCurrentTokenPlaceHolder == '"') // quoted
+ {
+ // Append the _escaped_ version of the quoted string:
+ // just skip it (because the quoted string must be on the same line).
+ AdvanceToNextToken();
+ if (!ContinueParse()) break;
+ char* q = CreateQuoted();
+ if (!q) break;
+ PR_Free(q);
+ if (!ContinueParse()) break;
+ } else {
+ // Append this character to the buffer.
+ char c = *fCurrentTokenPlaceHolder++;
+ if (c == '(')
+ numOpenParens++;
+ else if (c == ')') {
+ numOpenParens--;
+ if (numOpenParens == 0) break;
+ }
+ }
+ }
+
+ if (numOpenParens != 0 || !ContinueParse()) {
+ SetSyntaxError(true, "closing ')' not found in paren group");
+ return nullptr;
+ }
+
+ returnString.Append(parenGroupStart,
+ fCurrentTokenPlaceHolder - parenGroupStart);
+ AdvanceToNextToken();
+ return ToNewCString(returnString);
+}
diff --git a/comm/mailnews/imap/src/nsImapGenericParser.h b/comm/mailnews/imap/src/nsImapGenericParser.h
new file mode 100644
index 0000000000..50abffa5e1
--- /dev/null
+++ b/comm/mailnews/imap/src/nsImapGenericParser.h
@@ -0,0 +1,74 @@
+/* -*- 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/. */
+
+/*
+nsImapGenericParser is the base parser class used by the server parser and body
+shell parser
+*/
+
+#ifndef nsImapGenericParser_H
+#define nsImapGenericParser_H
+
+#define WHITESPACE " \015\012" // token delimiter
+
+class nsImapGenericParser {
+ public:
+ nsImapGenericParser();
+ virtual ~nsImapGenericParser();
+
+ // Add any specific stuff in the derived class
+ virtual bool LastCommandSuccessful();
+
+ bool SyntaxError() { return (fParserState & stateSyntaxErrorFlag) != 0; }
+ bool ContinueParse() { return fParserState == stateOK; }
+ bool Connected() { return !(fParserState & stateDisconnectedFlag); }
+ void SetConnected(bool error);
+
+ protected:
+ // This is a pure virtual member which must be overridden in the derived class
+ // for each different implementation of a nsImapGenericParser.
+ // For instance, one implementation (the nsIMAPServerState) might get the next
+ // line from an open socket, whereas another implementation might just get it
+ // from a buffer somewhere. This fills in nextLine with the buffer, and
+ // returns true if everything is OK. Returns false if there was some error
+ // encountered. In that case, we reset the parser.
+ virtual bool GetNextLineForParser(char** nextLine) = 0;
+
+ virtual void HandleMemoryFailure();
+ void skip_to_CRLF();
+ void skip_to_close_paren();
+ char* CreateString();
+ char* CreateAstring();
+ char* CreateNilString();
+ char* CreateLiteral();
+ char* CreateAtom(bool isAstring = false);
+ char* CreateQuoted(bool skipToEnd = true);
+ char* CreateParenGroup();
+ virtual void SetSyntaxError(bool error, const char* msg);
+
+ void AdvanceToNextToken();
+ void AdvanceToNextLine();
+ void AdvanceTokenizerStartingPoint(int32_t bytesToAdvance);
+ void ResetLexAnalyzer();
+
+ protected:
+ // use with care
+ const char* fNextToken;
+ char* fCurrentLine;
+ char* fLineOfTokens;
+ char* fStartOfLineOfTokens;
+ char* fCurrentTokenPlaceHolder;
+ bool fAtEndOfLine;
+
+ private:
+ enum nsImapGenericParserState {
+ stateOK = 0,
+ stateSyntaxErrorFlag = 0x1,
+ stateDisconnectedFlag = 0x2
+ };
+ uint32_t fParserState;
+};
+
+#endif
diff --git a/comm/mailnews/imap/src/nsImapHostSessionList.cpp b/comm/mailnews/imap/src/nsImapHostSessionList.cpp
new file mode 100644
index 0000000000..fbdbeac231
--- /dev/null
+++ b/comm/mailnews/imap/src/nsImapHostSessionList.cpp
@@ -0,0 +1,595 @@
+/* -*- 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 "nsImapHostSessionList.h"
+#include "nsImapNamespace.h"
+#include "nsIImapIncomingServer.h"
+#include "nsCOMPtr.h"
+#include "nsIMsgIncomingServer.h"
+#include "nsIObserverService.h"
+#include "nsServiceManagerUtils.h"
+#include "nsMsgUtils.h"
+#include "mozilla/Services.h"
+
+nsIMAPHostInfo::nsIMAPHostInfo(const char* serverKey,
+ nsIImapIncomingServer* server) {
+ fServerKey = serverKey;
+ NS_ASSERTION(server, "*** Fatal null imap incoming server...");
+ server->GetServerDirectory(fOnlineDir);
+ fNextHost = NULL;
+ fCapabilityFlags = kCapabilityUndefined;
+ fHierarchyDelimiters = NULL;
+#ifdef DEBUG_bienvenu1
+ fHaveWeEverDiscoveredFolders =
+ true; // try this, see what bad happens - we'll need to
+ // figure out a way to make new accounts have it be false
+#else
+ fHaveWeEverDiscoveredFolders = false; // try this, see what bad happens
+#endif
+ fDiscoveryForHostInProgress = false;
+ fCanonicalOnlineSubDir = NULL;
+ fNamespaceList = nsImapNamespaceList::CreatensImapNamespaceList();
+ fUsingSubscription = true;
+ server->GetUsingSubscription(&fUsingSubscription);
+ fOnlineTrashFolderExists = false;
+ fShouldAlwaysListInbox = true;
+ fPasswordVerifiedOnline = false;
+ fDeleteIsMoveToTrash = true;
+ fShowDeletedMessages = false;
+ fGotNamespaces = false;
+ fHaveAdminURL = false;
+ fNamespacesOverridable = true;
+ server->GetOverrideNamespaces(&fNamespacesOverridable);
+ fTempNamespaceList = nsImapNamespaceList::CreatensImapNamespaceList();
+}
+
+nsIMAPHostInfo::~nsIMAPHostInfo() {
+ PR_Free(fHierarchyDelimiters);
+ delete fNamespaceList;
+ delete fTempNamespaceList;
+}
+
+NS_IMPL_ISUPPORTS(nsImapHostSessionList, nsIImapHostSessionList, nsIObserver,
+ nsISupportsWeakReference)
+
+nsImapHostSessionList::nsImapHostSessionList() {
+ gCachedHostInfoMonitor = PR_NewMonitor(/* "accessing-hostlist-monitor"*/);
+ fHostInfoList = nullptr;
+}
+
+nsImapHostSessionList::~nsImapHostSessionList() {
+ ResetAll();
+ PR_DestroyMonitor(gCachedHostInfoMonitor);
+}
+
+nsresult nsImapHostSessionList::Init() {
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ NS_ENSURE_TRUE(observerService, NS_ERROR_UNEXPECTED);
+ observerService->AddObserver(this, "profile-before-change", true);
+ observerService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, true);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapHostSessionList::Observe(nsISupports* aSubject,
+ const char* aTopic,
+ const char16_t* someData) {
+ if (!strcmp(aTopic, "profile-before-change"))
+ ResetAll();
+ else if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ NS_ENSURE_TRUE(observerService, NS_ERROR_UNEXPECTED);
+ observerService->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
+ observerService->RemoveObserver(this, "profile-before-change");
+ }
+ return NS_OK;
+}
+
+nsIMAPHostInfo* nsImapHostSessionList::FindHost(const char* serverKey) {
+ nsIMAPHostInfo* host;
+
+ // ### should also check userName here, if NON NULL
+ for (host = fHostInfoList; host; host = host->fNextHost) {
+ if (host->fServerKey.Equals(serverKey, nsCaseInsensitiveCStringComparator))
+ return host;
+ }
+ return host;
+}
+
+// reset any cached connection info - delete the lot of 'em
+NS_IMETHODIMP nsImapHostSessionList::ResetAll() {
+ PR_EnterMonitor(gCachedHostInfoMonitor);
+ nsIMAPHostInfo* nextHost = NULL;
+ for (nsIMAPHostInfo* host = fHostInfoList; host; host = nextHost) {
+ nextHost = host->fNextHost;
+ delete host;
+ }
+ fHostInfoList = NULL;
+ PR_ExitMonitor(gCachedHostInfoMonitor);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapHostSessionList::AddHostToList(const char* serverKey,
+ nsIImapIncomingServer* server) {
+ nsIMAPHostInfo* newHost = NULL;
+ PR_EnterMonitor(gCachedHostInfoMonitor);
+ if (!FindHost(serverKey)) {
+ // stick it on the front
+ newHost = new nsIMAPHostInfo(serverKey, server);
+ if (newHost) {
+ newHost->fNextHost = fHostInfoList;
+ fHostInfoList = newHost;
+ }
+ }
+ PR_ExitMonitor(gCachedHostInfoMonitor);
+ return (newHost == NULL) ? NS_ERROR_ILLEGAL_VALUE : NS_OK;
+}
+
+NS_IMETHODIMP nsImapHostSessionList::GetPasswordForHost(const char* serverKey,
+ nsString& result) {
+ PR_EnterMonitor(gCachedHostInfoMonitor);
+ nsIMAPHostInfo* host = FindHost(serverKey);
+ if (host) result = host->fCachedPassword;
+ PR_ExitMonitor(gCachedHostInfoMonitor);
+ return (host == NULL) ? NS_ERROR_ILLEGAL_VALUE : NS_OK;
+}
+
+NS_IMETHODIMP nsImapHostSessionList::SetPasswordForHost(
+ const char* serverKey, const nsAString& password) {
+ PR_EnterMonitor(gCachedHostInfoMonitor);
+ nsIMAPHostInfo* host = FindHost(serverKey);
+ if (host) host->fCachedPassword = password;
+ PR_ExitMonitor(gCachedHostInfoMonitor);
+ return (host == NULL) ? NS_ERROR_ILLEGAL_VALUE : NS_OK;
+}
+
+NS_IMETHODIMP nsImapHostSessionList::SetPasswordVerifiedOnline(
+ const char* serverKey) {
+ PR_EnterMonitor(gCachedHostInfoMonitor);
+ nsIMAPHostInfo* host = FindHost(serverKey);
+ if (host) host->fPasswordVerifiedOnline = true;
+ PR_ExitMonitor(gCachedHostInfoMonitor);
+ return (host == NULL) ? NS_ERROR_ILLEGAL_VALUE : NS_OK;
+}
+
+NS_IMETHODIMP nsImapHostSessionList::GetPasswordVerifiedOnline(
+ const char* serverKey, bool& result) {
+ PR_EnterMonitor(gCachedHostInfoMonitor);
+ nsIMAPHostInfo* host = FindHost(serverKey);
+ if (host) result = host->fPasswordVerifiedOnline;
+ PR_ExitMonitor(gCachedHostInfoMonitor);
+ return (host == NULL) ? NS_ERROR_ILLEGAL_VALUE : NS_OK;
+}
+
+NS_IMETHODIMP nsImapHostSessionList::GetOnlineDirForHost(const char* serverKey,
+ nsString& result) {
+ PR_EnterMonitor(gCachedHostInfoMonitor);
+ nsIMAPHostInfo* host = FindHost(serverKey);
+ if (host) CopyASCIItoUTF16(host->fOnlineDir, result);
+ PR_ExitMonitor(gCachedHostInfoMonitor);
+ return (host == NULL) ? NS_ERROR_ILLEGAL_VALUE : NS_OK;
+}
+
+NS_IMETHODIMP nsImapHostSessionList::SetOnlineDirForHost(
+ const char* serverKey, const char* onlineDir) {
+ PR_EnterMonitor(gCachedHostInfoMonitor);
+ nsIMAPHostInfo* host = FindHost(serverKey);
+ if (host) {
+ if (onlineDir) host->fOnlineDir = onlineDir;
+ }
+ PR_ExitMonitor(gCachedHostInfoMonitor);
+ return (host == NULL) ? NS_ERROR_ILLEGAL_VALUE : NS_OK;
+}
+
+NS_IMETHODIMP nsImapHostSessionList::GetDeleteIsMoveToTrashForHost(
+ const char* serverKey, bool& result) {
+ PR_EnterMonitor(gCachedHostInfoMonitor);
+ nsIMAPHostInfo* host = FindHost(serverKey);
+ if (host) result = host->fDeleteIsMoveToTrash;
+ PR_ExitMonitor(gCachedHostInfoMonitor);
+ return (host == NULL) ? NS_ERROR_ILLEGAL_VALUE : NS_OK;
+}
+
+NS_IMETHODIMP nsImapHostSessionList::GetShowDeletedMessagesForHost(
+ const char* serverKey, bool& result) {
+ PR_EnterMonitor(gCachedHostInfoMonitor);
+ nsIMAPHostInfo* host = FindHost(serverKey);
+ if (host) result = host->fShowDeletedMessages;
+ PR_ExitMonitor(gCachedHostInfoMonitor);
+ return (host == NULL) ? NS_ERROR_ILLEGAL_VALUE : NS_OK;
+}
+
+NS_IMETHODIMP nsImapHostSessionList::SetDeleteIsMoveToTrashForHost(
+ const char* serverKey, bool isMoveToTrash) {
+ PR_EnterMonitor(gCachedHostInfoMonitor);
+ nsIMAPHostInfo* host = FindHost(serverKey);
+ if (host) host->fDeleteIsMoveToTrash = isMoveToTrash;
+ PR_ExitMonitor(gCachedHostInfoMonitor);
+ return (host == NULL) ? NS_ERROR_ILLEGAL_VALUE : NS_OK;
+}
+
+NS_IMETHODIMP nsImapHostSessionList::SetShowDeletedMessagesForHost(
+ const char* serverKey, bool showDeletedMessages) {
+ PR_EnterMonitor(gCachedHostInfoMonitor);
+ nsIMAPHostInfo* host = FindHost(serverKey);
+ if (host) host->fShowDeletedMessages = showDeletedMessages;
+ PR_ExitMonitor(gCachedHostInfoMonitor);
+ return (host == NULL) ? NS_ERROR_ILLEGAL_VALUE : NS_OK;
+}
+
+NS_IMETHODIMP nsImapHostSessionList::GetGotNamespacesForHost(
+ const char* serverKey, bool& result) {
+ PR_EnterMonitor(gCachedHostInfoMonitor);
+ nsIMAPHostInfo* host = FindHost(serverKey);
+ if (host) result = host->fGotNamespaces;
+ PR_ExitMonitor(gCachedHostInfoMonitor);
+ return (host == NULL) ? NS_ERROR_ILLEGAL_VALUE : NS_OK;
+}
+
+NS_IMETHODIMP nsImapHostSessionList::SetGotNamespacesForHost(
+ const char* serverKey, bool gotNamespaces) {
+ PR_EnterMonitor(gCachedHostInfoMonitor);
+ nsIMAPHostInfo* host = FindHost(serverKey);
+ if (host) host->fGotNamespaces = gotNamespaces;
+ PR_ExitMonitor(gCachedHostInfoMonitor);
+ return (host == NULL) ? NS_ERROR_ILLEGAL_VALUE : NS_OK;
+}
+
+NS_IMETHODIMP nsImapHostSessionList::GetHostIsUsingSubscription(
+ const char* serverKey, bool& result) {
+ PR_EnterMonitor(gCachedHostInfoMonitor);
+ nsIMAPHostInfo* host = FindHost(serverKey);
+ if (host) result = host->fUsingSubscription;
+ PR_ExitMonitor(gCachedHostInfoMonitor);
+ return (host == NULL) ? NS_ERROR_ILLEGAL_VALUE : NS_OK;
+}
+
+NS_IMETHODIMP nsImapHostSessionList::SetHostIsUsingSubscription(
+ const char* serverKey, bool usingSubscription) {
+ PR_EnterMonitor(gCachedHostInfoMonitor);
+ nsIMAPHostInfo* host = FindHost(serverKey);
+ if (host) host->fUsingSubscription = usingSubscription;
+ PR_ExitMonitor(gCachedHostInfoMonitor);
+ return (host == NULL) ? NS_ERROR_ILLEGAL_VALUE : NS_OK;
+}
+
+NS_IMETHODIMP nsImapHostSessionList::GetHostHasAdminURL(const char* serverKey,
+ bool& result) {
+ PR_EnterMonitor(gCachedHostInfoMonitor);
+ nsIMAPHostInfo* host = FindHost(serverKey);
+ if (host) result = host->fHaveAdminURL;
+ PR_ExitMonitor(gCachedHostInfoMonitor);
+ return (host == NULL) ? NS_ERROR_ILLEGAL_VALUE : NS_OK;
+}
+
+NS_IMETHODIMP nsImapHostSessionList::SetHostHasAdminURL(const char* serverKey,
+ bool haveAdminURL) {
+ PR_EnterMonitor(gCachedHostInfoMonitor);
+ nsIMAPHostInfo* host = FindHost(serverKey);
+ if (host) host->fHaveAdminURL = haveAdminURL;
+ PR_ExitMonitor(gCachedHostInfoMonitor);
+ return (host == NULL) ? NS_ERROR_ILLEGAL_VALUE : NS_OK;
+}
+
+NS_IMETHODIMP nsImapHostSessionList::GetHaveWeEverDiscoveredFoldersForHost(
+ const char* serverKey, bool& result) {
+ PR_EnterMonitor(gCachedHostInfoMonitor);
+ nsIMAPHostInfo* host = FindHost(serverKey);
+ if (host) result = host->fHaveWeEverDiscoveredFolders;
+ PR_ExitMonitor(gCachedHostInfoMonitor);
+ return (host == NULL) ? NS_ERROR_ILLEGAL_VALUE : NS_OK;
+}
+
+NS_IMETHODIMP nsImapHostSessionList::SetHaveWeEverDiscoveredFoldersForHost(
+ const char* serverKey, bool discovered) {
+ PR_EnterMonitor(gCachedHostInfoMonitor);
+ nsIMAPHostInfo* host = FindHost(serverKey);
+ if (host) host->fHaveWeEverDiscoveredFolders = discovered;
+ PR_ExitMonitor(gCachedHostInfoMonitor);
+ return (host == NULL) ? NS_ERROR_ILLEGAL_VALUE : NS_OK;
+}
+
+NS_IMETHODIMP nsImapHostSessionList::GetDiscoveryForHostInProgress(
+ const char* serverKey, bool& result) {
+ PR_EnterMonitor(gCachedHostInfoMonitor);
+ nsIMAPHostInfo* host = FindHost(serverKey);
+ if (host)
+ result = host->fDiscoveryForHostInProgress;
+ else
+ result = false;
+ PR_ExitMonitor(gCachedHostInfoMonitor);
+ return (host == NULL) ? NS_ERROR_ILLEGAL_VALUE : NS_OK;
+}
+
+NS_IMETHODIMP nsImapHostSessionList::SetDiscoveryForHostInProgress(
+ const char* serverKey, bool inProgress) {
+ PR_EnterMonitor(gCachedHostInfoMonitor);
+ nsIMAPHostInfo* host = FindHost(serverKey);
+ if (host) host->fDiscoveryForHostInProgress = inProgress;
+ PR_ExitMonitor(gCachedHostInfoMonitor);
+ return (host == NULL) ? NS_ERROR_ILLEGAL_VALUE : NS_OK;
+}
+
+NS_IMETHODIMP nsImapHostSessionList::SetOnlineTrashFolderExistsForHost(
+ const char* serverKey, bool exists) {
+ PR_EnterMonitor(gCachedHostInfoMonitor);
+ nsIMAPHostInfo* host = FindHost(serverKey);
+ if (host) host->fOnlineTrashFolderExists = exists;
+ PR_ExitMonitor(gCachedHostInfoMonitor);
+ return (host == NULL) ? NS_ERROR_ILLEGAL_VALUE : NS_OK;
+}
+
+NS_IMETHODIMP nsImapHostSessionList::GetOnlineTrashFolderExistsForHost(
+ const char* serverKey, bool& result) {
+ PR_EnterMonitor(gCachedHostInfoMonitor);
+ nsIMAPHostInfo* host = FindHost(serverKey);
+ if (host) result = host->fOnlineTrashFolderExists;
+ PR_ExitMonitor(gCachedHostInfoMonitor);
+ return (host == NULL) ? NS_ERROR_ILLEGAL_VALUE : NS_OK;
+}
+
+NS_IMETHODIMP nsImapHostSessionList::AddNewNamespaceForHost(
+ const char* serverKey, nsImapNamespace* ns) {
+ PR_EnterMonitor(gCachedHostInfoMonitor);
+ nsIMAPHostInfo* host = FindHost(serverKey);
+ if (host) host->fNamespaceList->AddNewNamespace(ns);
+ PR_ExitMonitor(gCachedHostInfoMonitor);
+ return (host == NULL) ? NS_ERROR_ILLEGAL_VALUE : NS_OK;
+}
+
+NS_IMETHODIMP nsImapHostSessionList::SetNamespaceFromPrefForHost(
+ const char* serverKey, const char* namespacePref,
+ EIMAPNamespaceType nstype) {
+ PR_EnterMonitor(gCachedHostInfoMonitor);
+ nsIMAPHostInfo* host = FindHost(serverKey);
+ if (host) {
+ if (namespacePref) {
+ int numNamespaces = host->fNamespaceList->UnserializeNamespaces(
+ namespacePref, nullptr, 0);
+ char** prefixes = (char**)PR_CALLOC(numNamespaces * sizeof(char*));
+ if (prefixes) {
+ int len = host->fNamespaceList->UnserializeNamespaces(
+ namespacePref, prefixes, numNamespaces);
+ for (int i = 0; i < len; i++) {
+ char* thisns = prefixes[i];
+ char delimiter = '/'; // a guess
+ if (PL_strlen(thisns) >= 1) delimiter = thisns[PL_strlen(thisns) - 1];
+ nsImapNamespace* ns =
+ new nsImapNamespace(nstype, thisns, delimiter, true);
+ if (ns) host->fNamespaceList->AddNewNamespace(ns);
+ PR_FREEIF(thisns);
+ }
+ PR_Free(prefixes);
+ }
+ }
+ }
+ PR_ExitMonitor(gCachedHostInfoMonitor);
+ return (host == NULL) ? NS_ERROR_ILLEGAL_VALUE : NS_OK;
+}
+
+NS_IMETHODIMP nsImapHostSessionList::GetNamespaceForMailboxForHost(
+ const char* serverKey, const char* mailbox_name, nsImapNamespace*& result) {
+ PR_EnterMonitor(gCachedHostInfoMonitor);
+ nsIMAPHostInfo* host = FindHost(serverKey);
+ if (host) result = host->fNamespaceList->GetNamespaceForMailbox(mailbox_name);
+ PR_ExitMonitor(gCachedHostInfoMonitor);
+ return (host == NULL) ? NS_ERROR_ILLEGAL_VALUE : NS_OK;
+}
+
+NS_IMETHODIMP nsImapHostSessionList::ClearPrefsNamespacesForHost(
+ const char* serverKey) {
+ PR_EnterMonitor(gCachedHostInfoMonitor);
+ nsIMAPHostInfo* host = FindHost(serverKey);
+ if (host) host->fNamespaceList->ClearNamespaces(true, false, true);
+ PR_ExitMonitor(gCachedHostInfoMonitor);
+ return (host == NULL) ? NS_ERROR_ILLEGAL_VALUE : NS_OK;
+}
+
+NS_IMETHODIMP nsImapHostSessionList::ClearServerAdvertisedNamespacesForHost(
+ const char* serverKey) {
+ PR_EnterMonitor(gCachedHostInfoMonitor);
+ nsIMAPHostInfo* host = FindHost(serverKey);
+ if (host) host->fNamespaceList->ClearNamespaces(false, true, true);
+ PR_ExitMonitor(gCachedHostInfoMonitor);
+ return (host == NULL) ? NS_ERROR_ILLEGAL_VALUE : NS_OK;
+}
+
+NS_IMETHODIMP nsImapHostSessionList::GetDefaultNamespaceOfTypeForHost(
+ const char* serverKey, EIMAPNamespaceType type, nsImapNamespace*& result) {
+ PR_EnterMonitor(gCachedHostInfoMonitor);
+ nsIMAPHostInfo* host = FindHost(serverKey);
+ if (host) result = host->fNamespaceList->GetDefaultNamespaceOfType(type);
+ PR_ExitMonitor(gCachedHostInfoMonitor);
+ return (host == NULL) ? NS_ERROR_ILLEGAL_VALUE : NS_OK;
+}
+
+NS_IMETHODIMP nsImapHostSessionList::GetNamespacesOverridableForHost(
+ const char* serverKey, bool& result) {
+ PR_EnterMonitor(gCachedHostInfoMonitor);
+ nsIMAPHostInfo* host = FindHost(serverKey);
+ if (host) result = host->fNamespacesOverridable;
+ PR_ExitMonitor(gCachedHostInfoMonitor);
+ return (host == NULL) ? NS_ERROR_ILLEGAL_VALUE : NS_OK;
+}
+
+NS_IMETHODIMP nsImapHostSessionList::SetNamespacesOverridableForHost(
+ const char* serverKey, bool overridable) {
+ PR_EnterMonitor(gCachedHostInfoMonitor);
+ nsIMAPHostInfo* host = FindHost(serverKey);
+ if (host) host->fNamespacesOverridable = overridable;
+ PR_ExitMonitor(gCachedHostInfoMonitor);
+ return (host == NULL) ? NS_ERROR_ILLEGAL_VALUE : NS_OK;
+}
+
+NS_IMETHODIMP nsImapHostSessionList::GetNumberOfNamespacesForHost(
+ const char* serverKey, uint32_t& result) {
+ int32_t intResult = 0;
+
+ PR_EnterMonitor(gCachedHostInfoMonitor);
+ nsIMAPHostInfo* host = FindHost(serverKey);
+ if (host) intResult = host->fNamespaceList->GetNumberOfNamespaces();
+ PR_ExitMonitor(gCachedHostInfoMonitor);
+ NS_ASSERTION(intResult >= 0, "negative number of namespaces");
+ result = (uint32_t)intResult;
+ return (host == NULL) ? NS_ERROR_ILLEGAL_VALUE : NS_OK;
+}
+
+NS_IMETHODIMP nsImapHostSessionList::GetNamespaceNumberForHost(
+ const char* serverKey, int32_t n, nsImapNamespace*& result) {
+ PR_EnterMonitor(gCachedHostInfoMonitor);
+ nsIMAPHostInfo* host = FindHost(serverKey);
+ if (host) result = host->fNamespaceList->GetNamespaceNumber(n);
+ PR_ExitMonitor(gCachedHostInfoMonitor);
+ return (host == NULL) ? NS_ERROR_ILLEGAL_VALUE : NS_OK;
+}
+
+nsresult nsImapHostSessionList::SetNamespacesPrefForHost(
+ nsIImapIncomingServer* aHost, EIMAPNamespaceType type, const char* pref) {
+ if (type == kPersonalNamespace)
+ aHost->SetPersonalNamespace(nsDependentCString(pref));
+ else if (type == kPublicNamespace)
+ aHost->SetPublicNamespace(nsDependentCString(pref));
+ else if (type == kOtherUsersNamespace)
+ aHost->SetOtherUsersNamespace(nsDependentCString(pref));
+ else
+ NS_ASSERTION(false, "bogus namespace type");
+ return NS_OK;
+}
+// do we need this? What should we do about the master thing?
+// Make sure this is running in the Mozilla thread when called
+NS_IMETHODIMP nsImapHostSessionList::CommitNamespacesForHost(
+ nsIImapIncomingServer* aHost) {
+ NS_ENSURE_ARG_POINTER(aHost);
+ nsCString serverKey;
+ nsCOMPtr<nsIMsgIncomingServer> incomingServer = do_QueryInterface(aHost);
+ if (!incomingServer) return NS_ERROR_NULL_POINTER;
+
+ nsresult rv = incomingServer->GetKey(serverKey);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ PR_EnterMonitor(gCachedHostInfoMonitor);
+ nsIMAPHostInfo* host = FindHost(serverKey.get());
+ if (host) {
+ host->fGotNamespaces =
+ true; // so we only issue NAMESPACE once per host per session.
+ EIMAPNamespaceType type = kPersonalNamespace;
+ for (int i = 1; i <= 3; i++) {
+ switch (i) {
+ case 1:
+ type = kPersonalNamespace;
+ break;
+ case 2:
+ type = kPublicNamespace;
+ break;
+ case 3:
+ type = kOtherUsersNamespace;
+ break;
+ default:
+ type = kPersonalNamespace;
+ break;
+ }
+
+ int32_t numInNS = host->fNamespaceList->GetNumberOfNamespaces(type);
+ if (numInNS == 0)
+ SetNamespacesPrefForHost(aHost, type, "");
+ else if (numInNS >= 1) {
+ char* pref = PR_smprintf("");
+ for (int count = 1; count <= numInNS; count++) {
+ nsImapNamespace* ns =
+ host->fNamespaceList->GetNamespaceNumber(count, type);
+ if (ns) {
+ if (count > 1) {
+ // append the comma
+ char* tempPref = PR_smprintf("%s,", pref);
+ PR_FREEIF(pref);
+ pref = tempPref;
+ }
+ char* tempPref = PR_smprintf("%s\"%s\"", pref, ns->GetPrefix());
+ PR_FREEIF(pref);
+ pref = tempPref;
+ }
+ }
+ if (pref) {
+ SetNamespacesPrefForHost(aHost, type, pref);
+ PR_Free(pref);
+ }
+ }
+ }
+ // clear, but don't delete the entries in, the temp namespace list
+ host->fTempNamespaceList->ClearNamespaces(true, true, false);
+
+ // Now reset all of libmsg's namespace references.
+ // Did I mention this needs to be running in the mozilla thread?
+ aHost->ResetNamespaceReferences();
+ }
+ PR_ExitMonitor(gCachedHostInfoMonitor);
+ return (host == NULL) ? NS_ERROR_ILLEGAL_VALUE : NS_OK;
+}
+
+NS_IMETHODIMP nsImapHostSessionList::FlushUncommittedNamespacesForHost(
+ const char* serverKey, bool& result) {
+ PR_EnterMonitor(gCachedHostInfoMonitor);
+ nsIMAPHostInfo* host = FindHost(serverKey);
+ if (host) host->fTempNamespaceList->ClearNamespaces(true, true, true);
+ PR_ExitMonitor(gCachedHostInfoMonitor);
+ return (host == NULL) ? NS_ERROR_ILLEGAL_VALUE : NS_OK;
+}
+
+// Returns NULL if there is no personal namespace on the given host
+NS_IMETHODIMP nsImapHostSessionList::GetOnlineInboxPathForHost(
+ const char* serverKey, nsString& result) {
+ PR_EnterMonitor(gCachedHostInfoMonitor);
+ nsIMAPHostInfo* host = FindHost(serverKey);
+ if (host) {
+ nsImapNamespace* ns = NULL;
+ ns = host->fNamespaceList->GetDefaultNamespaceOfType(kPersonalNamespace);
+ if (ns) {
+ CopyASCIItoUTF16(nsDependentCString(ns->GetPrefix()), result);
+ result.AppendLiteral("INBOX");
+ }
+ } else
+ result.Truncate();
+ PR_ExitMonitor(gCachedHostInfoMonitor);
+ return (host == NULL) ? NS_ERROR_ILLEGAL_VALUE : NS_OK;
+}
+
+NS_IMETHODIMP nsImapHostSessionList::GetShouldAlwaysListInboxForHost(
+ const char* /*serverKey*/, bool& result) {
+ result = true;
+
+ /*
+ PR_EnterMonitor(gCachedHostInfoMonitor);
+ nsIMAPHostInfo *host = FindHost(serverKey);
+ if (host)
+ ret = host->fShouldAlwaysListInbox;
+ PR_ExitMonitor(gCachedHostInfoMonitor);
+ */
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapHostSessionList::SetShouldAlwaysListInboxForHost(
+ const char* serverKey, bool shouldList) {
+ PR_EnterMonitor(gCachedHostInfoMonitor);
+ nsIMAPHostInfo* host = FindHost(serverKey);
+ if (host) host->fShouldAlwaysListInbox = shouldList;
+ PR_ExitMonitor(gCachedHostInfoMonitor);
+ return (host == NULL) ? NS_ERROR_ILLEGAL_VALUE : NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapHostSessionList::SetNamespaceHierarchyDelimiterFromMailboxForHost(
+ const char* serverKey, const char* boxName, char delimiter) {
+ PR_EnterMonitor(gCachedHostInfoMonitor);
+ nsIMAPHostInfo* host = FindHost(serverKey);
+ if (host) {
+ nsImapNamespace* ns = host->fNamespaceList->GetNamespaceForMailbox(boxName);
+ if (ns && !ns->GetIsDelimiterFilledIn()) ns->SetDelimiter(delimiter, true);
+ }
+ PR_ExitMonitor(gCachedHostInfoMonitor);
+ return (host) ? NS_OK : NS_ERROR_ILLEGAL_VALUE;
+}
diff --git a/comm/mailnews/imap/src/nsImapHostSessionList.h b/comm/mailnews/imap/src/nsImapHostSessionList.h
new file mode 100644
index 0000000000..325cbae868
--- /dev/null
+++ b/comm/mailnews/imap/src/nsImapHostSessionList.h
@@ -0,0 +1,169 @@
+/* -*- 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 _nsImapHostSessionList_H_
+#define _nsImapHostSessionList_H_
+
+#include "mozilla/Attributes.h"
+#include "nsImapCore.h"
+#include "../public/nsIImapHostSessionList.h"
+#include "nsIObserver.h"
+#include "nsWeakReference.h"
+#include "nspr.h"
+
+class nsImapNamespaceList;
+class nsIImapIncomingServer;
+
+class nsIMAPHostInfo {
+ public:
+ friend class nsImapHostSessionList;
+
+ nsIMAPHostInfo(const char* serverKey, nsIImapIncomingServer* server);
+ ~nsIMAPHostInfo();
+
+ protected:
+ nsCString fServerKey;
+ nsString fCachedPassword;
+ nsCString fOnlineDir;
+ nsIMAPHostInfo* fNextHost;
+ eIMAPCapabilityFlags fCapabilityFlags;
+ char* fHierarchyDelimiters; // string of top-level hierarchy delimiters
+ bool fHaveWeEverDiscoveredFolders;
+ bool fDiscoveryForHostInProgress;
+ char* fCanonicalOnlineSubDir;
+ nsImapNamespaceList *fNamespaceList, *fTempNamespaceList;
+ bool fNamespacesOverridable;
+ bool fUsingSubscription;
+ bool fOnlineTrashFolderExists;
+ bool fShouldAlwaysListInbox;
+ bool fHaveAdminURL;
+ bool fPasswordVerifiedOnline;
+ bool fDeleteIsMoveToTrash;
+ bool fShowDeletedMessages;
+ bool fGotNamespaces;
+};
+
+// this is an interface to a linked list of host info's
+class nsImapHostSessionList : public nsIImapHostSessionList,
+ public nsIObserver,
+ public nsSupportsWeakReference {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+
+ nsImapHostSessionList();
+ nsresult Init();
+ // Host List
+ NS_IMETHOD AddHostToList(const char* serverKey,
+ nsIImapIncomingServer* server) override;
+ NS_IMETHOD ResetAll() override;
+
+ // Capabilities
+ NS_IMETHOD GetHostHasAdminURL(const char* serverKey, bool& result) override;
+ NS_IMETHOD SetHostHasAdminURL(const char* serverKey,
+ bool hasAdminUrl) override;
+ // Subscription
+ NS_IMETHOD GetHostIsUsingSubscription(const char* serverKey,
+ bool& result) override;
+ NS_IMETHOD SetHostIsUsingSubscription(const char* serverKey,
+ bool usingSubscription) override;
+
+ // Passwords
+ NS_IMETHOD GetPasswordForHost(const char* serverKey,
+ nsString& result) override;
+ NS_IMETHOD SetPasswordForHost(const char* serverKey,
+ const nsAString& password) override;
+ NS_IMETHOD GetPasswordVerifiedOnline(const char* serverKey,
+ bool& result) override;
+ NS_IMETHOD SetPasswordVerifiedOnline(const char* serverKey) override;
+
+ // OnlineDir
+ NS_IMETHOD GetOnlineDirForHost(const char* serverKey,
+ nsString& result) override;
+ NS_IMETHOD SetOnlineDirForHost(const char* serverKey,
+ const char* onlineDir) override;
+
+ // Delete is move to trash folder
+ NS_IMETHOD GetDeleteIsMoveToTrashForHost(const char* serverKey,
+ bool& result) override;
+ NS_IMETHOD SetDeleteIsMoveToTrashForHost(const char* serverKey,
+ bool isMoveToTrash) override;
+ // imap delete model (or not)
+ NS_IMETHOD GetShowDeletedMessagesForHost(const char* serverKey,
+ bool& result) override;
+ NS_IMETHOD SetShowDeletedMessagesForHost(const char* serverKey,
+ bool showDeletedMessages) override;
+
+ // Get namespaces
+ NS_IMETHOD GetGotNamespacesForHost(const char* serverKey,
+ bool& result) override;
+ NS_IMETHOD SetGotNamespacesForHost(const char* serverKey,
+ bool gotNamespaces) override;
+ // Folders
+ NS_IMETHOD SetHaveWeEverDiscoveredFoldersForHost(const char* serverKey,
+ bool discovered) override;
+ NS_IMETHOD GetHaveWeEverDiscoveredFoldersForHost(const char* serverKey,
+ bool& result) override;
+ NS_IMETHOD SetDiscoveryForHostInProgress(const char* serverKey,
+ bool inProgress) override;
+ NS_IMETHOD GetDiscoveryForHostInProgress(const char* serverKey,
+ bool& result) override;
+
+ // Trash Folder
+ NS_IMETHOD SetOnlineTrashFolderExistsForHost(const char* serverKey,
+ bool exists) override;
+ NS_IMETHOD GetOnlineTrashFolderExistsForHost(const char* serverKey,
+ bool& result) override;
+
+ // INBOX
+ NS_IMETHOD GetOnlineInboxPathForHost(const char* serverKey,
+ nsString& result) override;
+ NS_IMETHOD GetShouldAlwaysListInboxForHost(const char* serverKey,
+ bool& result) override;
+ NS_IMETHOD SetShouldAlwaysListInboxForHost(const char* serverKey,
+ bool shouldList) override;
+
+ // Namespaces
+ NS_IMETHOD GetNamespaceForMailboxForHost(const char* serverKey,
+ const char* mailbox_name,
+ nsImapNamespace*& result) override;
+ NS_IMETHOD SetNamespaceFromPrefForHost(const char* serverKey,
+ const char* namespacePref,
+ EIMAPNamespaceType type) override;
+ NS_IMETHOD AddNewNamespaceForHost(const char* serverKey,
+ nsImapNamespace* ns) override;
+ NS_IMETHOD ClearServerAdvertisedNamespacesForHost(
+ const char* serverKey) override;
+ NS_IMETHOD ClearPrefsNamespacesForHost(const char* serverKey) override;
+ NS_IMETHOD GetDefaultNamespaceOfTypeForHost(
+ const char* serverKey, EIMAPNamespaceType type,
+ nsImapNamespace*& result) override;
+ NS_IMETHOD SetNamespacesOverridableForHost(const char* serverKey,
+ bool overridable) override;
+ NS_IMETHOD GetNamespacesOverridableForHost(const char* serverKey,
+ bool& result) override;
+ NS_IMETHOD GetNumberOfNamespacesForHost(const char* serverKey,
+ uint32_t& result) override;
+ NS_IMETHOD GetNamespaceNumberForHost(const char* serverKey, int32_t n,
+ nsImapNamespace*& result) override;
+ // ### dmb hoo boy, how are we going to do this?
+ NS_IMETHOD CommitNamespacesForHost(nsIImapIncomingServer* host) override;
+ NS_IMETHOD FlushUncommittedNamespacesForHost(const char* serverKey,
+ bool& result) override;
+
+ // Hierarchy Delimiters
+ NS_IMETHOD SetNamespaceHierarchyDelimiterFromMailboxForHost(
+ const char* serverKey, const char* boxName, char delimiter) override;
+
+ PRMonitor* gCachedHostInfoMonitor;
+ nsIMAPHostInfo* fHostInfoList;
+
+ protected:
+ virtual ~nsImapHostSessionList();
+ nsresult SetNamespacesPrefForHost(nsIImapIncomingServer* aHost,
+ EIMAPNamespaceType type, const char* pref);
+ nsIMAPHostInfo* FindHost(const char* serverKey);
+};
+#endif
diff --git a/comm/mailnews/imap/src/nsImapIncomingServer.cpp b/comm/mailnews/imap/src/nsImapIncomingServer.cpp
new file mode 100644
index 0000000000..67b5df6d53
--- /dev/null
+++ b/comm/mailnews/imap/src/nsImapIncomingServer.cpp
@@ -0,0 +1,3032 @@
+/* -*- 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 "netCore.h"
+#include "../public/nsIImapHostSessionList.h"
+#include "nsImapIncomingServer.h"
+#include "nsIMsgAccountManager.h"
+#include "nsIMsgIdentity.h"
+#include "nsIImapUrl.h"
+#include "nsIUrlListener.h"
+#include "nsThreadUtils.h"
+#include "nsImapProtocol.h"
+#include "nsCOMPtr.h"
+#include "nsIPrefBranch.h"
+#include "nsIPrefService.h"
+#include "nsMsgFolderFlags.h"
+#include "prmem.h"
+#include "plstr.h"
+#include "nsIMsgFolder.h"
+#include "nsIMsgWindow.h"
+#include "nsImapMailFolder.h"
+#include "nsIMsgMailNewsUrl.h"
+#include "nsIImapService.h"
+#include "nsMsgI18N.h"
+#include "nsIImapMockChannel.h"
+// for the memory cache...
+#include "nsICacheEntry.h"
+#include "nsImapUrl.h"
+#include "nsIMsgProtocolInfo.h"
+#include "nsIMsgMailSession.h"
+#include "nsImapNamespace.h"
+#include "nsArrayUtils.h"
+#include "nsMsgUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "nsCRTGlue.h"
+#include "mozilla/Components.h"
+#include "nsNetUtil.h"
+#include "mozilla/Utf8.h"
+#include "mozilla/LoadInfo.h"
+
+using namespace mozilla;
+
+// Despite its name, this contains a folder path, for example INBOX/Trash.
+#define PREF_TRASH_FOLDER_PATH "trash_folder_name"
+#define DEFAULT_TRASH_FOLDER_PATH "Trash" // XXX Is this a useful default?
+
+#define NS_SUBSCRIBABLESERVER_CID \
+ { \
+ 0x8510876a, 0x1dd2, 0x11b2, { \
+ 0x82, 0x53, 0x91, 0xf7, 0x1b, 0x34, 0x8a, 0x25 \
+ } \
+ }
+static NS_DEFINE_CID(kSubscribableServerCID, NS_SUBSCRIBABLESERVER_CID);
+#define NS_IIMAPHOSTSESSIONLIST_CID \
+ { \
+ 0x479ce8fc, 0xe725, 0x11d2, { \
+ 0xa5, 0x05, 0x00, 0x60, 0xb0, 0xfc, 0x04, 0xb7 \
+ } \
+ }
+static NS_DEFINE_CID(kCImapHostSessionListCID, NS_IIMAPHOSTSESSIONLIST_CID);
+
+NS_IMPL_ADDREF_INHERITED(nsImapIncomingServer, nsMsgIncomingServer)
+NS_IMPL_RELEASE_INHERITED(nsImapIncomingServer, nsMsgIncomingServer)
+
+NS_INTERFACE_MAP_BEGIN(nsImapIncomingServer)
+ NS_INTERFACE_MAP_ENTRY(nsIImapServerSink)
+ NS_INTERFACE_MAP_ENTRY(nsIImapIncomingServer)
+ NS_INTERFACE_MAP_ENTRY(nsISubscribableServer)
+ NS_INTERFACE_MAP_ENTRY(nsIUrlListener)
+NS_INTERFACE_MAP_END_INHERITING(nsMsgIncomingServer)
+
+nsImapIncomingServer::nsImapIncomingServer()
+ : mLock("nsImapIncomingServer.mLock") {
+ m_capability = kCapabilityUndefined;
+ mDoingSubscribeDialog = false;
+ mDoingLsub = false;
+ m_canHaveFilters = true;
+ m_userAuthenticated = false;
+ m_shuttingDown = false;
+ mUtf8AcceptEnabled = false;
+}
+
+nsImapIncomingServer::~nsImapIncomingServer() {
+ mozilla::DebugOnly<nsresult> rv = ClearInner();
+ NS_ASSERTION(NS_SUCCEEDED(rv), "ClearInner failed");
+ CloseCachedConnections();
+}
+
+NS_IMETHODIMP nsImapIncomingServer::SetKey(
+ const nsACString& aKey) // override nsMsgIncomingServer's implementation...
+{
+ nsMsgIncomingServer::SetKey(aKey);
+
+ // okay now that the key has been set, we need to add ourselves to the
+ // host session list...
+
+ // every time we create an imap incoming server, we need to add it to the
+ // host session list!!
+
+ nsresult rv;
+ nsCOMPtr<nsIImapHostSessionList> hostSession =
+ do_GetService(kCImapHostSessionListCID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCString key(aKey);
+ hostSession->AddHostToList(key.get(), this);
+ nsMsgImapDeleteModel deleteModel =
+ nsMsgImapDeleteModels::MoveToTrash; // default to trash
+ GetDeleteModel(&deleteModel);
+ hostSession->SetDeleteIsMoveToTrashForHost(
+ key.get(), deleteModel == nsMsgImapDeleteModels::MoveToTrash);
+ hostSession->SetShowDeletedMessagesForHost(
+ key.get(), deleteModel == nsMsgImapDeleteModels::IMAPDelete);
+
+ nsAutoCString onlineDir;
+ rv = GetServerDirectory(onlineDir);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!onlineDir.IsEmpty())
+ hostSession->SetOnlineDirForHost(key.get(), onlineDir.get());
+
+ nsCString personalNamespace;
+ nsCString publicNamespace;
+ nsCString otherUsersNamespace;
+
+ rv = GetPersonalNamespace(personalNamespace);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = GetPublicNamespace(publicNamespace);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = GetOtherUsersNamespace(otherUsersNamespace);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (personalNamespace.IsEmpty() && publicNamespace.IsEmpty() &&
+ otherUsersNamespace.IsEmpty())
+ personalNamespace.AssignLiteral("\"\"");
+
+ hostSession->SetNamespaceFromPrefForHost(key.get(), personalNamespace.get(),
+ kPersonalNamespace);
+
+ if (!publicNamespace.IsEmpty())
+ hostSession->SetNamespaceFromPrefForHost(key.get(), publicNamespace.get(),
+ kPublicNamespace);
+
+ if (!otherUsersNamespace.IsEmpty())
+ hostSession->SetNamespaceFromPrefForHost(
+ key.get(), otherUsersNamespace.get(), kOtherUsersNamespace);
+ return rv;
+}
+
+// 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
+nsImapIncomingServer::GetConstructedPrettyName(nsAString& retval) {
+ nsAutoCString username;
+ nsAutoCString hostName;
+ nsresult rv;
+
+ nsCOMPtr<nsIMsgAccountManager> accountManager =
+ do_GetService("@mozilla.org/messenger/account-manager;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgIdentity> identity;
+ rv =
+ accountManager->GetFirstIdentityForServer(this, getter_AddRefs(identity));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString emailAddress;
+
+ if (NS_SUCCEEDED(rv) && identity) {
+ nsCString identityEmailAddress;
+ identity->GetEmail(identityEmailAddress);
+ CopyASCIItoUTF16(identityEmailAddress, emailAddress);
+ } else {
+ rv = GetUsername(username);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = GetHostName(hostName);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!username.IsEmpty() && !hostName.IsEmpty()) {
+ CopyASCIItoUTF16(username, emailAddress);
+ emailAddress.Append('@');
+ emailAddress.Append(NS_ConvertASCIItoUTF16(hostName));
+ }
+ }
+
+ return GetFormattedStringFromName(emailAddress, "imapDefaultAccountName",
+ retval);
+}
+
+NS_IMETHODIMP nsImapIncomingServer::GetLocalStoreType(nsACString& type) {
+ type.AssignLiteral("imap");
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapIncomingServer::GetLocalDatabaseType(nsACString& type) {
+ type.AssignLiteral("imap");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::GetServerDirectory(nsACString& serverDirectory) {
+ return GetCharValue("server_sub_directory", serverDirectory);
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::SetServerDirectory(const nsACString& serverDirectory) {
+ nsCString serverKey;
+ nsresult rv = GetKey(serverKey);
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIImapHostSessionList> hostSession =
+ do_GetService(kCImapHostSessionListCID, &rv);
+ if (NS_SUCCEEDED(rv))
+ hostSession->SetOnlineDirForHost(
+ serverKey.get(), PromiseFlatCString(serverDirectory).get());
+ }
+ return SetCharValue("server_sub_directory", serverDirectory);
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::GetOverrideNamespaces(bool* bVal) {
+ return GetBoolValue("override_namespaces", bVal);
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::SetOverrideNamespaces(bool bVal) {
+ nsCString serverKey;
+ GetKey(serverKey);
+ if (!serverKey.IsEmpty()) {
+ nsresult rv;
+ nsCOMPtr<nsIImapHostSessionList> hostSession =
+ do_GetService(kCImapHostSessionListCID, &rv);
+ if (NS_SUCCEEDED(rv))
+ hostSession->SetNamespacesOverridableForHost(serverKey.get(), bVal);
+ }
+ return SetBoolValue("override_namespaces", bVal);
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::GetUsingSubscription(bool* bVal) {
+ return GetBoolValue("using_subscription", bVal);
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::SetUsingSubscription(bool bVal) {
+ nsCString serverKey;
+ GetKey(serverKey);
+ if (!serverKey.IsEmpty()) {
+ nsresult rv;
+ nsCOMPtr<nsIImapHostSessionList> hostSession =
+ do_GetService(kCImapHostSessionListCID, &rv);
+ if (NS_SUCCEEDED(rv))
+ hostSession->SetHostIsUsingSubscription(serverKey.get(), bVal);
+ }
+ return SetBoolValue("using_subscription", bVal);
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::GetMaximumConnectionsNumber(int32_t* aMaxConnections) {
+ NS_ENSURE_ARG_POINTER(aMaxConnections);
+
+ nsresult rv = GetIntValue("max_cached_connections", aMaxConnections);
+ // Get our maximum connection count. We need at least 1. If the value is 0,
+ // we use the default of 5. If it's negative, we treat that as 1.
+ if (NS_SUCCEEDED(rv) && *aMaxConnections > 0) return NS_OK;
+
+ *aMaxConnections = (NS_FAILED(rv) || (*aMaxConnections == 0)) ? 5 : 1;
+ (void)SetMaximumConnectionsNumber(*aMaxConnections);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::SetMaximumConnectionsNumber(int32_t aMaxConnections) {
+ return SetIntValue("max_cached_connections", aMaxConnections);
+}
+
+NS_IMPL_SERVERPREF_BOOL(nsImapIncomingServer, ForceSelect, "force_select_imap")
+
+NS_IMPL_SERVERPREF_BOOL(nsImapIncomingServer, DualUseFolders,
+ "dual_use_folders")
+
+NS_IMPL_SERVERPREF_STR(nsImapIncomingServer, AdminUrl, "admin_url")
+
+NS_IMPL_SERVERPREF_BOOL(nsImapIncomingServer, CleanupInboxOnExit,
+ "cleanup_inbox_on_exit")
+
+NS_IMPL_SERVERPREF_BOOL(nsImapIncomingServer, OfflineDownload,
+ "offline_download")
+
+NS_IMPL_SERVERPREF_BOOL(nsImapIncomingServer, DownloadBodiesOnGetNewMail,
+ "download_bodies_on_get_new_mail")
+
+NS_IMPL_SERVERPREF_BOOL(nsImapIncomingServer, AutoSyncOfflineStores,
+ "autosync_offline_stores")
+
+NS_IMPL_SERVERPREF_BOOL(nsImapIncomingServer, UseIdle, "use_idle")
+
+NS_IMPL_SERVERPREF_BOOL(nsImapIncomingServer, CheckAllFoldersForNew,
+ "check_all_folders_for_new")
+
+NS_IMPL_SERVERPREF_BOOL(nsImapIncomingServer, UseCondStore, "use_condstore")
+
+NS_IMPL_SERVERPREF_BOOL(nsImapIncomingServer, IsGMailServer, "is_gmail")
+
+NS_IMPL_SERVERPREF_BOOL(nsImapIncomingServer, UseCompressDeflate,
+ "use_compress_deflate")
+
+NS_IMPL_SERVERPREF_INT(nsImapIncomingServer, AutoSyncMaxAgeDays,
+ "autosync_max_age_days")
+
+NS_IMPL_SERVERPREF_BOOL(nsImapIncomingServer, AllowUTF8Accept,
+ "allow_utf8_accept")
+
+NS_IMETHODIMP
+nsImapIncomingServer::GetShuttingDown(bool* retval) {
+ NS_ENSURE_ARG_POINTER(retval);
+ *retval = m_shuttingDown;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::SetShuttingDown(bool val) {
+ m_shuttingDown = val;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::GetDeleteModel(int32_t* retval) {
+ NS_ENSURE_ARG(retval);
+ return GetIntValue("delete_model", retval);
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::SetDeleteModel(int32_t ivalue) {
+ nsresult rv = SetIntValue("delete_model", ivalue);
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIImapHostSessionList> hostSession =
+ do_GetService(kCImapHostSessionListCID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ hostSession->SetDeleteIsMoveToTrashForHost(
+ m_serverKey.get(), ivalue == nsMsgImapDeleteModels::MoveToTrash);
+ hostSession->SetShowDeletedMessagesForHost(
+ m_serverKey.get(), ivalue == nsMsgImapDeleteModels::IMAPDelete);
+
+ // Despite its name, this returns the trash folder path, for example
+ // INBOX/Trash.
+ nsAutoString trashFolderName;
+ nsresult rv = GetTrashFolderName(trashFolderName);
+ if (NS_SUCCEEDED(rv)) {
+ nsAutoCString trashFolderNameUtf7or8;
+ bool useUTF8 = false;
+ GetUtf8AcceptEnabled(&useUTF8);
+ if (useUTF8) {
+ CopyUTF16toUTF8(trashFolderName, trashFolderNameUtf7or8);
+ } else {
+ CopyUTF16toMUTF7(trashFolderName, trashFolderNameUtf7or8);
+ }
+ nsCOMPtr<nsIMsgFolder> trashFolder;
+ // 'trashFolderName' being a path here works well since this is appended
+ // to the server's root folder in GetFolder().
+ rv = GetFolder(trashFolderNameUtf7or8, getter_AddRefs(trashFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCString trashURI;
+ trashFolder->GetURI(trashURI);
+ rv = GetMsgFolderFromURI(trashFolder, trashURI,
+ getter_AddRefs(trashFolder));
+ if (NS_SUCCEEDED(rv) && trashFolder) {
+ // If the trash folder is used, set the flag, otherwise clear it.
+ if (ivalue == nsMsgImapDeleteModels::MoveToTrash)
+ trashFolder->SetFlag(nsMsgFolderFlags::Trash);
+ else
+ trashFolder->ClearFlag(nsMsgFolderFlags::Trash);
+ }
+ }
+ }
+ return rv;
+}
+
+NS_IMPL_SERVERPREF_INT(nsImapIncomingServer, TimeOutLimits, "timeout")
+
+NS_IMPL_SERVERPREF_STR(nsImapIncomingServer, ServerIDPref, "serverIDResponse")
+
+NS_IMPL_SERVERPREF_STR(nsImapIncomingServer, PersonalNamespace,
+ "namespace.personal")
+
+NS_IMPL_SERVERPREF_STR(nsImapIncomingServer, PublicNamespace,
+ "namespace.public")
+
+NS_IMPL_SERVERPREF_STR(nsImapIncomingServer, OtherUsersNamespace,
+ "namespace.other_users")
+
+NS_IMPL_SERVERPREF_BOOL(nsImapIncomingServer, FetchByChunks, "fetch_by_chunks")
+
+NS_IMPL_SERVERPREF_BOOL(nsImapIncomingServer, SendID, "send_client_info")
+
+NS_IMETHODIMP
+nsImapIncomingServer::GetIsAOLServer(bool* aBool) {
+ NS_ENSURE_ARG_POINTER(aBool);
+ *aBool = ((m_capability & kAOLImapCapability) != 0);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::SetIsAOLServer(bool aBool) {
+ if (aBool)
+ m_capability |= kAOLImapCapability;
+ else
+ m_capability &= ~kAOLImapCapability;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::UpdateTrySTARTTLSPref(bool aStartTLSSucceeded) {
+ SetSocketType(aStartTLSSucceeded ? nsMsgSocketType::alwaysSTARTTLS
+ : nsMsgSocketType::plain);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::GetImapConnectionAndLoadUrl(nsIImapUrl* aImapUrl,
+ nsISupports* aConsumer) {
+ nsCOMPtr<nsIImapProtocol> aProtocol;
+
+ nsresult rv = GetImapConnection(aImapUrl, getter_AddRefs(aProtocol));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsurl = do_QueryInterface(aImapUrl, &rv);
+ if (aProtocol) {
+ rv = aProtocol->LoadImapUrl(mailnewsurl, aConsumer);
+ // *** jt - in case of the time out situation or the connection gets
+ // terminated by some unforeseen problems let's give it a second chance
+ // to run the url
+ if (NS_FAILED(rv) && rv != NS_ERROR_ILLEGAL_VALUE) {
+ rv = aProtocol->LoadImapUrl(mailnewsurl, aConsumer);
+ }
+ } else { // unable to get an imap connection to run the url; add to the url
+ // queue
+ nsImapProtocol::LogImapUrl("queuing url", aImapUrl);
+ PR_CEnterMonitor(this);
+ m_urlQueue.AppendObject(aImapUrl);
+ m_urlConsumers.AppendElement(aConsumer);
+ NS_IF_ADDREF(aConsumer);
+ PR_CExitMonitor(this);
+ // let's try running it now - maybe the connection is free now.
+ bool urlRun;
+ rv = LoadNextQueuedUrl(nullptr, &urlRun);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::PrepareToRetryUrl(nsIImapUrl* aImapUrl,
+ nsIImapMockChannel** aChannel) {
+ NS_ENSURE_ARG_POINTER(aChannel);
+ NS_ENSURE_ARG_POINTER(aImapUrl);
+ // maybe there's more we could do here, but this is all we need now.
+ return aImapUrl->GetMockChannel(aChannel);
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::SuspendUrl(nsIImapUrl* aImapUrl) {
+ NS_ENSURE_ARG_POINTER(aImapUrl);
+ nsImapProtocol::LogImapUrl("suspending url", aImapUrl);
+ PR_CEnterMonitor(this);
+ m_urlQueue.AppendObject(aImapUrl);
+ m_urlConsumers.AppendElement(nullptr);
+ PR_CExitMonitor(this);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::RetryUrl(nsIImapUrl* aImapUrl,
+ nsIImapMockChannel* aChannel) {
+ nsresult rv;
+ // Get current thread envent queue
+ aImapUrl->SetMockChannel(aChannel);
+ nsCOMPtr<nsIImapProtocol> protocolInstance;
+ nsImapProtocol::LogImapUrl("creating protocol instance to retry queued url",
+ aImapUrl);
+ nsCOMPtr<nsIThread> thread(do_GetCurrentThread());
+ rv = GetImapConnection(aImapUrl, getter_AddRefs(protocolInstance));
+ if (NS_SUCCEEDED(rv) && protocolInstance) {
+ nsCOMPtr<nsIURI> url = do_QueryInterface(aImapUrl, &rv);
+ if (NS_SUCCEEDED(rv) && url) {
+ nsImapProtocol::LogImapUrl("retrying url", aImapUrl);
+ rv = protocolInstance->LoadImapUrl(
+ url, nullptr); // ### need to save the display consumer.
+ }
+ }
+ return rv;
+}
+
+// checks to see if there are any queued urls on this incoming server,
+// and if so, tries to run the oldest one. Returns true if the url is run
+// on the passed in protocol connection.
+NS_IMETHODIMP
+nsImapIncomingServer::LoadNextQueuedUrl(nsIImapProtocol* aProtocol,
+ bool* aResult) {
+ if (WeAreOffline()) return NS_MSG_ERROR_OFFLINE;
+
+ nsresult rv = NS_OK;
+ bool urlRun = false;
+ bool keepGoing = true;
+ nsCOMPtr<nsIImapProtocol> protocolInstance;
+
+ MutexAutoLock mon(mLock);
+ int32_t cnt = m_urlQueue.Count();
+
+ while (cnt > 0 && !urlRun && keepGoing) {
+ nsCOMPtr<nsIImapUrl> aImapUrl(m_urlQueue[0]);
+
+ bool removeUrlFromQueue = false;
+ if (aImapUrl) {
+ nsImapProtocol::LogImapUrl("considering playing queued url", aImapUrl);
+ rv = DoomUrlIfChannelHasError(aImapUrl, &removeUrlFromQueue);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // if we didn't doom the url, lets run it.
+ if (!removeUrlFromQueue) {
+ nsISupports* aConsumer = m_urlConsumers.ElementAt(0);
+ NS_IF_ADDREF(aConsumer);
+
+ nsImapProtocol::LogImapUrl(
+ "creating protocol instance to play queued url", aImapUrl);
+ rv = GetImapConnection(aImapUrl, getter_AddRefs(protocolInstance));
+ if (NS_SUCCEEDED(rv) && protocolInstance) {
+ nsCOMPtr<nsIURI> url = do_QueryInterface(aImapUrl, &rv);
+ if (NS_SUCCEEDED(rv) && url) {
+ nsImapProtocol::LogImapUrl("playing queued url", aImapUrl);
+ rv = protocolInstance->LoadImapUrl(url, aConsumer);
+ if (NS_SUCCEEDED(rv)) {
+ bool isInbox;
+ protocolInstance->IsBusy(&urlRun, &isInbox);
+ if (!urlRun)
+ nsImapProtocol::LogImapUrl("didn't need to run", aImapUrl);
+ removeUrlFromQueue = true;
+ } else {
+ nsImapProtocol::LogImapUrl("playing queued url failed", aImapUrl);
+ }
+ }
+ } else {
+ nsImapProtocol::LogImapUrl(
+ "failed creating protocol instance to play queued url", aImapUrl);
+ keepGoing = false;
+ }
+ NS_IF_RELEASE(aConsumer);
+ }
+ if (removeUrlFromQueue) {
+ m_urlQueue.RemoveObjectAt(0);
+ m_urlConsumers.RemoveElementAt(0);
+ }
+ }
+ cnt = m_urlQueue.Count();
+ }
+ if (aResult) *aResult = urlRun && aProtocol && aProtocol == protocolInstance;
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::AbortQueuedUrls() {
+ nsresult rv = NS_OK;
+
+ MutexAutoLock mon(mLock);
+ int32_t cnt = m_urlQueue.Count();
+
+ while (cnt > 0) {
+ nsCOMPtr<nsIImapUrl> aImapUrl(m_urlQueue[cnt - 1]);
+ bool removeUrlFromQueue = false;
+
+ if (aImapUrl) {
+ rv = DoomUrlIfChannelHasError(aImapUrl, &removeUrlFromQueue);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (removeUrlFromQueue) {
+ m_urlQueue.RemoveObjectAt(cnt - 1);
+ m_urlConsumers.RemoveElementAt(cnt - 1);
+ }
+ }
+ cnt--;
+ }
+
+ return rv;
+}
+
+// if this url has a channel with an error, doom it and its mem cache entries,
+// and notify url listeners.
+nsresult nsImapIncomingServer::DoomUrlIfChannelHasError(nsIImapUrl* aImapUrl,
+ bool* urlDoomed) {
+ nsresult rv = NS_OK;
+
+ nsCOMPtr<nsIMsgMailNewsUrl> aMailNewsUrl(do_QueryInterface(aImapUrl, &rv));
+
+ if (aMailNewsUrl && aImapUrl) {
+ nsCOMPtr<nsIImapMockChannel> mockChannel;
+
+ if (NS_SUCCEEDED(aImapUrl->GetMockChannel(getter_AddRefs(mockChannel))) &&
+ mockChannel) {
+ nsresult requestStatus;
+ mockChannel->GetStatus(&requestStatus);
+ if (NS_FAILED(requestStatus)) {
+ nsresult res;
+ *urlDoomed = true;
+ nsImapProtocol::LogImapUrl("dooming url", aImapUrl);
+
+ mockChannel
+ ->Close(); // try closing it to get channel listener nulled out.
+
+ if (aMailNewsUrl) {
+ nsCOMPtr<nsICacheEntry> cacheEntry;
+ res = aMailNewsUrl->GetMemCacheEntry(getter_AddRefs(cacheEntry));
+ if (NS_SUCCEEDED(res) && cacheEntry) cacheEntry->AsyncDoom(nullptr);
+ // we're aborting this url - tell listeners
+ aMailNewsUrl->SetUrlState(false, NS_MSG_ERROR_URL_ABORTED);
+ }
+ }
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::RemoveConnection(nsIImapProtocol* aImapConnection) {
+ PR_CEnterMonitor(this);
+ if (aImapConnection) m_connectionCache.RemoveObject(aImapConnection);
+
+ PR_CExitMonitor(this);
+ return NS_OK;
+}
+
+bool nsImapIncomingServer::ConnectionTimeOut(nsIImapProtocol* aConnection) {
+ bool retVal = false;
+ if (!aConnection) return retVal;
+ nsresult rv;
+
+ int32_t timeoutInMinutes = 0;
+ rv = GetTimeOutLimits(&timeoutInMinutes);
+ if (NS_FAILED(rv) || timeoutInMinutes <= 0 || timeoutInMinutes > 29) {
+ timeoutInMinutes = 29;
+ SetTimeOutLimits(timeoutInMinutes);
+ }
+
+ PRTime cacheTimeoutLimits = timeoutInMinutes * 60 * PR_USEC_PER_SEC;
+ PRTime lastActiveTimeStamp;
+ rv = aConnection->GetLastActiveTimeStamp(&lastActiveTimeStamp);
+
+ if (PR_Now() - lastActiveTimeStamp >= cacheTimeoutLimits) {
+ RemoveConnection(aConnection);
+ aConnection->TellThreadToDie(false);
+ retVal = true;
+ }
+ return retVal;
+}
+
+nsresult nsImapIncomingServer::GetImapConnection(
+ nsIImapUrl* aImapUrl, nsIImapProtocol** aImapConnection) {
+ NS_ENSURE_ARG_POINTER(aImapUrl);
+
+ nsresult rv = NS_OK;
+ bool canRunUrlImmediately = false;
+ bool canRunButBusy = false;
+ nsCOMPtr<nsIImapProtocol> connection;
+ nsCOMPtr<nsIImapProtocol> freeConnection;
+ bool isBusy = false;
+ bool isInboxConnection = false;
+
+ PR_CEnterMonitor(this);
+
+ int32_t maxConnections;
+ (void)GetMaximumConnectionsNumber(&maxConnections);
+
+ int32_t cnt = m_connectionCache.Count();
+ *aImapConnection = nullptr;
+
+ // iterate through the connection cache for a connection that can handle this
+ // url.
+ // loop until we find a connection that can run the url, or doesn't have to
+ // wait?
+ for (int32_t i = cnt - 1; i >= 0 && !canRunUrlImmediately && !canRunButBusy;
+ i--) {
+ connection = m_connectionCache[i];
+ if (connection) {
+ bool badConnection = ConnectionTimeOut(connection);
+ if (!badConnection) {
+ badConnection = NS_FAILED(connection->CanHandleUrl(
+ aImapUrl, &canRunUrlImmediately, &canRunButBusy));
+#ifdef DEBUG_bienvenu
+ nsAutoCString curSelectedFolderName;
+ if (connection)
+ connection->GetSelectedMailboxName(
+ getter_Copies(curSelectedFolderName));
+ // check that no other connection is in the same selected state.
+ if (!curSelectedFolderName.IsEmpty()) {
+ for (uint32_t j = 0; j < cnt; j++) {
+ if (j != i) {
+ nsCOMPtr<nsIImapProtocol> otherConnection =
+ do_QueryElementAt(m_connectionCache, j);
+ if (otherConnection) {
+ nsAutoCString otherSelectedFolderName;
+ otherConnection->GetSelectedMailboxName(
+ getter_Copies(otherSelectedFolderName));
+ NS_ASSERTION(
+ !curSelectedFolderName.Equals(otherSelectedFolderName),
+ "two connections selected on same folder");
+ }
+ }
+ }
+ }
+#endif // DEBUG_bienvenu
+ }
+ if (badConnection) {
+ connection = nullptr;
+ continue;
+ }
+ }
+
+ // if this connection is wrong, but it's not busy, check if we should
+ // designate it as the free connection.
+ if (!canRunUrlImmediately && !canRunButBusy && connection) {
+ rv = connection->IsBusy(&isBusy, &isInboxConnection);
+ if (NS_FAILED(rv)) continue;
+ // if max connections is <= 1, we have to re-use the inbox connection.
+ if (!isBusy && (!isInboxConnection || maxConnections <= 1)) {
+ if (!freeConnection)
+ freeConnection = connection;
+ else // check which is the better free connection to use.
+ { // We prefer one not in the selected state.
+ nsAutoCString selectedFolderName;
+ connection->GetSelectedMailboxName(getter_Copies(selectedFolderName));
+ if (selectedFolderName.IsEmpty()) freeConnection = connection;
+ }
+ }
+ }
+ // don't leave this loop with connection set if we can't use it!
+ if (!canRunButBusy && !canRunUrlImmediately) connection = nullptr;
+ }
+
+ nsImapState requiredState;
+ aImapUrl->GetRequiredImapState(&requiredState);
+ // refresh cnt in case we killed one or more dead connections. This
+ // will prevent us from not spinning up a new connection when all
+ // connections were dead.
+ cnt = m_connectionCache.Count();
+ // if we got here and we have a connection, then we should return it!
+ if (canRunUrlImmediately && connection) {
+ connection.forget(aImapConnection);
+ } else if (canRunButBusy) {
+ // do nothing; return NS_OK; for queuing
+ }
+ // CanHandleUrl will pretend that some types of urls require a selected state
+ // url (e.g., a folder delete or msg append) but we shouldn't create new
+ // connections for these types of urls if we have a free connection. So we
+ // check the actual required state here.
+ else if (cnt < maxConnections &&
+ (!freeConnection ||
+ requiredState == nsIImapUrl::nsImapSelectedState)) {
+ rv = CreateProtocolInstance(aImapConnection);
+ } else if (freeConnection) {
+ freeConnection.forget(aImapConnection);
+ } else {
+ if (cnt >= maxConnections)
+ nsImapProtocol::LogImapUrl("exceeded connection cache limit", aImapUrl);
+ // caller will queue the url
+ }
+
+ PR_CExitMonitor(this);
+ return rv;
+}
+
+nsresult nsImapIncomingServer::CreateProtocolInstance(
+ nsIImapProtocol** aImapConnection) {
+ // create a new connection and add it to the connection cache
+ // we may need to flag the protocol connection as busy so we don't get
+ // a race condition where someone else goes through this code
+
+ int32_t authMethod;
+ GetAuthMethod(&authMethod);
+ nsresult rv;
+ // pre-flight that we have nss - on the ui thread - for MD5 etc.
+ switch (authMethod) {
+ case nsMsgAuthMethod::passwordEncrypted:
+ case nsMsgAuthMethod::secure:
+ case nsMsgAuthMethod::anything: {
+ nsCOMPtr<nsISupports> dummyUsedToEnsureNSSIsInitialized =
+ do_GetService("@mozilla.org/psm;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } break;
+ default:
+ break;
+ }
+ nsCOMPtr<nsIImapHostSessionList> hostSession =
+ do_GetService(kCImapHostSessionListCID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ RefPtr<nsImapProtocol> protocolInstance(new nsImapProtocol());
+ rv = protocolInstance->Initialize(hostSession, this);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // It implements nsIChannel, and all channels require loadInfo.
+ protocolInstance->SetLoadInfo(new mozilla::net::LoadInfo(
+ nsContentUtils::GetSystemPrincipal(), nullptr, nullptr,
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER));
+
+ // take the protocol instance and add it to the connectionCache
+ m_connectionCache.AppendObject(protocolInstance);
+ protocolInstance.forget(aImapConnection);
+ return rv;
+}
+
+NS_IMETHODIMP nsImapIncomingServer::CloseConnectionForFolder(
+ nsIMsgFolder* aMsgFolder) {
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIImapProtocol> connection;
+ bool isBusy = false, isInbox = false;
+ nsCString inFolderName;
+ nsCString connectionFolderName;
+ nsCOMPtr<nsIMsgImapMailFolder> imapFolder = do_QueryInterface(aMsgFolder);
+
+ if (!imapFolder) return NS_ERROR_NULL_POINTER;
+
+ int32_t cnt = m_connectionCache.Count();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ imapFolder->GetOnlineName(inFolderName);
+ PR_CEnterMonitor(this);
+
+ for (int32_t i = 0; i < cnt; ++i) {
+ connection = m_connectionCache[i];
+ if (connection) {
+ rv = connection->GetSelectedMailboxName(
+ getter_Copies(connectionFolderName));
+ if (connectionFolderName.Equals(inFolderName)) {
+ rv = connection->IsBusy(&isBusy, &isInbox);
+ if (!isBusy) rv = connection->TellThreadToDie(true);
+ break; // found it, end of the loop
+ }
+ }
+ }
+
+ PR_CExitMonitor(this);
+ return rv;
+}
+
+NS_IMETHODIMP nsImapIncomingServer::ResetConnection(
+ const nsACString& folderName) {
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIImapProtocol> connection;
+ bool isBusy = false, isInbox = false;
+ nsCString curFolderName;
+
+ int32_t cnt = m_connectionCache.Count();
+
+ PR_CEnterMonitor(this);
+
+ for (int32_t i = 0; i < cnt; ++i) {
+ connection = m_connectionCache[i];
+ if (connection) {
+ rv = connection->GetSelectedMailboxName(getter_Copies(curFolderName));
+ if (curFolderName.Equals(folderName)) {
+ rv = connection->IsBusy(&isBusy, &isInbox);
+ if (!isBusy) rv = connection->ResetToAuthenticatedState();
+ break; // found it, end of the loop
+ }
+ }
+ }
+
+ PR_CExitMonitor(this);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::PerformExpand(nsIMsgWindow* aMsgWindow) {
+ nsString password;
+ nsresult rv;
+ rv = GetPassword(password);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (password.IsEmpty()) {
+ // Check if this is due to oauth2 showing empty password. If so, keep going.
+ int32_t authMethod = 0;
+ GetAuthMethod(&authMethod);
+ if (authMethod != nsMsgAuthMethod::OAuth2) return NS_OK;
+ }
+
+ rv = ResetFoldersToUnverified(nullptr);
+
+ nsCOMPtr<nsIMsgFolder> rootMsgFolder;
+ rv = GetRootFolder(getter_AddRefs(rootMsgFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!rootMsgFolder) return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIImapService> imapService =
+ do_GetService("@mozilla.org/messenger/imapservice;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIThread> thread(do_GetCurrentThread());
+ rv = imapService->DiscoverAllFolders(rootMsgFolder, this, aMsgWindow);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIImapHostSessionList> hostSessionList =
+ do_GetService(kCImapHostSessionListCID, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ nsAutoCString serverKey;
+ rv = GetKey(serverKey);
+ if (!serverKey.IsEmpty())
+ hostSessionList->SetDiscoveryForHostInProgress(serverKey.get(), true);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::VerifyLogon(nsIUrlListener* aUrlListener,
+ nsIMsgWindow* aMsgWindow, nsIURI** aURL) {
+ nsresult rv;
+
+ nsCOMPtr<nsIImapService> imapService =
+ do_GetService("@mozilla.org/messenger/imapservice;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIMsgFolder> rootFolder;
+ // this will create the resource if it doesn't exist, but it shouldn't
+ // do anything on disk.
+ rv = GetRootFolder(getter_AddRefs(rootFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return imapService->VerifyLogon(rootFolder, aUrlListener, aMsgWindow, aURL);
+}
+
+NS_IMETHODIMP nsImapIncomingServer::PerformBiff(nsIMsgWindow* aMsgWindow) {
+ nsCOMPtr<nsIMsgFolder> rootMsgFolder;
+ nsresult rv = GetRootMsgFolder(getter_AddRefs(rootMsgFolder));
+ if (NS_SUCCEEDED(rv)) {
+ SetPerformingBiff(true);
+ rv = rootMsgFolder->GetNewMessages(aMsgWindow, nullptr);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::CloseCachedConnections() {
+ nsCOMPtr<nsIImapProtocol> connection;
+ PR_CEnterMonitor(this);
+
+ // iterate through the connection cache closing open connections.
+ int32_t cnt = m_connectionCache.Count();
+
+ for (int32_t i = cnt; i > 0; --i) {
+ connection = m_connectionCache[i - 1];
+ if (connection) connection->TellThreadToDie(true);
+ }
+
+ PR_CExitMonitor(this);
+ return NS_OK;
+}
+
+nsresult nsImapIncomingServer::CreateRootFolderFromUri(
+ const nsACString& serverUri, nsIMsgFolder** rootFolder) {
+ nsImapMailFolder* newRootFolder = new nsImapMailFolder;
+ newRootFolder->Init(serverUri);
+ NS_ADDREF(*rootFolder = newRootFolder);
+ return NS_OK;
+}
+
+// nsIImapServerSink impl
+// aNewFolder will not be set if we're listing for the subscribe UI, since
+// that's the way 4.x worked.
+NS_IMETHODIMP nsImapIncomingServer::PossibleImapMailbox(
+ const nsACString& folderPath, char hierarchyDelimiter, int32_t boxFlags,
+ bool* aNewFolder) {
+ NS_ENSURE_ARG_POINTER(aNewFolder);
+ NS_ENSURE_TRUE(!folderPath.IsEmpty(), NS_ERROR_FAILURE);
+
+ // folderPath is in canonical format, i.e., hierarchy separator has been
+ // replaced with '/'
+ nsresult rv;
+ bool found = false;
+ bool haveParent = false;
+ nsCOMPtr<nsIMsgImapMailFolder> hostFolder;
+ nsCOMPtr<nsIMsgFolder> aFolder;
+ bool explicitlyVerify = false;
+
+ *aNewFolder = false;
+ nsCOMPtr<nsIMsgFolder> rootFolder;
+ rv = GetRootFolder(getter_AddRefs(rootFolder));
+
+ if (NS_FAILED(rv)) return rv;
+
+ nsAutoCString dupFolderPath(folderPath);
+ if (dupFolderPath.Last() == '/') {
+ dupFolderPath.SetLength(dupFolderPath.Length() - 1);
+ if (dupFolderPath.IsEmpty()) return NS_ERROR_FAILURE;
+ // *** this is what we did in 4.x in order to list uw folder only
+ // mailbox in order to get the \NoSelect flag
+ explicitlyVerify = !(boxFlags & kNameSpace);
+ }
+ if (mDoingSubscribeDialog) {
+ // Make sure the imapmailfolder object has the right delimiter because the
+ // unsubscribed folders (those not in the 'lsub' list) have the delimiter
+ // set to the default ('^').
+ if (rootFolder && !dupFolderPath.IsEmpty()) {
+ nsCOMPtr<nsIMsgFolder> msgFolder;
+ bool isNamespace = false;
+ bool noSelect = false;
+
+ rv = rootFolder->FindSubFolder(dupFolderPath, getter_AddRefs(msgFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+ m_subscribeFolders.AppendObject(msgFolder);
+ noSelect = (boxFlags & kNoselect) != 0;
+ nsCOMPtr<nsIMsgImapMailFolder> imapFolder =
+ do_QueryInterface(msgFolder, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ imapFolder->SetHierarchyDelimiter(hierarchyDelimiter);
+ isNamespace = (boxFlags & kNameSpace) != 0;
+ if (!isNamespace)
+ rv = AddTo(dupFolderPath,
+ mDoingLsub && !noSelect /* add as subscribed */, !noSelect,
+ mDoingLsub /* change if exists */);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return rv;
+ }
+ }
+
+ hostFolder = do_QueryInterface(rootFolder, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ nsAutoCString tempFolderName(dupFolderPath);
+ nsAutoCString tokenStr, remStr, changedStr;
+ int32_t slashPos = tempFolderName.FindChar('/');
+ if (slashPos > 0) {
+ tokenStr = StringHead(tempFolderName, slashPos);
+ remStr = Substring(tempFolderName, slashPos);
+ } else
+ tokenStr.Assign(tempFolderName);
+
+ if ((int32_t(PL_strcasecmp(tokenStr.get(), "INBOX")) == 0) &&
+ (strcmp(tokenStr.get(), "INBOX") != 0))
+ changedStr.AppendLiteral("INBOX");
+ else
+ changedStr.Append(tokenStr);
+
+ if (slashPos > 0) changedStr.Append(remStr);
+
+ dupFolderPath.Assign(changedStr);
+ nsAutoCString folderName(dupFolderPath);
+
+ nsAutoCString uri;
+ nsCString serverUri;
+ GetServerURI(serverUri);
+ uri.Assign(serverUri);
+ int32_t leafPos = folderName.RFindChar('/');
+ nsAutoCString parentName(folderName);
+ nsAutoCString parentUri(uri);
+
+ if (leafPos > 0) {
+ // If there is a hierarchy, there is a parent.
+ // Don't strip off slash if it's the first character
+ parentName.SetLength(leafPos);
+ folderName.Cut(0, leafPos + 1); // get rid of the parent name
+ haveParent = true;
+ parentUri.Append('/');
+ parentUri.Append(parentName);
+ }
+ if (folderPath.LowerCaseEqualsLiteral("inbox") &&
+ hierarchyDelimiter == kOnlineHierarchySeparatorNil) {
+ hierarchyDelimiter = '/'; // set to default in this case (as in 4.x)
+ hostFolder->SetHierarchyDelimiter(hierarchyDelimiter);
+ }
+
+ nsCOMPtr<nsIMsgFolder> child;
+
+ // nsCString possibleName(aSpec->allocatedPathName);
+ uri.Append('/');
+ uri.Append(dupFolderPath);
+ bool caseInsensitive = dupFolderPath.LowerCaseEqualsLiteral("inbox");
+ rootFolder->GetChildWithURI(uri, true, caseInsensitive,
+ getter_AddRefs(child));
+ // if we couldn't find this folder by URI, tell the imap code it's a new
+ // folder to us
+ *aNewFolder = !child;
+ if (child) found = true;
+ if (!found) {
+ // trying to find/discover the parent
+ if (haveParent) {
+ nsCOMPtr<nsIMsgFolder> parent;
+ bool parentIsNew;
+ caseInsensitive = parentName.LowerCaseEqualsLiteral("inbox");
+ rootFolder->GetChildWithURI(parentUri, true, caseInsensitive,
+ getter_AddRefs(parent));
+ if (!parent /* || parentFolder->GetFolderNeedsAdded()*/) {
+ PossibleImapMailbox(
+ parentName, hierarchyDelimiter,
+ kNoselect | // be defensive
+ ((boxFlags & // only inherit certain flags from the child
+ (kPublicMailbox | kOtherUsersMailbox | kPersonalMailbox))),
+ &parentIsNew);
+ }
+ }
+ rv = hostFolder->CreateClientSubfolderInfo(
+ dupFolderPath, hierarchyDelimiter, boxFlags, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ caseInsensitive = dupFolderPath.LowerCaseEqualsLiteral("inbox");
+ rootFolder->GetChildWithURI(uri, true, caseInsensitive,
+ getter_AddRefs(child));
+ }
+ if (child) {
+ nsCOMPtr<nsIMsgImapMailFolder> imapFolder = do_QueryInterface(child);
+ if (imapFolder) {
+ nsAutoCString onlineName;
+ nsAutoString unicodeName;
+ imapFolder->SetVerifiedAsOnlineFolder(true);
+ imapFolder->SetHierarchyDelimiter(hierarchyDelimiter);
+ if (boxFlags & kImapTrash) {
+ int32_t deleteModel;
+ GetDeleteModel(&deleteModel);
+ if (deleteModel == nsMsgImapDeleteModels::MoveToTrash)
+ child->SetFlag(nsMsgFolderFlags::Trash);
+ }
+
+ imapFolder->SetBoxFlags(boxFlags);
+ imapFolder->SetExplicitlyVerify(explicitlyVerify);
+ imapFolder->GetOnlineName(onlineName);
+
+ // online name needs to use the correct hierarchy delimiter (I think...)
+ // or the canonical path - one or the other, but be consistent.
+ dupFolderPath.ReplaceChar('/', hierarchyDelimiter);
+ if (hierarchyDelimiter != '/') {
+ nsImapUrl::UnescapeSlashes(dupFolderPath);
+ }
+
+ // GMail gives us a localized name for the inbox but doesn't let
+ // us select that localized name.
+ if (boxFlags & kImapInbox)
+ imapFolder->SetOnlineName("INBOX"_ns);
+ else if (onlineName.IsEmpty() || !onlineName.Equals(dupFolderPath))
+ imapFolder->SetOnlineName(dupFolderPath);
+
+ if (hierarchyDelimiter != '/') {
+ nsImapUrl::UnescapeSlashes(folderName);
+ }
+ if (NS_SUCCEEDED(CopyFolderNameToUTF16(folderName, unicodeName)))
+ child->SetPrettyName(unicodeName);
+ }
+ }
+ if (!found && child)
+ child->SetMsgDatabase(nullptr); // close the db, so we don't hold open all
+ // the .msf files for new folders
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapIncomingServer::AddFolderRights(
+ const nsACString& mailboxName, const nsACString& userName,
+ const nsACString& rights) {
+ nsCOMPtr<nsIMsgFolder> rootFolder;
+ nsresult rv = GetRootFolder(getter_AddRefs(rootFolder));
+ if (NS_SUCCEEDED(rv) && rootFolder) {
+ nsCOMPtr<nsIMsgImapMailFolder> imapRoot = do_QueryInterface(rootFolder);
+ if (imapRoot) {
+ nsCOMPtr<nsIMsgImapMailFolder> foundFolder;
+ rv = imapRoot->FindOnlineSubFolder(mailboxName,
+ getter_AddRefs(foundFolder));
+ if (NS_SUCCEEDED(rv) && foundFolder)
+ return foundFolder->AddFolderRights(userName, rights);
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapIncomingServer::FolderNeedsACLInitialized(
+ const nsACString& folderPath, bool* aNeedsACLInitialized) {
+ NS_ENSURE_ARG_POINTER(aNeedsACLInitialized);
+ nsCOMPtr<nsIMsgFolder> rootFolder;
+ nsresult rv = GetRootFolder(getter_AddRefs(rootFolder));
+ if (NS_SUCCEEDED(rv) && rootFolder) {
+ nsCOMPtr<nsIMsgImapMailFolder> imapRoot = do_QueryInterface(rootFolder);
+ if (imapRoot) {
+ nsCOMPtr<nsIMsgImapMailFolder> foundFolder;
+ rv = imapRoot->FindOnlineSubFolder(folderPath,
+ getter_AddRefs(foundFolder));
+ if (NS_SUCCEEDED(rv) && foundFolder) {
+ nsCOMPtr<nsIImapMailFolderSink> folderSink =
+ do_QueryInterface(foundFolder);
+ if (folderSink)
+ return folderSink->GetFolderNeedsACLListed(aNeedsACLInitialized);
+ }
+ }
+ }
+ *aNeedsACLInitialized = false; // maybe we want to say TRUE here...
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapIncomingServer::RefreshFolderRights(
+ const nsACString& folderPath) {
+ nsCOMPtr<nsIMsgFolder> rootFolder;
+ nsresult rv = GetRootFolder(getter_AddRefs(rootFolder));
+ if (NS_SUCCEEDED(rv) && rootFolder) {
+ nsCOMPtr<nsIMsgImapMailFolder> imapRoot = do_QueryInterface(rootFolder);
+ if (imapRoot) {
+ nsCOMPtr<nsIMsgImapMailFolder> foundFolder;
+ rv = imapRoot->FindOnlineSubFolder(folderPath,
+ getter_AddRefs(foundFolder));
+ if (NS_SUCCEEDED(rv) && foundFolder)
+ return foundFolder->RefreshFolderRights();
+ }
+ }
+ return rv;
+}
+
+nsresult nsImapIncomingServer::GetFolder(const nsACString& name,
+ nsIMsgFolder** pFolder) {
+ NS_ENSURE_ARG_POINTER(pFolder);
+ NS_ENSURE_TRUE(!name.IsEmpty(), NS_ERROR_FAILURE);
+ nsresult rv;
+ *pFolder = nullptr;
+
+ nsCOMPtr<nsIMsgFolder> rootFolder;
+ rv = GetRootFolder(getter_AddRefs(rootFolder));
+ if (NS_SUCCEEDED(rv) && rootFolder) {
+ nsCString uri;
+ rv = rootFolder->GetURI(uri);
+ if (NS_SUCCEEDED(rv) && !uri.IsEmpty()) {
+ nsAutoCString uriString(uri);
+ uriString.Append('/');
+ uriString.Append(name);
+ rv = GetOrCreateFolder(uriString, pFolder);
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapIncomingServer::OnlineFolderDelete(
+ const nsACString& aFolderName) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapIncomingServer::OnlineFolderCreateFailed(
+ const nsACString& aFolderName) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapIncomingServer::OnlineFolderRename(
+ nsIMsgWindow* msgWindow, const nsACString& oldName,
+ const nsACString& newName) {
+ nsresult rv = NS_ERROR_FAILURE;
+ if (!newName.IsEmpty()) {
+ nsCOMPtr<nsIMsgFolder> me;
+ rv = GetFolder(oldName, getter_AddRefs(me));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIMsgFolder> parent;
+ nsCString tmpNewName(newName);
+ int32_t folderStart = tmpNewName.RFindChar('/');
+ if (folderStart > 0) {
+ rv = GetFolder(StringHead(tmpNewName, folderStart),
+ getter_AddRefs(parent));
+ } else // root is the parent
+ rv = GetRootFolder(getter_AddRefs(parent));
+ if (NS_SUCCEEDED(rv) && parent) {
+ nsCOMPtr<nsIMsgImapMailFolder> folder;
+ folder = do_QueryInterface(me, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ folder->RenameLocal(tmpNewName, parent);
+ nsCOMPtr<nsIMsgImapMailFolder> parentImapFolder =
+ do_QueryInterface(parent);
+
+ if (parentImapFolder)
+ parentImapFolder->RenameClient(msgWindow, me, oldName, tmpNewName);
+
+ nsCOMPtr<nsIMsgFolder> newFolder;
+ nsString unicodeNewName;
+ // `tmpNewName` is in MUTF-7 or UTF-8. It needs to be convert to UTF-8.
+ CopyFolderNameToUTF16(tmpNewName, unicodeNewName);
+ CopyUTF16toUTF8(unicodeNewName, tmpNewName);
+ rv = GetFolder(tmpNewName, getter_AddRefs(newFolder));
+ if (NS_SUCCEEDED(rv)) {
+ newFolder->NotifyFolderEvent(kRenameCompleted);
+ }
+ }
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapIncomingServer::FolderIsNoSelect(
+ const nsACString& aFolderName, bool* result) {
+ NS_ENSURE_ARG_POINTER(result);
+ nsCOMPtr<nsIMsgFolder> msgFolder;
+ nsresult rv = GetFolder(aFolderName, getter_AddRefs(msgFolder));
+ if (NS_SUCCEEDED(rv) && msgFolder) {
+ uint32_t flags;
+ msgFolder->GetFlags(&flags);
+ *result = ((flags & nsMsgFolderFlags::ImapNoselect) != 0);
+ } else
+ *result = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapIncomingServer::SetFolderAdminURL(
+ const nsACString& aFolderName, const nsACString& aFolderAdminUrl) {
+ nsCOMPtr<nsIMsgFolder> rootFolder;
+ nsresult rv = GetRootFolder(getter_AddRefs(rootFolder));
+ if (NS_SUCCEEDED(rv) && rootFolder) {
+ nsCOMPtr<nsIMsgImapMailFolder> imapRoot = do_QueryInterface(rootFolder);
+ if (imapRoot) {
+ nsCOMPtr<nsIMsgImapMailFolder> foundFolder;
+ rv = imapRoot->FindOnlineSubFolder(aFolderName,
+ getter_AddRefs(foundFolder));
+ if (NS_SUCCEEDED(rv) && foundFolder)
+ return foundFolder->SetAdminUrl(aFolderAdminUrl);
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapIncomingServer::FolderVerifiedOnline(
+ const nsACString& folderName, bool* aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = false;
+ nsCOMPtr<nsIMsgFolder> rootFolder;
+ nsresult rv = GetRootFolder(getter_AddRefs(rootFolder));
+ if (NS_SUCCEEDED(rv) && rootFolder) {
+ nsCOMPtr<nsIMsgFolder> folder;
+ rv = rootFolder->FindSubFolder(folderName, getter_AddRefs(folder));
+ if (NS_SUCCEEDED(rv) && folder) {
+ nsCOMPtr<nsIMsgImapMailFolder> imapFolder = do_QueryInterface(folder);
+ if (imapFolder) imapFolder->GetVerifiedAsOnlineFolder(aResult);
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapIncomingServer::DiscoveryDone() {
+ if (mDoingSubscribeDialog) return NS_OK;
+
+ nsCOMPtr<nsIMsgFolder> rootMsgFolder;
+ nsresult rv = GetRootFolder(getter_AddRefs(rootMsgFolder));
+ if (NS_SUCCEEDED(rv) && rootMsgFolder) {
+ // GetResource() may return a node which is not in the folder
+ // tree hierarchy but in the rdf cache in case of the non-existing default
+ // Sent, Drafts, and Templates folders. The resource will be eventually
+ // released when the rdf service shuts down. When we create the default
+ // folders later on in the imap server, the subsequent GetResource() of the
+ // same uri will get us the cached rdf resource which should have the folder
+ // flag set appropriately.
+
+ 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));
+ if (NS_SUCCEEDED(rv) && identity) {
+ nsCString folderUri;
+ identity->GetFccFolder(folderUri);
+ nsCString existingUri;
+
+ if (CheckSpecialFolder(folderUri, nsMsgFolderFlags::SentMail,
+ existingUri)) {
+ identity->SetFccFolder(existingUri);
+ identity->SetFccFolderPickerMode("1"_ns);
+ }
+ identity->GetDraftFolder(folderUri);
+ if (CheckSpecialFolder(folderUri, nsMsgFolderFlags::Drafts,
+ existingUri)) {
+ identity->SetDraftFolder(existingUri);
+ identity->SetDraftsFolderPickerMode("1"_ns);
+ }
+ bool archiveEnabled;
+ identity->GetArchiveEnabled(&archiveEnabled);
+ if (archiveEnabled) {
+ identity->GetArchiveFolder(folderUri);
+ if (CheckSpecialFolder(folderUri, nsMsgFolderFlags::Archive,
+ existingUri)) {
+ identity->SetArchiveFolder(existingUri);
+ identity->SetArchivesFolderPickerMode("1"_ns);
+ }
+ }
+ identity->GetStationeryFolder(folderUri);
+ if (!folderUri.IsEmpty()) {
+ nsCOMPtr<nsIMsgFolder> folder;
+ rv = GetOrCreateFolder(folderUri, getter_AddRefs(folder));
+ if (NS_SUCCEEDED(rv)) rv = folder->SetFlag(nsMsgFolderFlags::Templates);
+ }
+ }
+
+ nsCOMPtr<nsISpamSettings> spamSettings;
+ rv = GetSpamSettings(getter_AddRefs(spamSettings));
+ if (NS_SUCCEEDED(rv) && spamSettings) {
+ nsCString spamFolderUri, existingUri;
+ spamSettings->GetSpamFolderURI(spamFolderUri);
+ if (CheckSpecialFolder(spamFolderUri, nsMsgFolderFlags::Junk,
+ existingUri)) {
+ // This only sets the cached values in the spam settings object.
+ spamSettings->SetActionTargetFolder(existingUri);
+ spamSettings->SetMoveTargetMode(
+ nsISpamSettings::MOVE_TARGET_MODE_FOLDER);
+ // Set the preferences too so that the values persist.
+ SetUnicharValue("spamActionTargetFolder",
+ NS_ConvertUTF8toUTF16(existingUri));
+ SetIntValue("moveTargetMode", nsISpamSettings::MOVE_TARGET_MODE_FOLDER);
+ }
+ }
+
+ bool isGMailServer;
+ GetIsGMailServer(&isGMailServer);
+
+ // Verify there is only one trash folder. Another might be present if
+ // the trash name has been changed. Or we might be a gmail server and
+ // want to switch to gmail's trash folder.
+ nsTArray<RefPtr<nsIMsgFolder>> trashFolders;
+ rv = rootMsgFolder->GetFoldersWithFlags(nsMsgFolderFlags::Trash,
+ trashFolders);
+
+ if (NS_SUCCEEDED(rv)) {
+ nsAutoString trashName;
+ if (NS_SUCCEEDED(GetTrashFolderName(trashName))) {
+ for (auto trashFolder : trashFolders) {
+ // If we're a gmail server, we clear the trash flags from folder(s)
+ // without the kImapXListTrash flag. For normal servers, we clear
+ // the trash folder flag if the folder name doesn't match the
+ // pref trash folder name.
+ nsAutoString retval;
+ rv = GetUnicharValue(PREF_TRASH_FOLDER_PATH, retval);
+ if (isGMailServer && (NS_FAILED(rv) || retval.IsEmpty())) {
+ nsCOMPtr<nsIMsgImapMailFolder> imapFolder(
+ do_QueryInterface(trashFolder));
+ int32_t boxFlags;
+ imapFolder->GetBoxFlags(&boxFlags);
+ if (boxFlags & kImapXListTrash) {
+ continue;
+ }
+ } else {
+ // Store the trash folder path. We maintain the full path in the
+ // trash_folder_name preference since the full path is stored
+ // there when selecting a trash folder in the Account Manager.
+ nsAutoCString trashURL;
+ rv = trashFolder->GetFolderURL(trashURL);
+ if (NS_FAILED(rv)) {
+ continue;
+ }
+ nsCOMPtr<nsIURI> uri;
+ rv = NS_NewURI(getter_AddRefs(uri), trashURL);
+ if (NS_FAILED(rv)) {
+ continue;
+ }
+ nsAutoCString trashPath;
+ uri->GetPathQueryRef(trashPath);
+ nsAutoCString unescapedName;
+ MsgUnescapeString(Substring(trashPath, 1), // Skip leading slash.
+ nsINetUtil::ESCAPE_URL_PATH, unescapedName);
+ nsAutoString nameUnicode;
+ if (NS_FAILED(CopyFolderNameToUTF16(unescapedName, nameUnicode)) ||
+ trashName.Equals(nameUnicode)) {
+ continue;
+ }
+ if (trashFolders.Length() == 1) {
+ // We got here because the preferred trash folder does not
+ // exist, but a folder got discovered to be the trash folder.
+ SetUnicharValue(PREF_TRASH_FOLDER_PATH, nameUnicode);
+ continue;
+ }
+ }
+ // We clear the trash folder flag if the trash folder path doesn't
+ // match mail.server.serverX.trash_folder_name.
+ trashFolder->ClearFlag(nsMsgFolderFlags::Trash);
+ }
+ }
+ }
+ }
+
+ bool usingSubscription = true;
+ GetUsingSubscription(&usingSubscription);
+
+ nsCOMArray<nsIMsgImapMailFolder> unverifiedFolders;
+ GetUnverifiedFolders(unverifiedFolders);
+
+ int32_t count = unverifiedFolders.Count();
+ for (int32_t k = 0; k < count; ++k) {
+ bool explicitlyVerify = false;
+ bool hasSubFolders = false;
+ uint32_t folderFlags;
+ nsCOMPtr<nsIMsgImapMailFolder> currentImapFolder(unverifiedFolders[k]);
+ nsCOMPtr<nsIMsgFolder> currentFolder(
+ do_QueryInterface(currentImapFolder, &rv));
+ if (NS_FAILED(rv)) continue;
+
+ currentFolder->GetFlags(&folderFlags);
+ if (folderFlags &
+ nsMsgFolderFlags::Virtual) // don't remove virtual folders
+ continue;
+
+ if ((!usingSubscription ||
+ (NS_SUCCEEDED(
+ currentImapFolder->GetExplicitlyVerify(&explicitlyVerify)) &&
+ explicitlyVerify)) ||
+ ((NS_SUCCEEDED(currentFolder->GetHasSubFolders(&hasSubFolders)) &&
+ hasSubFolders) &&
+ !NoDescendentsAreVerified(currentFolder))) {
+ bool isNamespace;
+ currentImapFolder->GetIsNamespace(&isNamespace);
+ if (!isNamespace) // don't list namespaces explicitly
+ {
+ // If there are no subfolders and this is unverified, we don't want to
+ // run this url. That is, we want to undiscover the folder.
+ // If there are subfolders and no descendants are verified, we want to
+ // undiscover all of the folders.
+ // Only if there are subfolders and at least one of them is verified
+ // do we want to refresh that folder's flags, because it won't be going
+ // away.
+ currentImapFolder->SetExplicitlyVerify(false);
+ currentImapFolder->List();
+ }
+ } else {
+ nsCOMPtr<nsIMsgFolder> parent;
+ currentFolder->GetParent(getter_AddRefs(parent));
+ if (parent) {
+ currentImapFolder->RemoveLocalSelf();
+ }
+ }
+ }
+
+ return rv;
+}
+
+// Check if the special folder corresponding to the uri exists. If not, check
+// if there already exists a folder with the special folder flag (the server may
+// have told us about a folder to use through XLIST). If so, return the uri of
+// the existing special folder. If not, set the special flag on the folder so
+// it will be there if and when the folder is created.
+// Return true if we found an existing special folder different than
+// the one specified in prefs, and the one specified by prefs doesn't exist.
+bool nsImapIncomingServer::CheckSpecialFolder(nsCString& folderUri,
+ uint32_t folderFlag,
+ nsCString& existingUri) {
+ nsCOMPtr<nsIMsgFolder> folder;
+ nsCOMPtr<nsIMsgFolder> rootMsgFolder;
+ nsresult rv = GetRootFolder(getter_AddRefs(rootMsgFolder));
+ NS_ENSURE_SUCCESS(rv, false);
+ nsCOMPtr<nsIMsgFolder> existingFolder;
+ rootMsgFolder->GetFolderWithFlags(folderFlag, getter_AddRefs(existingFolder));
+
+ if (!folderUri.IsEmpty() &&
+ NS_SUCCEEDED(GetOrCreateFolder(folderUri, getter_AddRefs(folder)))) {
+ nsCOMPtr<nsIMsgFolder> parent;
+ folder->GetParent(getter_AddRefs(parent));
+ if (parent) {
+ existingFolder = nullptr;
+ }
+ if (!existingFolder) {
+ folder->SetFlag(folderFlag);
+ }
+
+ nsString folderName;
+ folder->GetPrettyName(folderName);
+ // this will set the localized name based on the folder flag.
+ folder->SetPrettyName(folderName);
+ }
+
+ if (existingFolder) {
+ existingFolder->GetURI(existingUri);
+ return true;
+ }
+
+ return false;
+}
+
+bool nsImapIncomingServer::NoDescendentsAreVerified(
+ nsIMsgFolder* parentFolder) {
+ nsTArray<RefPtr<nsIMsgFolder>> subFolders;
+ nsresult rv = parentFolder->GetSubFolders(subFolders);
+ if (NS_SUCCEEDED(rv)) {
+ for (nsIMsgFolder* child : subFolders) {
+ nsCOMPtr<nsIMsgImapMailFolder> childImapFolder =
+ do_QueryInterface(child, &rv);
+ if (NS_SUCCEEDED(rv) && childImapFolder) {
+ bool childVerified = false;
+ rv = childImapFolder->GetVerifiedAsOnlineFolder(&childVerified);
+ if (NS_SUCCEEDED(rv) && childVerified) {
+ return false;
+ }
+ if (!NoDescendentsAreVerified(child)) {
+ return false;
+ }
+ }
+ }
+ }
+ // If we get this far we didn't find any verified.
+ return true;
+}
+
+bool nsImapIncomingServer::AllDescendentsAreNoSelect(
+ nsIMsgFolder* parentFolder) {
+ nsTArray<RefPtr<nsIMsgFolder>> subFolders;
+ nsresult rv = parentFolder->GetSubFolders(subFolders);
+ if (NS_SUCCEEDED(rv)) {
+ for (nsIMsgFolder* child : subFolders) {
+ nsCOMPtr<nsIMsgImapMailFolder> childImapFolder =
+ do_QueryInterface(child, &rv);
+ if (NS_SUCCEEDED(rv) && childImapFolder) {
+ uint32_t flags;
+ rv = child->GetFlags(&flags);
+ bool isNoSelect =
+ NS_SUCCEEDED(rv) && (flags & nsMsgFolderFlags::ImapNoselect);
+ if (!isNoSelect) {
+ return false;
+ }
+ if (!AllDescendentsAreNoSelect(child)) {
+ return false;
+ }
+ }
+ }
+ }
+ // If we get this far we found none without the Noselect flag.
+ return true;
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::PromptLoginFailed(nsIMsgWindow* aMsgWindow,
+ int32_t* aResult) {
+ nsAutoCString hostName;
+ GetHostName(hostName);
+
+ nsAutoCString userName;
+ GetUsername(userName);
+
+ nsAutoString accountName;
+ GetPrettyName(accountName);
+
+ return MsgPromptLoginFailed(aMsgWindow, hostName, userName, accountName,
+ aResult);
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::FEAlert(const nsAString& aAlertString,
+ nsIMsgMailNewsUrl* aUrl) {
+ GetStringBundle();
+
+ if (m_stringBundle) {
+ nsAutoString hostName;
+ nsresult rv = GetPrettyName(hostName);
+ if (NS_SUCCEEDED(rv)) {
+ nsString message;
+ nsString tempString(aAlertString);
+ AutoTArray<nsString, 2> params = {hostName, tempString};
+
+ rv = m_stringBundle->FormatStringFromName("imapServerAlert", params,
+ message);
+ if (NS_SUCCEEDED(rv)) {
+ aUrl->SetErrorCode("imap-server-alert"_ns);
+ aUrl->SetErrorMessage(message);
+
+ return AlertUser(message, aUrl);
+ }
+ }
+ }
+ return AlertUser(aAlertString, aUrl);
+}
+
+nsresult nsImapIncomingServer::AlertUser(const nsAString& aString,
+ nsIMsgMailNewsUrl* aUrl) {
+ nsresult rv;
+ nsCOMPtr<nsIMsgMailSession> mailSession =
+ do_GetService("@mozilla.org/messenger/services/session;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return mailSession->AlertUser(aString, aUrl);
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::FEAlertWithName(const char* aMsgName,
+ nsIMsgMailNewsUrl* aUrl) {
+ // don't bother the user if we're shutting down.
+ if (m_shuttingDown) return NS_OK;
+
+ GetStringBundle();
+
+ nsString message;
+
+ if (m_stringBundle) {
+ nsAutoCString hostName;
+ nsresult rv = GetHostName(hostName);
+ if (NS_SUCCEEDED(rv)) {
+ AutoTArray<nsString, 1> params;
+ CopyUTF8toUTF16(hostName, *params.AppendElement());
+ rv = m_stringBundle->FormatStringFromName(aMsgName, params, message);
+ if (NS_SUCCEEDED(rv)) {
+ aUrl->SetErrorCode(nsDependentCString(aMsgName));
+ aUrl->SetErrorMessage(message);
+
+ return AlertUser(message, aUrl);
+ }
+ }
+ }
+
+ // Error condition
+ message.AssignLiteral("String Name ");
+ message.AppendASCII(aMsgName);
+ FEAlert(message, aUrl);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapIncomingServer::FEAlertFromServer(
+ const nsACString& aServerString, nsIMsgMailNewsUrl* aUrl) {
+ NS_ENSURE_TRUE(!aServerString.IsEmpty(), NS_OK);
+
+ nsCString message(aServerString);
+ message.Trim(" \t\b\r\n");
+ NS_ENSURE_TRUE(!message.IsEmpty(), NS_OK);
+ if (message.Last() != '.') message.Append('.');
+
+ // Skip over the first two words (the command tag and "NO").
+ // Find the first word break.
+ int32_t pos = message.FindChar(' ');
+
+ // Find the second word break.
+ if (pos != -1) pos = message.FindChar(' ', pos + 1);
+
+ // Adjust the message.
+ if (pos != -1) message = Substring(message, pos + 1);
+
+ nsString hostName;
+ GetPrettyName(hostName);
+
+ AutoTArray<nsString, 3> formatStrings = {hostName};
+
+ const char* msgName;
+ nsString fullMessage;
+ nsCOMPtr<nsIImapUrl> imapUrl = do_QueryInterface(aUrl);
+ NS_ENSURE_TRUE(imapUrl, NS_ERROR_INVALID_ARG);
+
+ nsImapState imapState;
+ nsImapAction imapAction;
+
+ imapUrl->GetRequiredImapState(&imapState);
+ imapUrl->GetImapAction(&imapAction);
+ nsString folderName;
+
+ NS_ConvertUTF8toUTF16 unicodeMsg(message);
+
+ aUrl->SetErrorCode("imap-server-error"_ns);
+ aUrl->SetErrorMessage(unicodeMsg);
+
+ nsCOMPtr<nsIMsgFolder> folder;
+ if (imapState == nsIImapUrl::nsImapSelectedState ||
+ imapAction == nsIImapUrl::nsImapFolderStatus) {
+ aUrl->GetFolder(getter_AddRefs(folder));
+ if (folder) folder->GetPrettyName(folderName);
+ msgName = "imapFolderCommandFailed";
+ formatStrings.AppendElement(folderName);
+ } else {
+ msgName = "imapServerCommandFailed";
+ }
+
+ formatStrings.AppendElement(unicodeMsg);
+
+ nsresult rv = GetStringBundle();
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (m_stringBundle) {
+ rv = m_stringBundle->FormatStringFromName(msgName, formatStrings,
+ fullMessage);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return AlertUser(fullMessage, aUrl);
+}
+
+#define IMAP_MSGS_URL "chrome://messenger/locale/imapMsgs.properties"
+
+nsresult nsImapIncomingServer::GetStringBundle() {
+ if (m_stringBundle) return NS_OK;
+
+ nsCOMPtr<nsIStringBundleService> sBundleService =
+ mozilla::components::StringBundle::Service();
+ NS_ENSURE_TRUE(sBundleService, NS_ERROR_UNEXPECTED);
+ return sBundleService->CreateBundle(IMAP_MSGS_URL,
+ getter_AddRefs(m_stringBundle));
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::GetImapStringByName(const char* msgName,
+ nsAString& aString) {
+ nsresult rv = NS_OK;
+ GetStringBundle();
+ if (m_stringBundle) {
+ nsString res_str;
+ rv = m_stringBundle->GetStringFromName(msgName, res_str);
+ aString.Assign(res_str);
+ if (NS_SUCCEEDED(rv)) return rv;
+ }
+ aString.AssignLiteral("String Name ");
+ // mscott: FIX ME
+ aString.AppendASCII(msgName);
+ return NS_OK;
+}
+
+nsresult nsImapIncomingServer::ResetFoldersToUnverified(
+ nsIMsgFolder* parentFolder) {
+ nsresult rv = NS_OK;
+ if (!parentFolder) {
+ nsCOMPtr<nsIMsgFolder> rootFolder;
+ rv = GetRootFolder(getter_AddRefs(rootFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return ResetFoldersToUnverified(rootFolder);
+ }
+
+ nsCOMPtr<nsIMsgImapMailFolder> imapFolder =
+ do_QueryInterface(parentFolder, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = imapFolder->SetVerifiedAsOnlineFolder(false);
+ nsTArray<RefPtr<nsIMsgFolder>> subFolders;
+ rv = parentFolder->GetSubFolders(subFolders);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (nsIMsgFolder* child : subFolders) {
+ rv = ResetFoldersToUnverified(child);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return rv;
+}
+
+void nsImapIncomingServer::GetUnverifiedFolders(
+ nsCOMArray<nsIMsgImapMailFolder>& aFoldersArray) {
+ nsCOMPtr<nsIMsgFolder> rootFolder;
+ if (NS_FAILED(GetRootFolder(getter_AddRefs(rootFolder))) || !rootFolder)
+ return;
+
+ nsCOMPtr<nsIMsgImapMailFolder> imapRoot(do_QueryInterface(rootFolder));
+ // don't need to verify the root.
+ if (imapRoot) imapRoot->SetVerifiedAsOnlineFolder(true);
+
+ GetUnverifiedSubFolders(rootFolder, aFoldersArray);
+}
+
+void nsImapIncomingServer::GetUnverifiedSubFolders(
+ nsIMsgFolder* parentFolder,
+ nsCOMArray<nsIMsgImapMailFolder>& aFoldersArray) {
+ nsCOMPtr<nsIMsgImapMailFolder> imapFolder(do_QueryInterface(parentFolder));
+
+ bool verified = false, explicitlyVerify = false;
+ if (imapFolder) {
+ nsresult rv = imapFolder->GetVerifiedAsOnlineFolder(&verified);
+ if (NS_SUCCEEDED(rv))
+ rv = imapFolder->GetExplicitlyVerify(&explicitlyVerify);
+
+ if (NS_SUCCEEDED(rv) && (!verified || explicitlyVerify))
+ aFoldersArray.AppendObject(imapFolder);
+ }
+
+ nsTArray<RefPtr<nsIMsgFolder>> subFolders;
+ if (NS_SUCCEEDED(parentFolder->GetSubFolders(subFolders))) {
+ for (nsIMsgFolder* child : subFolders) {
+ GetUnverifiedSubFolders(child, aFoldersArray);
+ }
+ }
+}
+
+NS_IMETHODIMP nsImapIncomingServer::ForgetSessionPassword(bool modifyLogin) {
+ bool usingOauth2 = false;
+ if (modifyLogin) {
+ // Only need to check for oauth2 if modifyLogin is true.
+ int32_t authMethod = 0;
+ GetAuthMethod(&authMethod);
+ usingOauth2 = (authMethod == nsMsgAuthMethod::OAuth2);
+ }
+
+ // Clear the cached password if not using Oauth2 or if modifyLogin is false.
+ if (!usingOauth2 || !modifyLogin) {
+ nsresult rv = nsMsgIncomingServer::ForgetSessionPassword(modifyLogin);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // fix for bugscape bug #15485
+ // if we use turbo, and we logout, we need to make sure
+ // the server doesn't think it's authenticated.
+ // the biff timer continues to fire when you use turbo
+ // (see #143848). if we exited, we've set the password to null
+ // but if we're authenticated, and the biff timer goes off
+ // we'll still perform biff, because we use m_userAuthenticated
+ // to determine if we require a password for biff.
+ // (if authenticated, we don't require a password
+ // see nsMsgBiffManager::PerformBiff())
+ // performing biff without a password will pop up the prompt dialog
+ // which is pretty wacky, when it happens after you quit the application
+ m_userAuthenticated = false;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapIncomingServer::GetServerRequiresPasswordForBiff(
+ bool* aServerRequiresPasswordForBiff) {
+ NS_ENSURE_ARG_POINTER(aServerRequiresPasswordForBiff);
+ // if the user has already been authenticated, we've got the password
+ *aServerRequiresPasswordForBiff = !m_userAuthenticated;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapIncomingServer::ForgetPassword() {
+ return nsMsgIncomingServer::ForgetPassword();
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::AsyncGetPassword(nsIImapProtocol* aProtocol,
+ bool aNewPasswordRequested,
+ nsAString& aPassword) {
+ if (m_password.IsEmpty()) {
+ // We're now going to need to do something that will end up with us either
+ // poking login manager or prompting the user. We need to ensure we only
+ // do one prompt at a time (and login manager could cause a master password
+ // prompt), so we need to use the async prompter.
+ nsresult rv;
+ nsCOMPtr<nsIMsgAsyncPrompter> asyncPrompter =
+ do_GetService("@mozilla.org/messenger/msgAsyncPrompter;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIMsgAsyncPromptListener> promptListener(
+ do_QueryInterface(aProtocol));
+ rv = asyncPrompter->QueueAsyncAuthPrompt(m_serverKey, aNewPasswordRequested,
+ promptListener);
+ // Explicit NS_ENSURE_SUCCESS for debug purposes as errors tend to get
+ // hidden.
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ if (!m_password.IsEmpty()) aPassword = m_password;
+ return NS_OK;
+}
+
+// Get password already stored in login manager. This won't trigger a prompt
+// if no password string is present.
+NS_IMETHODIMP
+nsImapIncomingServer::SyncGetPassword(nsAString& aPassword) {
+ nsresult rv = NS_OK;
+ if (NS_SUCCEEDED(GetPasswordWithoutUI()) && !m_password.IsEmpty())
+ aPassword = m_password;
+ else
+ rv = NS_ERROR_NOT_AVAILABLE;
+ return rv;
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::PromptPassword(nsIMsgWindow* aMsgWindow,
+ nsAString& aPassword) {
+ nsAutoCString userName;
+ GetUsername(userName);
+
+ nsAutoCString hostName;
+ GetHostName(hostName);
+
+ nsresult rv = GetStringBundle();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ AutoTArray<nsString, 1> formatStrings;
+ CopyUTF8toUTF16(userName, *formatStrings.AppendElement());
+
+ nsString passwordTitle;
+ rv = m_stringBundle->FormatStringFromName(
+ "imapEnterPasswordPromptTitleWithUsername", formatStrings, passwordTitle);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ AutoTArray<nsString, 2> formatStrings2;
+ CopyUTF8toUTF16(userName, *formatStrings2.AppendElement());
+ CopyUTF8toUTF16(hostName, *formatStrings2.AppendElement());
+
+ nsString passwordText;
+ rv = m_stringBundle->FormatStringFromName("imapEnterServerPasswordPrompt",
+ formatStrings2, passwordText);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = GetPasswordWithUI(passwordText, passwordTitle, aPassword);
+ if (NS_SUCCEEDED(rv)) m_password = aPassword;
+ return rv;
+}
+
+// for the nsIImapServerSink interface
+NS_IMETHODIMP nsImapIncomingServer::SetCapability(
+ eIMAPCapabilityFlags capability) {
+ m_capability = capability;
+ SetIsGMailServer((capability & kGmailImapCapability) != 0);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::GetCapability(eIMAPCapabilityFlags* capability) {
+ NS_ENSURE_ARG_POINTER(capability);
+ *capability = m_capability;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapIncomingServer::SetServerID(const nsACString& aServerID) {
+ return SetServerIDPref(aServerID);
+}
+
+NS_IMETHODIMP nsImapIncomingServer::CommitNamespaces() {
+ nsresult rv;
+ nsCOMPtr<nsIImapHostSessionList> hostSession =
+ do_GetService(kCImapHostSessionListCID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return hostSession->CommitNamespacesForHost(this);
+}
+
+NS_IMETHODIMP nsImapIncomingServer::PseudoInterruptMsgLoad(
+ nsIMsgFolder* aImapFolder, nsIMsgWindow* aMsgWindow, bool* interrupted) {
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIImapProtocol> connection;
+ PR_CEnterMonitor(this);
+ // iterate through the connection cache for a connection that is loading
+ // a message in this folder and should be pseudo-interrupted.
+ int32_t cnt = m_connectionCache.Count();
+
+ for (int32_t i = 0; i < cnt; ++i) {
+ connection = m_connectionCache[i];
+ if (connection)
+ rv = connection->PseudoInterruptMsgLoad(aImapFolder, aMsgWindow,
+ interrupted);
+ }
+
+ PR_CExitMonitor(this);
+ return rv;
+}
+
+NS_IMETHODIMP nsImapIncomingServer::ResetNamespaceReferences() {
+ nsCOMPtr<nsIMsgFolder> rootFolder;
+ nsresult rv = GetRootFolder(getter_AddRefs(rootFolder));
+ if (NS_SUCCEEDED(rv) && rootFolder) {
+ nsCOMPtr<nsIMsgImapMailFolder> imapFolder = do_QueryInterface(rootFolder);
+ if (imapFolder) rv = imapFolder->ResetNamespaceReferences();
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapIncomingServer::SetUserAuthenticated(
+ bool aUserAuthenticated) {
+ m_userAuthenticated = aUserAuthenticated;
+ if (aUserAuthenticated) {
+ nsresult rv;
+ nsCOMPtr<nsIMsgAccountManager> accountManager =
+ do_GetService("@mozilla.org/messenger/account-manager;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ accountManager->SetUserNeedsToAuthenticate(false);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapIncomingServer::GetUserAuthenticated(
+ bool* aUserAuthenticated) {
+ NS_ENSURE_ARG_POINTER(aUserAuthenticated);
+ *aUserAuthenticated = m_userAuthenticated;
+ return NS_OK;
+}
+
+/* void SetMailServerUrls (in string manageMailAccount, in string manageLists,
+ * in string manageFilters); */
+NS_IMETHODIMP nsImapIncomingServer::SetMailServerUrls(
+ const nsACString& manageMailAccount, const nsACString& manageLists,
+ const nsACString& manageFilters) {
+ return SetManageMailAccountUrl(manageMailAccount);
+}
+
+NS_IMETHODIMP nsImapIncomingServer::SetManageMailAccountUrl(
+ const nsACString& manageMailAccountUrl) {
+ m_manageMailAccountUrl = manageMailAccountUrl;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapIncomingServer::GetManageMailAccountUrl(
+ nsACString& manageMailAccountUrl) {
+ manageMailAccountUrl = m_manageMailAccountUrl;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::StartPopulatingWithUri(nsIMsgWindow* aMsgWindow,
+ bool aForceToServer /*ignored*/,
+ const nsACString& uri) {
+ nsresult rv;
+ mDoingSubscribeDialog = true;
+
+ rv = EnsureInner();
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = mInner->StartPopulatingWithUri(aMsgWindow, aForceToServer, uri);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // imap always uses the canonical delimiter form of paths for subscribe ui.
+ rv = SetDelimiter('/');
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = SetShowFullName(false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCString serverUri;
+ rv = GetServerURI(serverUri);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIImapService> imapService =
+ do_GetService("@mozilla.org/messenger/imapservice;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ /*
+ if uri = imap://user@host/foo/bar, the serverUri is imap://user@host
+ to get path from uri, skip over imap://user@host + 1 (for the /)
+ */
+ return imapService->GetListOfFoldersWithPath(
+ this, aMsgWindow, Substring(uri, serverUri.Length() + 1));
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::StartPopulating(nsIMsgWindow* aMsgWindow,
+ bool aForceToServer /*ignored*/,
+ bool aGetOnlyNew) {
+ nsresult rv;
+ mDoingSubscribeDialog = true;
+
+ rv = EnsureInner();
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = mInner->StartPopulating(aMsgWindow, aForceToServer, aGetOnlyNew);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // imap always uses the canonical delimiter form of paths for subscribe ui.
+ rv = SetDelimiter('/');
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = SetShowFullName(false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIImapService> imapService =
+ do_GetService("@mozilla.org/messenger/imapservice;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return imapService->GetListOfFoldersOnServer(this, aMsgWindow);
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::OnStartRunningUrl(nsIURI* url) { return NS_OK; }
+
+NS_IMETHODIMP
+nsImapIncomingServer::OnStopRunningUrl(nsIURI* url, nsresult exitCode) {
+ nsresult rv = exitCode;
+
+ // xxx todo get msgWindow from url
+ nsCOMPtr<nsIMsgWindow> msgWindow;
+ nsCOMPtr<nsIImapUrl> imapUrl = do_QueryInterface(url);
+ if (imapUrl) {
+ nsImapAction imapAction = nsIImapUrl::nsImapTest;
+ imapUrl->GetImapAction(&imapAction);
+ switch (imapAction) {
+ case nsIImapUrl::nsImapDiscoverAllAndSubscribedBoxesUrl:
+ case nsIImapUrl::nsImapDiscoverChildrenUrl:
+ rv = UpdateSubscribed();
+ NS_ENSURE_SUCCESS(rv, rv);
+ mDoingSubscribeDialog = false;
+ rv = StopPopulating(msgWindow);
+ NS_ENSURE_SUCCESS(rv, rv);
+ break;
+ case nsIImapUrl::nsImapDiscoverAllBoxesUrl:
+ if (NS_SUCCEEDED(exitCode)) DiscoveryDone();
+ break;
+ case nsIImapUrl::nsImapFolderStatus: {
+ nsCOMPtr<nsIMsgFolder> msgFolder;
+ nsCOMPtr<nsIMsgMailNewsUrl> mailUrl = do_QueryInterface(imapUrl);
+ mailUrl->GetFolder(getter_AddRefs(msgFolder));
+ if (msgFolder) {
+ nsresult rv;
+ nsCOMPtr<nsIMsgMailSession> session =
+ do_GetService("@mozilla.org/messenger/services/session;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ bool folderOpen;
+ rv = session->IsFolderOpenInWindow(msgFolder, &folderOpen);
+ if (NS_SUCCEEDED(rv) && !folderOpen && msgFolder)
+ msgFolder->SetMsgDatabase(nullptr);
+ nsCOMPtr<nsIMsgImapMailFolder> imapFolder =
+ do_QueryInterface(msgFolder);
+ m_foldersToStat.RemoveObject(imapFolder);
+ }
+ // if we get an error running the url, it's better
+ // not to chain the next url.
+ if (NS_FAILED(exitCode) && exitCode != NS_MSG_ERROR_IMAP_COMMAND_FAILED)
+ m_foldersToStat.Clear();
+ if (m_foldersToStat.Count() > 0)
+ m_foldersToStat[0]->UpdateStatus(this, nullptr);
+ break;
+ }
+ default:
+ break;
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::SetIncomingServer(nsIMsgIncomingServer* aServer) {
+ nsresult rv = EnsureInner();
+ NS_ENSURE_SUCCESS(rv, rv);
+ return mInner->SetIncomingServer(aServer);
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::SetShowFullName(bool showFullName) {
+ nsresult rv = EnsureInner();
+ NS_ENSURE_SUCCESS(rv, rv);
+ return mInner->SetShowFullName(showFullName);
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::GetDelimiter(char* aDelimiter) {
+ nsresult rv = EnsureInner();
+ NS_ENSURE_SUCCESS(rv, rv);
+ return mInner->GetDelimiter(aDelimiter);
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::SetDelimiter(char aDelimiter) {
+ nsresult rv = EnsureInner();
+ NS_ENSURE_SUCCESS(rv, rv);
+ return mInner->SetDelimiter(aDelimiter);
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::SetAsSubscribed(const nsACString& path) {
+ nsresult rv = EnsureInner();
+ NS_ENSURE_SUCCESS(rv, rv);
+ return mInner->SetAsSubscribed(path);
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::UpdateSubscribed() { return NS_OK; }
+
+NS_IMETHODIMP
+nsImapIncomingServer::AddTo(const nsACString& aName, bool addAsSubscribed,
+ bool aSubscribable, bool changeIfExists) {
+ nsresult rv = EnsureInner();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // RFC 3501 allows UTF-8 in addition to MUTF-7.
+ // If it's not UTF-8, it's not 7bit-ASCII and cannot be MUTF-7 either.
+ // We just ignore it.
+ if (!mozilla::IsUtf8(aName)) return NS_OK;
+ // Now handle subscription folder names as UTF-8 so don't convert to MUTF-7.
+ return mInner->AddTo(aName, addAsSubscribed, aSubscribable, changeIfExists);
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::StopPopulating(nsIMsgWindow* aMsgWindow) {
+ nsresult rv = EnsureInner();
+ NS_ENSURE_SUCCESS(rv, rv);
+ return mInner->StopPopulating(aMsgWindow);
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::SubscribeCleanup() {
+ m_subscribeFolders.Clear();
+ return ClearInner();
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::SetSubscribeListener(nsISubscribeListener* aListener) {
+ nsresult rv = EnsureInner();
+ NS_ENSURE_SUCCESS(rv, rv);
+ return mInner->SetSubscribeListener(aListener);
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::GetSubscribeListener(nsISubscribeListener** aListener) {
+ nsresult rv = EnsureInner();
+ NS_ENSURE_SUCCESS(rv, rv);
+ return mInner->GetSubscribeListener(aListener);
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::Subscribe(const char16_t* aName) {
+ NS_ENSURE_ARG_POINTER(aName);
+ return SubscribeToFolder(nsDependentString(aName), true, nullptr);
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::Unsubscribe(const char16_t* aName) {
+ NS_ENSURE_ARG_POINTER(aName);
+
+ return SubscribeToFolder(nsDependentString(aName), false, nullptr);
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::SubscribeToFolder(const nsAString& aName, bool subscribe,
+ nsIURI** aUri) {
+ nsresult rv;
+ nsCOMPtr<nsIImapService> imapService =
+ do_GetService("@mozilla.org/messenger/imapservice;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgFolder> rootMsgFolder;
+ rv = GetRootFolder(getter_AddRefs(rootMsgFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Locate the folder so that the correct hierarchical delimiter is used in the
+ // folder pathnames, otherwise root's (ie, '^') is used and this is wrong.
+
+ // aName is not a genuine UTF-16 but just a zero-padded MUTF-7.
+ NS_ConvertUTF16toUTF8 folderCName(aName);
+ nsCOMPtr<nsIMsgFolder> msgFolder;
+ if (rootMsgFolder && !aName.IsEmpty())
+ rv = rootMsgFolder->FindSubFolder(folderCName, getter_AddRefs(msgFolder));
+
+ nsCOMPtr<nsIThread> thread(do_GetCurrentThread());
+
+ if (subscribe)
+ rv = imapService->SubscribeFolder(msgFolder, aName, nullptr, aUri);
+ else
+ rv = imapService->UnsubscribeFolder(msgFolder, aName, nullptr, nullptr);
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::SetDoingLsub(bool doingLsub) {
+ mDoingLsub = doingLsub;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::GetDoingLsub(bool* doingLsub) {
+ NS_ENSURE_ARG_POINTER(doingLsub);
+ *doingLsub = mDoingLsub;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::SetUtf8AcceptEnabled(bool enabled) {
+ mUtf8AcceptEnabled = enabled;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::GetUtf8AcceptEnabled(bool* enabled) {
+ NS_ENSURE_ARG_POINTER(enabled);
+ *enabled = mUtf8AcceptEnabled;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::ReDiscoverAllFolders() { return PerformExpand(nullptr); }
+
+NS_IMETHODIMP
+nsImapIncomingServer::SetState(const nsACString& path, bool state,
+ bool* stateChanged) {
+ nsresult rv = EnsureInner();
+ NS_ENSURE_SUCCESS(rv, rv);
+ return mInner->SetState(path, state, stateChanged);
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::HasChildren(const nsACString& path, bool* aHasChildren) {
+ nsresult rv = EnsureInner();
+ NS_ENSURE_SUCCESS(rv, rv);
+ return mInner->HasChildren(path, aHasChildren);
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::IsSubscribed(const nsACString& path,
+ bool* aIsSubscribed) {
+ nsresult rv = EnsureInner();
+ NS_ENSURE_SUCCESS(rv, rv);
+ return mInner->IsSubscribed(path, aIsSubscribed);
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::IsSubscribable(const nsACString& path,
+ bool* aIsSubscribable) {
+ nsresult rv = EnsureInner();
+ NS_ENSURE_SUCCESS(rv, rv);
+ return mInner->IsSubscribable(path, aIsSubscribable);
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::GetLeafName(const nsACString& path,
+ nsAString& aLeafName) {
+ nsresult rv = EnsureInner();
+ NS_ENSURE_SUCCESS(rv, rv);
+ return mInner->GetLeafName(path, aLeafName);
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::GetFirstChildURI(const nsACString& path,
+ nsACString& aResult) {
+ nsresult rv = EnsureInner();
+ NS_ENSURE_SUCCESS(rv, rv);
+ return mInner->GetFirstChildURI(path, aResult);
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::GetChildURIs(const nsACString& aPath,
+ nsTArray<nsCString>& aResult) {
+ nsresult rv = EnsureInner();
+ NS_ENSURE_SUCCESS(rv, rv);
+ return mInner->GetChildURIs(aPath, aResult);
+}
+
+nsresult nsImapIncomingServer::EnsureInner() {
+ nsresult rv = NS_OK;
+
+ if (mInner) return NS_OK;
+
+ mInner = do_CreateInstance(kSubscribableServerCID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return SetIncomingServer(this);
+}
+
+nsresult nsImapIncomingServer::ClearInner() {
+ nsresult rv = NS_OK;
+ if (mInner) {
+ rv = mInner->SetSubscribeListener(nullptr);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = mInner->SetIncomingServer(nullptr);
+ NS_ENSURE_SUCCESS(rv, rv);
+ mInner = nullptr;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::CommitSubscribeChanges() {
+ return ReDiscoverAllFolders();
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::GetCanBeDefaultServer(bool* canBeDefaultServer) {
+ NS_ENSURE_ARG_POINTER(canBeDefaultServer);
+ *canBeDefaultServer = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::GetCanCompactFoldersOnServer(
+ bool* canCompactFoldersOnServer) {
+ NS_ENSURE_ARG_POINTER(canCompactFoldersOnServer);
+ // Initialize canCompactFoldersOnServer true, a default value for IMAP
+ *canCompactFoldersOnServer = true;
+ GetPrefForServerAttribute("canCompactFoldersOnServer",
+ canCompactFoldersOnServer);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::GetCanUndoDeleteOnServer(bool* canUndoDeleteOnServer) {
+ NS_ENSURE_ARG_POINTER(canUndoDeleteOnServer);
+ // Initialize canUndoDeleteOnServer true, a default value for IMAP
+ *canUndoDeleteOnServer = true;
+ GetPrefForServerAttribute("canUndoDeleteOnServer", canUndoDeleteOnServer);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::GetCanSearchMessages(bool* canSearchMessages) {
+ NS_ENSURE_ARG_POINTER(canSearchMessages);
+ // Initialize canSearchMessages true, a default value for IMAP
+ *canSearchMessages = true;
+ GetPrefForServerAttribute("canSearchMessages", canSearchMessages);
+ return NS_OK;
+}
+
+nsresult nsImapIncomingServer::CreateHostSpecificPrefName(
+ const char* prefPrefix, nsAutoCString& prefName) {
+ NS_ENSURE_ARG_POINTER(prefPrefix);
+
+ nsCString hostName;
+ nsresult rv = GetHostName(hostName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ prefName = prefPrefix;
+ prefName.Append('.');
+ prefName.Append(hostName);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::GetSupportsDiskSpace(bool* aSupportsDiskSpace) {
+ NS_ENSURE_ARG_POINTER(aSupportsDiskSpace);
+ nsAutoCString prefName;
+ nsresult rv =
+ CreateHostSpecificPrefName("default_supports_diskspace", prefName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIPrefBranch> prefBranch =
+ do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv))
+ rv = prefBranch->GetBoolPref(prefName.get(), aSupportsDiskSpace);
+
+ // Couldn't get the default value with the hostname.
+ // Fall back on IMAP default value
+ if (NS_FAILED(rv)) // set default value
+ *aSupportsDiskSpace = true;
+ return NS_OK;
+}
+
+// count number of non-busy connections in cache
+NS_IMETHODIMP
+nsImapIncomingServer::GetNumIdleConnections(int32_t* aNumIdleConnections) {
+ NS_ENSURE_ARG_POINTER(aNumIdleConnections);
+ *aNumIdleConnections = 0;
+
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIImapProtocol> connection;
+ bool isBusy = false;
+ bool isInboxConnection;
+ PR_CEnterMonitor(this);
+
+ int32_t cnt = m_connectionCache.Count();
+
+ // loop counting idle connections
+ for (int32_t i = 0; i < cnt; ++i) {
+ connection = m_connectionCache[i];
+ if (connection) {
+ rv = connection->IsBusy(&isBusy, &isInboxConnection);
+ if (NS_FAILED(rv)) continue;
+ if (!isBusy) (*aNumIdleConnections)++;
+ }
+ }
+ PR_CExitMonitor(this);
+ return rv;
+}
+
+/**
+ * Get the preference that tells us whether the imap server in question allows
+ * us to create subfolders. Some ISPs might not want users to create any folders
+ * besides the existing ones.
+ * We do want to identify all those servers that don't allow creation of
+ * subfolders and take them out of the account picker in the Copies and Folder
+ * panel.
+ */
+NS_IMETHODIMP
+nsImapIncomingServer::GetCanCreateFoldersOnServer(
+ bool* aCanCreateFoldersOnServer) {
+ NS_ENSURE_ARG_POINTER(aCanCreateFoldersOnServer);
+ // Initialize aCanCreateFoldersOnServer true, a default value for IMAP
+ *aCanCreateFoldersOnServer = true;
+ GetPrefForServerAttribute("canCreateFolders", aCanCreateFoldersOnServer);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::GetOfflineSupportLevel(int32_t* aSupportLevel) {
+ NS_ENSURE_ARG_POINTER(aSupportLevel);
+ nsresult rv = NS_OK;
+
+ rv = GetIntValue("offline_support_level", aSupportLevel);
+ if (*aSupportLevel != OFFLINE_SUPPORT_LEVEL_UNDEFINED) return rv;
+
+ nsAutoCString prefName;
+ rv = CreateHostSpecificPrefName("default_offline_support_level", prefName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIPrefBranch> prefBranch =
+ do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv))
+ rv = prefBranch->GetIntPref(prefName.get(), aSupportLevel);
+
+ // Couldn't get the pref value with the hostname.
+ // Fall back on IMAP default value
+ if (NS_FAILED(rv)) // set default value
+ *aSupportLevel = OFFLINE_SUPPORT_LEVEL_REGULAR;
+ return NS_OK;
+}
+
+// Called only during the migration process. This routine enables the generation
+// of unique account name based on the username, hostname and the port. If the
+// port is valid and not a default one, it will be appended to the account name.
+NS_IMETHODIMP
+nsImapIncomingServer::GeneratePrettyNameForMigration(nsAString& aPrettyName) {
+ nsCString userName;
+ nsCString hostName;
+
+ /**
+ * Pretty name for migrated account is of format username@hostname:<port>,
+ * provided the port is valid and not the default
+ */
+ // Get user name to construct pretty name
+ nsresult rv = GetUsername(userName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Get host name to construct pretty name
+ rv = GetHostName(hostName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t defaultServerPort;
+ int32_t defaultSecureServerPort;
+
+ // Here, the final contract ID is already known, so use it directly for
+ // efficiency.
+ nsCOMPtr<nsIMsgProtocolInfo> protocolInfo =
+ do_GetService("@mozilla.org/messenger/protocol/info;1?type=imap", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Get the default port
+ rv = protocolInfo->GetDefaultServerPort(false, &defaultServerPort);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Get the default secure port
+ rv = protocolInfo->GetDefaultServerPort(true, &defaultSecureServerPort);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Get the current server port
+ int32_t serverPort = PORT_NOT_SET;
+ rv = GetPort(&serverPort);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Is the server secure ?
+ int32_t socketType;
+ rv = GetSocketType(&socketType);
+ NS_ENSURE_SUCCESS(rv, rv);
+ bool isSecure = (socketType == nsMsgSocketType::SSL);
+
+ // Is server port a default port ?
+ bool isItDefaultPort = false;
+ if (((serverPort == defaultServerPort) && !isSecure) ||
+ ((serverPort == defaultSecureServerPort) && isSecure))
+ isItDefaultPort = true;
+
+ // Construct pretty name from username and hostname
+ nsAutoString constructedPrettyName;
+ CopyASCIItoUTF16(userName, constructedPrettyName);
+ constructedPrettyName.Append('@');
+ constructedPrettyName.Append(NS_ConvertASCIItoUTF16(hostName));
+
+ // If the port is valid and not default, add port value to the pretty name
+ if ((serverPort > 0) && (!isItDefaultPort)) {
+ constructedPrettyName.Append(':');
+ constructedPrettyName.AppendInt(serverPort);
+ }
+
+ // Format the pretty name
+ return GetFormattedStringFromName(constructedPrettyName,
+ "imapDefaultAccountName", aPrettyName);
+}
+
+nsresult nsImapIncomingServer::GetFormattedStringFromName(
+ const nsAString& aValue, const char* aName, nsAString& aResult) {
+ nsresult rv = GetStringBundle();
+ if (m_stringBundle) {
+ nsString tmpVal(aValue);
+ AutoTArray<nsString, 1> formatStrings = {tmpVal};
+
+ nsString result;
+ rv = m_stringBundle->FormatStringFromName(aName, formatStrings, result);
+ aResult.Assign(result);
+ }
+ return rv;
+}
+
+nsresult nsImapIncomingServer::GetPrefForServerAttribute(const char* prefSuffix,
+ bool* prefValue) {
+ // Any caller of this function must initialize prefValue with a default value
+ // as this code will not set prefValue when the pref does not exist and return
+ // NS_OK anyway
+
+ if (!mPrefBranch) return NS_ERROR_NOT_INITIALIZED;
+
+ NS_ENSURE_ARG_POINTER(prefValue);
+
+ if (NS_FAILED(mPrefBranch->GetBoolPref(prefSuffix, prefValue)))
+ mDefPrefBranch->GetBoolPref(prefSuffix, prefValue);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::GetCanFileMessagesOnServer(
+ bool* aCanFileMessagesOnServer) {
+ NS_ENSURE_ARG_POINTER(aCanFileMessagesOnServer);
+ // Initialize aCanFileMessagesOnServer true, a default value for IMAP
+ *aCanFileMessagesOnServer = true;
+ GetPrefForServerAttribute("canFileMessages", aCanFileMessagesOnServer);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::SetSearchValue(const nsAString& searchValue) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::GetSupportsSubscribeSearch(bool* retVal) {
+ NS_ENSURE_ARG_POINTER(retVal);
+ *retVal = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::GetFolderView(nsITreeView** aView) {
+ nsresult rv = EnsureInner();
+ NS_ENSURE_SUCCESS(rv, rv);
+ return mInner->GetFolderView(aView);
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::GetFilterScope(nsMsgSearchScopeValue* filterScope) {
+ NS_ENSURE_ARG_POINTER(filterScope);
+ // If the inbox is enabled for offline use, then use the offline filter
+ // scope, else use the online filter scope.
+ //
+ // XXX We use the same scope for all folders with the same incoming server,
+ // yet it is possible to set the offline flag separately for each folder.
+ // Manual filters could perhaps check the offline status of each folder,
+ // though it's hard to see how to make that work since we only store filters
+ // per server.
+ //
+ nsCOMPtr<nsIMsgFolder> rootMsgFolder;
+ nsresult rv = GetRootMsgFolder(getter_AddRefs(rootMsgFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIMsgFolder> offlineInboxMsgFolder;
+ rv = rootMsgFolder->GetFolderWithFlags(
+ nsMsgFolderFlags::Inbox | nsMsgFolderFlags::Offline,
+ getter_AddRefs(offlineInboxMsgFolder));
+
+ *filterScope = offlineInboxMsgFolder ? nsMsgSearchScope::offlineMailFilter
+ : nsMsgSearchScope::onlineMailFilter;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::GetSearchScope(nsMsgSearchScopeValue* searchScope) {
+ NS_ENSURE_ARG_POINTER(searchScope);
+ *searchScope = WeAreOffline() ? nsMsgSearchScope::offlineMail
+ : nsMsgSearchScope::onlineMail;
+ return NS_OK;
+}
+
+// This is a recursive function. It gets new messages for current folder
+// first if it is marked, then calls itself recursively for each subfolder.
+NS_IMETHODIMP
+nsImapIncomingServer::GetNewMessagesForNonInboxFolders(nsIMsgFolder* aFolder,
+ nsIMsgWindow* aWindow,
+ bool forceAllFolders,
+ bool performingBiff) {
+ NS_ENSURE_ARG_POINTER(aFolder);
+ static bool gGotStatusPref = false;
+ static bool gUseStatus = false;
+
+ bool isServer;
+ (void)aFolder->GetIsServer(&isServer);
+ // Check this folder for new messages if it is marked to be checked
+ // or if we are forced to check all folders
+ uint32_t flags = 0;
+ aFolder->GetFlags(&flags);
+ nsresult rv;
+ nsCOMPtr<nsIMsgImapMailFolder> imapFolder = do_QueryInterface(aFolder, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ bool canOpen;
+ imapFolder->GetCanOpenFolder(&canOpen);
+ if (canOpen &&
+ ((forceAllFolders &&
+ !(flags & (nsMsgFolderFlags::Inbox | nsMsgFolderFlags::Trash |
+ nsMsgFolderFlags::Junk | nsMsgFolderFlags::Virtual))) ||
+ flags & nsMsgFolderFlags::CheckNew)) {
+ // Get new messages for this folder.
+ aFolder->SetGettingNewMessages(true);
+ if (performingBiff) imapFolder->SetPerformingBiff(true);
+ bool isOpen = false;
+ nsCOMPtr<nsIMsgMailSession> mailSession =
+ do_GetService("@mozilla.org/messenger/services/session;1");
+ if (mailSession && aFolder)
+ mailSession->IsFolderOpenInWindow(aFolder, &isOpen);
+ // eventually, the gGotStatusPref should go away, once we work out the kinks
+ // from using STATUS.
+ if (!gGotStatusPref) {
+ nsCOMPtr<nsIPrefBranch> prefBranch =
+ do_GetService(NS_PREFSERVICE_CONTRACTID);
+ if (prefBranch)
+ prefBranch->GetBoolPref("mail.imap.use_status_for_biff", &gUseStatus);
+ gGotStatusPref = true;
+ }
+ if (gUseStatus && !isOpen) {
+ if (!isServer && m_foldersToStat.IndexOf(imapFolder) == -1)
+ m_foldersToStat.AppendObject(imapFolder);
+ } else
+ aFolder->UpdateFolder(aWindow);
+ }
+
+ // Loop through all subfolders to get new messages for them.
+ nsTArray<RefPtr<nsIMsgFolder>> subFolders;
+ rv = aFolder->GetSubFolders(subFolders);
+ NS_ENSURE_SUCCESS(rv, rv);
+ for (nsIMsgFolder* msgFolder : subFolders) {
+ GetNewMessagesForNonInboxFolders(msgFolder, aWindow, forceAllFolders,
+ performingBiff);
+ }
+ if (isServer && m_foldersToStat.Count() > 0)
+ m_foldersToStat[0]->UpdateStatus(this, nullptr);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::GetArbitraryHeaders(nsACString& aResult) {
+ nsCOMPtr<nsIMsgFilterList> filterList;
+ nsresult rv = GetFilterList(nullptr, getter_AddRefs(filterList));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return filterList->GetArbitraryHeaders(aResult);
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::GetShowAttachmentsInline(bool* aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = true; // true per default
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> prefBranch =
+ do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ prefBranch->GetBoolPref("mail.inline_attachments", aResult);
+ return NS_OK; // In case this pref is not set we need to return NS_OK.
+}
+
+NS_IMETHODIMP nsImapIncomingServer::SetSocketType(int32_t aSocketType) {
+ int32_t oldSocketType;
+ nsresult rv = GetSocketType(&oldSocketType);
+ if (NS_SUCCEEDED(rv) && oldSocketType != aSocketType)
+ CloseCachedConnections();
+ return nsMsgIncomingServer::SetSocketType(aSocketType);
+}
+
+// use canonical format in originalUri & convertedUri
+NS_IMETHODIMP
+nsImapIncomingServer::GetUriWithNamespacePrefixIfNecessary(
+ int32_t namespaceType, const nsACString& originalUri,
+ nsACString& convertedUri) {
+ nsresult rv = NS_OK;
+ nsAutoCString serverKey;
+ rv = GetKey(serverKey);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIImapHostSessionList> hostSessionList =
+ do_GetService(kCImapHostSessionListCID, &rv);
+ nsImapNamespace* ns = nullptr;
+ rv = hostSessionList->GetDefaultNamespaceOfTypeForHost(
+ serverKey.get(), (EIMAPNamespaceType)namespaceType, ns);
+ if (ns) {
+ nsAutoCString namespacePrefix(ns->GetPrefix());
+ if (!namespacePrefix.IsEmpty()) {
+ // check if namespacePrefix is the same as the online directory; if so,
+ // ignore it.
+ nsAutoCString onlineDir;
+ rv = GetServerDirectory(onlineDir);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!onlineDir.IsEmpty()) {
+ char delimiter = ns->GetDelimiter();
+ if (onlineDir.Last() != delimiter) onlineDir += delimiter;
+ if (onlineDir.Equals(namespacePrefix)) return NS_OK;
+ }
+
+ namespacePrefix.ReplaceChar(ns->GetDelimiter(),
+ '/'); // use canonical format
+ nsCString uri(originalUri);
+ int32_t index = uri.Find("//"); // find scheme
+ index = uri.FindChar('/', index + 2); // find '/' after scheme
+ // it may be the case that this is the INBOX uri, in which case
+ // we don't want to prepend the namespace. In that case, the uri ends with
+ // "INBOX", but the namespace is "INBOX/", so they don't match.
+ if (uri.Find(namespacePrefix, index + 1) != index + 1 &&
+ !Substring(uri, index + 1).LowerCaseEqualsLiteral("inbox"))
+ uri.Insert(namespacePrefix, index + 1); // insert namespace prefix
+ convertedUri = uri;
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapIncomingServer::GetTrashFolderName(nsAString& retval) {
+ // Despite its name, this returns a path, for example INBOX/Trash.
+ nsresult rv = GetUnicharValue(PREF_TRASH_FOLDER_PATH, retval);
+ if (NS_FAILED(rv)) return rv;
+ if (retval.IsEmpty())
+ retval = NS_LITERAL_STRING_FROM_CSTRING(DEFAULT_TRASH_FOLDER_PATH);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapIncomingServer::SetTrashFolderName(
+ const nsAString& chvalue) {
+ // Clear trash flag from the old pref.
+ // Despite its name, this returns the trash folder path, for example
+ // INBOX/Trash.
+ bool useUTF8 = false;
+ GetUtf8AcceptEnabled(&useUTF8);
+ nsAutoString oldTrashName;
+ nsresult rv = GetTrashFolderName(oldTrashName);
+ if (NS_SUCCEEDED(rv)) {
+ nsAutoCString oldTrashNameUtf7or8;
+ nsCOMPtr<nsIMsgFolder> oldFolder;
+ // 'trashFolderName' being a path here works well since this is appended
+ // to the server's root folder in GetFolder().
+ if (useUTF8) {
+ CopyUTF16toUTF8(oldTrashName, oldTrashNameUtf7or8);
+ } else {
+ CopyUTF16toMUTF7(oldTrashName, oldTrashNameUtf7or8);
+ }
+ rv = GetFolder(oldTrashNameUtf7or8, getter_AddRefs(oldFolder));
+ if (NS_SUCCEEDED(rv) && oldFolder)
+ oldFolder->ClearFlag(nsMsgFolderFlags::Trash);
+ }
+
+ // If the user configured delete mode (model) is currently "move to trash",
+ // mark the newly designated trash folder name as the active trash
+ // destination folder.
+ int32_t deleteModel;
+ rv = GetDeleteModel(&deleteModel);
+ if (NS_SUCCEEDED(rv) && (deleteModel == nsMsgImapDeleteModels::MoveToTrash)) {
+ nsAutoCString newTrashNameUtf7or8;
+ if (useUTF8) {
+ CopyUTF16toUTF8(PromiseFlatString(chvalue), newTrashNameUtf7or8);
+ } else {
+ CopyUTF16toMUTF7(PromiseFlatString(chvalue), newTrashNameUtf7or8);
+ }
+ nsCOMPtr<nsIMsgFolder> newTrashFolder;
+ rv = GetFolder(newTrashNameUtf7or8, getter_AddRefs(newTrashFolder));
+ if (NS_SUCCEEDED(rv) && newTrashFolder)
+ newTrashFolder->SetFlag(nsMsgFolderFlags::Trash);
+ }
+
+ return SetUnicharValue(PREF_TRASH_FOLDER_PATH, chvalue);
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::GetMsgFolderFromURI(nsIMsgFolder* aFolderResource,
+ const nsACString& aURI,
+ nsIMsgFolder** aFolder) {
+ nsCOMPtr<nsIMsgFolder> msgFolder;
+ bool namespacePrefixAdded = false;
+ nsCString folderUriWithNamespace;
+
+ // clang-format off
+ // Check if the folder exists as is...
+ nsresult rv = GetExistingMsgFolder(aURI, folderUriWithNamespace,
+ namespacePrefixAdded, false,
+ getter_AddRefs(msgFolder));
+
+ // Or try again with a case-insensitive lookup
+ if (NS_FAILED(rv) || !msgFolder)
+ rv = GetExistingMsgFolder(aURI, folderUriWithNamespace,
+ namespacePrefixAdded, true,
+ getter_AddRefs(msgFolder));
+ // clang-format on
+
+ if (NS_FAILED(rv) || !msgFolder) {
+ // we didn't find the folder so we will have to create a new one.
+ if (namespacePrefixAdded) {
+ nsCOMPtr<nsIMsgFolder> folder;
+ rv = GetOrCreateFolder(folderUriWithNamespace, getter_AddRefs(folder));
+ NS_ENSURE_SUCCESS(rv, rv);
+ msgFolder = folder;
+ } else
+ msgFolder = aFolderResource;
+ }
+
+ msgFolder.forget(aFolder);
+ return (aFolder ? NS_OK : NS_ERROR_FAILURE);
+}
+
+nsresult nsImapIncomingServer::GetExistingMsgFolder(
+ const nsACString& aURI, nsACString& aFolderUriWithNamespace,
+ bool& aNamespacePrefixAdded, bool aCaseInsensitive,
+ nsIMsgFolder** aFolder) {
+ nsCOMPtr<nsIMsgFolder> rootMsgFolder;
+ nsresult rv = GetRootMsgFolder(getter_AddRefs(rootMsgFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aNamespacePrefixAdded = false;
+ // Check if the folder exists as is...Even if we have a personal namespace,
+ // it might be in another namespace (e.g., shared) and this will catch that.
+ rv = rootMsgFolder->GetChildWithURI(aURI, true, aCaseInsensitive, aFolder);
+
+ // If we couldn't find the folder as is, check if we need to prepend the
+ // personal namespace
+ if (!*aFolder) {
+ GetUriWithNamespacePrefixIfNecessary(kPersonalNamespace, aURI,
+ aFolderUriWithNamespace);
+ if (!aFolderUriWithNamespace.IsEmpty()) {
+ aNamespacePrefixAdded = true;
+ rv = rootMsgFolder->GetChildWithURI(aFolderUriWithNamespace, true,
+ aCaseInsensitive, aFolder);
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::CramMD5Hash(const char* decodedChallenge, const char* key,
+ char** result) {
+ NS_ENSURE_ARG_POINTER(decodedChallenge);
+ NS_ENSURE_ARG_POINTER(key);
+
+ unsigned char resultDigest[DIGEST_LENGTH];
+ nsresult rv = MSGCramMD5(decodedChallenge, strlen(decodedChallenge), key,
+ strlen(key), resultDigest);
+ NS_ENSURE_SUCCESS(rv, rv);
+ *result = (char*)malloc(DIGEST_LENGTH);
+ if (*result) memcpy(*result, resultDigest, DIGEST_LENGTH);
+ return (*result) ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::GetLoginUsername(nsACString& aLoginUsername) {
+ return GetUsername(aLoginUsername);
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::GetOriginalUsername(nsACString& aUsername) {
+ return GetUsername(aUsername);
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::GetServerKey(nsACString& aServerKey) {
+ return GetKey(aServerKey);
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::GetServerPassword(nsAString& aPassword) {
+ return GetPassword(aPassword);
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::RemoveServerConnection(nsIImapProtocol* aProtocol) {
+ return RemoveConnection(aProtocol);
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::GetServerShuttingDown(bool* aShuttingDown) {
+ return GetShuttingDown(aShuttingDown);
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::ResetServerConnection(const nsACString& aFolderName) {
+ return ResetConnection(aFolderName);
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::SetServerDoingLsub(bool aDoingLsub) {
+ return SetDoingLsub(aDoingLsub);
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::SetServerUtf8AcceptEnabled(bool enabled) {
+ return SetUtf8AcceptEnabled(enabled);
+}
diff --git a/comm/mailnews/imap/src/nsImapIncomingServer.h b/comm/mailnews/imap/src/nsImapIncomingServer.h
new file mode 100644
index 0000000000..03f4f29ca6
--- /dev/null
+++ b/comm/mailnews/imap/src/nsImapIncomingServer.h
@@ -0,0 +1,150 @@
+/* -*- 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 __nsImapIncomingServer_h
+#define __nsImapIncomingServer_h
+
+#include "mozilla/Attributes.h"
+#include "msgCore.h"
+#include "nsImapCore.h"
+#include "nsIImapIncomingServer.h"
+#include "nsMsgIncomingServer.h"
+#include "nsIImapServerSink.h"
+#include "nsIStringBundle.h"
+#include "nsISubscribableServer.h"
+#include "nsIUrlListener.h"
+#include "nsIMsgImapMailFolder.h"
+#include "nsCOMArray.h"
+#include "nsTArray.h"
+#include "mozilla/Mutex.h"
+
+/* get some implementation from nsMsgIncomingServer */
+class nsImapIncomingServer : public nsMsgIncomingServer,
+ public nsIImapIncomingServer,
+ public nsIImapServerSink,
+ public nsISubscribableServer,
+ public nsIUrlListener {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+
+ nsImapIncomingServer();
+
+ // overriding nsMsgIncomingServer methods
+ NS_IMETHOD SetKey(const nsACString& aKey)
+ override; // override nsMsgIncomingServer's implementation...
+ NS_IMETHOD GetLocalStoreType(nsACString& type) override;
+ NS_IMETHOD GetLocalDatabaseType(nsACString& type) override;
+
+ NS_DECL_NSIIMAPINCOMINGSERVER
+ NS_DECL_NSIIMAPSERVERSINK
+ NS_DECL_NSISUBSCRIBABLESERVER
+ NS_DECL_NSIURLLISTENER
+
+ NS_IMETHOD PerformBiff(nsIMsgWindow* aMsgWindow) override;
+ NS_IMETHOD PerformExpand(nsIMsgWindow* aMsgWindow) override;
+ NS_IMETHOD CloseCachedConnections() override;
+ NS_IMETHOD GetConstructedPrettyName(nsAString& retval) override;
+ NS_IMETHOD GetCanBeDefaultServer(bool* canBeDefaultServer) override;
+ NS_IMETHOD GetCanCompactFoldersOnServer(
+ bool* canCompactFoldersOnServer) override;
+ NS_IMETHOD GetCanUndoDeleteOnServer(bool* canUndoDeleteOnServer) override;
+ NS_IMETHOD GetCanSearchMessages(bool* canSearchMessages) override;
+ NS_IMETHOD GetOfflineSupportLevel(int32_t* aSupportLevel) override;
+ NS_IMETHOD GeneratePrettyNameForMigration(nsAString& aPrettyName) override;
+ NS_IMETHOD GetSupportsDiskSpace(bool* aSupportsDiskSpace) override;
+ NS_IMETHOD GetCanCreateFoldersOnServer(
+ bool* aCanCreateFoldersOnServer) override;
+ NS_IMETHOD GetCanFileMessagesOnServer(
+ bool* aCanFileMessagesOnServer) override;
+ NS_IMETHOD GetFilterScope(nsMsgSearchScopeValue* filterScope) override;
+ NS_IMETHOD GetSearchScope(nsMsgSearchScopeValue* searchScope) override;
+ NS_IMETHOD GetServerRequiresPasswordForBiff(
+ bool* aServerRequiresPasswordForBiff) override;
+ NS_IMETHOD GetNumIdleConnections(int32_t* aNumIdleConnections);
+ NS_IMETHOD ForgetSessionPassword(bool modifyLogin) override;
+ NS_IMETHOD GetMsgFolderFromURI(nsIMsgFolder* aFolderResource,
+ const nsACString& aURI,
+ nsIMsgFolder** aFolder) override;
+ NS_IMETHOD SetSocketType(int32_t aSocketType) override;
+ NS_IMETHOD VerifyLogon(nsIUrlListener* aUrlListener, nsIMsgWindow* aMsgWindow,
+ nsIURI** aURL) override;
+
+ protected:
+ virtual ~nsImapIncomingServer();
+ nsresult GetFolder(const nsACString& name, nsIMsgFolder** pFolder);
+ virtual nsresult CreateRootFolderFromUri(const nsACString& serverUri,
+ nsIMsgFolder** rootFolder) override;
+ nsresult ResetFoldersToUnverified(nsIMsgFolder* parentFolder);
+ void GetUnverifiedSubFolders(nsIMsgFolder* parentFolder,
+ nsCOMArray<nsIMsgImapMailFolder>& aFoldersArray);
+ void GetUnverifiedFolders(nsCOMArray<nsIMsgImapMailFolder>& aFolderArray);
+ bool NoDescendentsAreVerified(nsIMsgFolder* parentFolder);
+ bool AllDescendentsAreNoSelect(nsIMsgFolder* parentFolder);
+
+ nsresult GetStringBundle();
+ static nsresult AlertUser(const nsAString& aString, nsIMsgMailNewsUrl* aUrl);
+
+ private:
+ nsresult SubscribeToFolder(const char16_t* aName, bool subscribe);
+ nsresult GetImapConnection(nsIImapUrl* aImapUrl,
+ nsIImapProtocol** aImapConnection);
+ nsresult CreateProtocolInstance(nsIImapProtocol** aImapConnection);
+ nsresult CreateHostSpecificPrefName(const char* prefPrefix,
+ nsAutoCString& prefName);
+
+ nsresult DoomUrlIfChannelHasError(nsIImapUrl* aImapUrl, bool* urlDoomed);
+ bool ConnectionTimeOut(nsIImapProtocol* aImapConnection);
+ nsresult GetFormattedStringFromName(const nsAString& aValue,
+ const char* aName, nsAString& aResult);
+ nsresult GetPrefForServerAttribute(const char* prefSuffix, bool* prefValue);
+ bool CheckSpecialFolder(nsCString& folderUri, uint32_t folderFlag,
+ nsCString& existingUri);
+
+ nsCOMArray<nsIImapProtocol> m_connectionCache;
+
+ /**
+ * All requests waiting for a real connection.
+ * Each URL object holds a reference to the nsIImapMockChannel that
+ * represents the request.
+ */
+ nsCOMArray<nsIImapUrl> m_urlQueue;
+
+ /**
+ * Consumers for the queued urls. The number of elements here should match
+ * that of m_urlQueue. So requests with no consumer should have a nullptr
+ * entry here.
+ */
+ nsTArray<nsISupports*> m_urlConsumers;
+
+ nsCOMPtr<nsIStringBundle> m_stringBundle;
+ nsCOMArray<nsIMsgFolder>
+ m_subscribeFolders; // used to keep folder resources around while
+ // subscribe UI is up.
+ nsCOMArray<nsIMsgImapMailFolder>
+ m_foldersToStat; // folders to check for new mail with Status
+ eIMAPCapabilityFlags m_capability;
+ nsCString m_manageMailAccountUrl;
+ bool m_userAuthenticated;
+ bool mDoingSubscribeDialog;
+ bool mDoingLsub;
+ bool m_shuttingDown;
+ bool mUtf8AcceptEnabled;
+
+ mozilla::Mutex mLock;
+ // subscribe dialog stuff
+ nsresult AddFolderToSubscribeDialog(const char* parentUri, const char* uri,
+ const char* folderName);
+ nsCOMPtr<nsISubscribableServer> mInner;
+ nsresult EnsureInner();
+ nsresult ClearInner();
+
+ // Utility function for checking folder existence
+ nsresult GetExistingMsgFolder(const nsACString& aURI,
+ nsACString& folderUriWithNamespace,
+ bool& namespacePrefixAdded,
+ bool caseInsensitive, nsIMsgFolder** aFolder);
+};
+
+#endif
diff --git a/comm/mailnews/imap/src/nsImapMailFolder.cpp b/comm/mailnews/imap/src/nsImapMailFolder.cpp
new file mode 100644
index 0000000000..1ec8482383
--- /dev/null
+++ b/comm/mailnews/imap/src/nsImapMailFolder.cpp
@@ -0,0 +1,9095 @@
+/* -*- 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 "prmem.h"
+#include "nsImapMailFolder.h"
+#include "nsIImapService.h"
+#include "nsIFile.h"
+#include "nsAnonymousTemporaryFile.h"
+#include "nsIUrlListener.h"
+#include "nsCOMPtr.h"
+#include "nsMsgFolderFlags.h"
+#include "nsISeekableStream.h"
+#include "nsThreadUtils.h"
+#include "nsIImapUrl.h"
+#include "nsImapUtils.h"
+#include "nsMsgUtils.h"
+#include "nsIMsgMailSession.h"
+#include "nsITransactionManager.h"
+#include "nsImapUndoTxn.h"
+#include "../public/nsIImapHostSessionList.h"
+#include "nsIMsgCopyService.h"
+#include "nsICopyMessageStreamListener.h"
+#include "nsImapStringBundle.h"
+#include "nsIMsgFolderCacheElement.h"
+#include "nsTextFormatter.h"
+#include "nsIPrefBranch.h"
+#include "nsIPrefService.h"
+#include "nsMsgI18N.h"
+#include "nsIMsgFilter.h"
+#include "nsIMsgFilterService.h"
+#include "nsIMsgSearchCustomTerm.h"
+#include "nsIMsgSearchTerm.h"
+#include "nsImapMoveCoalescer.h"
+#include "nsIPrompt.h"
+#include "nsIDocShell.h"
+#include "nsUnicharUtils.h"
+#include "nsIImapFlagAndUidState.h"
+#include "nsIImapHeaderXferInfo.h"
+#include "nsIMessenger.h"
+#include "nsIMsgSearchAdapter.h"
+#include "nsIImapMockChannel.h"
+#include "nsIProgressEventSink.h"
+#include "nsIMsgWindow.h"
+#include "nsIMsgFolder.h" // TO include biffState enum. Change to bool later...
+#include "nsIMsgLocalMailFolder.h"
+#include "nsIMsgOfflineImapOperation.h"
+#include "nsImapOfflineSync.h"
+#include "nsIImapMailFolderSink.h"
+#include "nsIImapServerSink.h"
+#include "nsIMsgAccountManager.h"
+#include "nsQuickSort.h"
+#include "nsIImapMockChannel.h"
+#include "nsNetUtil.h"
+#include "nsImapNamespace.h"
+#include "nsIMsgFolderCompactor.h"
+#include "nsMsgMessageFlags.h"
+#include "nsISpamSettings.h"
+#include <time.h>
+#include "nsIMsgMailNewsUrl.h"
+#include "nsEmbedCID.h"
+#include "nsIMsgComposeService.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsIDirectoryEnumerator.h"
+#include "nsIMsgIdentity.h"
+#include "nsIMsgFolderNotificationService.h"
+#include "nsNativeCharsetUtils.h"
+#include "nsIExternalProtocolService.h"
+#include "nsCExternalHandlerService.h"
+#include "prprf.h"
+#include "nsAutoSyncManager.h"
+#include "nsIMsgFilterCustomAction.h"
+#include "nsMsgReadStateTxn.h"
+#include "nsStringEnumerator.h"
+#include "nsIMsgStatusFeedback.h"
+#include "nsMsgLineBuffer.h"
+#include "mozilla/Logging.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/SlicedInputStream.h"
+#include "nsStringStream.h"
+#include "nsIStreamListener.h"
+#include "nsITimer.h"
+#include "nsReadableUtils.h"
+#include "UrlListener.h"
+#include "nsIObserverService.h"
+
+#define NS_PARSEMAILMSGSTATE_CID \
+ { /* 2B79AC51-1459-11d3-8097-006008128C4E */ \
+ 0x2b79ac51, 0x1459, 0x11d3, { \
+ 0x80, 0x97, 0x0, 0x60, 0x8, 0x12, 0x8c, 0x4e \
+ } \
+ }
+static NS_DEFINE_CID(kParseMailMsgStateCID, NS_PARSEMAILMSGSTATE_CID);
+
+#define NS_IIMAPHOSTSESSIONLIST_CID \
+ { \
+ 0x479ce8fc, 0xe725, 0x11d2, { \
+ 0xa5, 0x05, 0x00, 0x60, 0xb0, 0xfc, 0x04, 0xb7 \
+ } \
+ }
+static NS_DEFINE_CID(kCImapHostSessionList, NS_IIMAPHOSTSESSIONLIST_CID);
+
+#define MAILNEWS_CUSTOM_HEADERS "mailnews.customHeaders"
+
+using namespace mozilla;
+
+extern LazyLogModule gAutoSyncLog; // defined in nsAutoSyncManager.cpp
+extern LazyLogModule IMAP; // defined in nsImapProtocol.cpp
+extern LazyLogModule IMAP_CS; // For CONDSTORE, defined in nsImapProtocol.cpp
+extern LazyLogModule FILTERLOGMODULE; // defined in nsMsgFilterService.cpp
+LazyLogModule IMAP_KW("IMAP_KW"); // for logging keyword (tag) processing
+
+/*
+ Copies the contents of srcDir into destDir.
+ destDir will be created if it doesn't exist.
+*/
+
+static nsresult RecursiveCopy(nsIFile* srcDir, nsIFile* destDir) {
+ bool isDir;
+ nsresult rv = srcDir->IsDirectory(&isDir);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!isDir) return NS_ERROR_INVALID_ARG;
+
+ bool exists;
+ rv = destDir->Exists(&exists);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!exists) {
+ rv = destDir->Create(nsIFile::DIRECTORY_TYPE, 0775);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ nsCOMPtr<nsIDirectoryEnumerator> dirIterator;
+ rv = srcDir->GetDirectoryEntries(getter_AddRefs(dirIterator));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool hasMore = false;
+ while (NS_SUCCEEDED(dirIterator->HasMoreElements(&hasMore)) && hasMore) {
+ nsCOMPtr<nsIFile> dirEntry;
+ rv = dirIterator->GetNextFile(getter_AddRefs(dirEntry));
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!dirEntry) continue;
+ rv = dirEntry->IsDirectory(&isDir);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (isDir) {
+ nsCOMPtr<nsIFile> newChild;
+ rv = destDir->Clone(getter_AddRefs(newChild));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsAutoString leafName;
+ dirEntry->GetLeafName(leafName);
+ newChild->AppendRelativePath(leafName);
+ rv = newChild->Exists(&exists);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!exists) {
+ rv = newChild->Create(nsIFile::DIRECTORY_TYPE, 0775);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ rv = RecursiveCopy(dirEntry, newChild);
+ } else {
+ rv = dirEntry->CopyTo(destDir, EmptyString());
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ return rv;
+}
+
+//
+// nsMsgQuota
+//
+NS_IMPL_ISUPPORTS(nsMsgQuota, nsIMsgQuota)
+
+nsMsgQuota::nsMsgQuota(const nsACString& aName, const uint64_t& aUsage,
+ const uint64_t& aLimit)
+ : mName(aName), mUsage(aUsage), mLimit(aLimit) {}
+
+nsMsgQuota::~nsMsgQuota() {}
+
+/**
+ * Note: These quota access function are not called but still must be defined
+ * for the linker.
+ */
+NS_IMETHODIMP nsMsgQuota::GetName(nsACString& aName) {
+ aName = mName;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgQuota::SetName(const nsACString& aName) {
+ mName = aName;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgQuota::GetUsage(uint64_t* aUsage) {
+ *aUsage = mUsage;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgQuota::SetUsage(uint64_t aUsage) {
+ mUsage = aUsage;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgQuota::GetLimit(uint64_t* aLimit) {
+ *aLimit = mLimit;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgQuota::SetLimit(uint64_t aLimit) {
+ mLimit = aLimit;
+ return NS_OK;
+}
+
+//
+// nsImapMailFolder
+//
+nsImapMailFolder::nsImapMailFolder()
+ : m_initialized(false),
+ m_haveDiscoveredAllFolders(false),
+ m_curMsgUid(0),
+ m_nextMessageByteLength(0),
+ m_urlRunning(false),
+ m_verifiedAsOnlineFolder(false),
+ m_explicitlyVerify(false),
+ m_folderIsNamespace(false),
+ m_folderNeedsSubscribing(false),
+ m_folderNeedsAdded(false),
+ m_folderNeedsACLListed(true),
+ m_performingBiff(false),
+ m_updatingFolder(false),
+ m_applyIncomingFilters(false),
+ m_downloadingFolderForOfflineUse(false),
+ m_filterListRequiresBody(false),
+ m_folderQuotaCommandIssued(false),
+ m_folderQuotaDataIsValid(false) {
+ m_boxFlags = 0;
+ m_uidValidity = kUidUnknown;
+ m_numServerRecentMessages = 0;
+ m_numServerUnseenMessages = 0;
+ m_numServerTotalMessages = 0;
+ m_nextUID = nsMsgKey_None;
+ m_hierarchyDelimiter = kOnlineHierarchySeparatorUnknown;
+ m_folderACL = nullptr;
+ m_aclFlags = 0;
+ m_supportedUserFlags = 0;
+ m_namespace = nullptr;
+ m_pendingPlaybackReq = nullptr;
+}
+
+nsImapMailFolder::~nsImapMailFolder() {
+ delete m_folderACL;
+
+ // cleanup any pending request
+ delete m_pendingPlaybackReq;
+}
+
+NS_IMPL_ADDREF_INHERITED(nsImapMailFolder, nsMsgDBFolder)
+NS_IMPL_RELEASE_INHERITED(nsImapMailFolder, nsMsgDBFolder)
+NS_IMPL_QUERY_HEAD(nsImapMailFolder)
+NS_IMPL_QUERY_BODY(nsIMsgImapMailFolder)
+NS_IMPL_QUERY_BODY(nsICopyMessageListener)
+NS_IMPL_QUERY_BODY(nsIImapMailFolderSink)
+NS_IMPL_QUERY_BODY(nsIImapMessageSink)
+NS_IMPL_QUERY_BODY(nsIUrlListener)
+NS_IMPL_QUERY_BODY(nsIMsgFilterHitNotify)
+NS_IMPL_QUERY_TAIL_INHERITING(nsMsgDBFolder)
+
+nsresult nsImapMailFolder::AddDirectorySeparator(nsIFile* path) {
+ if (mURI.Equals(kImapRootURI)) {
+ // don't concat the full separator with .sbd
+ } else {
+ // see if there's a dir with the same name ending with .sbd
+ nsAutoString leafName;
+ path->GetLeafName(leafName);
+ leafName.AppendLiteral(FOLDER_SUFFIX);
+ path->SetLeafName(leafName);
+ }
+
+ return NS_OK;
+}
+
+static bool nsShouldIgnoreFile(nsString& name) {
+ if (StringEndsWith(name, NS_LITERAL_STRING_FROM_CSTRING(SUMMARY_SUFFIX),
+ nsCaseInsensitiveStringComparator)) {
+ name.SetLength(name.Length() -
+ SUMMARY_SUFFIX_LENGTH); // truncate the string
+ return false;
+ }
+ return true;
+}
+
+nsresult nsImapMailFolder::CreateChildFromURI(const nsACString& uri,
+ nsIMsgFolder** folder) {
+ nsImapMailFolder* newFolder = new nsImapMailFolder;
+ newFolder->Init(uri);
+ NS_ADDREF(*folder = newFolder);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailFolder::AddSubfolder(const nsAString& aName,
+ nsIMsgFolder** aChild) {
+ NS_ENSURE_ARG_POINTER(aChild);
+
+ int32_t flags = 0;
+ nsresult rv;
+
+ nsAutoCString uri(mURI);
+ uri.Append('/');
+
+ nsAutoCString escapedName;
+ rv = NS_MsgEscapeEncodeURLPath(aName, escapedName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ 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, NS_ERROR_FAILURE);
+
+ // Ensure the containing dir exists.
+ nsCOMPtr<nsIFile> path;
+ rv = CreateDirectoryForFolder(getter_AddRefs(path));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ folder->GetFlags((uint32_t*)&flags);
+
+ flags |= nsMsgFolderFlags::Mail;
+
+ nsCOMPtr<nsIImapIncomingServer> imapServer;
+ GetImapIncomingServer(getter_AddRefs(imapServer));
+ if (imapServer) {
+ bool setNewFoldersForOffline = false;
+ rv = imapServer->GetOfflineDownload(&setNewFoldersForOffline);
+ if (NS_SUCCEEDED(rv) && setNewFoldersForOffline)
+ flags |= nsMsgFolderFlags::Offline;
+ }
+
+ folder->SetParent(this);
+
+ folder->SetFlags(flags);
+
+ mSubFolders.AppendObject(folder);
+ folder.forget(aChild);
+
+ // New child needs to inherit hierarchyDelimiter.
+ nsCOMPtr<nsIMsgImapMailFolder> imapChild = do_QueryInterface(*aChild);
+ if (imapChild) {
+ imapChild->SetHierarchyDelimiter(m_hierarchyDelimiter);
+ }
+ NotifyFolderAdded(*aChild);
+ return rv;
+}
+
+// Creates a new child nsIMsgFolder locally, with no IMAP traffic.
+nsresult nsImapMailFolder::AddSubfolderWithPath(nsAString& name,
+ nsIFile* dbPath,
+ nsIMsgFolder** child,
+ bool brandNew) {
+ NS_ENSURE_ARG_POINTER(child);
+ nsresult rv;
+
+ nsAutoCString uri(mURI);
+ uri.Append('/');
+ AppendUTF16toUTF8(name, uri);
+
+ bool isServer;
+ rv = GetIsServer(&isServer);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool isInbox = isServer && name.LowerCaseEqualsLiteral("inbox");
+
+ // will make sure mSubFolders does not have duplicates because of bogus msf
+ // files.
+ nsCOMPtr<nsIMsgFolder> msgFolder;
+ rv = GetChildWithURI(uri, false /*deep*/, isInbox /*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->SetFilePath(dbPath);
+ nsCOMPtr<nsIMsgImapMailFolder> imapFolder = do_QueryInterface(folder, &rv);
+ mozilla::Unused << imapFolder;
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t flags = 0;
+ folder->GetFlags(&flags);
+
+ folder->SetParent(this);
+ flags |= nsMsgFolderFlags::Mail;
+
+ uint32_t pFlags;
+ GetFlags(&pFlags);
+ bool isParentInbox = pFlags & nsMsgFolderFlags::Inbox;
+
+ nsCOMPtr<nsIImapIncomingServer> imapServer;
+ rv = GetImapIncomingServer(getter_AddRefs(imapServer));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Only set these if these are top level children or parent is inbox
+ if (isInbox)
+ flags |= nsMsgFolderFlags::Inbox;
+ else if (isServer || isParentInbox) {
+ nsMsgImapDeleteModel deleteModel;
+ imapServer->GetDeleteModel(&deleteModel);
+ if (deleteModel == nsMsgImapDeleteModels::MoveToTrash) {
+ nsAutoString trashName;
+ GetTrashFolderName(trashName);
+ if (name.Equals(trashName)) flags |= nsMsgFolderFlags::Trash;
+ }
+ }
+
+ // Make the folder offline if it is newly created and the offline_download
+ // pref is true, unless it's the Trash or Junk folder.
+ if (brandNew &&
+ !(flags & (nsMsgFolderFlags::Trash | nsMsgFolderFlags::Junk))) {
+ bool setNewFoldersForOffline = false;
+ rv = imapServer->GetOfflineDownload(&setNewFoldersForOffline);
+ if (NS_SUCCEEDED(rv) && setNewFoldersForOffline)
+ flags |= nsMsgFolderFlags::Offline;
+ }
+
+ folder->SetFlags(flags);
+
+ if (folder) mSubFolders.AppendObject(folder);
+ folder.forget(child);
+ return NS_OK;
+}
+
+// Create child nsIMsgFolders by scanning the filesystem to find .msf files.
+// No IMAP traffic.
+nsresult nsImapMailFolder::CreateSubFolders(nsIFile* path) {
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIDirectoryEnumerator> directoryEnumerator;
+ rv = path->GetDirectoryEntries(getter_AddRefs(directoryEnumerator));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // For each .msf file in the directory...
+ bool hasMore = false;
+ while (NS_SUCCEEDED(directoryEnumerator->HasMoreElements(&hasMore)) &&
+ hasMore) {
+ nsCOMPtr<nsIFile> currentFolderPath;
+ rv = directoryEnumerator->GetNextFile(getter_AddRefs(currentFolderPath));
+ if (NS_FAILED(rv) || !currentFolderPath) continue;
+
+ nsAutoString currentFolderNameStr; // online name
+ nsAutoString currentFolderDBNameStr; // possibly munged name
+ currentFolderPath->GetLeafName(currentFolderNameStr);
+ // Skip if not an .msf file.
+ // (NOTE: nsShouldIgnoreFile() strips the trailing ".msf" here)
+ if (nsShouldIgnoreFile(currentFolderNameStr)) continue;
+
+ // OK, here we need to get the online name from the folder cache if we can.
+ // If we can, use that to create the sub-folder
+ nsCOMPtr<nsIFile> curFolder =
+ do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIFile> dbFile = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ dbFile->InitWithFile(currentFolderPath);
+ curFolder->InitWithFile(currentFolderPath);
+ // don't strip off the .msf in currentFolderPath.
+ currentFolderPath->SetLeafName(currentFolderNameStr);
+ currentFolderDBNameStr = currentFolderNameStr;
+ nsAutoString utfLeafName = currentFolderNameStr;
+
+ if (curFolder) {
+ nsCOMPtr<nsIMsgFolderCacheElement> cacheElement;
+ rv = GetFolderCacheElemFromFile(dbFile, getter_AddRefs(cacheElement));
+ if (NS_SUCCEEDED(rv) && cacheElement) {
+ nsCString onlineFullUtfName;
+
+ uint32_t folderFlags;
+ rv = cacheElement->GetCachedUInt32("flags", &folderFlags);
+ if (NS_SUCCEEDED(rv) &&
+ folderFlags & nsMsgFolderFlags::Virtual) // ignore virtual folders
+ continue;
+ int32_t hierarchyDelimiter;
+ rv = cacheElement->GetCachedInt32("hierDelim", &hierarchyDelimiter);
+ if (NS_SUCCEEDED(rv) &&
+ hierarchyDelimiter == kOnlineHierarchySeparatorUnknown) {
+ currentFolderPath->Remove(false);
+ continue; // blow away .msf files for folders with unknown delimiter.
+ }
+ rv = cacheElement->GetCachedString("onlineName", onlineFullUtfName);
+ if (NS_SUCCEEDED(rv) && !onlineFullUtfName.IsEmpty()) {
+ CopyFolderNameToUTF16(onlineFullUtfName, currentFolderNameStr);
+ char delimiter = 0;
+ GetHierarchyDelimiter(&delimiter);
+ int32_t leafPos = currentFolderNameStr.RFindChar(delimiter);
+ if (leafPos > 0) currentFolderNameStr.Cut(0, leafPos + 1);
+
+ // Take the full online name, and determine the leaf name.
+ CopyUTF8toUTF16(onlineFullUtfName, utfLeafName);
+ leafPos = utfLeafName.RFindChar(delimiter);
+ if (leafPos > 0) utfLeafName.Cut(0, leafPos + 1);
+ }
+ }
+ }
+ // make the imap folder remember the file spec it was created with.
+ nsCOMPtr<nsIFile> msfFilePath =
+ do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ msfFilePath->InitWithFile(currentFolderPath);
+ if (NS_SUCCEEDED(rv) && msfFilePath) {
+ // leaf name is the db name w/o .msf (nsShouldIgnoreFile strips it off)
+ // so this trims the .msf off the file spec.
+ msfFilePath->SetLeafName(currentFolderDBNameStr);
+ }
+ // Use the name as the uri for the folder.
+ nsCOMPtr<nsIMsgFolder> child;
+ AddSubfolderWithPath(utfLeafName, msfFilePath, getter_AddRefs(child));
+ if (child) {
+ // use the unicode name as the "pretty" name. Set it so it won't be
+ // automatically computed from the URI.
+ if (!currentFolderNameStr.IsEmpty())
+ child->SetPrettyName(currentFolderNameStr);
+ child->SetMsgDatabase(nullptr);
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapMailFolder::GetSubFolders(
+ nsTArray<RefPtr<nsIMsgFolder>>& folders) {
+ bool isServer;
+ nsresult rv = GetIsServer(&isServer);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!m_initialized) {
+ nsCOMPtr<nsIFile> pathFile;
+ rv = GetFilePath(getter_AddRefs(pathFile));
+ if (NS_FAILED(rv)) return rv;
+
+ // host directory does not need .sbd tacked on
+ if (!isServer) {
+ rv = AddDirectorySeparator(pathFile);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ m_initialized = true; // need to set this here to avoid infinite recursion
+ // from CreateSubfolders.
+ // we have to treat the root folder specially, because it's name
+ // doesn't end with .sbd
+
+ int32_t newFlags = nsMsgFolderFlags::Mail;
+ bool isDirectory = false;
+ pathFile->IsDirectory(&isDirectory);
+ if (isDirectory) {
+ newFlags |= (nsMsgFolderFlags::Directory | nsMsgFolderFlags::Elided);
+ if (!mIsServer) SetFlag(newFlags);
+ rv = CreateSubFolders(pathFile);
+ }
+ if (isServer) {
+ nsCOMPtr<nsIMsgFolder> inboxFolder;
+
+ GetFolderWithFlags(nsMsgFolderFlags::Inbox, getter_AddRefs(inboxFolder));
+ if (!inboxFolder) {
+ // create an inbox if we don't have one.
+ CreateClientSubfolderInfo("INBOX"_ns, kOnlineHierarchySeparatorUnknown,
+ 0, true);
+ }
+ }
+
+ // Force initialisation recursively.
+ for (nsIMsgFolder* f : mSubFolders) {
+ nsTArray<RefPtr<nsIMsgFolder>> dummy;
+ rv = f->GetSubFolders(dummy);
+ if (NS_FAILED(rv)) {
+ break;
+ }
+ }
+
+ UpdateSummaryTotals(false);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ return nsMsgDBFolder::GetSubFolders(folders);
+}
+
+// Makes sure the database is open and exists. If the database is valid then
+// returns NS_OK. Otherwise returns a failure error value.
+nsresult nsImapMailFolder::GetDatabase() {
+ nsresult rv = NS_OK;
+ if (!mDatabase) {
+ nsCOMPtr<nsIMsgDBService> msgDBService =
+ do_GetService("@mozilla.org/msgDatabase/msgDBService;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Create the database, blowing it away if it needs to be rebuilt
+ rv = msgDBService->OpenFolderDB(this, false, getter_AddRefs(mDatabase));
+ if (NS_FAILED(rv))
+ rv = msgDBService->CreateNewDB(this, getter_AddRefs(mDatabase));
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // UpdateNewMessages/UpdateSummaryTotals can null mDatabase, so we save a
+ // local copy
+ nsCOMPtr<nsIMsgDatabase> database(mDatabase);
+ UpdateNewMessages();
+ if (mAddListener) database->AddListener(this);
+ UpdateSummaryTotals(true);
+ mDatabase = database;
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapMailFolder::UpdateFolder(nsIMsgWindow* inMsgWindow) {
+ return UpdateFolderWithListener(inMsgWindow, nullptr);
+}
+
+NS_IMETHODIMP nsImapMailFolder::UpdateFolderWithListener(
+ nsIMsgWindow* aMsgWindow, nsIUrlListener* aUrlListener) {
+ nsresult rv;
+ // If this is the inbox, filters will be applied. Otherwise, we test the
+ // inherited folder property "applyIncomingFilters" (which defaults to empty).
+ // If this inherited property has the string value "true", we will apply
+ // filters even if this is not the inbox folder.
+ nsCString applyIncomingFilters;
+ GetInheritedStringProperty("applyIncomingFilters", applyIncomingFilters);
+ m_applyIncomingFilters = applyIncomingFilters.EqualsLiteral("true");
+
+ nsString folderName;
+ GetPrettyName(folderName);
+ MOZ_LOG(FILTERLOGMODULE, LogLevel::Debug,
+ ("(Imap) nsImapMailFolder::UpdateFolderWithListener() on folder '%s'",
+ NS_ConvertUTF16toUTF8(folderName).get()));
+ if (mFlags & nsMsgFolderFlags::Inbox || m_applyIncomingFilters) {
+ MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
+ ("(Imap) Preparing filter run on folder '%s'",
+ NS_ConvertUTF16toUTF8(folderName).get()));
+
+ if (!m_filterList) {
+ rv = GetFilterList(aMsgWindow, getter_AddRefs(m_filterList));
+ if (NS_FAILED(rv)) {
+ MOZ_LOG(FILTERLOGMODULE, LogLevel::Error,
+ ("(Imap) Loading of filter list failed"));
+ }
+ }
+
+ // if there's no msg window, but someone is updating the inbox, we're
+ // doing something biff-like, and may download headers, so make biff notify.
+ if (!aMsgWindow && mFlags & nsMsgFolderFlags::Inbox)
+ SetPerformingBiff(true);
+ }
+
+ if (m_filterList) {
+ nsCString listId;
+ m_filterList->GetListId(listId);
+ MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
+ ("(Imap) Preparing filter list %s", listId.get()));
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool canFileMessagesOnServer = true;
+ rv = server->GetCanFileMessagesOnServer(&canFileMessagesOnServer);
+ // the mdn filter is for filing return receipts into the sent folder
+ // some servers (like AOL mail servers)
+ // can't file to the sent folder, so we don't add the filter for those
+ // servers
+ if (canFileMessagesOnServer) {
+ rv = server->ConfigureTemporaryFilters(m_filterList);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // If a body filter is enabled for an offline folder, delay the filter
+ // application until after message has been downloaded.
+ m_filterListRequiresBody = false;
+
+ if (mFlags & nsMsgFolderFlags::Offline) {
+ nsCOMPtr<nsIMsgFilterService> filterService =
+ do_GetService("@mozilla.org/messenger/services/filters;1", &rv);
+ uint32_t filterCount = 0;
+ m_filterList->GetFilterCount(&filterCount);
+ for (uint32_t index = 0; index < filterCount && !m_filterListRequiresBody;
+ ++index) {
+ nsCOMPtr<nsIMsgFilter> filter;
+ m_filterList->GetFilterAt(index, getter_AddRefs(filter));
+ if (!filter) continue;
+ nsMsgFilterTypeType filterType;
+ filter->GetFilterType(&filterType);
+ if (!(filterType & nsMsgFilterType::Incoming)) continue;
+ bool enabled = false;
+ filter->GetEnabled(&enabled);
+ if (!enabled) continue;
+ nsTArray<RefPtr<nsIMsgSearchTerm>> searchTerms;
+ filter->GetSearchTerms(searchTerms);
+ for (nsIMsgSearchTerm* term : searchTerms) {
+ nsMsgSearchAttribValue attrib;
+ rv = term->GetAttrib(&attrib);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (attrib == nsMsgSearchAttrib::Body)
+ m_filterListRequiresBody = true;
+ else if (attrib == nsMsgSearchAttrib::Custom) {
+ nsAutoCString customId;
+ rv = term->GetCustomId(customId);
+ nsCOMPtr<nsIMsgSearchCustomTerm> customTerm;
+ if (NS_SUCCEEDED(rv) && filterService)
+ rv = filterService->GetCustomTerm(customId,
+ getter_AddRefs(customTerm));
+ bool needsBody = false;
+ if (NS_SUCCEEDED(rv) && customTerm)
+ rv = customTerm->GetNeedsBody(&needsBody);
+ if (NS_SUCCEEDED(rv) && needsBody) m_filterListRequiresBody = true;
+ }
+ if (m_filterListRequiresBody) {
+ break;
+ }
+ }
+
+ // Also check if filter actions need the body, as this
+ // is supported in custom actions.
+ uint32_t numActions = 0;
+ filter->GetActionCount(&numActions);
+ for (uint32_t actionIndex = 0;
+ actionIndex < numActions && !m_filterListRequiresBody;
+ actionIndex++) {
+ nsCOMPtr<nsIMsgRuleAction> action;
+ rv = filter->GetActionAt(actionIndex, getter_AddRefs(action));
+ if (NS_FAILED(rv) || !action) continue;
+
+ nsCOMPtr<nsIMsgFilterCustomAction> customAction;
+ rv = action->GetCustomAction(getter_AddRefs(customAction));
+ if (NS_FAILED(rv) || !customAction) continue;
+
+ bool needsBody = false;
+ customAction->GetNeedsBody(&needsBody);
+ if (needsBody) m_filterListRequiresBody = true;
+ }
+ }
+ }
+ MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
+ ("(Imap) Filters require the message body: %s",
+ (m_filterListRequiresBody ? "true" : "false")));
+ }
+
+ bool isServer;
+ rv = GetIsServer(&isServer);
+ if (NS_SUCCEEDED(rv) && isServer) {
+ if (!m_haveDiscoveredAllFolders) {
+ bool hasSubFolders = false;
+ GetHasSubFolders(&hasSubFolders);
+ if (!hasSubFolders) {
+ rv = CreateClientSubfolderInfo(
+ "Inbox"_ns, kOnlineHierarchySeparatorUnknown, 0, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ m_haveDiscoveredAllFolders = true;
+ }
+ }
+
+ rv = GetDatabase();
+ if (NS_FAILED(rv)) {
+ ThrowAlertMsg("errorGettingDB", aMsgWindow);
+ return rv;
+ }
+
+ bool hasOfflineEvents = false;
+ GetFlag(nsMsgFolderFlags::OfflineEvents, &hasOfflineEvents);
+
+ if (!WeAreOffline()) {
+ if (hasOfflineEvents) {
+ // hold a reference to the offline sync object. If ProcessNextOperation
+ // runs a url, a reference will be added to it. Otherwise, it will get
+ // destroyed when the refptr goes out of scope.
+ RefPtr<nsImapOfflineSync> goOnline = new nsImapOfflineSync();
+ goOnline->Init(aMsgWindow, this, this, false);
+ if (goOnline) {
+ m_urlListener = aUrlListener;
+ return goOnline->ProcessNextOperation();
+ }
+ }
+ }
+
+ // Check it we're password protecting the local store.
+ if (!PromptForMasterPasswordIfNecessary()) return NS_ERROR_FAILURE;
+
+ bool canOpenThisFolder = true;
+ GetCanOpenFolder(&canOpenThisFolder);
+ // Don't run select if we can't select the folder...
+ if (!m_urlRunning && canOpenThisFolder && !isServer) {
+ nsCOMPtr<nsIImapService> imapService =
+ do_GetService("@mozilla.org/messenger/imapservice;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ /* Do a discovery in its own url if needed. Do before SELECT url. */
+ nsCOMPtr<nsIImapHostSessionList> hostSession =
+ do_GetService(kCImapHostSessionList, &rv);
+ if (NS_SUCCEEDED(rv) && hostSession) {
+ bool foundMailboxesAlready = false;
+ nsCString serverKey;
+ GetServerKey(serverKey);
+ hostSession->GetHaveWeEverDiscoveredFoldersForHost(serverKey.get(),
+ foundMailboxesAlready);
+ if (!foundMailboxesAlready) {
+ bool discoveryInProgress = false;
+ // See if discovery in progress and not yet finished.
+ hostSession->GetDiscoveryForHostInProgress(serverKey.get(),
+ discoveryInProgress);
+ if (!discoveryInProgress) {
+ nsCOMPtr<nsIMsgFolder> rootFolder;
+ rv = GetRootFolder(getter_AddRefs(rootFolder));
+ if (NS_SUCCEEDED(rv) && rootFolder) {
+ rv = imapService->DiscoverAllFolders(rootFolder, this, aMsgWindow);
+ if (NS_SUCCEEDED(rv))
+ hostSession->SetDiscoveryForHostInProgress(serverKey.get(), true);
+ }
+ }
+ }
+ }
+
+ nsCOMPtr<nsIURI> url;
+ rv = imapService->SelectFolder(this, m_urlListener, aMsgWindow,
+ getter_AddRefs(url));
+ if (NS_SUCCEEDED(rv)) {
+ m_urlRunning = true;
+ m_updatingFolder = true;
+ }
+ if (url) {
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(url, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ mailnewsUrl->RegisterListener(this);
+ m_urlListener = aUrlListener;
+ }
+
+ // Allow IMAP folder auto-compact to occur when online or offline.
+ if (aMsgWindow) AutoCompact(aMsgWindow);
+
+ if (rv == NS_MSG_ERROR_OFFLINE || rv == NS_BINDING_ABORTED) {
+ rv = NS_OK;
+ NotifyFolderEvent(kFolderLoaded);
+ }
+ } else {
+ // Tell the front end that the folder is loaded if we're not going to
+ // actually run a url.
+ if (!m_updatingFolder) // if we're already running an update url, we'll let
+ // that one send the folder loaded
+ NotifyFolderEvent(kFolderLoaded);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapMailFolder::CreateSubfolder(const nsAString& folderName,
+ nsIMsgWindow* msgWindow) {
+ if (folderName.IsEmpty()) return NS_MSG_ERROR_INVALID_FOLDER_NAME;
+
+ nsresult rv;
+ nsAutoString trashName;
+ GetTrashFolderName(trashName);
+ if (folderName.Equals(trashName)) // Trash , a special folder
+ {
+ ThrowAlertMsg("folderExists", msgWindow);
+ return NS_MSG_FOLDER_EXISTS;
+ }
+ if (mIsServer &&
+ folderName.LowerCaseEqualsLiteral("inbox")) // Inbox, a special folder
+ {
+ ThrowAlertMsg("folderExists", msgWindow);
+ return NS_MSG_FOLDER_EXISTS;
+ }
+
+ nsCOMPtr<nsIImapService> imapService =
+ do_GetService("@mozilla.org/messenger/imapservice;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIURI> url;
+ return imapService->CreateFolder(this, folderName, this, getter_AddRefs(url));
+}
+
+NS_IMETHODIMP nsImapMailFolder::CreateClientSubfolderInfo(
+ const nsACString& folderName, char hierarchyDelimiter, int32_t flags,
+ bool suppressNotification) {
+ nsresult rv = NS_OK;
+
+ // Get a directory based on our current path.
+ nsCOMPtr<nsIFile> path;
+ rv = CreateDirectoryForFolder(getter_AddRefs(path));
+ if (NS_FAILED(rv)) return rv;
+
+ NS_ConvertUTF8toUTF16 leafName(folderName);
+ nsAutoString folderNameStr;
+ nsAutoString parentName = leafName;
+ // use RFind, because folder can start with a delimiter and
+ // not be a leaf folder.
+ int32_t folderStart = leafName.RFindChar('/');
+ if (folderStart > 0) {
+ nsCOMPtr<nsIMsgImapMailFolder> imapFolder;
+ nsAutoCString uri(mURI);
+ leafName.Assign(Substring(parentName, folderStart + 1));
+ parentName.SetLength(folderStart);
+
+ rv = CreateDirectoryForFolder(getter_AddRefs(path));
+ NS_ENSURE_SUCCESS(rv, rv);
+ uri.Append('/');
+ uri.Append(NS_ConvertUTF16toUTF8(parentName));
+ nsCOMPtr<nsIMsgFolder> folder;
+ rv = GetOrCreateFolder(uri, getter_AddRefs(folder));
+ NS_ENSURE_SUCCESS(rv, rv);
+ imapFolder = do_QueryInterface(folder, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsAutoCString leafnameC;
+ CopyUTF16toUTF8(leafName, leafnameC);
+ return imapFolder->CreateClientSubfolderInfo(leafnameC, hierarchyDelimiter,
+ flags, suppressNotification);
+ }
+
+ // if we get here, it's really a leaf, and "this" is the parent.
+ folderNameStr = leafName;
+
+ // Create an empty database for this mail folder, set its name from the user
+ nsCOMPtr<nsIMsgDatabase> mailDBFactory;
+ nsCOMPtr<nsIMsgFolder> child;
+
+ nsCOMPtr<nsIMsgDBService> msgDBService =
+ do_GetService("@mozilla.org/msgDatabase/msgDBService;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIMsgDatabase> unusedDB;
+ nsCOMPtr<nsIFile> dbFile;
+
+ // warning, path will be changed
+ rv = CreateFileForDB(folderNameStr, path, getter_AddRefs(dbFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Now let's create the actual new folder
+ rv = AddSubfolderWithPath(folderNameStr, dbFile, getter_AddRefs(child), true);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = msgDBService->OpenMailDBFromFile(dbFile, child, true, true,
+ getter_AddRefs(unusedDB));
+ if (rv == NS_MSG_ERROR_FOLDER_SUMMARY_MISSING) rv = NS_OK;
+
+ if (NS_SUCCEEDED(rv) && unusedDB) {
+ // need to set the folder name
+ nsCOMPtr<nsIDBFolderInfo> folderInfo;
+ rv = unusedDB->GetDBFolderInfo(getter_AddRefs(folderInfo));
+ nsCOMPtr<nsIMsgImapMailFolder> imapFolder = do_QueryInterface(child, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ nsAutoCString onlineName(m_onlineFolderName);
+ if (!onlineName.IsEmpty()) onlineName.Append(hierarchyDelimiter);
+ onlineName.Append(NS_ConvertUTF16toUTF8(folderNameStr));
+ imapFolder->SetVerifiedAsOnlineFolder(true);
+ imapFolder->SetOnlineName(onlineName);
+ imapFolder->SetHierarchyDelimiter(hierarchyDelimiter);
+ imapFolder->SetBoxFlags(flags);
+
+ // Now that the child is created and the boxflags are set we can be sure
+ // all special folder flags are known. The child may get its flags already
+ // in AddSubfolderWithPath if they were in FolderCache, but that's
+ // not always the case.
+ uint32_t flags = 0;
+ child->GetFlags(&flags);
+
+ // Set the offline use flag for the newly created folder if the
+ // offline_download preference is true, unless it's the Trash or Junk
+ // folder.
+ if (!(flags & (nsMsgFolderFlags::Trash | nsMsgFolderFlags::Junk))) {
+ nsCOMPtr<nsIImapIncomingServer> imapServer;
+ rv = GetImapIncomingServer(getter_AddRefs(imapServer));
+ NS_ENSURE_SUCCESS(rv, rv);
+ bool setNewFoldersForOffline = false;
+ rv = imapServer->GetOfflineDownload(&setNewFoldersForOffline);
+ if (NS_SUCCEEDED(rv) && setNewFoldersForOffline)
+ flags |= nsMsgFolderFlags::Offline;
+ } else {
+ flags &= ~nsMsgFolderFlags::Offline; // clear offline flag if set
+ }
+
+ flags |= nsMsgFolderFlags::Elided;
+ child->SetFlags(flags);
+
+ nsString unicodeName;
+ rv = CopyFolderNameToUTF16(nsCString(folderName), unicodeName);
+ if (NS_SUCCEEDED(rv)) child->SetPrettyName(unicodeName);
+
+ // store the online name as the mailbox name in the db folder info
+ // I don't think anyone uses the mailbox name, so we'll use it
+ // to restore the online name when blowing away an imap db.
+ if (folderInfo)
+ folderInfo->SetMailboxName(NS_ConvertUTF8toUTF16(onlineName));
+ }
+
+ unusedDB->SetSummaryValid(true);
+ unusedDB->Commit(nsMsgDBCommitType::kLargeCommit);
+ unusedDB->Close(true);
+ // don't want to hold onto this newly created db.
+ child->SetMsgDatabase(nullptr);
+ }
+
+ if (!suppressNotification) {
+ if (NS_SUCCEEDED(rv) && child) {
+ NotifyFolderAdded(child);
+ child->NotifyFolderEvent(kFolderCreateCompleted);
+ nsCOMPtr<nsIMsgFolderNotificationService> notifier(
+ do_GetService("@mozilla.org/messenger/msgnotificationservice;1"));
+ if (notifier) notifier->NotifyFolderAdded(child);
+ } else {
+ NotifyFolderEvent(kFolderCreateFailed);
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapMailFolder::List() {
+ nsresult rv;
+ nsCOMPtr<nsIImapService> imapService =
+ do_GetService("@mozilla.org/messenger/imapservice;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return imapService->ListFolder(this, this);
+}
+
+NS_IMETHODIMP nsImapMailFolder::RemoveLocalSelf() {
+ // Kill the local folder and its storage.
+ return nsMsgDBFolder::DeleteSelf(nullptr);
+}
+
+NS_IMETHODIMP nsImapMailFolder::CreateStorageIfMissing(
+ nsIUrlListener* urlListener) {
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIMsgFolder> msgParent;
+ GetParent(getter_AddRefs(msgParent));
+
+ // parent is probably not set because *this* was probably created by rdf
+ // and not by folder discovery. So, we have to compute the parent.
+ if (!msgParent) {
+ nsAutoCString folderName(mURI);
+
+ int32_t leafPos = folderName.RFindChar('/');
+ nsAutoCString parentName(folderName);
+
+ if (leafPos > 0) {
+ // If there is a hierarchy, there is a parent.
+ // Don't strip off slash if it's the first character
+ parentName.SetLength(leafPos);
+ rv = GetOrCreateFolder(parentName, getter_AddRefs(msgParent));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+ if (msgParent) {
+ nsString folderName;
+ GetName(folderName);
+ nsresult rv;
+ nsCOMPtr<nsIImapService> imapService =
+ do_GetService("@mozilla.org/messenger/imapservice;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ imapService->EnsureFolderExists(msgParent, folderName, nullptr,
+ urlListener);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapMailFolder::GetVerifiedAsOnlineFolder(
+ bool* aVerifiedAsOnlineFolder) {
+ NS_ENSURE_ARG_POINTER(aVerifiedAsOnlineFolder);
+ *aVerifiedAsOnlineFolder = m_verifiedAsOnlineFolder;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailFolder::SetVerifiedAsOnlineFolder(
+ bool aVerifiedAsOnlineFolder) {
+ m_verifiedAsOnlineFolder = aVerifiedAsOnlineFolder;
+ // mark ancestors as verified as well
+ if (aVerifiedAsOnlineFolder) {
+ nsCOMPtr<nsIMsgFolder> parent;
+ do {
+ GetParent(getter_AddRefs(parent));
+ if (parent) {
+ nsCOMPtr<nsIMsgImapMailFolder> imapParent = do_QueryInterface(parent);
+ if (imapParent) {
+ bool verifiedOnline;
+ imapParent->GetVerifiedAsOnlineFolder(&verifiedOnline);
+ if (verifiedOnline) break;
+ imapParent->SetVerifiedAsOnlineFolder(true);
+ }
+ }
+ } while (parent);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailFolder::GetOnlineDelimiter(char* onlineDelimiter) {
+ return GetHierarchyDelimiter(onlineDelimiter);
+}
+
+NS_IMETHODIMP nsImapMailFolder::SetHierarchyDelimiter(
+ char aHierarchyDelimiter) {
+ m_hierarchyDelimiter = aHierarchyDelimiter;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailFolder::GetHierarchyDelimiter(
+ char* aHierarchyDelimiter) {
+ NS_ENSURE_ARG_POINTER(aHierarchyDelimiter);
+ if (mIsServer) {
+ // if it's the root folder, we don't know the delimiter. So look at the
+ // first child.
+ int32_t count = mSubFolders.Count();
+ if (count > 0) {
+ nsCOMPtr<nsIMsgImapMailFolder> childFolder(
+ do_QueryInterface(mSubFolders[0]));
+ if (childFolder) {
+ nsresult rv = childFolder->GetHierarchyDelimiter(aHierarchyDelimiter);
+ // some code uses m_hierarchyDelimiter directly, so we should set it.
+ m_hierarchyDelimiter = *aHierarchyDelimiter;
+ return rv;
+ }
+ }
+ }
+ ReadDBFolderInfo(false); // update cache first.
+ *aHierarchyDelimiter = m_hierarchyDelimiter;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailFolder::SetBoxFlags(int32_t aBoxFlags) {
+ ReadDBFolderInfo(false);
+
+ m_boxFlags = aBoxFlags;
+ uint32_t newFlags = mFlags;
+
+ newFlags |= nsMsgFolderFlags::ImapBox;
+
+ if (m_boxFlags & kNoinferiors)
+ newFlags |= nsMsgFolderFlags::ImapNoinferiors;
+ else
+ newFlags &= ~nsMsgFolderFlags::ImapNoinferiors;
+ if (m_boxFlags & kNoselect)
+ newFlags |= nsMsgFolderFlags::ImapNoselect;
+ else
+ newFlags &= ~nsMsgFolderFlags::ImapNoselect;
+ if (m_boxFlags & kPublicMailbox)
+ newFlags |= nsMsgFolderFlags::ImapPublic;
+ else
+ newFlags &= ~nsMsgFolderFlags::ImapPublic;
+ if (m_boxFlags & kOtherUsersMailbox)
+ newFlags |= nsMsgFolderFlags::ImapOtherUser;
+ else
+ newFlags &= ~nsMsgFolderFlags::ImapOtherUser;
+ if (m_boxFlags & kPersonalMailbox)
+ newFlags |= nsMsgFolderFlags::ImapPersonal;
+ else
+ newFlags &= ~nsMsgFolderFlags::ImapPersonal;
+
+ // The following are all flags returned by XLIST.
+ // nsImapIncomingServer::DiscoveryDone checks for these folders.
+ if (m_boxFlags & kImapDrafts) newFlags |= nsMsgFolderFlags::Drafts;
+
+ if (m_boxFlags & kImapSpam) newFlags |= nsMsgFolderFlags::Junk;
+
+ if (m_boxFlags & kImapSent) newFlags |= nsMsgFolderFlags::SentMail;
+
+ if (m_boxFlags & kImapInbox) newFlags |= nsMsgFolderFlags::Inbox;
+
+ if (m_boxFlags & kImapXListTrash) {
+ nsCOMPtr<nsIImapIncomingServer> imapServer;
+ nsMsgImapDeleteModel deleteModel = nsMsgImapDeleteModels::MoveToTrash;
+ (void)GetImapIncomingServer(getter_AddRefs(imapServer));
+ if (imapServer) imapServer->GetDeleteModel(&deleteModel);
+ if (deleteModel == nsMsgImapDeleteModels::MoveToTrash)
+ newFlags |= nsMsgFolderFlags::Trash;
+ }
+ // Treat the GMail all mail folder as the archive folder.
+ if (m_boxFlags & (kImapAllMail | kImapArchive))
+ newFlags |= nsMsgFolderFlags::Archive;
+
+ SetFlags(newFlags);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailFolder::GetBoxFlags(int32_t* aBoxFlags) {
+ NS_ENSURE_ARG_POINTER(aBoxFlags);
+ *aBoxFlags = m_boxFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailFolder::GetExplicitlyVerify(bool* aExplicitlyVerify) {
+ NS_ENSURE_ARG_POINTER(aExplicitlyVerify);
+ *aExplicitlyVerify = m_explicitlyVerify;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailFolder::SetExplicitlyVerify(bool aExplicitlyVerify) {
+ m_explicitlyVerify = aExplicitlyVerify;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailFolder::GetNoSelect(bool* aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+ return GetFlag(nsMsgFolderFlags::ImapNoselect, aResult);
+}
+
+NS_IMETHODIMP nsImapMailFolder::ApplyRetentionSettings() {
+ int32_t numDaysToKeepOfflineMsgs = -1;
+
+ // Check if we've limited the offline storage by age.
+ nsCOMPtr<nsIImapIncomingServer> imapServer;
+ nsresult rv = GetImapIncomingServer(getter_AddRefs(imapServer));
+ NS_ENSURE_SUCCESS(rv, rv);
+ imapServer->GetAutoSyncMaxAgeDays(&numDaysToKeepOfflineMsgs);
+
+ nsCOMPtr<nsIMsgDatabase> holdDBOpen;
+ if (numDaysToKeepOfflineMsgs > 0) {
+ bool dbWasCached = mDatabase != nullptr;
+ rv = GetDatabase();
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIMsgEnumerator> hdrs;
+ rv = mDatabase->EnumerateMessages(getter_AddRefs(hdrs));
+ NS_ENSURE_SUCCESS(rv, rv);
+ bool hasMore = false;
+
+ PRTime cutOffDay =
+ MsgConvertAgeInDaysToCutoffDate(numDaysToKeepOfflineMsgs);
+
+ // so now cutOffDay is the PRTime cut-off point. Any offline msg with
+ // a date less than that will get marked for pending removal.
+ while (NS_SUCCEEDED(rv = hdrs->HasMoreElements(&hasMore)) && hasMore) {
+ nsCOMPtr<nsIMsgDBHdr> header;
+ rv = hdrs->GetNext(getter_AddRefs(header));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t msgFlags;
+ PRTime msgDate;
+ header->GetFlags(&msgFlags);
+ if (msgFlags & nsMsgMessageFlags::Offline) {
+ header->GetDate(&msgDate);
+ MarkPendingRemoval(header, msgDate < cutOffDay);
+ // I'm horribly tempted to break out of the loop if we've found
+ // a message after the cut-off date, because messages will most likely
+ // be in date order in the db, but there are always edge cases.
+ }
+ }
+ if (!dbWasCached) {
+ holdDBOpen = mDatabase;
+ mDatabase = nullptr;
+ }
+ }
+ return nsMsgDBFolder::ApplyRetentionSettings();
+}
+
+/**
+ * The listener will get called when both the online expunge and the offline
+ * store compaction are finished (if the latter is needed).
+ */
+nsresult nsImapMailFolder::ExpungeAndCompact(nsIUrlListener* aListener,
+ nsIMsgWindow* aMsgWindow) {
+ GetDatabase();
+ // now's a good time to apply the retention settings. If we do delete any
+ // messages, the expunge is going to have to wait until the delete to
+ // finish before it can run, but the multiple-connection protection code
+ // should handle that.
+ if (mDatabase) ApplyRetentionSettings();
+
+ // Things to hold in existence until both expunge and compact are complete.
+ RefPtr<nsImapMailFolder> folder = this;
+ nsCOMPtr<nsIUrlListener> finalListener = aListener;
+ nsCOMPtr<nsIMsgWindow> msgWindow = aMsgWindow;
+
+ // doCompact implements OnStopRunningUrl()
+ // NOTE: The caller will be expecting that their listener will be invoked, so
+ // we need to be careful that all execution paths in here do that. We either
+ // call it directly, or pass it along to the foldercompactor to call.
+ auto doCompact = [folder, finalListener, msgWindow](
+ nsIURI* url, nsresult status) -> nsresult {
+ nsCOMPtr<nsIMsgPluggableStore> msgStore;
+ nsresult rv = folder->GetMsgStore(getter_AddRefs(msgStore));
+ if (NS_FAILED(rv)) {
+ if (finalListener) {
+ return finalListener->OnStopRunningUrl(nullptr, rv);
+ }
+ return rv;
+ }
+ bool storeSupportsCompaction;
+ msgStore->GetSupportsCompaction(&storeSupportsCompaction);
+ if (storeSupportsCompaction && folder->mFlags & nsMsgFolderFlags::Offline) {
+ nsCOMPtr<nsIMsgFolderCompactor> folderCompactor =
+ do_CreateInstance("@mozilla.org/messenger/foldercompactor;1", &rv);
+ if (NS_FAILED(rv)) {
+ if (finalListener) {
+ return finalListener->OnStopRunningUrl(nullptr, rv);
+ }
+ return rv;
+ }
+ return folderCompactor->CompactFolders({folder}, finalListener,
+ msgWindow);
+ }
+ // Not going to run a compaction, so signal that we're all done.
+ if (finalListener) {
+ return finalListener->OnStopRunningUrl(nullptr, NS_OK);
+ }
+ return NS_OK;
+ };
+
+ if (WeAreOffline()) {
+ // Can't run an expunge. Kick off the next stage (compact) immediately.
+ return doCompact(nullptr, NS_OK);
+ }
+
+ // Run the expunge, followed by the compaction.
+ RefPtr<UrlListener> expungeListener = new UrlListener();
+ expungeListener->mStopFn = doCompact;
+ return Expunge(expungeListener, aMsgWindow);
+}
+
+// IMAP compact implies an Expunge.
+NS_IMETHODIMP nsImapMailFolder::Compact(nsIUrlListener* aListener,
+ nsIMsgWindow* aMsgWindow) {
+ return ExpungeAndCompact(aListener, aMsgWindow);
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::NotifyCompactCompleted() { return NS_OK; }
+
+NS_IMETHODIMP nsImapMailFolder::MarkPendingRemoval(nsIMsgDBHdr* aHdr,
+ bool aMark) {
+ NS_ENSURE_ARG_POINTER(aHdr);
+ uint32_t offlineMessageSize;
+ aHdr->GetOfflineMessageSize(&offlineMessageSize);
+ aHdr->SetStringProperty("pendingRemoval", aMark ? "1"_ns : ""_ns);
+ if (!aMark) return NS_OK;
+ nsresult rv = GetDatabase();
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIDBFolderInfo> dbFolderInfo;
+ rv = mDatabase->GetDBFolderInfo(getter_AddRefs(dbFolderInfo));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return dbFolderInfo->ChangeExpungedBytes(offlineMessageSize);
+}
+
+NS_IMETHODIMP nsImapMailFolder::Expunge(nsIUrlListener* aListener,
+ nsIMsgWindow* aMsgWindow) {
+ nsresult rv;
+ nsCOMPtr<nsIImapService> imapService =
+ do_GetService("@mozilla.org/messenger/imapservice;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return imapService->Expunge(this, aListener, aMsgWindow);
+}
+
+NS_IMETHODIMP nsImapMailFolder::CompactAll(nsIUrlListener* aListener,
+ nsIMsgWindow* aMsgWindow) {
+ nsresult rv;
+
+ nsCOMPtr<nsIMsgFolderCompactor> folderCompactor =
+ do_CreateInstance("@mozilla.org/messenger/foldercompactor;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgFolder> rootFolder;
+ rv = GetRootFolder(getter_AddRefs(rootFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgWindow> msgWindow = aMsgWindow;
+
+ // Set up a callable which will start the compaction phase.
+ auto doCompact = [folderCompactor, rootFolder,
+ listener = nsCOMPtr<nsIUrlListener>(aListener),
+ msgWindow]() {
+ // Collect all the compactable folders.
+ nsTArray<RefPtr<nsIMsgFolder>> foldersToCompact;
+ nsTArray<RefPtr<nsIMsgFolder>> allDescendants;
+ rootFolder->GetDescendants(allDescendants);
+ for (auto folder : allDescendants) {
+ uint32_t flags;
+ folder->GetFlags(&flags);
+ if (flags &
+ (nsMsgFolderFlags::Virtual | nsMsgFolderFlags::ImapNoselect)) {
+ continue;
+ }
+ // Folder can be compacted?
+ nsCOMPtr<nsIMsgPluggableStore> msgStore;
+ folder->GetMsgStore(getter_AddRefs(msgStore));
+ if (!msgStore) {
+ continue;
+ }
+ bool storeSupportsCompaction;
+ msgStore->GetSupportsCompaction(&storeSupportsCompaction);
+ if (storeSupportsCompaction) {
+ foldersToCompact.AppendElement(folder);
+ }
+ }
+ nsresult rv =
+ folderCompactor->CompactFolders(foldersToCompact, listener, msgWindow);
+ if (NS_FAILED(rv) && listener) {
+ // Make sure the listener hears about the failure.
+ listener->OnStopRunningUrl(nullptr, rv);
+ }
+ };
+
+ // Collect all the expungeable folders.
+ nsTArray<RefPtr<nsIMsgImapMailFolder>> foldersToExpunge;
+ nsTArray<RefPtr<nsIMsgFolder>> allDescendants;
+ rootFolder->GetDescendants(allDescendants);
+ for (auto folder : allDescendants) {
+ nsCOMPtr<nsIMsgImapMailFolder> imapFolder(do_QueryInterface(folder));
+ if (!imapFolder) {
+ continue;
+ }
+ uint32_t folderFlags;
+ folder->GetFlags(&folderFlags);
+ if (!(folderFlags &
+ (nsMsgFolderFlags::Virtual | nsMsgFolderFlags::ImapNoselect))) {
+ foldersToExpunge.AppendElement(imapFolder);
+ }
+ }
+
+ if (!WeAreOffline() && !foldersToExpunge.IsEmpty()) {
+ // Kick off expunge on all the folders (the IMAP protocol will handle
+ // queuing them up as needed).
+
+ // A listener to track the completed expunges.
+ RefPtr<UrlListener> l = new UrlListener();
+ l->mStopFn = [expungeCount = foldersToExpunge.Length(), doCompact](
+ nsIURI* url, nsresult status) mutable -> nsresult {
+ // NOTE: we're ignoring expunge result code - nothing much we can do
+ // here to recover, so just plough on.
+ --expungeCount;
+ if (expungeCount == 0) {
+ // All the expunges are done so start compacting.
+ doCompact();
+ }
+ return NS_OK;
+ };
+ // Go!
+ for (auto& imapFolder : foldersToExpunge) {
+ rv = imapFolder->Expunge(l, aMsgWindow);
+ if (NS_FAILED(rv)) {
+ // Make sure expungeCount is kept in sync!
+ l->OnStopRunningUrl(nullptr, rv);
+ }
+ }
+ } else {
+ // No expunging. Start the compaction immediately.
+ doCompact();
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailFolder::UpdateStatus(nsIUrlListener* aListener,
+ nsIMsgWindow* aMsgWindow) {
+ nsresult rv;
+ nsCOMPtr<nsIImapService> imapService =
+ do_GetService("@mozilla.org/messenger/imapservice;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIURI> uri;
+ rv = imapService->UpdateFolderStatus(this, aListener, getter_AddRefs(uri));
+ if (uri && !aMsgWindow) {
+ nsCOMPtr<nsIMsgMailNewsUrl> mailNewsUrl = do_QueryInterface(uri, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // if no msg window, we won't put up error messages (this is almost
+ // certainly a biff-inspired status)
+ mailNewsUrl->SetSuppressErrorMsgs(true);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapMailFolder::EmptyTrash(nsIUrlListener* aListener) {
+ nsCOMPtr<nsIMsgFolder> trashFolder;
+ nsresult rv = GetTrashFolder(getter_AddRefs(trashFolder));
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIMsgAccountManager> accountManager =
+ do_GetService("@mozilla.org/messenger/account-manager;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // if we are emptying trash on exit and we are an aol server then don't
+ // perform this operation because it's causing a hang that we haven't been
+ // able to figure out yet this is an rtm fix and we'll look for the right
+ // solution post rtm.
+ bool empytingOnExit = false;
+ accountManager->GetEmptyTrashInProgress(&empytingOnExit);
+ if (empytingOnExit) {
+ nsCOMPtr<nsIImapIncomingServer> imapServer;
+ rv = GetImapIncomingServer(getter_AddRefs(imapServer));
+ if (imapServer) {
+ bool isAOLServer = false;
+ imapServer->GetIsAOLServer(&isAOLServer);
+ if (isAOLServer)
+ return NS_ERROR_FAILURE; // we will not be performing an empty
+ // trash....
+ } // if we fetched an imap server
+ } // if emptying trash on exit which is done through the account manager.
+
+ if (WeAreOffline()) {
+ nsCOMPtr<nsIMsgDatabase> trashDB;
+ rv = trashFolder->GetMsgDatabase(getter_AddRefs(trashDB));
+ if (trashDB) {
+ nsCOMPtr<nsIMsgOfflineOpsDatabase> opsDb =
+ do_QueryInterface(trashDB, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsMsgKey fakeKey;
+ opsDb->GetNextFakeOfflineMsgKey(&fakeKey);
+
+ nsCOMPtr<nsIMsgOfflineImapOperation> op;
+ rv = opsDb->GetOfflineOpForKey(fakeKey, true, getter_AddRefs(op));
+ trashFolder->SetFlag(nsMsgFolderFlags::OfflineEvents);
+ op->SetOperation(nsIMsgOfflineImapOperation::kDeleteAllMsgs);
+ }
+ return rv;
+ }
+
+ nsCOMPtr<nsIImapService> imapService =
+ do_GetService("@mozilla.org/messenger/imapservice;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (aListener)
+ rv = imapService->DeleteAllMessages(trashFolder, aListener);
+ else {
+ nsCOMPtr<nsIUrlListener> urlListener = do_QueryInterface(trashFolder);
+ rv = imapService->DeleteAllMessages(trashFolder, urlListener);
+ }
+ // Return an error if this failed. We want the empty trash on exit code
+ // to know if this fails so that it doesn't block waiting for empty trash to
+ // finish.
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Delete any subfolders under Trash.
+ nsTArray<RefPtr<nsIMsgFolder>> subFolders;
+ rv = trashFolder->GetSubFolders(subFolders);
+ NS_ENSURE_SUCCESS(rv, rv);
+ while (!subFolders.IsEmpty()) {
+ RefPtr<nsIMsgFolder> f = subFolders.PopLastElement();
+ rv = trashFolder->PropagateDelete(f, true);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ nsCOMPtr<nsIDBFolderInfo> transferInfo;
+ rv = trashFolder->GetDBTransferInfo(getter_AddRefs(transferInfo));
+ NS_ENSURE_SUCCESS(rv, rv);
+ // Bulk-delete all the messages by deleting the msf file and storage.
+ // This is a little kludgy.
+ rv = trashFolder->DeleteStorage();
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (transferInfo) trashFolder->SetDBTransferInfo(transferInfo);
+ trashFolder->SetSizeOnDisk(0);
+
+ // The trash folder has effectively been deleted.
+ nsCOMPtr<nsIMsgFolderNotificationService> notifier(
+ do_GetService("@mozilla.org/messenger/msgnotificationservice;1"));
+ if (notifier) notifier->NotifyFolderDeleted(trashFolder);
+
+ return NS_OK;
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapMailFolder::DeleteStorage() {
+ nsresult rv = nsMsgDBFolder::DeleteStorage();
+
+ // Should notify nsIMsgFolderListeners about the folder getting deleted?
+ return rv;
+}
+
+NS_IMETHODIMP nsImapMailFolder::Rename(const nsAString& newName,
+ nsIMsgWindow* msgWindow) {
+ if (mFlags & nsMsgFolderFlags::Virtual)
+ return nsMsgDBFolder::Rename(newName, msgWindow);
+ nsresult rv;
+ nsAutoString newNameStr(newName);
+ if (newNameStr.FindChar(m_hierarchyDelimiter, 0) != kNotFound) {
+ nsCOMPtr<nsIDocShell> docShell;
+ if (msgWindow) msgWindow->GetRootDocShell(getter_AddRefs(docShell));
+ if (docShell) {
+ nsCOMPtr<nsIStringBundle> bundle;
+ rv = IMAPGetStringBundle(getter_AddRefs(bundle));
+ if (NS_SUCCEEDED(rv) && bundle) {
+ AutoTArray<nsString, 1> formatStrings;
+ formatStrings.AppendElement()->Append(m_hierarchyDelimiter);
+ nsString alertString;
+ rv = bundle->FormatStringFromName("imapSpecialChar2", formatStrings,
+ alertString);
+ nsCOMPtr<nsIPrompt> dialog(do_GetInterface(docShell));
+ // setting up the dialog title
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsString dialogTitle;
+ nsString accountName;
+ rv = server->GetPrettyName(accountName);
+ NS_ENSURE_SUCCESS(rv, rv);
+ AutoTArray<nsString, 1> titleParams = {accountName};
+ rv = bundle->FormatStringFromName("imapAlertDialogTitle", titleParams,
+ dialogTitle);
+
+ if (dialog && !alertString.IsEmpty())
+ dialog->Alert(dialogTitle.get(), alertString.get());
+ }
+ }
+ return NS_ERROR_FAILURE;
+ }
+ nsCOMPtr<nsIImapIncomingServer> incomingImapServer;
+ GetImapIncomingServer(getter_AddRefs(incomingImapServer));
+ if (incomingImapServer) RecursiveCloseActiveConnections(incomingImapServer);
+
+ nsCOMPtr<nsIImapService> imapService =
+ do_GetService("@mozilla.org/messenger/imapservice;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return imapService->RenameLeaf(this, newName, this, msgWindow);
+}
+
+NS_IMETHODIMP nsImapMailFolder::RecursiveCloseActiveConnections(
+ nsIImapIncomingServer* incomingImapServer) {
+ NS_ENSURE_ARG(incomingImapServer);
+
+ nsCOMPtr<nsIMsgImapMailFolder> folder;
+ int32_t count = mSubFolders.Count();
+ for (int32_t i = 0; i < count; i++) {
+ folder = do_QueryInterface(mSubFolders[i]);
+ if (folder) folder->RecursiveCloseActiveConnections(incomingImapServer);
+
+ incomingImapServer->CloseConnectionForFolder(mSubFolders[i]);
+ }
+ return NS_OK;
+}
+
+// this is called *after* we've done the rename on the server.
+NS_IMETHODIMP nsImapMailFolder::PrepareToRename() {
+ nsCOMPtr<nsIMsgImapMailFolder> folder;
+ int32_t count = mSubFolders.Count();
+ for (int32_t i = 0; i < count; i++) {
+ folder = do_QueryInterface(mSubFolders[i]);
+ if (folder) folder->PrepareToRename();
+ }
+
+ SetOnlineName(EmptyCString());
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailFolder::RenameLocal(const nsACString& newName,
+ nsIMsgFolder* parent) {
+ nsAutoCString leafname(newName);
+ nsAutoCString parentName;
+ // newName always in the canonical form "greatparent/parentname/leafname"
+ int32_t leafpos = leafname.RFindChar('/');
+ if (leafpos > 0) leafname.Cut(0, leafpos + 1);
+ m_msgParser = nullptr;
+ PrepareToRename();
+ CloseAndBackupFolderDB(leafname);
+
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIFile> oldPathFile;
+ rv = GetFilePath(getter_AddRefs(oldPathFile));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIFile> parentPathFile;
+ rv = parent->GetFilePath(getter_AddRefs(parentPathFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool isDirectory = false;
+ parentPathFile->IsDirectory(&isDirectory);
+ if (!isDirectory) AddDirectorySeparator(parentPathFile);
+
+ nsCOMPtr<nsIFile> dirFile;
+
+ int32_t count = mSubFolders.Count();
+ if (count > 0) {
+ rv = CreateDirectoryForFolder(getter_AddRefs(dirFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ nsCOMPtr<nsIFile> oldSummaryFile;
+ rv = GetSummaryFileLocation(oldPathFile, getter_AddRefs(oldSummaryFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString newNameStr;
+ oldSummaryFile->Remove(false);
+ if (count > 0) {
+ newNameStr = leafname;
+ NS_MsgHashIfNecessary(newNameStr);
+ newNameStr.AppendLiteral(FOLDER_SUFFIX8);
+ nsAutoCString leafName;
+ dirFile->GetNativeLeafName(leafName);
+ if (!leafName.Equals(newNameStr))
+ return dirFile->MoveToNative(
+ nullptr,
+ newNameStr); // in case of rename operation leaf names will differ
+
+ parentPathFile->AppendNative(
+ newNameStr); // only for move we need to progress further in case the
+ // parent differs
+ bool isDirectory = false;
+ parentPathFile->IsDirectory(&isDirectory);
+ if (!isDirectory) {
+ rv = parentPathFile->Create(nsIFile::DIRECTORY_TYPE, 0700);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ NS_ERROR("Directory already exists.");
+ }
+ rv = RecursiveCopy(dirFile, parentPathFile);
+ NS_ENSURE_SUCCESS(rv, rv);
+ dirFile->Remove(true); // moving folders
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapMailFolder::GetPrettyName(nsAString& prettyName) {
+ return GetName(prettyName);
+}
+
+NS_IMETHODIMP nsImapMailFolder::UpdateSummaryTotals(bool force) {
+ // bug 72871 inserted the mIsServer check for IMAP
+ return mIsServer ? NS_OK : nsMsgDBFolder::UpdateSummaryTotals(force);
+}
+
+NS_IMETHODIMP nsImapMailFolder::GetDeletable(bool* deletable) {
+ NS_ENSURE_ARG_POINTER(deletable);
+
+ bool isServer;
+ GetIsServer(&isServer);
+
+ *deletable = !(isServer || (mFlags & nsMsgFolderFlags::SpecialUse));
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailFolder::GetSizeOnDisk(int64_t* size) {
+ NS_ENSURE_ARG_POINTER(size);
+
+ bool isServer = false;
+ nsresult rv = GetIsServer(&isServer);
+ // If this is the rootFolder, return 0 as a safe value.
+ if (NS_FAILED(rv) || isServer) mFolderSize = 0;
+
+ *size = mFolderSize;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::GetCanCreateSubfolders(bool* aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = !(mFlags &
+ (nsMsgFolderFlags::ImapNoinferiors | nsMsgFolderFlags::Virtual));
+
+ bool isServer = false;
+ GetIsServer(&isServer);
+ if (!isServer) {
+ nsCOMPtr<nsIImapIncomingServer> imapServer;
+ nsresult rv = GetImapIncomingServer(getter_AddRefs(imapServer));
+ bool dualUseFolders = true;
+ if (NS_SUCCEEDED(rv) && imapServer)
+ imapServer->GetDualUseFolders(&dualUseFolders);
+ if (!dualUseFolders && *aResult)
+ *aResult = (mFlags & nsMsgFolderFlags::ImapNoselect);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::GetCanSubscribe(bool* aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = false;
+
+ bool isImapServer = false;
+ nsresult rv = GetIsServer(&isImapServer);
+ if (NS_FAILED(rv)) return rv;
+ // you can only subscribe to imap servers, not imap folders
+ *aResult = isImapServer;
+ return NS_OK;
+}
+
+nsresult nsImapMailFolder::GetServerKey(nsACString& serverKey) {
+ // look for matching imap folders, then pop folders
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ nsresult rv = GetServer(getter_AddRefs(server));
+ if (NS_SUCCEEDED(rv)) rv = server->GetKey(serverKey);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::GetImapIncomingServer(
+ nsIImapIncomingServer** aImapIncomingServer) {
+ NS_ENSURE_ARG(aImapIncomingServer);
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ if (NS_SUCCEEDED(GetServer(getter_AddRefs(server))) && server) {
+ nsCOMPtr<nsIImapIncomingServer> incomingServer = do_QueryInterface(server);
+ NS_ENSURE_TRUE(incomingServer, NS_ERROR_NO_INTERFACE);
+ incomingServer.forget(aImapIncomingServer);
+ return NS_OK;
+ }
+ return NS_ERROR_NULL_POINTER;
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::AddMessageDispositionState(
+ nsIMsgDBHdr* aMessage, nsMsgDispositionState aDispositionFlag) {
+ nsMsgDBFolder::AddMessageDispositionState(aMessage, aDispositionFlag);
+
+ // set the mark message answered flag on the server for this message...
+ if (aMessage) {
+ nsMsgKey msgKey;
+ aMessage->GetMessageKey(&msgKey);
+
+ if (aDispositionFlag == nsIMsgFolder::nsMsgDispositionState_Replied)
+ StoreImapFlags(kImapMsgAnsweredFlag, true, {msgKey}, nullptr);
+ else if (aDispositionFlag == nsIMsgFolder::nsMsgDispositionState_Forwarded)
+ StoreImapFlags(kImapMsgForwardedFlag, true, {msgKey}, nullptr);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::MarkMessagesRead(
+ const nsTArray<RefPtr<nsIMsgDBHdr>>& messages, bool markRead) {
+ // tell the folder to do it, which will mark them read in the db.
+ nsresult rv = nsMsgDBFolder::MarkMessagesRead(messages, markRead);
+ if (NS_SUCCEEDED(rv)) {
+ nsAutoCString messageIds;
+ nsTArray<nsMsgKey> keysToMarkRead;
+ rv = BuildIdsAndKeyArray(messages, messageIds, keysToMarkRead);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ StoreImapFlags(kImapMsgSeenFlag, markRead, keysToMarkRead, nullptr);
+ rv = GetDatabase();
+ if (NS_SUCCEEDED(rv)) mDatabase->Commit(nsMsgDBCommitType::kLargeCommit);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::MarkAllMessagesRead(nsIMsgWindow* aMsgWindow) {
+ nsresult rv = GetDatabase();
+ if (NS_SUCCEEDED(rv)) {
+ nsTArray<nsMsgKey> thoseMarked;
+ EnableNotifications(allMessageCountNotifications, false);
+ rv = mDatabase->MarkAllRead(thoseMarked);
+ EnableNotifications(allMessageCountNotifications, true);
+ if (NS_SUCCEEDED(rv) && thoseMarked.Length() > 0) {
+ rv = StoreImapFlags(kImapMsgSeenFlag, true, thoseMarked, nullptr);
+ mDatabase->Commit(nsMsgDBCommitType::kLargeCommit);
+
+ // Setup a undo-state
+ if (aMsgWindow)
+ rv = AddMarkAllReadUndoAction(aMsgWindow, thoseMarked.Elements(),
+ thoseMarked.Length());
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapMailFolder::MarkThreadRead(nsIMsgThread* thread) {
+ nsresult rv = GetDatabase();
+ if (NS_SUCCEEDED(rv)) {
+ nsTArray<nsMsgKey> keys;
+ rv = mDatabase->MarkThreadRead(thread, nullptr, keys);
+ if (NS_SUCCEEDED(rv) && keys.Length() > 0) {
+ rv = StoreImapFlags(kImapMsgSeenFlag, true, keys, nullptr);
+ mDatabase->Commit(nsMsgDBCommitType::kLargeCommit);
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapMailFolder::ReadFromFolderCacheElem(
+ nsIMsgFolderCacheElement* element) {
+ nsresult rv = nsMsgDBFolder::ReadFromFolderCacheElem(element);
+ int32_t hierarchyDelimiter = kOnlineHierarchySeparatorUnknown;
+ nsCString onlineName;
+
+ element->GetCachedUInt32("boxFlags", (uint32_t*)&m_boxFlags);
+ if (NS_SUCCEEDED(element->GetCachedInt32("hierDelim", &hierarchyDelimiter)) &&
+ hierarchyDelimiter != kOnlineHierarchySeparatorUnknown)
+ m_hierarchyDelimiter = (char)hierarchyDelimiter;
+ rv = element->GetCachedString("onlineName", onlineName);
+ if (NS_SUCCEEDED(rv) && !onlineName.IsEmpty())
+ m_onlineFolderName.Assign(onlineName);
+
+ m_aclFlags = kAclInvalid; // init to invalid value.
+ element->GetCachedUInt32("aclFlags", &m_aclFlags);
+ element->GetCachedInt32("serverTotal", &m_numServerTotalMessages);
+ element->GetCachedInt32("serverUnseen", &m_numServerUnseenMessages);
+ element->GetCachedInt32("serverRecent", &m_numServerRecentMessages);
+ element->GetCachedInt32("nextUID", &m_nextUID);
+ int32_t lastSyncTimeInSec;
+ if (NS_FAILED(element->GetCachedInt32("lastSyncTimeInSec",
+ (int32_t*)&lastSyncTimeInSec)))
+ lastSyncTimeInSec = 0U;
+
+ // make sure that auto-sync state object is created
+ InitAutoSyncState();
+ m_autoSyncStateObj->SetLastSyncTimeInSec(lastSyncTimeInSec);
+
+ return rv;
+}
+
+NS_IMETHODIMP nsImapMailFolder::WriteToFolderCacheElem(
+ nsIMsgFolderCacheElement* element) {
+ nsresult rv = nsMsgDBFolder::WriteToFolderCacheElem(element);
+ element->SetCachedUInt32("boxFlags", (uint32_t)m_boxFlags);
+ element->SetCachedInt32("hierDelim", (int32_t)m_hierarchyDelimiter);
+ element->SetCachedString("onlineName", m_onlineFolderName);
+ element->SetCachedUInt32("aclFlags", m_aclFlags);
+ element->SetCachedInt32("serverTotal", m_numServerTotalMessages);
+ element->SetCachedInt32("serverUnseen", m_numServerUnseenMessages);
+ element->SetCachedInt32("serverRecent", m_numServerRecentMessages);
+ if (m_nextUID != (int32_t)nsMsgKey_None)
+ element->SetCachedInt32("nextUID", m_nextUID);
+
+ // store folder's last sync time
+ if (m_autoSyncStateObj) {
+ PRTime lastSyncTime;
+ m_autoSyncStateObj->GetLastSyncTime(&lastSyncTime);
+ // store in sec
+ element->SetCachedInt32("lastSyncTimeInSec",
+ (int32_t)(lastSyncTime / PR_USEC_PER_SEC));
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::MarkMessagesFlagged(
+ const nsTArray<RefPtr<nsIMsgDBHdr>>& messages, bool markFlagged) {
+ nsresult rv;
+ // tell the folder to do it, which will mark them read in the db.
+ rv = nsMsgDBFolder::MarkMessagesFlagged(messages, markFlagged);
+ if (NS_SUCCEEDED(rv)) {
+ nsAutoCString messageIds;
+ nsTArray<nsMsgKey> keysToMarkFlagged;
+ rv = BuildIdsAndKeyArray(messages, messageIds, keysToMarkFlagged);
+ if (NS_FAILED(rv)) return rv;
+ rv = StoreImapFlags(kImapMsgFlaggedFlag, markFlagged, keysToMarkFlagged,
+ nullptr);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = GetDatabase();
+ NS_ENSURE_SUCCESS(rv, rv);
+ mDatabase->Commit(nsMsgDBCommitType::kLargeCommit);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapMailFolder::SetOnlineName(
+ const nsACString& aOnlineFolderName) {
+ nsresult rv;
+ nsCOMPtr<nsIMsgDatabase> db;
+ nsCOMPtr<nsIDBFolderInfo> folderInfo;
+ rv = GetDBFolderInfoAndDB(getter_AddRefs(folderInfo), getter_AddRefs(db));
+ // do this after GetDBFolderInfoAndDB, because it crunches m_onlineFolderName
+ // (not sure why)
+ m_onlineFolderName = aOnlineFolderName;
+ if (NS_SUCCEEDED(rv) && folderInfo) {
+ nsAutoString onlineName;
+ CopyUTF8toUTF16(aOnlineFolderName, onlineName);
+ rv = folderInfo->SetProperty("onlineName", onlineName);
+ rv = folderInfo->SetMailboxName(onlineName);
+ // so, when are we going to commit this? Definitely not every time!
+ // We could check if the online name has changed.
+ db->Commit(nsMsgDBCommitType::kLargeCommit);
+ }
+ folderInfo = nullptr;
+ return rv;
+}
+
+NS_IMETHODIMP nsImapMailFolder::GetOnlineName(nsACString& aOnlineFolderName) {
+ ReadDBFolderInfo(false); // update cache first.
+ aOnlineFolderName = m_onlineFolderName;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::GetDBFolderInfoAndDB(nsIDBFolderInfo** folderInfo,
+ nsIMsgDatabase** db) {
+ NS_ENSURE_ARG_POINTER(folderInfo);
+ NS_ENSURE_ARG_POINTER(db);
+
+ nsresult rv = GetDatabase();
+ if (NS_FAILED(rv)) return rv;
+
+ NS_ADDREF(*db = mDatabase);
+
+ rv = (*db)->GetDBFolderInfo(folderInfo);
+ if (NS_FAILED(rv))
+ return rv; // GetDBFolderInfo can't return NS_OK if !folderInfo
+
+ nsCString onlineName;
+ rv = (*folderInfo)->GetCharProperty("onlineName", onlineName);
+ if (NS_FAILED(rv)) return rv;
+
+ if (!onlineName.IsEmpty())
+ m_onlineFolderName.Assign(onlineName);
+ else {
+ nsAutoString autoOnlineName;
+ (*folderInfo)->GetMailboxName(autoOnlineName);
+ if (autoOnlineName.IsEmpty()) {
+ nsCString uri;
+ rv = GetURI(uri);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCString hostname;
+ rv = GetHostname(hostname);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCString onlineCName;
+ rv = nsImapURI2FullName(kImapRootURI, hostname.get(), uri.get(),
+ getter_Copies(onlineCName));
+ // Note: check for unknown separator '^' only became needed
+ // with UTF8=ACCEPT modification and haven't found why. Online name
+ // contained the '^' delimiter and gmail said "NO" when folder under
+ // [Gmail] is created and selected.
+ if ((m_hierarchyDelimiter != '/') &&
+ (m_hierarchyDelimiter != kOnlineHierarchySeparatorUnknown))
+ onlineCName.ReplaceChar('/', m_hierarchyDelimiter);
+ // XXX: What if online name contains slashes? Breaks?
+ m_onlineFolderName.Assign(onlineCName);
+ CopyUTF8toUTF16(onlineCName, autoOnlineName);
+ }
+ (*folderInfo)->SetProperty("onlineName", autoOnlineName);
+ }
+ return rv;
+}
+
+/* static */
+nsresult nsImapMailFolder::BuildIdsAndKeyArray(
+ const nsTArray<RefPtr<nsIMsgDBHdr>>& messages, nsCString& msgIds,
+ nsTArray<nsMsgKey>& keyArray) {
+ keyArray.Clear();
+ keyArray.SetCapacity(messages.Length());
+ // build up message keys.
+ for (auto msgDBHdr : messages) {
+ nsMsgKey key;
+ nsresult rv = msgDBHdr->GetMessageKey(&key);
+ if (NS_SUCCEEDED(rv)) keyArray.AppendElement(key);
+ }
+ return AllocateUidStringFromKeys(keyArray, msgIds);
+}
+
+/* static */
+nsresult nsImapMailFolder::AllocateUidStringFromKeys(
+ const nsTArray<nsMsgKey>& keys, nsCString& msgIds) {
+ if (keys.IsEmpty()) return NS_ERROR_INVALID_ARG;
+ nsresult rv = NS_OK;
+ uint32_t startSequence;
+ startSequence = keys[0];
+ uint32_t curSequenceEnd = startSequence;
+ uint32_t total = keys.Length();
+ // sort keys and then generate ranges instead of singletons!
+ nsTArray<nsMsgKey> sorted(keys.Clone());
+ sorted.Sort();
+ for (uint32_t keyIndex = 0; keyIndex < total; keyIndex++) {
+ uint32_t curKey = sorted[keyIndex];
+ uint32_t nextKey =
+ (keyIndex + 1 < total) ? sorted[keyIndex + 1] : 0xFFFFFFFF;
+ bool lastKey = (nextKey == 0xFFFFFFFF);
+
+ if (lastKey) curSequenceEnd = curKey;
+ if (nextKey == (uint32_t)curSequenceEnd + 1 && !lastKey) {
+ curSequenceEnd = nextKey;
+ continue;
+ }
+ if (curSequenceEnd > startSequence) {
+ AppendUid(msgIds, startSequence);
+ msgIds += ':';
+ AppendUid(msgIds, curSequenceEnd);
+ if (!lastKey) msgIds += ',';
+ startSequence = nextKey;
+ curSequenceEnd = startSequence;
+ } else {
+ startSequence = nextKey;
+ curSequenceEnd = startSequence;
+ AppendUid(msgIds, sorted[keyIndex]);
+ if (!lastKey) msgIds += ',';
+ }
+ }
+ return rv;
+}
+
+nsresult nsImapMailFolder::MarkMessagesImapDeleted(nsTArray<nsMsgKey>* keyArray,
+ bool deleted,
+ nsIMsgDatabase* db) {
+ for (uint32_t kindex = 0; kindex < keyArray->Length(); kindex++) {
+ nsMsgKey key = keyArray->ElementAt(kindex);
+ db->MarkImapDeleted(key, deleted, nullptr);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailFolder::DeleteMessages(
+ nsTArray<RefPtr<nsIMsgDBHdr>> const& msgHeaders, nsIMsgWindow* msgWindow,
+ bool deleteStorage, bool isMove, nsIMsgCopyServiceListener* listener,
+ bool allowUndo) {
+ // *** jt - assuming delete is move to the trash folder for now
+ nsAutoCString uri;
+ bool deleteImmediatelyNoTrash = false;
+ nsAutoCString messageIds;
+ nsTArray<nsMsgKey> srcKeyArray;
+ bool deleteMsgs = true; // used for toggling delete status - default is true
+ nsMsgImapDeleteModel deleteModel = nsMsgImapDeleteModels::MoveToTrash;
+ imapMessageFlagsType messageFlags = kImapMsgDeletedFlag;
+
+ nsCOMPtr<nsIImapIncomingServer> imapServer;
+ nsresult rv = GetFlag(nsMsgFolderFlags::Trash, &deleteImmediatelyNoTrash);
+ rv = GetImapIncomingServer(getter_AddRefs(imapServer));
+
+ if (NS_SUCCEEDED(rv) && imapServer) {
+ imapServer->GetDeleteModel(&deleteModel);
+ if (deleteModel != nsMsgImapDeleteModels::MoveToTrash || deleteStorage)
+ deleteImmediatelyNoTrash = true;
+ // if we're deleting a message, we should pseudo-interrupt the msg
+ // load of the current message.
+ bool interrupted = false;
+ imapServer->PseudoInterruptMsgLoad(this, msgWindow, &interrupted);
+ }
+
+ rv = BuildIdsAndKeyArray(msgHeaders, messageIds, srcKeyArray);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIMsgFolder> rootFolder;
+ nsCOMPtr<nsIMsgFolder> trashFolder;
+
+ if (!deleteImmediatelyNoTrash) {
+ rv = GetRootFolder(getter_AddRefs(rootFolder));
+ if (NS_SUCCEEDED(rv) && rootFolder) {
+ rootFolder->GetFolderWithFlags(nsMsgFolderFlags::Trash,
+ getter_AddRefs(trashFolder));
+ NS_ASSERTION(trashFolder, "couldn't find trash");
+ // if we can't find the trash, we'll just have to do an imap delete and
+ // pretend this is the trash
+ if (!trashFolder) deleteImmediatelyNoTrash = true;
+ }
+ }
+
+ if ((NS_SUCCEEDED(rv) && deleteImmediatelyNoTrash) ||
+ deleteModel == nsMsgImapDeleteModels::IMAPDelete) {
+ if (allowUndo) {
+ // need to take care of these two delete models
+ RefPtr<nsImapMoveCopyMsgTxn> undoMsgTxn = new nsImapMoveCopyMsgTxn;
+ if (!undoMsgTxn ||
+ NS_FAILED(undoMsgTxn->Init(this, &srcKeyArray, messageIds.get(),
+ nullptr, true, isMove)))
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ undoMsgTxn->SetTransactionType(nsIMessenger::eDeleteMsg);
+ // we're adding this undo action before the delete is successful. This is
+ // evil, but 4.5 did it as well.
+ nsCOMPtr<nsITransactionManager> txnMgr;
+ if (msgWindow) msgWindow->GetTransactionManager(getter_AddRefs(txnMgr));
+ if (txnMgr) txnMgr->DoTransaction(undoMsgTxn);
+ }
+
+ if (deleteModel == nsMsgImapDeleteModels::IMAPDelete && !deleteStorage) {
+ deleteMsgs = false;
+ for (nsIMsgDBHdr* msgHdr : msgHeaders) {
+ if (!msgHdr) {
+ continue;
+ }
+ uint32_t flags;
+ msgHdr->GetFlags(&flags);
+ if (!(flags & nsMsgMessageFlags::IMAPDeleted)) {
+ deleteMsgs = true;
+ break;
+ }
+ }
+ }
+ // if copy service listener is also a url listener, pass that
+ // url listener into StoreImapFlags.
+ nsCOMPtr<nsIUrlListener> urlListener = do_QueryInterface(listener);
+ if (deleteMsgs) messageFlags |= kImapMsgSeenFlag;
+ rv = StoreImapFlags(messageFlags, deleteMsgs, srcKeyArray, urlListener);
+
+ if (NS_SUCCEEDED(rv)) {
+ if (mDatabase) {
+ nsCOMPtr<nsIMsgDatabase> database(mDatabase);
+ if (deleteModel == nsMsgImapDeleteModels::IMAPDelete)
+ MarkMessagesImapDeleted(&srcKeyArray, deleteMsgs, database);
+ else {
+ EnableNotifications(allMessageCountNotifications,
+ false); //"remove it immediately" model
+ // Notify if this is an actual delete.
+ if (!isMove) {
+ nsCOMPtr<nsIMsgFolderNotificationService> notifier(do_GetService(
+ "@mozilla.org/messenger/msgnotificationservice;1"));
+ if (notifier) notifier->NotifyMsgsDeleted(msgHeaders);
+ }
+ DeleteStoreMessages(msgHeaders);
+ database->DeleteMessages(srcKeyArray, nullptr);
+ EnableNotifications(allMessageCountNotifications, true);
+ }
+ if (listener) {
+ listener->OnStartCopy();
+ listener->OnStopCopy(NS_OK);
+ }
+ NotifyFolderEvent(kDeleteOrMoveMsgCompleted);
+ }
+ }
+ return rv;
+ }
+
+ // have to move the messages to the trash
+ if (trashFolder) {
+ nsCOMPtr<nsIMsgFolder> srcFolder;
+ nsCOMPtr<nsISupports> srcSupport;
+
+ rv = QueryInterface(NS_GET_IID(nsIMsgFolder), getter_AddRefs(srcFolder));
+ nsCOMPtr<nsIMsgCopyService> copyService =
+ do_GetService("@mozilla.org/messenger/messagecopyservice;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = copyService->CopyMessages(srcFolder, msgHeaders, trashFolder, true,
+ listener, msgWindow, allowUndo);
+ }
+
+ return rv;
+}
+
+// check if folder is the trash, or a descendent of the trash
+// so we can tell if the folders we're deleting from it should
+// be *really* deleted.
+bool nsImapMailFolder::TrashOrDescendentOfTrash(nsIMsgFolder* folder) {
+ NS_ENSURE_TRUE(folder, false);
+ nsCOMPtr<nsIMsgFolder> parent;
+ nsCOMPtr<nsIMsgFolder> curFolder = folder;
+ nsresult rv;
+ uint32_t flags = 0;
+ do {
+ rv = curFolder->GetFlags(&flags);
+ if (NS_FAILED(rv)) return false;
+ if (flags & nsMsgFolderFlags::Trash) return true;
+ curFolder->GetParent(getter_AddRefs(parent));
+ if (!parent) return false;
+ curFolder = parent;
+ } while (NS_SUCCEEDED(rv) && curFolder);
+ return false;
+}
+NS_IMETHODIMP
+nsImapMailFolder::DeleteSelf(nsIMsgWindow* msgWindow) {
+ nsCOMPtr<nsIMsgFolder> trashFolder;
+ nsresult rv;
+ uint32_t folderFlags;
+
+ // No IMAP shenanigans required for virtual folders.
+ GetFlags(&folderFlags);
+ if (folderFlags & nsMsgFolderFlags::Virtual) {
+ return nsMsgDBFolder::DeleteSelf(nullptr);
+ }
+
+ // "this" is the folder we're deleting from
+ bool deleteNoTrash = TrashOrDescendentOfTrash(this) || !DeleteIsMoveToTrash();
+ bool confirmDeletion = true;
+
+ nsCOMPtr<nsIImapService> imapService =
+ do_GetService("@mozilla.org/messenger/imapservice;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!deleteNoTrash) {
+ rv = GetTrashFolder(getter_AddRefs(trashFolder));
+ // If we can't find the trash folder and we are supposed to move it to the
+ // trash return failure.
+ if (NS_FAILED(rv) || !trashFolder) return NS_ERROR_FAILURE;
+ bool canHaveSubFoldersOfTrash = true;
+ trashFolder->GetCanCreateSubfolders(&canHaveSubFoldersOfTrash);
+ if (canHaveSubFoldersOfTrash) // UW server doesn't set NOINFERIORS - check
+ // dual use pref
+ {
+ nsCOMPtr<nsIImapIncomingServer> imapServer;
+ rv = GetImapIncomingServer(getter_AddRefs(imapServer));
+ NS_ENSURE_SUCCESS(rv, rv);
+ bool serverSupportsDualUseFolders;
+ imapServer->GetDualUseFolders(&serverSupportsDualUseFolders);
+ if (!serverSupportsDualUseFolders) canHaveSubFoldersOfTrash = false;
+ }
+ if (!canHaveSubFoldersOfTrash) deleteNoTrash = true;
+ nsCOMPtr<nsIPrefBranch> prefBranch(
+ do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ prefBranch->GetBoolPref("mailnews.confirm.moveFoldersToTrash",
+ &confirmDeletion);
+ }
+
+ // If we are deleting folder immediately, ask user for confirmation.
+ bool confirmed = false;
+ if (confirmDeletion || deleteNoTrash) {
+ nsCOMPtr<nsIStringBundle> bundle;
+ rv = IMAPGetStringBundle(getter_AddRefs(bundle));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString folderName;
+ rv = GetName(folderName);
+ NS_ENSURE_SUCCESS(rv, rv);
+ AutoTArray<nsString, 1> formatStrings = {folderName};
+
+ nsAutoString deleteFolderDialogTitle;
+ rv = bundle->GetStringFromName("imapDeleteFolderDialogTitle",
+ deleteFolderDialogTitle);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString deleteFolderButtonLabel;
+ rv = bundle->GetStringFromName("imapDeleteFolderButtonLabel",
+ deleteFolderButtonLabel);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString confirmationStr;
+ rv = bundle->FormatStringFromName(
+ (deleteNoTrash) ? "imapDeleteNoTrash" : "imapMoveFolderToTrash",
+ formatStrings, confirmationStr);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!msgWindow) return NS_ERROR_NULL_POINTER;
+ nsCOMPtr<nsIDocShell> docShell;
+ msgWindow->GetRootDocShell(getter_AddRefs(docShell));
+ nsCOMPtr<nsIPrompt> dialog;
+ if (docShell) dialog = do_GetInterface(docShell);
+ if (dialog) {
+ int32_t buttonPressed = 0;
+ // Default the dialog to "cancel".
+ const uint32_t buttonFlags =
+ (nsIPrompt::BUTTON_TITLE_IS_STRING * nsIPrompt::BUTTON_POS_0) +
+ (nsIPrompt::BUTTON_TITLE_CANCEL * nsIPrompt::BUTTON_POS_1);
+
+ bool dummyValue = false;
+ rv = dialog->ConfirmEx(deleteFolderDialogTitle.get(),
+ confirmationStr.get(), buttonFlags,
+ deleteFolderButtonLabel.get(), nullptr, nullptr,
+ nullptr, &dummyValue, &buttonPressed);
+ NS_ENSURE_SUCCESS(rv, rv);
+ confirmed = !buttonPressed; // "ok" is in position 0
+ }
+ } else {
+ confirmed = true;
+ }
+
+ if (confirmed) {
+ if (deleteNoTrash) {
+ rv = imapService->DeleteFolder(this, this, msgWindow);
+ nsMsgDBFolder::DeleteSelf(msgWindow);
+ } else {
+ bool match = false;
+ rv = MatchOrChangeFilterDestination(nullptr, false, &match);
+ if (match) {
+ bool confirm = false;
+ ConfirmFolderDeletionForFilter(msgWindow, &confirm);
+ if (!confirm) return NS_OK;
+ }
+ rv = imapService->MoveFolder(this, trashFolder, this, msgWindow);
+ }
+ }
+ return rv;
+}
+
+// FIXME: helper function to know whether we should check all IMAP folders
+// for new mail; this is necessary because of a legacy hidden preference
+// mail.check_all_imap_folders_for_new (now replaced by per-server preference
+// mail.server.%serverkey%.check_all_folders_for_new), still present in some
+// profiles.
+/*static*/
+bool nsImapMailFolder::ShouldCheckAllFolders(
+ nsIImapIncomingServer* imapServer) {
+ // Check legacy global preference to see if we should check all folders for
+ // new messages, or just the inbox and marked ones.
+ bool checkAllFolders = false;
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> prefBranch =
+ do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, false);
+ // This pref might not exist, which is OK.
+ (void)prefBranch->GetBoolPref("mail.check_all_imap_folders_for_new",
+ &checkAllFolders);
+
+ if (checkAllFolders) return true;
+
+ // If the legacy preference doesn't exist or has its default value (False),
+ // the true preference is read.
+ imapServer->GetCheckAllFoldersForNew(&checkAllFolders);
+ return checkAllFolders;
+}
+
+// Called by Biff, or when user presses GetMsg button.
+NS_IMETHODIMP nsImapMailFolder::GetNewMessages(nsIMsgWindow* aWindow,
+ nsIUrlListener* aListener) {
+ nsCOMPtr<nsIMsgFolder> rootFolder;
+ nsresult rv = GetRootFolder(getter_AddRefs(rootFolder));
+ if (NS_SUCCEEDED(rv) && rootFolder) {
+ nsCOMPtr<nsIImapIncomingServer> imapServer;
+ rv = GetImapIncomingServer(getter_AddRefs(imapServer));
+ NS_ENSURE_SUCCESS(rv, rv);
+ bool performingBiff = false;
+ nsCOMPtr<nsIMsgIncomingServer> incomingServer =
+ do_QueryInterface(imapServer, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ incomingServer->GetPerformingBiff(&performingBiff);
+ m_urlListener = aListener;
+
+ // See if we should check all folders for new messages, or just the inbox
+ // and marked ones
+ bool checkAllFolders = ShouldCheckAllFolders(imapServer);
+
+ // Get new messages for inbox
+ nsCOMPtr<nsIMsgFolder> inbox;
+ rv = rootFolder->GetFolderWithFlags(nsMsgFolderFlags::Inbox,
+ getter_AddRefs(inbox));
+ if (inbox) {
+ nsCOMPtr<nsIMsgImapMailFolder> imapFolder = do_QueryInterface(inbox, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ imapFolder->SetPerformingBiff(performingBiff);
+ inbox->SetGettingNewMessages(true);
+ rv = inbox->UpdateFolder(aWindow);
+ }
+ // Get new messages for other folders if marked, or all of them if the pref
+ // is set
+ rv = imapServer->GetNewMessagesForNonInboxFolders(
+ rootFolder, aWindow, checkAllFolders, performingBiff);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapMailFolder::Shutdown(bool shutdownChildren) {
+ m_filterList = nullptr;
+ m_initialized = false;
+ // mPath is used to decide if folder pathname needs to be reconstructed in
+ // GetPath().
+ mPath = nullptr;
+ m_moveCoalescer = nullptr;
+ m_msgParser = nullptr;
+ if (m_playbackTimer) {
+ m_playbackTimer->Cancel();
+ m_playbackTimer = nullptr;
+ }
+ m_pendingOfflineMoves.Clear();
+ return nsMsgDBFolder::Shutdown(shutdownChildren);
+}
+
+nsresult nsImapMailFolder::GetBodysToDownload(
+ nsTArray<nsMsgKey>* keysOfMessagesToDownload) {
+ NS_ENSURE_ARG(keysOfMessagesToDownload);
+ NS_ENSURE_TRUE(mDatabase, NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsIMsgEnumerator> enumerator;
+ nsresult rv = mDatabase->EnumerateMessages(getter_AddRefs(enumerator));
+ if (NS_SUCCEEDED(rv) && enumerator) {
+ bool hasMore;
+ nsCOMPtr<nsIMsgDBHdr> header;
+ nsMsgKey msgKey;
+ while (NS_SUCCEEDED(rv = enumerator->HasMoreElements(&hasMore)) &&
+ hasMore) {
+ rv = enumerator->GetNext(getter_AddRefs(header));
+ NS_ENSURE_SUCCESS(rv, rv);
+ bool shouldStoreMsgOffline = false;
+ header->GetMessageKey(&msgKey);
+ // MsgFitsDownloadCriteria ignores nsMsgFolderFlags::Offline, which we
+ // want
+ if (m_downloadingFolderForOfflineUse)
+ MsgFitsDownloadCriteria(msgKey, &shouldStoreMsgOffline);
+ else
+ ShouldStoreMsgOffline(msgKey, &shouldStoreMsgOffline);
+ if (shouldStoreMsgOffline)
+ keysOfMessagesToDownload->AppendElement(msgKey);
+ }
+ if (MOZ_LOG_TEST(gAutoSyncLog, mozilla::LogLevel::Debug) && header) {
+ // Log this only if folder is not empty.
+ uint32_t msgFlags = 0;
+ header->GetFlags(&msgFlags);
+ MOZ_LOG(gAutoSyncLog, mozilla::LogLevel::Debug,
+ ("%s: num keys to download=%zu, last key=%d, last msg flag=0x%x "
+ "nsMsgMessageFlags::Offline=0x%x",
+ __func__, keysOfMessagesToDownload->Length(), msgKey, msgFlags,
+ nsMsgMessageFlags::Offline));
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapMailFolder::OnNewIdleMessages() {
+ nsresult rv;
+ nsCOMPtr<nsIImapIncomingServer> imapServer;
+ rv = GetImapIncomingServer(getter_AddRefs(imapServer));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool checkAllFolders = ShouldCheckAllFolders(imapServer);
+
+ // only trigger biff if we're checking all new folders for new messages, or
+ // this particular folder, but excluding trash,junk, sent, and no select
+ // folders, by default.
+ if ((checkAllFolders &&
+ !(mFlags &
+ (nsMsgFolderFlags::Trash | nsMsgFolderFlags::Junk |
+ nsMsgFolderFlags::SentMail | nsMsgFolderFlags::ImapNoselect))) ||
+ (mFlags & (nsMsgFolderFlags::CheckNew | nsMsgFolderFlags::Inbox)))
+ SetPerformingBiff(true);
+ return UpdateFolder(nullptr);
+}
+
+NS_IMETHODIMP nsImapMailFolder::UpdateImapMailboxInfo(
+ nsIImapProtocol* aProtocol, nsIMailboxSpec* aSpec) {
+ nsresult rv;
+ ChangeNumPendingTotalMessages(-mNumPendingTotalMessages);
+ ChangeNumPendingUnread(-mNumPendingUnreadMessages);
+ m_numServerRecentMessages = 0; // clear this since we selected the folder.
+
+ if (!mDatabase) GetDatabase();
+
+ bool folderSelected;
+ rv = aSpec->GetFolderSelected(&folderSelected);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsTArray<nsMsgKey> existingKeys;
+ nsTArray<nsMsgKey> keysToDelete;
+ uint32_t numNewUnread;
+ nsCOMPtr<nsIDBFolderInfo> dbFolderInfo;
+ int32_t imapUIDValidity = 0;
+ if (mDatabase) {
+ rv = mDatabase->GetDBFolderInfo(getter_AddRefs(dbFolderInfo));
+ if (NS_SUCCEEDED(rv) && dbFolderInfo) {
+ dbFolderInfo->GetImapUidValidity(&imapUIDValidity);
+ uint64_t mailboxHighestModSeq;
+ aSpec->GetHighestModSeq(&mailboxHighestModSeq);
+ MOZ_LOG(IMAP_CS, mozilla::LogLevel::Debug,
+ ("UpdateImapMailboxInfo(): Store highest MODSEQ=%" PRIu64
+ " for folder=%s",
+ mailboxHighestModSeq, m_onlineFolderName.get()));
+ char intStrBuf[40];
+ PR_snprintf(intStrBuf, sizeof(intStrBuf), "%llu", mailboxHighestModSeq);
+ dbFolderInfo->SetCharProperty(kModSeqPropertyName,
+ nsDependentCString(intStrBuf));
+ }
+ nsTArray<nsMsgKey> keys;
+ rv = mDatabase->ListAllKeys(keys);
+ NS_ENSURE_SUCCESS(rv, rv);
+ existingKeys.AppendElements(keys);
+ nsCOMPtr<nsIMsgOfflineOpsDatabase> opsDb =
+ do_QueryInterface(mDatabase, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ opsDb->ListAllOfflineDeletes(existingKeys);
+ }
+ int32_t folderValidity;
+ aSpec->GetFolder_UIDVALIDITY(&folderValidity);
+ nsCOMPtr<nsIImapFlagAndUidState> flagState;
+ aSpec->GetFlagState(getter_AddRefs(flagState));
+
+ // remember what the supported user flags are.
+ uint32_t supportedUserFlags;
+ aSpec->GetSupportedUserFlags(&supportedUserFlags);
+ SetSupportedUserFlags(supportedUserFlags);
+
+ m_uidValidity = folderValidity;
+
+ if (imapUIDValidity != folderValidity) {
+ NS_ASSERTION(imapUIDValidity == kUidUnknown,
+ "uid validity seems to have changed, blowing away db");
+ nsCOMPtr<nsIFile> pathFile;
+ rv = GetFilePath(getter_AddRefs(pathFile));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIMsgDBService> msgDBService =
+ do_GetService("@mozilla.org/msgDatabase/msgDBService;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIDBFolderInfo> transferInfo;
+ if (dbFolderInfo)
+ dbFolderInfo->GetTransferInfo(getter_AddRefs(transferInfo));
+
+ // A backup message database might have been created earlier, for example
+ // if the user requested a reindex. We'll use the earlier one if we can,
+ // otherwise we'll try to backup at this point.
+ nsresult rvbackup = OpenBackupMsgDatabase();
+ if (mDatabase) {
+ dbFolderInfo = nullptr;
+ if (NS_FAILED(rvbackup)) {
+ CloseAndBackupFolderDB(EmptyCString());
+ if (NS_FAILED(OpenBackupMsgDatabase()) && mBackupDatabase) {
+ mBackupDatabase->RemoveListener(this);
+ mBackupDatabase = nullptr;
+ }
+ } else
+ mDatabase->ForceClosed();
+ }
+ mDatabase = nullptr;
+
+ nsCOMPtr<nsIFile> summaryFile;
+ rv = GetSummaryFileLocation(pathFile, getter_AddRefs(summaryFile));
+ // Remove summary file.
+ if (NS_SUCCEEDED(rv) && summaryFile) summaryFile->Remove(false);
+
+ // Create a new summary file, update the folder message counts, and
+ // Close the summary file db.
+ rv = msgDBService->CreateNewDB(this, getter_AddRefs(mDatabase));
+
+ if (NS_FAILED(rv) && mDatabase) {
+ mDatabase->ForceClosed();
+ mDatabase = nullptr;
+ } else if (NS_SUCCEEDED(rv) && mDatabase) {
+ if (transferInfo) SetDBTransferInfo(transferInfo);
+
+ SummaryChanged();
+ if (mDatabase) {
+ if (mAddListener) mDatabase->AddListener(this);
+ rv = mDatabase->GetDBFolderInfo(getter_AddRefs(dbFolderInfo));
+ }
+ }
+ // store the new UIDVALIDITY value
+
+ if (NS_SUCCEEDED(rv) && dbFolderInfo) {
+ dbFolderInfo->SetImapUidValidity(folderValidity);
+ // need to forget highest mod seq when uid validity rolls.
+ MOZ_LOG(IMAP_CS, mozilla::LogLevel::Debug,
+ ("UpdateImapMailboxInfo(): UIDVALIDITY changed, reset highest "
+ "MODSEQ and UID for folder=%s",
+ m_onlineFolderName.get()));
+ dbFolderInfo->SetCharProperty(kModSeqPropertyName, EmptyCString());
+ dbFolderInfo->SetUint32Property(kHighestRecordedUIDPropertyName, 0);
+ }
+ // delete all my msgs, the keys are bogus now
+ // add every message in this folder
+ existingKeys.Clear();
+ // keysToDelete.CopyArray(&existingKeys);
+
+ if (flagState) {
+ nsTArray<nsMsgKey> no_existingKeys;
+ FindKeysToAdd(no_existingKeys, m_keysToFetch, numNewUnread, flagState);
+ }
+ if (NS_FAILED(rv)) pathFile->Remove(false);
+
+ } else if (!flagState /*&& !NET_IsOffline() */) // if there are no messages
+ // on the server
+ keysToDelete = existingKeys.Clone();
+ else /* if ( !NET_IsOffline()) */
+ {
+ uint32_t boxFlags;
+ aSpec->GetBox_flags(&boxFlags);
+ // FindKeysToDelete and FindKeysToAdd require sorted lists
+ existingKeys.Sort();
+ FindKeysToDelete(existingKeys, keysToDelete, flagState, boxFlags);
+ // if this is the result of an expunge then don't grab headers
+ if (!(boxFlags & kJustExpunged))
+ FindKeysToAdd(existingKeys, m_keysToFetch, numNewUnread, flagState);
+ }
+ m_totalKeysToFetch = m_keysToFetch.Length();
+ if (!keysToDelete.IsEmpty() && mDatabase) {
+ nsTArray<RefPtr<nsIMsgDBHdr>> hdrsToDelete;
+ MsgGetHeadersFromKeys(mDatabase, keysToDelete, hdrsToDelete);
+ // Notify nsIMsgFolderListeners of a mass delete, but only if we actually
+ // have headers
+ if (!hdrsToDelete.IsEmpty()) {
+ nsCOMPtr<nsIMsgFolderNotificationService> notifier(
+ do_GetService("@mozilla.org/messenger/msgnotificationservice;1"));
+ if (notifier) notifier->NotifyMsgsDeleted(hdrsToDelete);
+ }
+ DeleteStoreMessages(hdrsToDelete);
+ EnableNotifications(nsIMsgFolder::allMessageCountNotifications, false);
+ mDatabase->DeleteMessages(keysToDelete, nullptr);
+ EnableNotifications(nsIMsgFolder::allMessageCountNotifications, true);
+ }
+ int32_t numUnreadFromServer;
+ aSpec->GetNumUnseenMessages(&numUnreadFromServer);
+
+ bool partialUIDFetch;
+ flagState->GetPartialUIDFetch(&partialUIDFetch);
+
+ // For partial UID fetches (i.e., occurs when CONDSTORE in effect), we can
+ // only trust the numUnread from the server. However, even that will only be
+ // correct if a recent imap STATUS occurred as indicated by
+ // numUnreadFromServer greater than -1.
+ if (partialUIDFetch) numNewUnread = numUnreadFromServer;
+
+ // If we are performing biff for this folder, tell the
+ // stand-alone biff about the new high water mark
+ if (m_performingBiff && numNewUnread &&
+ static_cast<int32_t>(numNewUnread) != -1) {
+ // We must ensure that the server knows that we are performing biff.
+ // Otherwise the stand-alone biff won't fire.
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ if (NS_SUCCEEDED(GetServer(getter_AddRefs(server))) && server)
+ server->SetPerformingBiff(true);
+ SetNumNewMessages(numNewUnread);
+ }
+ SyncFlags(flagState);
+ if (mDatabase && numUnreadFromServer > -1 &&
+ (int32_t)(mNumUnreadMessages + m_keysToFetch.Length()) >
+ numUnreadFromServer)
+ mDatabase->SyncCounts();
+
+ if (!m_keysToFetch.IsEmpty() && aProtocol)
+ PrepareToAddHeadersToMailDB(aProtocol);
+ else {
+ bool gettingNewMessages;
+ GetGettingNewMessages(&gettingNewMessages);
+ if (gettingNewMessages)
+ ProgressStatusString(aProtocol, "imapNoNewMessages", nullptr);
+ SetPerformingBiff(false);
+ }
+ aSpec->GetNumMessages(&m_numServerTotalMessages);
+ if (numUnreadFromServer > -1) m_numServerUnseenMessages = numUnreadFromServer;
+ aSpec->GetNumRecentMessages(&m_numServerRecentMessages);
+
+ // some servers don't return UIDNEXT on SELECT - don't crunch
+ // existing values in that case.
+ int32_t nextUID;
+ aSpec->GetNextUID(&nextUID);
+ if (nextUID != (int32_t)nsMsgKey_None) m_nextUID = nextUID;
+
+ return rv;
+}
+
+/**
+ * Called after successful imap STATUS response occurs. Have valid unseen value
+ * if folderstatus URL produced an imap STATUS. If a NOOP occurs instead (doing
+ * folderstatus from a connection SELECTed on the same folder) there is no
+ * UNSEEN returned by NOOP.
+ */
+NS_IMETHODIMP nsImapMailFolder::UpdateImapMailboxStatus(
+ nsIImapProtocol* aProtocol, nsIMailboxSpec* aSpec) {
+ NS_ENSURE_ARG_POINTER(aSpec);
+ int32_t numUnread, numTotal;
+ aSpec->GetNumUnseenMessages(&numUnread);
+ aSpec->GetNumMessages(&numTotal);
+ aSpec->GetNumRecentMessages(&m_numServerRecentMessages);
+ int32_t prevNextUID = m_nextUID;
+ aSpec->GetNextUID(&m_nextUID);
+ bool summaryChanged = false;
+
+ // If m_numServerUnseenMessages is 0, it means
+ // this is the first time we've done a Status.
+ // In that case, we count all the previous pending unread messages we know
+ // about as unread messages. We may want to do similar things with total
+ // messages, but the total messages include deleted messages if the folder
+ // hasn't been expunged.
+ int32_t previousUnreadMessages =
+ (m_numServerUnseenMessages)
+ ? m_numServerUnseenMessages
+ : mNumPendingUnreadMessages + mNumUnreadMessages;
+ if (numUnread == -1) {
+ // A noop occurred so don't know server's UNSEEN number, keep using the
+ // previously known unread count.
+ MOZ_LOG(IMAP, mozilla::LogLevel::Debug,
+ ("%s: folder=%s, unread was -1, set numUnread to previousUnread=%d",
+ __func__, m_onlineFolderName.get(), previousUnreadMessages));
+ numUnread = previousUnreadMessages;
+ }
+ if (numUnread != previousUnreadMessages || m_nextUID != prevNextUID) {
+ int32_t unreadDelta =
+ numUnread - (mNumPendingUnreadMessages + mNumUnreadMessages);
+ if (numUnread - previousUnreadMessages != unreadDelta)
+ NS_WARNING("unread count should match server count");
+ ChangeNumPendingUnread(unreadDelta);
+ if (unreadDelta > 0 &&
+ !(mFlags & (nsMsgFolderFlags::Trash | nsMsgFolderFlags::Junk))) {
+ SetHasNewMessages(true);
+ SetNumNewMessages(unreadDelta);
+ SetBiffState(nsMsgBiffState_NewMail);
+ }
+ summaryChanged = true;
+ }
+ SetPerformingBiff(false);
+ if (m_numServerUnseenMessages != numUnread ||
+ m_numServerTotalMessages != numTotal) {
+ if (numUnread > m_numServerUnseenMessages ||
+ m_numServerTotalMessages > numTotal)
+ NotifyHasPendingMsgs();
+ summaryChanged = true;
+ m_numServerUnseenMessages = numUnread;
+ m_numServerTotalMessages = numTotal;
+ }
+ if (summaryChanged) SummaryChanged();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailFolder::ParseMsgHdrs(
+ nsIImapProtocol* aProtocol, nsIImapHeaderXferInfo* aHdrXferInfo) {
+ NS_ENSURE_ARG_POINTER(aHdrXferInfo);
+ int32_t numHdrs;
+ nsCOMPtr<nsIImapHeaderInfo> headerInfo;
+ nsCOMPtr<nsIImapUrl> aImapUrl;
+ nsImapAction imapAction = nsIImapUrl::nsImapTest; // unused value.
+ if (!mDatabase) GetDatabase();
+
+ nsresult rv = aHdrXferInfo->GetNumHeaders(&numHdrs);
+ if (aProtocol) {
+ (void)aProtocol->GetRunningImapURL(getter_AddRefs(aImapUrl));
+ if (aImapUrl) aImapUrl->GetImapAction(&imapAction);
+ }
+ for (uint32_t i = 0; NS_SUCCEEDED(rv) && (int32_t)i < numHdrs; i++) {
+ rv = aHdrXferInfo->GetHeader(i, getter_AddRefs(headerInfo));
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!headerInfo) break;
+ int32_t msgSize;
+ nsMsgKey msgKey;
+ bool containsKey;
+ nsCString msgHdrs;
+ headerInfo->GetMsgSize(&msgSize);
+ headerInfo->GetMsgUid(&msgKey);
+ if (msgKey == nsMsgKey_None) // not a valid uid.
+ continue;
+ if (imapAction == nsIImapUrl::nsImapMsgPreview) {
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ headerInfo->GetMsgHdrs(msgHdrs);
+ // create an input stream based on the hdr string.
+ nsCOMPtr<nsIStringInputStream> inputStream =
+ do_CreateInstance("@mozilla.org/io/string-input-stream;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ inputStream->ShareData(msgHdrs.get(), msgHdrs.Length());
+ GetMessageHeader(msgKey, getter_AddRefs(msgHdr));
+ if (msgHdr) {
+ GetMsgPreviewTextFromStream(msgHdr, inputStream);
+ }
+ continue;
+ }
+ if (mDatabase &&
+ NS_SUCCEEDED(mDatabase->ContainsKey(msgKey, &containsKey)) &&
+ containsKey) {
+ NS_ERROR("downloading hdrs for hdr we already have");
+ continue;
+ }
+ nsresult rv = SetupHeaderParseStream(msgSize, EmptyCString(), nullptr);
+ NS_ENSURE_SUCCESS(rv, rv);
+ headerInfo->GetMsgHdrs(msgHdrs);
+ rv = ParseAdoptedHeaderLine(msgHdrs.get(), msgKey);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = NormalEndHeaderParseStream(aProtocol, aImapUrl);
+ }
+ return rv;
+}
+
+nsresult nsImapMailFolder::SetupHeaderParseStream(
+ uint32_t aSize, const nsACString& content_type, nsIMailboxSpec* boxSpec) {
+ if (!mDatabase) GetDatabase();
+ m_nextMessageByteLength = aSize;
+ if (!m_msgParser) {
+ nsresult rv;
+ m_msgParser = do_CreateInstance(kParseMailMsgStateCID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else
+ m_msgParser->Clear();
+
+ m_msgParser->SetMailDB(mDatabase);
+ if (mBackupDatabase) m_msgParser->SetBackupMailDB(mBackupDatabase);
+ return m_msgParser->SetState(nsIMsgParseMailMsgState::ParseHeadersState);
+}
+
+nsresult nsImapMailFolder::ParseAdoptedHeaderLine(const char* aMessageLine,
+ nsMsgKey aMsgKey) {
+ // we can get blocks that contain more than one line,
+ // but they never contain partial lines
+ const char* str = aMessageLine;
+ m_curMsgUid = aMsgKey;
+ m_msgParser->SetNewKey(m_curMsgUid);
+ // m_envelope_pos, for local folders,
+ // is the msg key. Setting this will set the msg key for the new header.
+
+ int32_t len = strlen(str);
+ char* currentEOL = PL_strstr(str, MSG_LINEBREAK);
+ const char* currentLine = str;
+ while (currentLine < (str + len)) {
+ if (currentEOL) {
+ m_msgParser->ParseAFolderLine(
+ currentLine, (currentEOL + MSG_LINEBREAK_LEN) - currentLine);
+ currentLine = currentEOL + MSG_LINEBREAK_LEN;
+ currentEOL = PL_strstr(currentLine, MSG_LINEBREAK);
+ } else {
+ m_msgParser->ParseAFolderLine(currentLine, PL_strlen(currentLine));
+ currentLine = str + len + 1;
+ }
+ }
+ return NS_OK;
+}
+
+nsresult nsImapMailFolder::NormalEndHeaderParseStream(
+ nsIImapProtocol* aProtocol, nsIImapUrl* imapUrl) {
+ nsCOMPtr<nsIMsgDBHdr> newMsgHdr;
+ nsresult rv;
+ NS_ENSURE_TRUE(m_msgParser, NS_ERROR_NULL_POINTER);
+
+ nsMailboxParseState parseState;
+ m_msgParser->GetState(&parseState);
+ if (parseState == nsIMsgParseMailMsgState::ParseHeadersState)
+ m_msgParser->ParseAFolderLine(CRLF, 2);
+ rv = m_msgParser->GetNewMsgHdr(getter_AddRefs(newMsgHdr));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ char* headers;
+ int32_t headersSize;
+
+ nsCOMPtr<nsIMsgWindow> msgWindow;
+ nsCOMPtr<nsIMsgMailNewsUrl> msgUrl;
+ if (imapUrl) {
+ msgUrl = do_QueryInterface(imapUrl, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ msgUrl->GetMsgWindow(getter_AddRefs(msgWindow));
+ }
+
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIImapIncomingServer> imapServer = do_QueryInterface(server);
+ rv = imapServer->GetIsGMailServer(&m_isGmailServer);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ newMsgHdr->SetMessageKey(m_curMsgUid);
+ TweakHeaderFlags(aProtocol, newMsgHdr);
+ uint32_t messageSize;
+ if (NS_SUCCEEDED(newMsgHdr->GetMessageSize(&messageSize)))
+ mFolderSize += messageSize;
+ m_msgMovedByFilter = false;
+
+ nsMsgKey highestUID = 0;
+ nsCOMPtr<nsIDBFolderInfo> dbFolderInfo;
+ if (mDatabase) mDatabase->GetDBFolderInfo(getter_AddRefs(dbFolderInfo));
+ if (dbFolderInfo)
+ dbFolderInfo->GetUint32Property(kHighestRecordedUIDPropertyName, 0,
+ &highestUID);
+
+ // If this is the inbox, try to apply filters. Otherwise, test the inherited
+ // folder property "applyIncomingFilters" (which defaults to empty). If this
+ // inherited property has the string value "true", then apply filters even
+ // if this is not the Inbox folder.
+ if (mFlags & nsMsgFolderFlags::Inbox || m_applyIncomingFilters) {
+ // Use highwater to determine whether to filter?
+ bool filterOnHighwater = false;
+ nsCOMPtr<nsIPrefBranch> prefBranch(
+ do_GetService(NS_PREFSERVICE_CONTRACTID));
+ if (prefBranch)
+ prefBranch->GetBoolPref("mail.imap.filter_on_new", &filterOnHighwater);
+
+ uint32_t msgFlags;
+ newMsgHdr->GetFlags(&msgFlags);
+
+ // clang-format off
+ bool doFilter = filterOnHighwater
+ // Filter on largest UUID and not deleted.
+ ? m_curMsgUid > highestUID && !(msgFlags & nsMsgMessageFlags::IMAPDeleted)
+ // Filter on unread and not deleted.
+ : !(msgFlags & (nsMsgMessageFlags::Read | nsMsgMessageFlags::IMAPDeleted));
+ // clang-format on
+
+ if (doFilter)
+ MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
+ ("(Imap) New message parsed, and filters will be run on it"));
+ else
+ MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
+ ("(Imap) New message parsed, but filters will not be run on it"));
+
+ if (doFilter) {
+ int32_t duplicateAction = nsIMsgIncomingServer::keepDups;
+ if (server) server->GetIncomingDuplicateAction(&duplicateAction);
+ if ((duplicateAction != nsIMsgIncomingServer::keepDups) &&
+ mFlags & nsMsgFolderFlags::Inbox) {
+ bool isDup;
+ server->IsNewHdrDuplicate(newMsgHdr, &isDup);
+ if (isDup) {
+ // we want to do something similar to applying filter hits.
+ // if a dup is marked read, it shouldn't trigger biff.
+ // Same for deleting it or moving it to trash.
+ switch (duplicateAction) {
+ case nsIMsgIncomingServer::deleteDups: {
+ uint32_t newFlags;
+ newMsgHdr->OrFlags(
+ nsMsgMessageFlags::Read | nsMsgMessageFlags::IMAPDeleted,
+ &newFlags);
+ StoreImapFlags(kImapMsgSeenFlag | kImapMsgDeletedFlag, true,
+ {m_curMsgUid}, nullptr);
+ m_msgMovedByFilter = true;
+ } break;
+ case nsIMsgIncomingServer::moveDupsToTrash: {
+ nsCOMPtr<nsIMsgFolder> trash;
+ GetTrashFolder(getter_AddRefs(trash));
+ if (trash) {
+ nsCString trashUri;
+ trash->GetURI(trashUri);
+ nsresult err = MoveIncorporatedMessage(
+ newMsgHdr, mDatabase, trashUri, nullptr, msgWindow);
+ if (NS_SUCCEEDED(err)) m_msgMovedByFilter = true;
+ }
+ } break;
+ case nsIMsgIncomingServer::markDupsRead: {
+ uint32_t newFlags;
+ newMsgHdr->OrFlags(nsMsgMessageFlags::Read, &newFlags);
+ StoreImapFlags(kImapMsgSeenFlag, true, {m_curMsgUid}, nullptr);
+ } break;
+ }
+ int32_t numNewMessages;
+ GetNumNewMessages(false, &numNewMessages);
+ SetNumNewMessages(numNewMessages - 1);
+ }
+ }
+ rv = m_msgParser->GetAllHeaders(&headers, &headersSize);
+
+ if (NS_SUCCEEDED(rv) && headers && !m_msgMovedByFilter &&
+ !m_filterListRequiresBody) {
+ if (m_filterList) {
+ GetMoveCoalescer(); // not sure why we're doing this here.
+ MOZ_LOG(FILTERLOGMODULE, LogLevel::Debug,
+ ("(Imap) ApplyFilterToHdr from "
+ "nsImapMailFolder::NormalEndHeaderParseStream()"));
+ m_filterList->ApplyFiltersToHdr(
+ nsMsgFilterType::InboxRule, newMsgHdr, this, mDatabase,
+ nsDependentCSubstring(headers, headersSize), this, msgWindow);
+ NotifyFolderEvent(kFiltersApplied);
+ }
+ }
+ }
+ }
+ // here we need to tweak flags from uid state..
+ if (mDatabase && (!m_msgMovedByFilter || ShowDeletedMessages())) {
+ nsCOMPtr<nsIMsgFolderNotificationService> notifier(
+ do_GetService("@mozilla.org/messenger/msgnotificationservice;1"));
+ // Check if this header corresponds to a pseudo header
+ // we have from doing a pseudo-offline move and then downloading
+ // the real header from the server. In that case, we notify
+ // db/folder listeners that the pseudo-header has become the new
+ // header, i.e., the key has changed.
+ nsCString newMessageId;
+ newMsgHdr->GetMessageId(getter_Copies(newMessageId));
+ nsMsgKey pseudoKey =
+ m_pseudoHdrs.MaybeGet(newMessageId).valueOr(nsMsgKey_None);
+ if (notifier && pseudoKey != nsMsgKey_None) {
+ notifier->NotifyMsgKeyChanged(pseudoKey, newMsgHdr);
+ m_pseudoHdrs.Remove(newMessageId);
+ }
+ mDatabase->AddNewHdrToDB(newMsgHdr, true);
+ if (notifier) notifier->NotifyMsgAdded(newMsgHdr);
+ // mark the header as not yet reported classified
+ OrProcessingFlags(m_curMsgUid, nsMsgProcessingFlags::NotReportedClassified);
+ }
+ // adjust highestRecordedUID
+ if (dbFolderInfo) {
+ if (m_curMsgUid > highestUID) {
+ MOZ_LOG(IMAP_CS, mozilla::LogLevel::Debug,
+ ("NormalEndHeaderParseStream(): Store new highest UID=%" PRIu32
+ " for folder=%s",
+ m_curMsgUid, m_onlineFolderName.get()));
+ dbFolderInfo->SetUint32Property(kHighestRecordedUIDPropertyName,
+ m_curMsgUid);
+ }
+ }
+
+ if (m_isGmailServer) {
+ nsCOMPtr<nsIImapFlagAndUidState> flagState;
+ aProtocol->GetFlagAndUidState(getter_AddRefs(flagState));
+ nsCString msgIDValue;
+ nsCString threadIDValue;
+ nsCString labelsValue;
+ flagState->GetCustomAttribute(m_curMsgUid, "X-GM-MSGID"_ns, msgIDValue);
+ flagState->GetCustomAttribute(m_curMsgUid, "X-GM-THRID"_ns, threadIDValue);
+ flagState->GetCustomAttribute(m_curMsgUid, "X-GM-LABELS"_ns, labelsValue);
+ newMsgHdr->SetStringProperty("X-GM-MSGID", msgIDValue);
+ newMsgHdr->SetStringProperty("X-GM-THRID", threadIDValue);
+ newMsgHdr->SetStringProperty("X-GM-LABELS", labelsValue);
+ }
+
+ m_msgParser->Clear(); // clear out parser, because it holds onto a msg hdr.
+ m_msgParser->SetMailDB(nullptr); // tell it to let go of the db too.
+ // I don't think we want to do this - it does bad things like set the size
+ // incorrectly.
+ // m_msgParser->FinishHeader();
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailFolder::AbortHeaderParseStream(
+ nsIImapProtocol* aProtocol) {
+ nsresult rv = NS_ERROR_FAILURE;
+ return rv;
+}
+
+NS_IMETHODIMP nsImapMailFolder::BeginCopy() {
+ NS_ENSURE_TRUE(m_copyState, NS_ERROR_NULL_POINTER);
+ nsresult rv;
+ if (m_copyState->m_tmpFile) // leftover file spec nuke it
+ {
+ rv = m_copyState->m_tmpFile->Remove(false);
+ if (NS_FAILED(rv)) {
+ nsCString nativePath = m_copyState->m_tmpFile->HumanReadablePath();
+ MOZ_LOG(IMAP, mozilla::LogLevel::Info,
+ ("couldn't remove prev temp file %s: %" PRIx32, nativePath.get(),
+ static_cast<uint32_t>(rv)));
+ }
+ m_copyState->m_tmpFile = nullptr;
+ }
+
+ rv = NS_OpenAnonymousTemporaryNsIFile(getter_AddRefs(m_copyState->m_tmpFile));
+ if (NS_FAILED(rv)) {
+ MOZ_LOG(IMAP, mozilla::LogLevel::Info,
+ ("Couldn't create temp file: %" PRIx32, static_cast<uint32_t>(rv)));
+ OnCopyCompleted(m_copyState->m_srcSupport, rv);
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIOutputStream> fileOutputStream;
+ rv = MsgNewBufferedFileOutputStream(
+ getter_AddRefs(m_copyState->m_msgFileStream), m_copyState->m_tmpFile, -1,
+ 00600);
+ if (NS_FAILED(rv))
+ MOZ_LOG(IMAP, mozilla::LogLevel::Info,
+ ("couldn't create output file stream: %" PRIx32,
+ static_cast<uint32_t>(rv)));
+
+ if (!m_copyState->m_dataBuffer)
+ m_copyState->m_dataBuffer = (char*)PR_CALLOC(COPY_BUFFER_SIZE + 1);
+ NS_ENSURE_TRUE(m_copyState->m_dataBuffer, NS_ERROR_OUT_OF_MEMORY);
+ m_copyState->m_dataBufferSize = COPY_BUFFER_SIZE;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailFolder::CopyDataToOutputStreamForAppend(
+ nsIInputStream* aIStream, int32_t aLength, nsIOutputStream* outputStream) {
+ uint32_t readCount;
+ uint32_t writeCount;
+ if (!m_copyState) m_copyState = new nsImapMailCopyState();
+
+ if (aLength + m_copyState->m_leftOver > m_copyState->m_dataBufferSize) {
+ char* newBuffer = (char*)PR_REALLOC(m_copyState->m_dataBuffer,
+ aLength + m_copyState->m_leftOver + 1);
+ NS_ENSURE_TRUE(newBuffer, NS_ERROR_OUT_OF_MEMORY);
+ m_copyState->m_dataBuffer = newBuffer;
+ m_copyState->m_dataBufferSize = aLength + m_copyState->m_leftOver;
+ }
+
+ char *start, *end;
+ uint32_t linebreak_len = 1;
+
+ nsresult rv = aIStream->Read(
+ m_copyState->m_dataBuffer + m_copyState->m_leftOver, aLength, &readCount);
+ if (NS_FAILED(rv)) return rv;
+
+ m_copyState->m_leftOver += readCount;
+ m_copyState->m_dataBuffer[m_copyState->m_leftOver] = '\0';
+
+ start = m_copyState->m_dataBuffer;
+ if (m_copyState->m_eatLF) {
+ if (*start == '\n') start++;
+ m_copyState->m_eatLF = false;
+ }
+ end = PL_strpbrk(start, "\r\n");
+ if (end && *end == '\r' && *(end + 1) == '\n') linebreak_len = 2;
+
+ while (start && end) {
+ if (PL_strncasecmp(start, "X-Mozilla-Status:", 17) &&
+ PL_strncasecmp(start, "X-Mozilla-Status2:", 18) &&
+ PL_strncmp(start, "From - ", 7)) {
+ rv = outputStream->Write(start, end - start, &writeCount);
+ rv = outputStream->Write(CRLF, 2, &writeCount);
+ }
+ start = end + linebreak_len;
+ if (start >= m_copyState->m_dataBuffer + m_copyState->m_leftOver) {
+ m_copyState->m_leftOver = 0;
+ break;
+ }
+ linebreak_len = 1;
+
+ end = PL_strpbrk(start, "\r\n");
+ if (end && *end == '\r') {
+ if (*(end + 1) == '\n')
+ linebreak_len = 2;
+ else if (!*(end + 1)) // block might have split CRLF so remember if
+ m_copyState->m_eatLF = true; // we should eat LF
+ }
+
+ if (start && !end) {
+ m_copyState->m_leftOver -= (start - m_copyState->m_dataBuffer);
+ memcpy(m_copyState->m_dataBuffer, start,
+ m_copyState->m_leftOver + 1); // including null
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapMailFolder::CopyDataDone() {
+ m_copyState = nullptr;
+ return NS_OK;
+}
+
+// sICopyMessageListener methods, BeginCopy, CopyData, EndCopy, EndMove,
+// StartMessage, EndMessage
+NS_IMETHODIMP nsImapMailFolder::CopyData(nsIInputStream* aIStream,
+ int32_t aLength) {
+ NS_ENSURE_TRUE(
+ m_copyState && m_copyState->m_msgFileStream && m_copyState->m_dataBuffer,
+ NS_ERROR_NULL_POINTER);
+ nsresult rv = CopyDataToOutputStreamForAppend(aIStream, aLength,
+ m_copyState->m_msgFileStream);
+ if (NS_FAILED(rv)) {
+ MOZ_LOG(IMAP, mozilla::LogLevel::Info,
+ ("CopyData failed: %" PRIx32, static_cast<uint32_t>(rv)));
+ OnCopyCompleted(m_copyState->m_srcSupport, rv);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapMailFolder::EndCopy(bool copySucceeded) {
+ nsresult rv = copySucceeded ? NS_OK : NS_ERROR_FAILURE;
+ if (copySucceeded && m_copyState && m_copyState->m_msgFileStream) {
+ nsCOMPtr<nsIUrlListener> urlListener;
+ m_copyState->m_msgFileStream->Close();
+ // m_tmpFile can be stale because we wrote to it
+ nsCOMPtr<nsIFile> tmpFile;
+ m_copyState->m_tmpFile->Clone(getter_AddRefs(tmpFile));
+ m_copyState->m_tmpFile = tmpFile;
+ nsCOMPtr<nsIImapService> imapService =
+ do_GetService("@mozilla.org/messenger/imapservice;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv =
+ QueryInterface(NS_GET_IID(nsIUrlListener), getter_AddRefs(urlListener));
+ rv = imapService->AppendMessageFromFile(
+ m_copyState->m_tmpFile, this, EmptyCString(), true,
+ m_copyState->m_selectedState, urlListener, m_copyState,
+ m_copyState->m_msgWindow);
+ }
+ if (NS_FAILED(rv) || !copySucceeded)
+ MOZ_LOG(IMAP, mozilla::LogLevel::Info,
+ ("EndCopy failed: %" PRIx32, static_cast<uint32_t>(rv)));
+ return rv;
+}
+
+NS_IMETHODIMP nsImapMailFolder::EndMove(bool moveSucceeded) { return NS_OK; }
+// this is the beginning of the next message copied
+NS_IMETHODIMP nsImapMailFolder::StartMessage() { return NS_OK; }
+
+// just finished the current message.
+NS_IMETHODIMP nsImapMailFolder::EndMessage(nsMsgKey key) { return NS_OK; }
+
+NS_IMETHODIMP nsImapMailFolder::ApplyFilterHit(nsIMsgFilter* filter,
+ nsIMsgWindow* msgWindow,
+ bool* applyMore) {
+ //
+ // This routine is called indirectly from ApplyFiltersToHdr in two
+ // circumstances, controlled by m_filterListRequiresBody:
+ //
+ // If false, after headers are parsed in NormalEndHeaderParseStream.
+ // If true, after the message body is downloaded in NormalEndMsgWriteStream.
+ //
+ // In NormalEndHeaderParseStream, the message has not been added to the
+ // database, and it is important that database notifications and count
+ // updates do not occur. In NormalEndMsgWriteStream, the message has been
+ // added to the database, and database notifications and count updates
+ // should be performed.
+ //
+
+ NS_ENSURE_ARG_POINTER(filter);
+ NS_ENSURE_ARG_POINTER(applyMore);
+
+ nsresult rv = NS_OK;
+
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ if (m_filterListRequiresBody)
+ GetMessageHeader(m_curMsgUid, getter_AddRefs(msgHdr));
+ else if (m_msgParser)
+ m_msgParser->GetNewMsgHdr(getter_AddRefs(msgHdr));
+ NS_ENSURE_TRUE(msgHdr,
+ NS_ERROR_NULL_POINTER); // fatal error, cannot apply filters
+
+ bool deleteToTrash = DeleteIsMoveToTrash();
+
+ nsTArray<RefPtr<nsIMsgRuleAction>> filterActionList;
+ rv = filter->GetSortedActionList(filterActionList);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t numActions = filterActionList.Length();
+
+ nsCString msgId;
+ msgHdr->GetMessageId(getter_Copies(msgId));
+ nsMsgKey msgKey;
+ msgHdr->GetMessageKey(&msgKey);
+ MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
+ ("(Imap) Applying %" PRIu32
+ " filter actions on message with key %" PRIu32,
+ numActions, msgKeyToInt(msgKey)));
+ MOZ_LOG(FILTERLOGMODULE, LogLevel::Debug,
+ ("(Imap) Message ID: %s", msgId.get()));
+
+ bool loggingEnabled = false;
+ if (m_filterList && numActions)
+ (void)m_filterList->GetLoggingEnabled(&loggingEnabled);
+
+ bool msgIsNew = true;
+
+ rv = GetDatabase();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsresult finalResult = NS_OK; // result of all actions
+ for (uint32_t actionIndex = 0; actionIndex < numActions; actionIndex++) {
+ nsCOMPtr<nsIMsgRuleAction> filterAction(filterActionList[actionIndex]);
+ if (!filterAction) {
+ MOZ_LOG(FILTERLOGMODULE, LogLevel::Warning,
+ ("(Imap) Filter action at index %" PRIu32 " invalid, skipping",
+ actionIndex));
+ continue;
+ }
+
+ rv = NS_OK; // result of the current action
+ nsMsgRuleActionType actionType;
+ if (NS_SUCCEEDED(filterAction->GetType(&actionType))) {
+ MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
+ ("(Imap) Running filter action at index %" PRIu32
+ ", action type = %i",
+ actionIndex, actionType));
+ if (loggingEnabled) (void)filter->LogRuleHit(filterAction, msgHdr);
+
+ nsCString actionTargetFolderUri;
+ if (actionType == nsMsgFilterAction::MoveToFolder ||
+ actionType == nsMsgFilterAction::CopyToFolder) {
+ rv = filterAction->GetTargetFolderUri(actionTargetFolderUri);
+ if (NS_FAILED(rv) || actionTargetFolderUri.IsEmpty()) {
+ // clang-format off
+ MOZ_LOG(FILTERLOGMODULE, LogLevel::Warning,
+ ("(Imap) Target URI for Copy/Move action is empty, skipping"));
+ // clang-format on
+ NS_ASSERTION(false, "actionTargetFolderUri is empty");
+ continue;
+ }
+ }
+
+ uint32_t msgFlags;
+ msgHdr->GetFlags(&msgFlags);
+ bool isRead = (msgFlags & nsMsgMessageFlags::Read);
+
+ switch (actionType) {
+ case nsMsgFilterAction::Delete: {
+ if (deleteToTrash) {
+ // set value to trash folder
+ nsCOMPtr<nsIMsgFolder> mailTrash;
+ rv = GetTrashFolder(getter_AddRefs(mailTrash));
+ if (NS_SUCCEEDED(rv) && mailTrash) {
+ rv = mailTrash->GetURI(actionTargetFolderUri);
+ if (NS_FAILED(rv)) break;
+ }
+ // msgHdr->OrFlags(nsMsgMessageFlags::Read, &newFlags); // mark
+ // read in trash.
+ } else {
+ mDatabase->MarkHdrRead(msgHdr, true, nullptr);
+ mDatabase->MarkImapDeleted(msgKey, true, nullptr);
+ rv = StoreImapFlags(kImapMsgSeenFlag | kImapMsgDeletedFlag, true,
+ {msgKey}, nullptr);
+ if (NS_FAILED(rv)) break;
+ // this will prevent us from adding the header to the db.
+ m_msgMovedByFilter = true;
+ }
+ msgIsNew = false;
+ }
+ // note that delete falls through to move.
+ [[fallthrough]];
+ case nsMsgFilterAction::MoveToFolder: {
+ // if moving to a different file, do it.
+ nsCString uri;
+ rv = GetURI(uri);
+ if (NS_FAILED(rv)) break;
+
+ if (!actionTargetFolderUri.Equals(uri)) {
+ msgHdr->GetFlags(&msgFlags);
+ if (msgFlags & nsMsgMessageFlags::MDNReportNeeded && !isRead) {
+ mDatabase->MarkMDNNeeded(msgKey, false, nullptr);
+ mDatabase->MarkMDNSent(msgKey, true, nullptr);
+ }
+ nsresult rv = MoveIncorporatedMessage(
+ msgHdr, mDatabase, actionTargetFolderUri, filter, msgWindow);
+ if (NS_SUCCEEDED(rv)) {
+ m_msgMovedByFilter = true;
+ } else {
+ if (loggingEnabled) {
+ (void)filter->LogRuleHitFail(filterAction, msgHdr, rv,
+ "filterFailureMoveFailed"_ns);
+ }
+ }
+ }
+ // don't apply any more filters, even if it was a move to the same
+ // folder
+ *applyMore = false;
+ } break;
+ case nsMsgFilterAction::CopyToFolder: {
+ nsCString uri;
+ rv = GetURI(uri);
+ if (NS_FAILED(rv)) break;
+
+ if (!actionTargetFolderUri.Equals(uri)) {
+ // XXXshaver I'm not actually 100% what the right semantics are for
+ // MDNs and copied messages, but I suspect deep down inside that
+ // we probably want to suppress them only on the copies.
+ msgHdr->GetFlags(&msgFlags);
+ if (msgFlags & nsMsgMessageFlags::MDNReportNeeded && !isRead) {
+ mDatabase->MarkMDNNeeded(msgKey, false, nullptr);
+ mDatabase->MarkMDNSent(msgKey, true, nullptr);
+ }
+
+ nsCOMPtr<nsIMsgFolder> dstFolder;
+ rv = GetExistingFolder(actionTargetFolderUri,
+ getter_AddRefs(dstFolder));
+ if (NS_FAILED(rv)) break;
+
+ nsCOMPtr<nsIMsgCopyService> copyService = do_GetService(
+ "@mozilla.org/messenger/messagecopyservice;1", &rv);
+ if (NS_FAILED(rv)) break;
+ rv = copyService->CopyMessages(this, {&*msgHdr}, dstFolder, false,
+ nullptr, msgWindow, false);
+ if (NS_FAILED(rv)) {
+ if (loggingEnabled) {
+ (void)filter->LogRuleHitFail(filterAction, msgHdr, rv,
+ "filterFailureCopyFailed"_ns);
+ }
+ }
+ }
+ } break;
+ case nsMsgFilterAction::MarkRead: {
+ mDatabase->MarkHdrRead(msgHdr, true, nullptr);
+ rv = StoreImapFlags(kImapMsgSeenFlag, true, {msgKey}, nullptr);
+ msgIsNew = false;
+ } break;
+ case nsMsgFilterAction::MarkUnread: {
+ mDatabase->MarkHdrRead(msgHdr, false, nullptr);
+ rv = StoreImapFlags(kImapMsgSeenFlag, false, {msgKey}, nullptr);
+ msgIsNew = true;
+ } break;
+ case nsMsgFilterAction::MarkFlagged: {
+ mDatabase->MarkHdrMarked(msgHdr, true, nullptr);
+ rv = StoreImapFlags(kImapMsgFlaggedFlag, true, {msgKey}, nullptr);
+ } break;
+ case nsMsgFilterAction::KillThread:
+ case nsMsgFilterAction::WatchThread: {
+ nsCOMPtr<nsIMsgThread> msgThread;
+ nsMsgKey threadKey;
+ mDatabase->GetThreadContainingMsgHdr(msgHdr,
+ getter_AddRefs(msgThread));
+ if (msgThread) {
+ msgThread->GetThreadKey(&threadKey);
+ if (actionType == nsMsgFilterAction::KillThread)
+ rv = mDatabase->MarkThreadIgnored(msgThread, threadKey, true,
+ nullptr);
+ else
+ rv = mDatabase->MarkThreadWatched(msgThread, threadKey, true,
+ nullptr);
+ } else {
+ if (actionType == nsMsgFilterAction::KillThread)
+ rv = msgHdr->SetUint32Property("ProtoThreadFlags",
+ nsMsgMessageFlags::Ignored);
+ else
+ rv = msgHdr->SetUint32Property("ProtoThreadFlags",
+ nsMsgMessageFlags::Watched);
+ }
+ if (actionType == nsMsgFilterAction::KillThread) {
+ mDatabase->MarkHdrRead(msgHdr, true, nullptr);
+ rv = StoreImapFlags(kImapMsgSeenFlag, true, {msgKey}, nullptr);
+ msgIsNew = false;
+ }
+ } break;
+ case nsMsgFilterAction::KillSubthread: {
+ mDatabase->MarkHeaderKilled(msgHdr, true, nullptr);
+ mDatabase->MarkHdrRead(msgHdr, true, nullptr);
+ rv = StoreImapFlags(kImapMsgSeenFlag, true, {msgKey}, nullptr);
+ msgIsNew = false;
+ } break;
+ case nsMsgFilterAction::ChangePriority: {
+ nsMsgPriorityValue filterPriority; // a int32_t
+ filterAction->GetPriority(&filterPriority);
+ rv = mDatabase->SetUint32PropertyByHdr(
+ msgHdr, "priority", static_cast<uint32_t>(filterPriority));
+ } break;
+ case nsMsgFilterAction::AddTag: {
+ nsCString keyword;
+ filterAction->GetStrValue(keyword);
+ rv = AddKeywordsToMessages({&*msgHdr}, keyword);
+ } break;
+ case nsMsgFilterAction::JunkScore: {
+ nsAutoCString junkScoreStr;
+ int32_t junkScore;
+ filterAction->GetJunkScore(&junkScore);
+ junkScoreStr.AppendInt(junkScore);
+ rv = mDatabase->SetStringProperty(msgKey, "junkscore", junkScoreStr);
+ mDatabase->SetStringProperty(msgKey, "junkscoreorigin", "filter"_ns);
+
+ // If score is available, set up to store junk status on server.
+ if (junkScore == nsIJunkMailPlugin::IS_SPAM_SCORE ||
+ junkScore == nsIJunkMailPlugin::IS_HAM_SCORE) {
+ nsTArray<nsMsgKey>* keysToClassify = m_moveCoalescer->GetKeyBucket(
+ (junkScore == nsIJunkMailPlugin::IS_SPAM_SCORE) ? 0 : 1);
+ NS_ASSERTION(keysToClassify, "error getting key bucket");
+ if (keysToClassify) keysToClassify->AppendElement(msgKey);
+ if (msgIsNew && junkScore == nsIJunkMailPlugin::IS_SPAM_SCORE) {
+ msgIsNew = false;
+ mDatabase->MarkHdrNotNew(msgHdr, nullptr);
+ // nsMsgDBFolder::SendFlagNotifications by the call to
+ // SetBiffState(nsMsgBiffState_NoMail) will reset numNewMessages
+ // only if the message is also read and database notifications
+ // are active, but we are not going to mark it read in this
+ // action, preferring to leave the choice to the user.
+ // So correct numNewMessages.
+ if (m_filterListRequiresBody) {
+ msgHdr->GetFlags(&msgFlags);
+ if (!(msgFlags & nsMsgMessageFlags::Read)) {
+ int32_t numNewMessages;
+ GetNumNewMessages(false, &numNewMessages);
+ SetNumNewMessages(--numNewMessages);
+ SetHasNewMessages(numNewMessages != 0);
+ }
+ }
+ }
+ }
+ } break;
+ case nsMsgFilterAction::Forward: {
+ nsCString forwardTo;
+ filterAction->GetStrValue(forwardTo);
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = GetServer(getter_AddRefs(server));
+ if (NS_FAILED(rv)) break;
+ if (!forwardTo.IsEmpty()) {
+ nsCOMPtr<nsIMsgComposeService> compService =
+ do_GetService("@mozilla.org/messengercompose;1", &rv);
+ if (NS_FAILED(rv)) break;
+ rv = compService->ForwardMessage(
+ NS_ConvertUTF8toUTF16(forwardTo), msgHdr, msgWindow, server,
+ nsIMsgComposeService::kForwardAsDefault);
+ }
+ } break;
+
+ case nsMsgFilterAction::Reply: {
+ nsCString replyTemplateUri;
+ filterAction->GetStrValue(replyTemplateUri);
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = GetServer(getter_AddRefs(server));
+ if (NS_FAILED(rv)) break;
+ if (!replyTemplateUri.IsEmpty()) {
+ nsCOMPtr<nsIMsgComposeService> compService =
+ do_GetService("@mozilla.org/messengercompose;1", &rv);
+ if (NS_SUCCEEDED(rv) && compService) {
+ rv = compService->ReplyWithTemplate(msgHdr, replyTemplateUri,
+ msgWindow, server);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("ReplyWithTemplate failed");
+ if (rv == NS_ERROR_ABORT) {
+ (void)filter->LogRuleHitFail(
+ filterAction, msgHdr, rv,
+ "filterFailureSendingReplyAborted"_ns);
+ } else {
+ (void)filter->LogRuleHitFail(
+ filterAction, msgHdr, rv,
+ "filterFailureSendingReplyError"_ns);
+ }
+ }
+ }
+ }
+ } break;
+
+ case nsMsgFilterAction::StopExecution: {
+ // don't apply any more filters
+ *applyMore = false;
+ rv = NS_OK;
+ } break;
+
+ case nsMsgFilterAction::Custom: {
+ nsCOMPtr<nsIMsgFilterCustomAction> customAction;
+ rv = filterAction->GetCustomAction(getter_AddRefs(customAction));
+ if (NS_FAILED(rv)) break;
+
+ nsAutoCString value;
+ rv = filterAction->GetStrValue(value);
+ if (NS_FAILED(rv)) break;
+
+ rv = customAction->ApplyAction({&*msgHdr}, value, nullptr,
+ nsMsgFilterType::InboxRule, msgWindow);
+ // allow custom action to affect new
+ msgHdr->GetFlags(&msgFlags);
+ if (!(msgFlags & nsMsgMessageFlags::New)) msgIsNew = false;
+ } break;
+
+ default:
+ NS_ERROR("unexpected filter action");
+ rv = NS_ERROR_UNEXPECTED;
+ break;
+ }
+ }
+ if (NS_FAILED(rv)) {
+ finalResult = rv;
+ MOZ_LOG(FILTERLOGMODULE, LogLevel::Error,
+ ("(Imap) Action execution failed with error: %" PRIx32,
+ static_cast<uint32_t>(rv)));
+ if (loggingEnabled) {
+ (void)filter->LogRuleHitFail(filterAction, msgHdr, rv,
+ "filterFailureAction"_ns);
+ }
+ } else {
+ MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
+ ("(Imap) Action execution succeeded"));
+ }
+ }
+ if (!msgIsNew) {
+ int32_t numNewMessages;
+ GetNumNewMessages(false, &numNewMessages);
+ // When database notifications are active, new counts will be reset
+ // to zero in nsMsgDBFolder::SendFlagNotifications by the call to
+ // SetBiffState(nsMsgBiffState_NoMail), so don't repeat them here.
+ if (!m_filterListRequiresBody) SetNumNewMessages(--numNewMessages);
+ if (mDatabase) mDatabase->MarkHdrNotNew(msgHdr, nullptr);
+ MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
+ ("(Imap) Message will not be marked new"));
+ }
+ MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
+ ("(Imap) Finished executing actions"));
+ return finalResult;
+}
+
+NS_IMETHODIMP nsImapMailFolder::SetImapFlags(const char* uids, int32_t flags,
+ nsIURI** url) {
+ nsresult rv;
+ nsCOMPtr<nsIImapService> imapService =
+ do_GetService("@mozilla.org/messenger/imapservice;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return imapService->SetMessageFlags(this, this, url, nsAutoCString(uids),
+ flags, true);
+}
+
+// "this" is the parent folder
+NS_IMETHODIMP nsImapMailFolder::PlaybackOfflineFolderCreate(
+ const nsAString& aFolderName, nsIMsgWindow* aWindow, nsIURI** url) {
+ nsresult rv;
+ nsCOMPtr<nsIImapService> imapService =
+ do_GetService("@mozilla.org/messenger/imapservice;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return imapService->CreateFolder(this, aFolderName, this, url);
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::ReplayOfflineMoveCopy(const nsTArray<nsMsgKey>& aMsgKeys,
+ bool isMove, nsIMsgFolder* aDstFolder,
+ nsIUrlListener* aUrlListener,
+ nsIMsgWindow* aWindow,
+ bool srcFolderOffline) {
+ nsresult rv;
+
+ nsCOMPtr<nsIMsgImapMailFolder> imapFolder = do_QueryInterface(aDstFolder);
+ if (imapFolder) {
+ nsImapMailFolder* destImapFolder =
+ static_cast<nsImapMailFolder*>(aDstFolder);
+ nsCOMPtr<nsIMsgDatabase> dstFolderDB;
+ aDstFolder->GetMsgDatabase(getter_AddRefs(dstFolderDB));
+ nsCOMPtr<nsIMsgOfflineOpsDatabase> opsDb =
+ do_QueryInterface(dstFolderDB, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (opsDb) {
+ // find the fake header in the destination db, and use that to
+ // set the pending attributes on the real headers. To do this,
+ // we need to iterate over the offline ops in the destination db,
+ // looking for ones with matching keys and source folder uri.
+ // If we find that offline op, its "key" will be the key of the fake
+ // header, so we just need to get the header for that key
+ // from the dest db.
+ nsTArray<nsMsgKey> offlineOps;
+ if (NS_SUCCEEDED(opsDb->ListAllOfflineOpIds(offlineOps))) {
+ nsTArray<RefPtr<nsIMsgDBHdr>> messages;
+ nsCString srcFolderUri;
+ GetURI(srcFolderUri);
+ nsCOMPtr<nsIMsgOfflineImapOperation> currentOp;
+ for (uint32_t opIndex = 0; opIndex < offlineOps.Length(); opIndex++) {
+ opsDb->GetOfflineOpForKey(offlineOps[opIndex], false,
+ getter_AddRefs(currentOp));
+ if (currentOp) {
+ nsCString opSrcUri;
+ currentOp->GetSourceFolderURI(opSrcUri);
+ if (opSrcUri.Equals(srcFolderUri)) {
+ nsMsgKey srcMessageKey;
+ currentOp->GetSrcMessageKey(&srcMessageKey);
+ for (auto key : aMsgKeys) {
+ if (srcMessageKey == key) {
+ nsCOMPtr<nsIMsgDBHdr> fakeDestHdr;
+ dstFolderDB->GetMsgHdrForKey(offlineOps[opIndex],
+ getter_AddRefs(fakeDestHdr));
+ if (fakeDestHdr) messages.AppendElement(fakeDestHdr);
+ break;
+ }
+ }
+ }
+ }
+ }
+ // 3rd parameter: Sets offline flag.
+ destImapFolder->SetPendingAttributes(messages, isMove,
+ srcFolderOffline);
+ }
+ }
+ // if we can't get the dst folder db, we should still try to playback
+ // the offline move/copy.
+ }
+
+ nsCOMPtr<nsIImapService> imapService =
+ do_GetService("@mozilla.org/messenger/imapservice;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIURI> resultUrl;
+ nsAutoCString uids;
+ AllocateUidStringFromKeys(aMsgKeys, uids);
+ rv = imapService->OnlineMessageCopy(this, uids, aDstFolder, true, isMove,
+ aUrlListener, getter_AddRefs(resultUrl),
+ nullptr, aWindow);
+ if (resultUrl) {
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(resultUrl, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIUrlListener> folderListener = do_QueryInterface(aDstFolder);
+ if (folderListener) mailnewsUrl->RegisterListener(folderListener);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapMailFolder::AddMoveResultPseudoKey(nsMsgKey aMsgKey) {
+ nsresult rv = GetDatabase();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgDBHdr> pseudoHdr;
+ rv = mDatabase->GetMsgHdrForKey(aMsgKey, getter_AddRefs(pseudoHdr));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCString messageId;
+ pseudoHdr->GetMessageId(getter_Copies(messageId));
+ // err on the side of caution and ignore messages w/o messageid.
+ if (messageId.IsEmpty()) return NS_OK;
+ m_pseudoHdrs.InsertOrUpdate(messageId, aMsgKey);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailFolder::StoreImapFlags(int32_t flags, bool addFlags,
+ const nsTArray<nsMsgKey>& keys,
+ nsIUrlListener* aUrlListener) {
+ nsresult rv;
+ if (!WeAreOffline()) {
+ nsCOMPtr<nsIImapService> imapService =
+ do_GetService("@mozilla.org/messenger/imapservice;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsAutoCString msgIds;
+ AllocateUidStringFromKeys(keys, msgIds);
+ if (addFlags)
+ imapService->AddMessageFlags(this, aUrlListener ? aUrlListener : this,
+ msgIds, flags, true);
+ else
+ imapService->SubtractMessageFlags(
+ this, aUrlListener ? aUrlListener : this, msgIds, flags, true);
+ } else {
+ rv = GetDatabase();
+ if (NS_SUCCEEDED(rv) && mDatabase) {
+ nsCOMPtr<nsIMsgOfflineOpsDatabase> opsDb =
+ do_QueryInterface(mDatabase, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ for (auto key : keys) {
+ nsCOMPtr<nsIMsgOfflineImapOperation> op;
+ rv = opsDb->GetOfflineOpForKey(key, true, getter_AddRefs(op));
+ SetFlag(nsMsgFolderFlags::OfflineEvents);
+ if (NS_SUCCEEDED(rv) && op) {
+ imapMessageFlagsType newFlags;
+ op->GetNewFlags(&newFlags);
+ op->SetFlagOperation(addFlags ? newFlags | flags : newFlags & ~flags);
+ }
+ }
+ opsDb->Commit(nsMsgDBCommitType::kLargeCommit); // flush offline flags
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapMailFolder::LiteSelect(nsIUrlListener* aUrlListener,
+ nsIMsgWindow* aMsgWindow) {
+ nsresult rv;
+ nsCOMPtr<nsIImapService> imapService =
+ do_GetService("@mozilla.org/messenger/imapservice;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIURI> outUri;
+ return imapService->LiteSelectFolder(this, aUrlListener, aMsgWindow,
+ getter_AddRefs(outUri));
+}
+
+nsresult nsImapMailFolder::GetFolderOwnerUserName(nsACString& userName) {
+ if ((mFlags & nsMsgFolderFlags::ImapPersonal) ||
+ !(mFlags &
+ (nsMsgFolderFlags::ImapPublic | nsMsgFolderFlags::ImapOtherUser))) {
+ // this is one of our personal mail folders
+ // return our username on this host
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ nsresult rv = GetServer(getter_AddRefs(server));
+ return NS_FAILED(rv) ? rv : server->GetUsername(userName);
+ }
+
+ // the only other type of owner is if it's in the other users' namespace
+ if (!(mFlags & nsMsgFolderFlags::ImapOtherUser)) return NS_OK;
+
+ if (m_ownerUserName.IsEmpty()) {
+ nsCString onlineName;
+ GetOnlineName(onlineName);
+ m_ownerUserName = nsImapNamespaceList::GetFolderOwnerNameFromPath(
+ GetNamespaceForFolder(), onlineName.get());
+ }
+ userName = m_ownerUserName;
+ return NS_OK;
+}
+
+nsImapNamespace* nsImapMailFolder::GetNamespaceForFolder() {
+ if (!m_namespace) {
+#ifdef DEBUG_bienvenu
+ // Make sure this isn't causing us to open the database
+ NS_ASSERTION(m_hierarchyDelimiter != kOnlineHierarchySeparatorUnknown,
+ "haven't set hierarchy delimiter");
+#endif
+ nsCString serverKey;
+ nsCString onlineName;
+ GetServerKey(serverKey);
+ GetOnlineName(onlineName);
+ char hierarchyDelimiter;
+ GetHierarchyDelimiter(&hierarchyDelimiter);
+
+ m_namespace = nsImapNamespaceList::GetNamespaceForFolder(
+ serverKey.get(), onlineName.get(), hierarchyDelimiter);
+ NS_ASSERTION(m_namespace, "didn't get namespace for folder");
+ if (m_namespace) {
+ nsImapNamespaceList::SuggestHierarchySeparatorForNamespace(
+ m_namespace, hierarchyDelimiter);
+ m_folderIsNamespace = nsImapNamespaceList::GetFolderIsNamespace(
+ serverKey.get(), onlineName.get(), hierarchyDelimiter, m_namespace);
+ }
+ }
+ return m_namespace;
+}
+
+void nsImapMailFolder::SetNamespaceForFolder(nsImapNamespace* ns) {
+#ifdef DEBUG_bienvenu
+ NS_ASSERTION(ns, "null namespace");
+#endif
+ m_namespace = ns;
+}
+
+NS_IMETHODIMP nsImapMailFolder::FolderPrivileges(nsIMsgWindow* window) {
+ NS_ENSURE_ARG_POINTER(window);
+ nsresult rv = NS_OK; // if no window...
+ if (!m_adminUrl.IsEmpty()) {
+ nsCOMPtr<nsIExternalProtocolService> extProtService =
+ do_GetService(NS_EXTERNALPROTOCOLSERVICE_CONTRACTID);
+ if (extProtService) {
+ nsAutoCString scheme;
+ nsCOMPtr<nsIURI> uri;
+ if (NS_FAILED(rv = NS_NewURI(getter_AddRefs(uri), m_adminUrl.get())))
+ return rv;
+ uri->GetScheme(scheme);
+ if (!scheme.IsEmpty()) {
+ // if the URL scheme does not correspond to an exposed protocol, then we
+ // need to hand this link click over to the external protocol handler.
+ bool isExposed;
+ rv = extProtService->IsExposedProtocol(scheme.get(), &isExposed);
+ if (NS_SUCCEEDED(rv) && !isExposed)
+ return extProtService->LoadURI(uri, nullptr, nullptr, nullptr, false,
+ false);
+ }
+ }
+ } else {
+ nsCOMPtr<nsIImapService> imapService =
+ do_GetService("@mozilla.org/messenger/imapservice;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = imapService->GetFolderAdminUrl(this, window, this, nullptr);
+ if (NS_SUCCEEDED(rv)) m_urlRunning = true;
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapMailFolder::GetHasAdminUrl(bool* aBool) {
+ NS_ENSURE_ARG_POINTER(aBool);
+ nsCOMPtr<nsIImapIncomingServer> imapServer;
+ nsresult rv = GetImapIncomingServer(getter_AddRefs(imapServer));
+ nsCString manageMailAccountUrl;
+ if (NS_SUCCEEDED(rv) && imapServer)
+ rv = imapServer->GetManageMailAccountUrl(manageMailAccountUrl);
+ *aBool = (NS_SUCCEEDED(rv) && !manageMailAccountUrl.IsEmpty());
+ return rv;
+}
+
+NS_IMETHODIMP nsImapMailFolder::GetAdminUrl(nsACString& aResult) {
+ aResult = m_adminUrl;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailFolder::SetAdminUrl(const nsACString& adminUrl) {
+ m_adminUrl = adminUrl;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailFolder::GetHdrParser(
+ nsIMsgParseMailMsgState** aHdrParser) {
+ NS_ENSURE_ARG_POINTER(aHdrParser);
+ NS_IF_ADDREF(*aHdrParser = m_msgParser);
+ return NS_OK;
+}
+
+// this is used to issue an arbitrary imap command on the passed in msgs.
+// It assumes the command needs to be run in the selected state.
+NS_IMETHODIMP nsImapMailFolder::IssueCommandOnMsgs(const nsACString& command,
+ const char* uids,
+ nsIMsgWindow* aWindow,
+ nsIURI** url) {
+ nsresult rv;
+ nsCOMPtr<nsIImapService> imapService =
+ do_GetService("@mozilla.org/messenger/imapservice;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return imapService->IssueCommandOnMsgs(this, aWindow, command,
+ nsDependentCString(uids), url);
+}
+
+NS_IMETHODIMP nsImapMailFolder::FetchCustomMsgAttribute(
+ const nsACString& attribute, const char* uids, nsIMsgWindow* aWindow,
+ nsIURI** url) {
+ nsresult rv;
+ nsCOMPtr<nsIImapService> imapService =
+ do_GetService("@mozilla.org/messenger/imapservice;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return imapService->FetchCustomMsgAttribute(this, aWindow, attribute,
+ nsDependentCString(uids), url);
+}
+
+nsresult nsImapMailFolder::MoveIncorporatedMessage(
+ nsIMsgDBHdr* mailHdr, nsIMsgDatabase* sourceDB,
+ const nsACString& destFolderUri, nsIMsgFilter* filter,
+ nsIMsgWindow* msgWindow) {
+ nsresult rv;
+ if (m_moveCoalescer) {
+ nsCOMPtr<nsIMsgFolder> destIFolder;
+ rv = GetOrCreateFolder(destFolderUri, getter_AddRefs(destIFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (destIFolder) {
+ // check if the destination is a real folder (by checking for null parent)
+ // and if it can file messages (e.g., servers or news folders can't file
+ // messages). Or read only imap folders...
+ bool canFileMessages = true;
+ nsCOMPtr<nsIMsgFolder> parentFolder;
+ destIFolder->GetParent(getter_AddRefs(parentFolder));
+ if (parentFolder) destIFolder->GetCanFileMessages(&canFileMessages);
+ if (filter && (!parentFolder || !canFileMessages)) {
+ filter->SetEnabled(false);
+ m_filterList->SaveToDefaultFile();
+ destIFolder->ThrowAlertMsg("filterDisabled", msgWindow);
+ return NS_MSG_NOT_A_MAIL_FOLDER;
+ }
+ // put the header into the source db, since it needs to be there when we
+ // copy it and we need a valid header to pass to
+ // StartAsyncCopyMessagesInto
+ nsMsgKey keyToFilter;
+ mailHdr->GetMessageKey(&keyToFilter);
+
+ if (sourceDB && destIFolder) {
+ bool imapDeleteIsMoveToTrash = DeleteIsMoveToTrash();
+ m_moveCoalescer->AddMove(destIFolder, keyToFilter);
+ // For each folder, we need to keep track of the ids we want to move to
+ // that folder - we used to store them in the MSG_FolderInfo and then
+ // when we'd finished downloading headers, we'd iterate through all the
+ // folders looking for the ones that needed messages moved into them -
+ // perhaps instead we could keep track of nsIMsgFolder,
+ // nsTArray<nsMsgKey> pairs here in the imap code. nsTArray<nsMsgKey>
+ // *idsToMoveFromInbox = msgFolder->GetImapIdsToMoveFromInbox();
+ // idsToMoveFromInbox->AppendElement(keyToFilter);
+ if (imapDeleteIsMoveToTrash) {
+ }
+ bool isRead = false;
+ mailHdr->GetIsRead(&isRead);
+ if (imapDeleteIsMoveToTrash) rv = NS_OK;
+ }
+ }
+ } else
+ rv = NS_ERROR_UNEXPECTED;
+
+ // we have to return an error because we do not actually move the message
+ // it is done async and that can fail
+ return rv;
+}
+
+/**
+ * This method assumes that key arrays and flag states are sorted by increasing
+ * key.
+ */
+void nsImapMailFolder::FindKeysToDelete(const nsTArray<nsMsgKey>& existingKeys,
+ nsTArray<nsMsgKey>& keysToDelete,
+ nsIImapFlagAndUidState* flagState,
+ uint32_t boxFlags) {
+ bool showDeletedMessages = ShowDeletedMessages();
+ int32_t numMessageInFlagState;
+ bool partialUIDFetch;
+ uint32_t uidOfMessage;
+ imapMessageFlagsType flags;
+
+ flagState->GetNumberOfMessages(&numMessageInFlagState);
+ flagState->GetPartialUIDFetch(&partialUIDFetch);
+
+ // if we're doing a partialUIDFetch, just delete the keys from the db
+ // that have the deleted flag set (if not using imap delete model)
+ // and return.
+ if (partialUIDFetch) {
+ if (!showDeletedMessages) {
+ for (uint32_t i = 0; (int32_t)i < numMessageInFlagState; i++) {
+ flagState->GetUidOfMessage(i, &uidOfMessage);
+ // flag state will be zero filled up to first real uid, so ignore those.
+ if (uidOfMessage) {
+ flagState->GetMessageFlags(i, &flags);
+ if (flags & kImapMsgDeletedFlag)
+ keysToDelete.AppendElement(uidOfMessage);
+ }
+ }
+ } else if (boxFlags & kJustExpunged) {
+ // we've just issued an expunge with a partial flag state. We should
+ // delete headers with the imap deleted flag set, because we can't
+ // tell from the expunge response which messages were deleted.
+ nsCOMPtr<nsIMsgEnumerator> hdrs;
+ nsresult rv = GetMessages(getter_AddRefs(hdrs));
+ NS_ENSURE_SUCCESS_VOID(rv);
+ bool hasMore = false;
+ while (NS_SUCCEEDED(rv = hdrs->HasMoreElements(&hasMore)) && hasMore) {
+ nsCOMPtr<nsIMsgDBHdr> header;
+ rv = hdrs->GetNext(getter_AddRefs(header));
+ NS_ENSURE_SUCCESS_VOID(rv);
+ uint32_t msgFlags;
+ header->GetFlags(&msgFlags);
+ if (msgFlags & nsMsgMessageFlags::IMAPDeleted) {
+ nsMsgKey msgKey;
+ header->GetMessageKey(&msgKey);
+ keysToDelete.AppendElement(msgKey);
+ }
+ }
+ }
+ return;
+ }
+ // otherwise, we have a complete set of uid's and flags, so we delete
+ // anything that's in existingKeys but not in the flag state, as well
+ // as messages with the deleted flag set.
+ uint32_t total = existingKeys.Length();
+ int onlineIndex = 0; // current index into flagState
+ for (uint32_t keyIndex = 0; keyIndex < total; keyIndex++) {
+ while (
+ (onlineIndex < numMessageInFlagState) &&
+ NS_SUCCEEDED(flagState->GetUidOfMessage(onlineIndex, &uidOfMessage)) &&
+ (existingKeys[keyIndex] > uidOfMessage))
+ onlineIndex++;
+
+ flagState->GetUidOfMessage(onlineIndex, &uidOfMessage);
+ flagState->GetMessageFlags(onlineIndex, &flags);
+ // delete this key if it is not there or marked deleted
+ if ((onlineIndex >= numMessageInFlagState) ||
+ (existingKeys[keyIndex] != uidOfMessage) ||
+ ((flags & kImapMsgDeletedFlag) && !showDeletedMessages)) {
+ nsMsgKey doomedKey = existingKeys[keyIndex];
+ if ((int32_t)doomedKey <= 0 && doomedKey != nsMsgKey_None) continue;
+
+ keysToDelete.AppendElement(existingKeys[keyIndex]);
+ }
+
+ flagState->GetUidOfMessage(onlineIndex, &uidOfMessage);
+ if (existingKeys[keyIndex] == uidOfMessage) onlineIndex++;
+ }
+}
+
+void nsImapMailFolder::FindKeysToAdd(const nsTArray<nsMsgKey>& existingKeys,
+ nsTArray<nsMsgKey>& keysToFetch,
+ uint32_t& numNewUnread,
+ nsIImapFlagAndUidState* flagState) {
+ bool showDeletedMessages = ShowDeletedMessages();
+ int dbIndex = 0; // current index into existingKeys
+ int32_t existTotal, numberOfKnownKeys;
+ int32_t messageIndex;
+
+ numNewUnread = 0;
+ existTotal = numberOfKnownKeys = existingKeys.Length();
+ flagState->GetNumberOfMessages(&messageIndex);
+ bool partialUIDFetch;
+ flagState->GetPartialUIDFetch(&partialUIDFetch);
+
+ for (int32_t flagIndex = 0; flagIndex < messageIndex; flagIndex++) {
+ uint32_t uidOfMessage;
+ flagState->GetUidOfMessage(flagIndex, &uidOfMessage);
+ while ((flagIndex < numberOfKnownKeys) && (dbIndex < existTotal) &&
+ existingKeys[dbIndex] < uidOfMessage)
+ dbIndex++;
+
+ if ((flagIndex >= numberOfKnownKeys) || (dbIndex >= existTotal) ||
+ (existingKeys[dbIndex] != uidOfMessage)) {
+ numberOfKnownKeys++;
+
+ imapMessageFlagsType flags;
+ flagState->GetMessageFlags(flagIndex, &flags);
+ NS_ASSERTION(uidOfMessage != nsMsgKey_None, "got invalid msg key");
+ if (uidOfMessage && uidOfMessage != nsMsgKey_None &&
+ (showDeletedMessages || !(flags & kImapMsgDeletedFlag))) {
+ if (mDatabase) {
+ bool dbContainsKey;
+ if (NS_SUCCEEDED(
+ mDatabase->ContainsKey(uidOfMessage, &dbContainsKey)) &&
+ dbContainsKey) {
+ // this is expected in the partial uid fetch case because the
+ // flag state does not contain all messages, so the db has
+ // messages the flag state doesn't know about.
+ if (!partialUIDFetch) NS_ERROR("db has key - flagState messed up?");
+ continue;
+ }
+ }
+ keysToFetch.AppendElement(uidOfMessage);
+ if (!(flags & kImapMsgSeenFlag)) numNewUnread++;
+ }
+ }
+ }
+}
+
+NS_IMETHODIMP nsImapMailFolder::GetMsgHdrsToDownload(
+ bool* aMoreToDownload, int32_t* aTotalCount, nsTArray<nsMsgKey>& aKeys) {
+ NS_ENSURE_ARG_POINTER(aMoreToDownload);
+ NS_ENSURE_ARG_POINTER(aTotalCount);
+ aKeys.Clear();
+
+ *aMoreToDownload = false;
+ *aTotalCount = m_totalKeysToFetch;
+ if (m_keysToFetch.IsEmpty()) {
+ return NS_OK;
+ }
+
+ // if folder isn't open in a window, no reason to limit the number of headers
+ // we download.
+ nsCOMPtr<nsIMsgMailSession> session =
+ do_GetService("@mozilla.org/messenger/services/session;1");
+ bool folderOpen = false;
+ if (session) session->IsFolderOpenInWindow(this, &folderOpen);
+
+ int32_t hdrChunkSize = 200;
+ if (folderOpen) {
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> prefBranch(
+ do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (prefBranch)
+ prefBranch->GetIntPref("mail.imap.hdr_chunk_size", &hdrChunkSize);
+ }
+ int32_t numKeysToFetch = m_keysToFetch.Length();
+ int32_t startIndex = 0;
+ if (folderOpen && hdrChunkSize > 0 &&
+ (int32_t)m_keysToFetch.Length() > hdrChunkSize) {
+ numKeysToFetch = hdrChunkSize;
+ *aMoreToDownload = true;
+ startIndex = m_keysToFetch.Length() - hdrChunkSize;
+ }
+ aKeys.AppendElements(&m_keysToFetch[startIndex], numKeysToFetch);
+ // Remove these for the incremental header download case, so that
+ // we know we don't have to download them again.
+ m_keysToFetch.RemoveElementsAt(startIndex, numKeysToFetch);
+
+ return NS_OK;
+}
+
+void nsImapMailFolder::PrepareToAddHeadersToMailDB(nsIImapProtocol* aProtocol) {
+ // now, tell it we don't need any bodies.
+ nsTArray<nsMsgKey> noBodies;
+ aProtocol->NotifyBodysToDownload(noBodies);
+}
+
+void nsImapMailFolder::TweakHeaderFlags(nsIImapProtocol* aProtocol,
+ nsIMsgDBHdr* tweakMe) {
+ if (mDatabase && aProtocol && tweakMe) {
+ tweakMe->SetMessageKey(m_curMsgUid);
+ tweakMe->SetMessageSize(m_nextMessageByteLength);
+
+ bool foundIt = false;
+
+ nsCOMPtr<nsIImapFlagAndUidState> flagState;
+ nsresult rv = aProtocol->GetFlagAndUidState(getter_AddRefs(flagState));
+ NS_ENSURE_SUCCESS_VOID(rv);
+ rv = flagState->HasMessage(m_curMsgUid, &foundIt);
+
+ if (NS_SUCCEEDED(rv) && foundIt) {
+ imapMessageFlagsType imap_flags;
+ nsCString customFlags;
+ flagState->GetMessageFlagsByUid(m_curMsgUid, &imap_flags);
+ if (imap_flags & kImapMsgCustomKeywordFlag) {
+ flagState->GetCustomFlags(m_curMsgUid, getter_Copies(customFlags));
+ }
+
+ // make a mask and clear these message flags
+ uint32_t mask = nsMsgMessageFlags::Read | nsMsgMessageFlags::Replied |
+ nsMsgMessageFlags::Marked |
+ nsMsgMessageFlags::IMAPDeleted |
+ nsMsgMessageFlags::Labels;
+ uint32_t dbHdrFlags;
+
+ tweakMe->GetFlags(&dbHdrFlags);
+ tweakMe->AndFlags(~mask, &dbHdrFlags);
+
+ // set the new value for these flags
+ uint32_t newFlags = 0;
+ if (imap_flags & kImapMsgSeenFlag)
+ newFlags |= nsMsgMessageFlags::Read;
+ else // if (imap_flags & kImapMsgRecentFlag)
+ newFlags |= nsMsgMessageFlags::New;
+
+ // Okay here is the MDN needed logic (if DNT header seen):
+ /* if server support user defined flag:
+ XXX TODO: Fix badly formatted comment which doesn't reflect the code.
+ MDNSent flag set => clear kMDNNeeded flag
+ MDNSent flag not set => do nothing, leave kMDNNeeded on
+ else if
+ not nsMsgMessageFlags::New => clear kMDNNeeded flag
+ nsMsgMessageFlags::New => do nothing, leave kMDNNeeded on
+ */
+ uint16_t userFlags;
+ rv = aProtocol->GetSupportedUserFlags(&userFlags);
+ if (NS_SUCCEEDED(rv) && (userFlags & (kImapMsgSupportUserFlag |
+ kImapMsgSupportMDNSentFlag))) {
+ if (imap_flags & kImapMsgMDNSentFlag) {
+ newFlags |= nsMsgMessageFlags::MDNReportSent;
+ if (dbHdrFlags & nsMsgMessageFlags::MDNReportNeeded)
+ tweakMe->AndFlags(~nsMsgMessageFlags::MDNReportNeeded, &dbHdrFlags);
+ }
+ }
+
+ if (imap_flags & kImapMsgAnsweredFlag)
+ newFlags |= nsMsgMessageFlags::Replied;
+ if (imap_flags & kImapMsgFlaggedFlag)
+ newFlags |= nsMsgMessageFlags::Marked;
+ if (imap_flags & kImapMsgDeletedFlag)
+ newFlags |= nsMsgMessageFlags::IMAPDeleted;
+ if (imap_flags & kImapMsgForwardedFlag)
+ newFlags |= nsMsgMessageFlags::Forwarded;
+ if (newFlags) tweakMe->OrFlags(newFlags, &dbHdrFlags);
+ if (!customFlags.IsEmpty())
+ (void)HandleCustomFlags(m_curMsgUid, tweakMe, userFlags, customFlags);
+ }
+ }
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::SetupMsgWriteStream(nsIFile* aFile, bool addDummyEnvelope) {
+ nsresult rv;
+ aFile->Remove(false);
+ m_tempMessageStreamBytesWritten = 0;
+ rv = MsgNewBufferedFileOutputStream(
+ getter_AddRefs(m_tempMessageStream), aFile,
+ PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE, 00700);
+ if (m_tempMessageStream && addDummyEnvelope) {
+ nsAutoCString result;
+ char* ct;
+ uint32_t writeCount;
+ time_t now = time((time_t*)0);
+ ct = ctime(&now);
+ ct[24] = 0;
+ result = "From - ";
+ result += ct;
+ result += MSG_LINEBREAK;
+
+ rv = m_tempMessageStream->Write(result.get(), result.Length(), &writeCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+ m_tempMessageStreamBytesWritten += writeCount;
+
+ result = "X-Mozilla-Status: 0001";
+ result += MSG_LINEBREAK;
+ rv = m_tempMessageStream->Write(result.get(), result.Length(), &writeCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+ m_tempMessageStreamBytesWritten += writeCount;
+
+ result = "X-Mozilla-Status2: 00000000";
+ result += MSG_LINEBREAK;
+ rv = m_tempMessageStream->Write(result.get(), result.Length(), &writeCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+ m_tempMessageStreamBytesWritten += writeCount;
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapMailFolder::DownloadMessagesForOffline(
+ nsTArray<RefPtr<nsIMsgDBHdr>> const& messages, nsIMsgWindow* window) {
+ nsAutoCString messageIds;
+ nsTArray<nsMsgKey> srcKeyArray;
+ nsresult rv = BuildIdsAndKeyArray(messages, messageIds, srcKeyArray);
+ if (NS_FAILED(rv) || messageIds.IsEmpty()) return rv;
+
+ nsCOMPtr<nsIImapService> imapService =
+ do_GetService("@mozilla.org/messenger/imapservice;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = AcquireSemaphore(static_cast<nsIMsgFolder*>(this));
+ if (NS_FAILED(rv)) {
+ ThrowAlertMsg("operationFailedFolderBusy", window);
+ return rv;
+ }
+ return imapService->DownloadMessagesForOffline(messageIds, this, this,
+ window);
+}
+
+NS_IMETHODIMP nsImapMailFolder::DownloadAllForOffline(nsIUrlListener* listener,
+ nsIMsgWindow* msgWindow) {
+ nsresult rv;
+ nsCOMPtr<nsIURI> runningURI;
+ bool noSelect;
+ GetFlag(nsMsgFolderFlags::ImapNoselect, &noSelect);
+
+ if (!noSelect) {
+ nsAutoCString messageIdsToDownload;
+ nsTArray<nsMsgKey> msgsToDownload;
+
+ GetDatabase();
+ m_downloadingFolderForOfflineUse = true;
+
+ rv = AcquireSemaphore(static_cast<nsIMsgFolder*>(this));
+ if (NS_FAILED(rv)) {
+ m_downloadingFolderForOfflineUse = false;
+ ThrowAlertMsg("operationFailedFolderBusy", msgWindow);
+ return rv;
+ }
+ nsCOMPtr<nsIImapService> imapService =
+ do_GetService("@mozilla.org/messenger/imapservice;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Selecting the folder with nsIImapUrl::shouldStoreMsgOffline true will
+ // cause us to fetch any message bodies we don't have.
+ m_urlListener = listener;
+ rv = imapService->SelectFolder(this, this, msgWindow,
+ getter_AddRefs(runningURI));
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIImapUrl> imapUrl(do_QueryInterface(runningURI));
+ if (imapUrl) imapUrl->SetStoreResultsOffline(true);
+ m_urlRunning = true;
+ }
+ } else
+ rv = NS_MSG_FOLDER_UNREADABLE;
+ return rv;
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::ParseAdoptedMsgLine(const char* adoptedMessageLine,
+ nsMsgKey uidOfMessage,
+ nsIImapUrl* aImapUrl) {
+ NS_ENSURE_ARG_POINTER(aImapUrl);
+ uint32_t count = 0;
+ nsresult rv;
+ // remember the uid of the message we're downloading.
+ m_curMsgUid = uidOfMessage;
+ if (!m_offlineHeader) {
+ rv = GetMessageHeader(uidOfMessage, getter_AddRefs(m_offlineHeader));
+ if (NS_SUCCEEDED(rv) && !m_offlineHeader) rv = NS_ERROR_UNEXPECTED;
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = StartNewOfflineMessage();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ // adoptedMessageLine is actually a string with a lot of message lines,
+ // separated by native line terminators we need to count the number of
+ // MSG_LINEBREAK's to determine how much to increment m_numOfflineMsgLines by.
+ const char* nextLine = adoptedMessageLine;
+ do {
+ m_numOfflineMsgLines++;
+ nextLine = PL_strstr(nextLine, MSG_LINEBREAK);
+ if (nextLine) nextLine += MSG_LINEBREAK_LEN;
+ } while (nextLine && *nextLine);
+
+ if (m_tempMessageStream) {
+ rv = m_tempMessageStream->Write(adoptedMessageLine,
+ PL_strlen(adoptedMessageLine), &count);
+ NS_ENSURE_SUCCESS(rv, rv);
+ m_tempMessageStreamBytesWritten += count;
+ }
+ return NS_OK;
+}
+
+void nsImapMailFolder::EndOfflineDownload() {
+ if (m_tempMessageStream) {
+ m_tempMessageStream->Close();
+ m_tempMessageStream = nullptr;
+ ReleaseSemaphore(static_cast<nsIMsgFolder*>(this));
+ if (mDatabase) mDatabase->Commit(nsMsgDBCommitType::kLargeCommit);
+ }
+ m_offlineHeader = nullptr;
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::NormalEndMsgWriteStream(nsMsgKey uidOfMessage, bool markRead,
+ nsIImapUrl* imapUrl,
+ int32_t updatedMessageSize) {
+ if (updatedMessageSize != -1) {
+ // retrieve the message header to update size, if we don't already have it
+ nsCOMPtr<nsIMsgDBHdr> msgHeader = m_offlineHeader;
+ if (!msgHeader) GetMessageHeader(uidOfMessage, getter_AddRefs(msgHeader));
+ if (msgHeader) {
+ uint32_t msgSize;
+ msgHeader->GetMessageSize(&msgSize);
+ MOZ_LOG(IMAP, mozilla::LogLevel::Debug,
+ ("Updating stored message size from %u, new size %d", msgSize,
+ updatedMessageSize));
+ msgHeader->SetMessageSize(updatedMessageSize);
+ // only commit here if this isn't an offline message
+ // offline header gets committed in EndNewOfflineMessage() called below
+ if (mDatabase && !m_offlineHeader)
+ mDatabase->Commit(nsMsgDBCommitType::kLargeCommit);
+ } else
+ NS_WARNING(
+ "Failed to get message header when trying to update message size");
+ }
+
+ if (m_offlineHeader) EndNewOfflineMessage(NS_OK);
+
+ m_curMsgUid = uidOfMessage;
+
+ // Apply filter now if it needed a body
+ if (m_filterListRequiresBody) {
+ if (m_filterList) {
+ nsCOMPtr<nsIMsgDBHdr> newMsgHdr;
+ GetMessageHeader(uidOfMessage, getter_AddRefs(newMsgHdr));
+ GetMoveCoalescer();
+ nsCOMPtr<nsIMsgWindow> msgWindow;
+ if (imapUrl) {
+ nsresult rv;
+ nsCOMPtr<nsIMsgMailNewsUrl> msgUrl;
+ msgUrl = do_QueryInterface(imapUrl, &rv);
+ if (msgUrl && NS_SUCCEEDED(rv))
+ msgUrl->GetMsgWindow(getter_AddRefs(msgWindow));
+ }
+ m_filterList->ApplyFiltersToHdr(nsMsgFilterType::InboxRule, newMsgHdr,
+ this, mDatabase, EmptyCString(), this,
+ msgWindow);
+ NotifyFolderEvent(kFiltersApplied);
+ }
+ // Process filter plugins and other items normally done at the end of
+ // HeaderFetchCompleted.
+ bool pendingMoves = m_moveCoalescer && m_moveCoalescer->HasPendingMoves();
+ PlaybackCoalescedOperations();
+
+ bool filtersRun;
+ CallFilterPlugins(nullptr, &filtersRun);
+ int32_t numNewBiffMsgs = 0;
+ if (m_performingBiff) GetNumNewMessages(false, &numNewBiffMsgs);
+
+ if (!filtersRun && m_performingBiff && mDatabase && numNewBiffMsgs > 0 &&
+ (!pendingMoves || !ShowPreviewText())) {
+ // If we are performing biff for this folder, tell the
+ // stand-alone biff about the new high water mark
+ // We must ensure that the server knows that we are performing biff.
+ // Otherwise the stand-alone biff won't fire.
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ if (NS_SUCCEEDED(GetServer(getter_AddRefs(server))) && server)
+ server->SetPerformingBiff(true);
+
+ SetBiffState(nsIMsgFolder::nsMsgBiffState_NewMail);
+ if (server) server->SetPerformingBiff(false);
+ m_performingBiff = false;
+ }
+
+ if (m_filterList) (void)m_filterList->FlushLogIfNecessary();
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::AbortMsgWriteStream() {
+ m_offlineHeader = nullptr;
+ return NS_ERROR_FAILURE;
+}
+
+// message move/copy related methods
+NS_IMETHODIMP
+nsImapMailFolder::OnlineCopyCompleted(nsIImapProtocol* aProtocol,
+ ImapOnlineCopyState aCopyState) {
+ NS_ENSURE_ARG_POINTER(aProtocol);
+
+ nsresult rv;
+ if (aCopyState == ImapOnlineCopyStateType::kSuccessfulCopy) {
+ nsCOMPtr<nsIImapUrl> imapUrl;
+ rv = aProtocol->GetRunningImapURL(getter_AddRefs(imapUrl));
+ if (NS_FAILED(rv) || !imapUrl) return NS_ERROR_FAILURE;
+ nsImapAction action;
+ rv = imapUrl->GetImapAction(&action);
+ if (NS_FAILED(rv)) return rv;
+ if (action != nsIImapUrl::nsImapOnlineToOfflineMove)
+ return NS_ERROR_FAILURE; // don't assert here...
+ nsCString messageIds;
+ rv = imapUrl->GetListOfMessageIds(messageIds);
+ if (NS_FAILED(rv)) return rv;
+ nsCOMPtr<nsIImapService> imapService =
+ do_GetService("@mozilla.org/messenger/imapservice;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return imapService->AddMessageFlags(this, nullptr, messageIds,
+ kImapMsgDeletedFlag, true);
+ }
+ /* unhandled copystate */
+ if (m_copyState) // whoops, this is the wrong folder - should use the source
+ // folder
+ {
+ nsCOMPtr<nsIMsgFolder> srcFolder;
+ srcFolder = do_QueryInterface(m_copyState->m_srcSupport, &rv);
+ if (srcFolder) srcFolder->NotifyFolderEvent(kDeleteOrMoveMsgCompleted);
+ } else
+ rv = NS_ERROR_FAILURE;
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::CloseMockChannel(nsIImapMockChannel* aChannel) {
+ aChannel->Close();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::ReleaseUrlCacheEntry(nsIMsgMailNewsUrl* aUrl) {
+ NS_ENSURE_ARG_POINTER(aUrl);
+ return aUrl->SetMemCacheEntry(nullptr);
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::BeginMessageUpload() { return NS_ERROR_FAILURE; }
+
+nsresult nsImapMailFolder::HandleCustomFlags(nsMsgKey uidOfMessage,
+ nsIMsgDBHdr* dbHdr,
+ uint16_t userFlags,
+ nsCString& keywords) {
+ nsresult rv = GetDatabase();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ ToLowerCase(keywords);
+ bool messageClassified = true;
+ // ### TODO: we really should parse the keywords into space delimited keywords
+ // before checking
+ // Mac Mail, Yahoo uses "NotJunk"
+ if (FindInReadable("NonJunk"_ns, keywords,
+ nsCaseInsensitiveCStringComparator) ||
+ FindInReadable("NotJunk"_ns, keywords,
+ nsCaseInsensitiveCStringComparator)) {
+ nsAutoCString msgJunkScore;
+ msgJunkScore.AppendInt(nsIJunkMailPlugin::IS_HAM_SCORE);
+ mDatabase->SetStringProperty(uidOfMessage, "junkscore", msgJunkScore);
+ } else if (FindInReadable("Junk"_ns, keywords,
+ nsCaseInsensitiveCStringComparator)) {
+ uint32_t newFlags;
+ dbHdr->AndFlags(~nsMsgMessageFlags::New, &newFlags);
+ nsAutoCString msgJunkScore;
+ msgJunkScore.AppendInt(nsIJunkMailPlugin::IS_SPAM_SCORE);
+ mDatabase->SetStringProperty(uidOfMessage, "junkscore", msgJunkScore);
+ } else
+ messageClassified = false;
+ if (messageClassified) {
+ // only set the junkscore origin if it wasn't set before.
+ nsCString existingProperty;
+ dbHdr->GetStringProperty("junkscoreorigin", existingProperty);
+ if (existingProperty.IsEmpty())
+ dbHdr->SetStringProperty("junkscoreorigin", "imapflag"_ns);
+ }
+
+ if (!(userFlags & kImapMsgSupportUserFlag)) {
+ nsCString localKeywords;
+ nsCString prevKeywords;
+ dbHdr->GetStringProperty("keywords", localKeywords);
+ dbHdr->GetStringProperty("prevkeywords", prevKeywords);
+ // localKeywords: tags currently stored in database for the message.
+ // keywords: tags stored in server and obtained when flags for the message
+ // were last fetched. (Parameter of this function.)
+ // prevKeywords: saved keywords from previous call of this function.
+ // clang-format off
+ MOZ_LOG(IMAP_KW, mozilla::LogLevel::Debug,
+ ("UID=%" PRIu32 ", localKeywords=|%s| keywords=|%s|, prevKeywords=|%s|",
+ uidOfMessage, localKeywords.get(), keywords.get(), prevKeywords.get()));
+ // clang-format on
+
+ // Store keywords to detect changes on next call of this function.
+ dbHdr->SetStringProperty("prevkeywords", keywords);
+
+ // Parse the space separated strings into arrays.
+ nsTArray<nsCString> localKeywordArray;
+ nsTArray<nsCString> keywordArray;
+ nsTArray<nsCString> prevKeywordArray;
+ ParseString(localKeywords, ' ', localKeywordArray);
+ ParseString(keywords, ' ', keywordArray);
+ ParseString(prevKeywords, ' ', prevKeywordArray);
+
+ // If keyword not received now but was the last time, remove it from
+ // the localKeywords. This means the keyword was removed by another user
+ // sharing the folder.
+ for (uint32_t i = 0; i < prevKeywordArray.Length(); i++) {
+ bool inRcvd = keywordArray.Contains(prevKeywordArray[i]);
+ bool inLocal = localKeywordArray.Contains(prevKeywordArray[i]);
+ if (!inRcvd && inLocal)
+ localKeywordArray.RemoveElement(prevKeywordArray[i]);
+ }
+
+ // Combine local and rcvd keyword arrays into a single string
+ // so it can be passed to SetStringProperty(). If element of
+ // local already in rcvd, avoid duplicates in combined string.
+ nsAutoCString combinedKeywords;
+ for (uint32_t i = 0; i < localKeywordArray.Length(); i++) {
+ if (!keywordArray.Contains(localKeywordArray[i])) {
+ combinedKeywords.Append(localKeywordArray[i]);
+ combinedKeywords.Append(' ');
+ }
+ }
+ for (uint32_t i = 0; i < keywordArray.Length(); i++) {
+ combinedKeywords.Append(keywordArray[i]);
+ combinedKeywords.Append(' ');
+ }
+ MOZ_LOG(IMAP_KW, mozilla::LogLevel::Debug,
+ ("combinedKeywords stored = |%s|", combinedKeywords.get()));
+ // combinedKeywords are tags being stored in database for the message.
+ return dbHdr->SetStringProperty("keywords", combinedKeywords);
+ }
+ return (userFlags & kImapMsgSupportUserFlag)
+ ? dbHdr->SetStringProperty("keywords", keywords)
+ : NS_OK;
+}
+
+// synchronize the message flags in the database with the server flags
+nsresult nsImapMailFolder::SyncFlags(nsIImapFlagAndUidState* flagState) {
+ nsresult rv = GetDatabase(); // we need a database for this
+ NS_ENSURE_SUCCESS(rv, rv);
+ bool partialUIDFetch;
+ flagState->GetPartialUIDFetch(&partialUIDFetch);
+
+ // update all of the database flags
+ int32_t messageIndex;
+ uint32_t messageSize;
+
+ // Take this opportunity to recalculate the folder size, if we're not a
+ // partial (condstore) fetch.
+ int64_t newFolderSize = 0;
+
+ flagState->GetNumberOfMessages(&messageIndex);
+
+ uint16_t supportedUserFlags;
+ flagState->GetSupportedUserFlags(&supportedUserFlags);
+
+ for (int32_t flagIndex = 0; flagIndex < messageIndex; flagIndex++) {
+ uint32_t uidOfMessage;
+ flagState->GetUidOfMessage(flagIndex, &uidOfMessage);
+ imapMessageFlagsType flags;
+ flagState->GetMessageFlags(flagIndex, &flags);
+ bool containsKey;
+ rv = mDatabase->ContainsKey(uidOfMessage, &containsKey);
+ // if we don't have the header, don't diddle the flags.
+ // GetMsgHdrForKey will create the header if it doesn't exist.
+ if (NS_FAILED(rv) || !containsKey) continue;
+
+ nsCOMPtr<nsIMsgDBHdr> dbHdr;
+ rv = mDatabase->GetMsgHdrForKey(uidOfMessage, getter_AddRefs(dbHdr));
+ if (NS_FAILED(rv)) continue;
+ if (NS_SUCCEEDED(dbHdr->GetMessageSize(&messageSize)))
+ newFolderSize += messageSize;
+
+ nsCString keywords;
+ if (NS_SUCCEEDED(
+ flagState->GetCustomFlags(uidOfMessage, getter_Copies(keywords))))
+ HandleCustomFlags(uidOfMessage, dbHdr, supportedUserFlags, keywords);
+
+ NotifyMessageFlagsFromHdr(dbHdr, uidOfMessage, flags);
+ }
+ if (!partialUIDFetch && newFolderSize != mFolderSize) {
+ int64_t oldFolderSize = mFolderSize;
+ mFolderSize = newFolderSize;
+ NotifyIntPropertyChanged(kFolderSize, oldFolderSize, mFolderSize);
+ }
+
+ return NS_OK;
+}
+
+// helper routine to sync the flags on a given header
+nsresult nsImapMailFolder::NotifyMessageFlagsFromHdr(nsIMsgDBHdr* dbHdr,
+ nsMsgKey msgKey,
+ uint32_t flags) {
+ nsresult rv = GetDatabase();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Although it may seem strange to keep a local reference of mDatabase here,
+ // the current lifetime management of databases requires that methods
+ // sometimes null the database when they think they opened it. Unfortunately
+ // experience shows this happens when we don't expect, so for crash protection
+ // best practice with the current flawed database management is to keep a
+ // local reference when there will be complex calls in a method. See bug
+ // 1312254.
+ nsCOMPtr<nsIMsgDatabase> database(mDatabase);
+ NS_ENSURE_STATE(database);
+
+ database->MarkHdrRead(dbHdr, (flags & kImapMsgSeenFlag) != 0, nullptr);
+ database->MarkHdrReplied(dbHdr, (flags & kImapMsgAnsweredFlag) != 0, nullptr);
+ database->MarkHdrMarked(dbHdr, (flags & kImapMsgFlaggedFlag) != 0, nullptr);
+ database->MarkImapDeleted(msgKey, (flags & kImapMsgDeletedFlag) != 0,
+ nullptr);
+
+ uint32_t supportedFlags;
+ GetSupportedUserFlags(&supportedFlags);
+ if (supportedFlags & kImapMsgSupportForwardedFlag)
+ database->MarkForwarded(msgKey, (flags & kImapMsgForwardedFlag) != 0,
+ nullptr);
+ if (supportedFlags & kImapMsgSupportMDNSentFlag)
+ database->MarkMDNSent(msgKey, (flags & kImapMsgMDNSentFlag) != 0, nullptr);
+
+ return NS_OK;
+}
+
+// message flags operation - this is called from the imap protocol,
+// proxied over from the imap thread to the ui thread, when a flag changes
+NS_IMETHODIMP
+nsImapMailFolder::NotifyMessageFlags(uint32_t aFlags,
+ const nsACString& aKeywords,
+ nsMsgKey aMsgKey,
+ uint64_t aHighestModSeq) {
+ if (NS_SUCCEEDED(GetDatabase()) && mDatabase) {
+ bool msgDeleted = aFlags & kImapMsgDeletedFlag;
+ if (aHighestModSeq || msgDeleted) {
+ nsCOMPtr<nsIDBFolderInfo> dbFolderInfo;
+ mDatabase->GetDBFolderInfo(getter_AddRefs(dbFolderInfo));
+ if (dbFolderInfo) {
+ if (aHighestModSeq) {
+ char intStrBuf[40];
+ PR_snprintf(intStrBuf, sizeof(intStrBuf), "%llu", aHighestModSeq);
+ MOZ_LOG(IMAP_CS, mozilla::LogLevel::Debug,
+ ("NotifyMessageFlags(): Store highest MODSEQ=%" PRIu64
+ " for folder=%s",
+ aHighestModSeq, m_onlineFolderName.get()));
+ dbFolderInfo->SetCharProperty(kModSeqPropertyName,
+ nsDependentCString(intStrBuf));
+ }
+ if (msgDeleted) {
+ uint32_t oldDeletedCount;
+ dbFolderInfo->GetUint32Property(kDeletedHdrCountPropertyName, 0,
+ &oldDeletedCount);
+ dbFolderInfo->SetUint32Property(kDeletedHdrCountPropertyName,
+ oldDeletedCount + 1);
+ }
+ }
+ }
+ nsCOMPtr<nsIMsgDBHdr> dbHdr;
+ bool containsKey;
+ nsresult rv = mDatabase->ContainsKey(aMsgKey, &containsKey);
+ // if we don't have the header, don't diddle the flags.
+ // GetMsgHdrForKey will create the header if it doesn't exist.
+ if (NS_FAILED(rv) || !containsKey) return rv;
+ rv = mDatabase->GetMsgHdrForKey(aMsgKey, getter_AddRefs(dbHdr));
+ if (NS_SUCCEEDED(rv) && dbHdr) {
+ uint32_t supportedUserFlags;
+ GetSupportedUserFlags(&supportedUserFlags);
+ NotifyMessageFlagsFromHdr(dbHdr, aMsgKey, aFlags);
+ nsCString keywords(aKeywords);
+ HandleCustomFlags(aMsgKey, dbHdr, supportedUserFlags, keywords);
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::NotifyMessageDeleted(const char* onlineFolderName,
+ bool deleteAllMsgs,
+ const char* msgIdString) {
+ if (deleteAllMsgs) return NS_OK;
+
+ if (!msgIdString) return NS_OK;
+
+ nsTArray<nsMsgKey> affectedMessages;
+ ParseUidString(msgIdString, affectedMessages);
+
+ if (!ShowDeletedMessages()) {
+ GetDatabase();
+ NS_ENSURE_TRUE(mDatabase, NS_OK);
+ if (!ShowDeletedMessages()) {
+ if (!affectedMessages.IsEmpty()) // perhaps Search deleted these messages
+ {
+ DeleteStoreMessages(affectedMessages);
+ mDatabase->DeleteMessages(affectedMessages, nullptr);
+ }
+ } else // && !imapDeleteIsMoveToTrash // TODO: can this ever be executed?
+ SetIMAPDeletedFlag(mDatabase, affectedMessages, false);
+ }
+ return NS_OK;
+}
+
+bool nsImapMailFolder::ShowDeletedMessages() {
+ nsresult rv;
+ nsCOMPtr<nsIImapHostSessionList> hostSession =
+ do_GetService(kCImapHostSessionList, &rv);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ bool showDeleted = false;
+ nsCString serverKey;
+ GetServerKey(serverKey);
+ hostSession->GetShowDeletedMessagesForHost(serverKey.get(), showDeleted);
+
+ return showDeleted;
+}
+
+bool nsImapMailFolder::DeleteIsMoveToTrash() {
+ nsresult err;
+ nsCOMPtr<nsIImapHostSessionList> hostSession =
+ do_GetService(kCImapHostSessionList, &err);
+ NS_ENSURE_SUCCESS(err, true);
+ bool rv = true;
+
+ nsCString serverKey;
+ GetServerKey(serverKey);
+ hostSession->GetDeleteIsMoveToTrashForHost(serverKey.get(), rv);
+ return rv;
+}
+
+nsresult nsImapMailFolder::GetTrashFolder(nsIMsgFolder** pTrashFolder) {
+ NS_ENSURE_ARG_POINTER(pTrashFolder);
+ nsCOMPtr<nsIMsgFolder> rootFolder;
+ nsresult rv = GetRootFolder(getter_AddRefs(rootFolder));
+ if (NS_SUCCEEDED(rv) && rootFolder) {
+ rv = rootFolder->GetFolderWithFlags(nsMsgFolderFlags::Trash, pTrashFolder);
+ if (!*pTrashFolder) rv = NS_ERROR_FAILURE;
+ }
+ return rv;
+}
+
+// store nsMsgMessageFlags::IMAPDeleted in the specified mailhdr records
+void nsImapMailFolder::SetIMAPDeletedFlag(nsIMsgDatabase* mailDB,
+ const nsTArray<nsMsgKey>& msgids,
+ bool markDeleted) {
+ nsresult markStatus = NS_OK;
+ uint32_t total = msgids.Length();
+
+ for (uint32_t msgIndex = 0; NS_SUCCEEDED(markStatus) && (msgIndex < total);
+ msgIndex++)
+ markStatus =
+ mailDB->MarkImapDeleted(msgids[msgIndex], markDeleted, nullptr);
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::GetMessageSizeFromDB(const char* id, uint32_t* size) {
+ NS_ENSURE_ARG_POINTER(size);
+
+ *size = 0;
+ nsresult rv = GetDatabase();
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (id) {
+ nsMsgKey key = msgKeyFromInt(ParseUint64Str(id));
+ nsCOMPtr<nsIMsgDBHdr> mailHdr;
+ rv = mDatabase->GetMsgHdrForKey(key, getter_AddRefs(mailHdr));
+ if (NS_SUCCEEDED(rv) && mailHdr) rv = mailHdr->GetMessageSize(size);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::GetCurMoveCopyMessageInfo(nsIImapUrl* runningUrl,
+ PRTime* aDate,
+ nsACString& aKeywords,
+ uint32_t* aResult) {
+ nsCOMPtr<nsISupports> copyState;
+ runningUrl->GetCopyState(getter_AddRefs(copyState));
+ if (copyState) {
+ nsCOMPtr<nsImapMailCopyState> mailCopyState = do_QueryInterface(copyState);
+ uint32_t supportedFlags = 0;
+ GetSupportedUserFlags(&supportedFlags);
+ if (mailCopyState &&
+ mailCopyState->m_curIndex < mailCopyState->m_messages.Length()) {
+ nsIMsgDBHdr* message =
+ mailCopyState->m_messages[mailCopyState->m_curIndex];
+ message->GetFlags(aResult);
+ if (aDate) message->GetDate(aDate);
+ if (supportedFlags & kImapMsgSupportUserFlag) {
+ // setup the custom imap keywords, which includes the message keywords
+ // plus any junk status
+ nsCString junkscore;
+ message->GetStringProperty("junkscore", junkscore);
+ bool isJunk = false, isNotJunk = false;
+ if (!junkscore.IsEmpty()) {
+ if (junkscore.EqualsLiteral("0"))
+ isNotJunk = true;
+ else
+ isJunk = true;
+ }
+
+ nsCString keywords; // MsgFindKeyword can't use nsACString
+ message->GetStringProperty("keywords", keywords);
+ int32_t start;
+ int32_t length;
+ bool hasJunk = MsgFindKeyword("junk"_ns, keywords, &start, &length);
+ if (hasJunk && !isJunk)
+ keywords.Cut(start, length);
+ else if (!hasJunk && isJunk)
+ keywords.AppendLiteral(" Junk");
+ bool hasNonJunk =
+ MsgFindKeyword("nonjunk"_ns, keywords, &start, &length);
+ if (!hasNonJunk)
+ hasNonJunk = MsgFindKeyword("notjunk"_ns, keywords, &start, &length);
+ if (hasNonJunk && !isNotJunk)
+ keywords.Cut(start, length);
+ else if (!hasNonJunk && isNotJunk)
+ keywords.AppendLiteral(" NonJunk");
+
+ // Cleanup extra spaces
+ while (!keywords.IsEmpty() && keywords.First() == ' ')
+ keywords.Cut(0, 1);
+ while (!keywords.IsEmpty() && keywords.Last() == ' ')
+ keywords.Cut(keywords.Length() - 1, 1);
+ while (!keywords.IsEmpty() && (start = keywords.Find(" "_ns)) >= 0)
+ keywords.Cut(start, 1);
+ aKeywords.Assign(keywords);
+ }
+ }
+ // if we don't have a source header, and it's not the drafts folder,
+ // then mark the message read, since it must be an append to the
+ // fcc or templates folder.
+ else if (mailCopyState) {
+ *aResult = mailCopyState->m_newMsgFlags;
+ if (supportedFlags & kImapMsgSupportUserFlag)
+ aKeywords.Assign(mailCopyState->m_newMsgKeywords);
+ }
+ }
+ return NS_OK;
+}
+
+// nsIUrlListener implementation.
+NS_IMETHODIMP
+nsImapMailFolder::OnStartRunningUrl(nsIURI* aUrl) {
+ NS_ASSERTION(aUrl, "sanity check - need to be be running non-null url");
+ nsCOMPtr<nsIMsgMailNewsUrl> mailUrl = do_QueryInterface(aUrl);
+ if (mailUrl) {
+ bool updatingFolder;
+ mailUrl->GetUpdatingFolder(&updatingFolder);
+ m_updatingFolder = updatingFolder;
+ }
+ m_urlRunning = true;
+ return NS_OK;
+}
+
+// nsIUrlListener implementation.
+// nsImapMailFolder passes itself as a listener when it kicks off operations
+// on the nsIImapService. So, when the operation completes, this gets called
+// to handle all the different operations, using a big switch statement.
+NS_IMETHODIMP
+nsImapMailFolder::OnStopRunningUrl(nsIURI* aUrl, nsresult aExitCode) {
+ nsresult rv;
+ bool endedOfflineDownload = false;
+ nsImapAction imapAction = nsIImapUrl::nsImapTest;
+ m_urlRunning = false;
+ m_updatingFolder = false;
+ nsCOMPtr<nsIMsgMailSession> session =
+ do_GetService("@mozilla.org/messenger/services/session;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (aUrl) {
+ nsCOMPtr<nsIImapUrl> imapUrl = do_QueryInterface(aUrl, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ bool downloadingForOfflineUse;
+ imapUrl->GetStoreResultsOffline(&downloadingForOfflineUse);
+ bool hasSemaphore = false;
+ // if we have the folder locked, clear it.
+ TestSemaphore(static_cast<nsIMsgFolder*>(this), &hasSemaphore);
+ if (hasSemaphore) ReleaseSemaphore(static_cast<nsIMsgFolder*>(this));
+ if (downloadingForOfflineUse) {
+ endedOfflineDownload = true;
+ EndOfflineDownload();
+ }
+ nsCOMPtr<nsIMsgWindow> msgWindow;
+ nsCOMPtr<nsIMsgMailNewsUrl> mailUrl = do_QueryInterface(aUrl);
+ bool folderOpen = false;
+ if (mailUrl) mailUrl->GetMsgWindow(getter_AddRefs(msgWindow));
+ if (session) session->IsFolderOpenInWindow(this, &folderOpen);
+#ifdef DEBUG_bienvenu
+ printf("stop running url %s\n", aUrl->GetSpecOrDefault().get());
+#endif
+
+ if (imapUrl) {
+ DisplayStatusMsg(imapUrl, EmptyString());
+ imapUrl->GetImapAction(&imapAction);
+ if (imapAction == nsIImapUrl::nsImapMsgFetch ||
+ imapAction == nsIImapUrl::nsImapMsgDownloadForOffline) {
+ ReleaseSemaphore(static_cast<nsIMsgFolder*>(this));
+ if (!endedOfflineDownload) EndOfflineDownload();
+ }
+
+ // Notify move, copy or delete (online operations)
+ // Not sure whether nsImapDeleteMsg is even used, deletes in all three
+ // models use nsImapAddMsgFlags.
+ nsCOMPtr<nsIMsgFolderNotificationService> notifier(
+ do_GetService("@mozilla.org/messenger/msgnotificationservice;1"));
+ if (notifier && m_copyState) {
+ if (imapAction == nsIImapUrl::nsImapOnlineMove) {
+ notifier->NotifyMsgsMoveCopyCompleted(true, m_copyState->m_messages,
+ this, {});
+ } else if (imapAction == nsIImapUrl::nsImapOnlineCopy) {
+ notifier->NotifyMsgsMoveCopyCompleted(false, m_copyState->m_messages,
+ this, {});
+ } else if (imapAction == nsIImapUrl::nsImapDeleteMsg) {
+ notifier->NotifyMsgsDeleted(m_copyState->m_messages);
+ }
+ }
+
+ switch (imapAction) {
+ case nsIImapUrl::nsImapDeleteMsg:
+ case nsIImapUrl::nsImapOnlineMove:
+ case nsIImapUrl::nsImapOnlineCopy:
+ if (NS_SUCCEEDED(aExitCode)) {
+ if (folderOpen)
+ UpdateFolder(msgWindow);
+ else
+ UpdatePendingCounts();
+ }
+
+ if (m_copyState) {
+ nsCOMPtr<nsIMsgFolder> srcFolder =
+ do_QueryInterface(m_copyState->m_srcSupport, &rv);
+ if (m_copyState->m_isMove && !m_copyState->m_isCrossServerOp) {
+ if (NS_SUCCEEDED(aExitCode)) {
+ nsCOMPtr<nsIMsgDatabase> srcDB;
+ if (srcFolder)
+ rv = srcFolder->GetMsgDatabase(getter_AddRefs(srcDB));
+ if (NS_SUCCEEDED(rv) && srcDB) {
+ RefPtr<nsImapMoveCopyMsgTxn> msgTxn;
+ nsTArray<nsMsgKey> srcKeyArray;
+ if (m_copyState->m_allowUndo) {
+ msgTxn = m_copyState->m_undoMsgTxn;
+ if (msgTxn) msgTxn->GetSrcKeyArray(srcKeyArray);
+ } else {
+ nsAutoCString messageIds;
+ rv = BuildIdsAndKeyArray(m_copyState->m_messages,
+ messageIds, srcKeyArray);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (!ShowDeletedMessages()) {
+ // We only reach here for same-server operations
+ // (!m_copyState->m_isCrossServerOp in if above), so we can
+ // assume that the src is also imap that uses offline
+ // storage.
+ DeleteStoreMessages(srcKeyArray, srcFolder);
+ srcDB->DeleteMessages(srcKeyArray, nullptr);
+ } else
+ MarkMessagesImapDeleted(&srcKeyArray, true, srcDB);
+ }
+ srcFolder->EnableNotifications(allMessageCountNotifications,
+ true);
+ // even if we're showing deleted messages,
+ // we still need to notify FE so it will show the imap deleted
+ // flag
+ srcFolder->NotifyFolderEvent(kDeleteOrMoveMsgCompleted);
+ // is there a way to see that we think we have new msgs?
+ nsCOMPtr<nsIPrefBranch> prefBranch(
+ do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ if (NS_SUCCEEDED(rv)) {
+ bool showPreviewText;
+ prefBranch->GetBoolPref("mail.biff.alert.show_preview",
+ &showPreviewText);
+ // if we're showing preview text, update ourselves if we got a
+ // new unread message copied so that we can download the new
+ // headers and have a chance to preview the msg bodies.
+ if (!folderOpen && showPreviewText &&
+ m_copyState->m_unreadCount > 0 &&
+ !(mFlags &
+ (nsMsgFolderFlags::Trash | nsMsgFolderFlags::Junk)))
+ UpdateFolder(msgWindow);
+ }
+ } else {
+ srcFolder->EnableNotifications(allMessageCountNotifications,
+ true);
+ srcFolder->NotifyFolderEvent(kDeleteOrMoveMsgFailed);
+ }
+ }
+ if (m_copyState->m_msgWindow &&
+ m_copyState->m_undoMsgTxn && // may be null from filters
+ NS_SUCCEEDED(
+ aExitCode)) // we should do this only if move/copy succeeds
+ {
+ nsCOMPtr<nsITransactionManager> txnMgr;
+ m_copyState->m_msgWindow->GetTransactionManager(
+ getter_AddRefs(txnMgr));
+ if (txnMgr) {
+ RefPtr<nsImapMoveCopyMsgTxn> txn = m_copyState->m_undoMsgTxn;
+ mozilla::DebugOnly<nsresult> rv2 = txnMgr->DoTransaction(txn);
+ NS_ASSERTION(NS_SUCCEEDED(rv2), "doing transaction failed");
+ }
+ }
+ // nsImapUrl can hold a pointer to our m_copyState, so force a
+ // release here (see Bug 1586494).
+ imapUrl->SetCopyState(nullptr);
+ (void)OnCopyCompleted(m_copyState->m_srcSupport, aExitCode);
+ }
+
+ // we're the dest folder of a move/copy - if we're not open in the ui,
+ // then we should clear our nsMsgDatabase pointer. Otherwise, the db
+ // would be open until the user selected it and then selected another
+ // folder. but don't do this for the trash or inbox - we'll leave them
+ // open
+ if (!folderOpen &&
+ !(mFlags & (nsMsgFolderFlags::Trash | nsMsgFolderFlags::Inbox)))
+ SetMsgDatabase(nullptr);
+ break;
+ case nsIImapUrl::nsImapSubtractMsgFlags: {
+ // this isn't really right - we'd like to know we were
+ // deleting a message to start with, but it probably
+ // won't do any harm.
+ imapMessageFlagsType flags = 0;
+ imapUrl->GetMsgFlags(&flags);
+ // we need to subtract the delete flag in db only in case when we show
+ // deleted msgs
+ if (flags & kImapMsgDeletedFlag && ShowDeletedMessages()) {
+ nsCOMPtr<nsIMsgDatabase> db;
+ rv = GetMsgDatabase(getter_AddRefs(db));
+ if (NS_SUCCEEDED(rv) && db) {
+ nsTArray<nsMsgKey> keyArray;
+ nsCString keyString;
+ imapUrl->GetListOfMessageIds(keyString);
+ ParseUidString(keyString.get(), keyArray);
+ MarkMessagesImapDeleted(&keyArray, false, db);
+ db->Commit(nsMsgDBCommitType::kLargeCommit);
+ }
+ }
+ } break;
+ case nsIImapUrl::nsImapAddMsgFlags: {
+ imapMessageFlagsType flags = 0;
+ imapUrl->GetMsgFlags(&flags);
+ if (flags & kImapMsgDeletedFlag) {
+ // we need to delete headers from db only when we don't show deleted
+ // msgs
+ if (!ShowDeletedMessages()) {
+ nsCOMPtr<nsIMsgDatabase> db;
+ rv = GetMsgDatabase(getter_AddRefs(db));
+ if (NS_SUCCEEDED(rv) && db) {
+ nsTArray<nsMsgKey> keyArray;
+ nsCString keyString;
+ imapUrl->GetListOfMessageIds(keyString);
+ ParseUidString(keyString.get(), keyArray);
+
+ // For pluggable stores that do not support compaction, we need
+ // to delete the messages now.
+ bool supportsCompaction = false;
+ nsCOMPtr<nsIMsgPluggableStore> offlineStore;
+ (void)GetMsgStore(getter_AddRefs(offlineStore));
+ if (offlineStore)
+ offlineStore->GetSupportsCompaction(&supportsCompaction);
+
+ nsTArray<RefPtr<nsIMsgDBHdr>> msgHdrs;
+ if (notifier || !supportsCompaction) {
+ MsgGetHeadersFromKeys(db, keyArray, msgHdrs);
+ }
+
+ // Notify listeners of delete.
+ if (notifier && !msgHdrs.IsEmpty()) {
+ // XXX Currently, the DeleteMessages below gets executed twice
+ // on deletes. Once in DeleteMessages, once here. The second
+ // time, it silently fails to delete. This is why we're also
+ // checking whether the array is empty.
+ notifier->NotifyMsgsDeleted(msgHdrs);
+ }
+
+ if (!supportsCompaction && !msgHdrs.IsEmpty())
+ DeleteStoreMessages(msgHdrs);
+
+ db->DeleteMessages(keyArray, nullptr);
+ db->SetSummaryValid(true);
+ db->Commit(nsMsgDBCommitType::kLargeCommit);
+ }
+ }
+ }
+ } break;
+ case nsIImapUrl::nsImapAppendMsgFromFile:
+ case nsIImapUrl::nsImapAppendDraftFromFile:
+ if (m_copyState) {
+ if (NS_SUCCEEDED(aExitCode)) {
+ UpdatePendingCounts();
+
+ m_copyState->m_curIndex++;
+ if (m_copyState->m_curIndex >= m_copyState->m_messages.Length()) {
+ nsCOMPtr<nsIUrlListener> saveUrlListener = m_urlListener;
+ if (folderOpen) {
+ // This gives a way for the caller to get notified
+ // when the UpdateFolder url is done.
+ // (if the nsIMsgCopyServiceListener also implements
+ // nsIUrlListener)
+ if (m_copyState->m_listener)
+ m_urlListener = do_QueryInterface(m_copyState->m_listener);
+ }
+ if (m_copyState->m_msgWindow && m_copyState->m_undoMsgTxn) {
+ nsCOMPtr<nsITransactionManager> txnMgr;
+ m_copyState->m_msgWindow->GetTransactionManager(
+ getter_AddRefs(txnMgr));
+ if (txnMgr) {
+ RefPtr<nsImapMoveCopyMsgTxn> txn =
+ m_copyState->m_undoMsgTxn;
+ txnMgr->DoTransaction(txn);
+ }
+ }
+ (void)OnCopyCompleted(m_copyState->m_srcSupport, aExitCode);
+ if (folderOpen ||
+ imapAction == nsIImapUrl::nsImapAppendDraftFromFile) {
+ UpdateFolderWithListener(msgWindow, m_urlListener);
+ m_urlListener = saveUrlListener;
+ }
+ }
+ } else {
+ // clear the copyState if copy has failed
+ (void)OnCopyCompleted(m_copyState->m_srcSupport, aExitCode);
+ }
+ }
+ break;
+ case nsIImapUrl::nsImapMoveFolderHierarchy:
+ if (m_copyState) // delete folder gets here, but w/o an m_copyState
+ {
+ nsCOMPtr<nsIMsgCopyService> copyService = do_GetService(
+ "@mozilla.org/messenger/messagecopyservice;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIMsgFolder> srcFolder =
+ do_QueryInterface(m_copyState->m_srcSupport);
+ if (srcFolder) {
+ copyService->NotifyCompletion(m_copyState->m_srcSupport, this,
+ aExitCode);
+ }
+ m_copyState = nullptr;
+ }
+ break;
+ case nsIImapUrl::nsImapRenameFolder:
+ if (NS_FAILED(aExitCode)) {
+ NotifyFolderEvent(kRenameCompleted);
+ }
+ break;
+ case nsIImapUrl::nsImapDeleteAllMsgs:
+ if (NS_SUCCEEDED(aExitCode)) {
+ if (folderOpen)
+ UpdateFolder(msgWindow);
+ else {
+ ChangeNumPendingTotalMessages(-mNumPendingTotalMessages);
+ ChangeNumPendingUnread(-mNumPendingUnreadMessages);
+ m_numServerUnseenMessages = 0;
+ }
+ }
+ break;
+ case nsIImapUrl::nsImapListFolder:
+ if (NS_SUCCEEDED(aExitCode)) {
+ // listing folder will open db; don't leave the db open.
+ SetMsgDatabase(nullptr);
+ if (!m_verifiedAsOnlineFolder) {
+ // If folder is not verified, we remove it.
+ nsCOMPtr<nsIMsgFolder> parent;
+ rv = GetParent(getter_AddRefs(parent));
+ if (NS_SUCCEEDED(rv) && parent) {
+ nsCOMPtr<nsIMsgImapMailFolder> imapParent =
+ do_QueryInterface(parent);
+ if (imapParent) this->RemoveLocalSelf();
+ }
+ }
+ }
+ break;
+ case nsIImapUrl::nsImapRefreshFolderUrls:
+ // we finished getting an admin url for the folder.
+ if (!m_adminUrl.IsEmpty()) FolderPrivileges(msgWindow);
+ break;
+ case nsIImapUrl::nsImapCreateFolder:
+ if (NS_FAILED(aExitCode)) // if success notification already done
+ {
+ NotifyFolderEvent(kFolderCreateFailed);
+ }
+ break;
+ case nsIImapUrl::nsImapSubscribe:
+ if (NS_SUCCEEDED(aExitCode) && msgWindow) {
+ nsCString canonicalFolderName;
+ imapUrl->CreateCanonicalSourceFolderPathString(
+ getter_Copies(canonicalFolderName));
+ nsCOMPtr<nsIMsgFolder> rootFolder;
+ nsresult rv = GetRootFolder(getter_AddRefs(rootFolder));
+ if (NS_SUCCEEDED(rv) && rootFolder) {
+ nsCOMPtr<nsIMsgImapMailFolder> imapRoot =
+ do_QueryInterface(rootFolder);
+ if (imapRoot) {
+ nsCOMPtr<nsIMsgImapMailFolder> foundFolder;
+ rv = imapRoot->FindOnlineSubFolder(canonicalFolderName,
+ getter_AddRefs(foundFolder));
+ if (NS_SUCCEEDED(rv) && foundFolder) {
+ nsCString uri;
+ nsCOMPtr<nsIMsgFolder> msgFolder =
+ do_QueryInterface(foundFolder);
+ if (msgFolder) {
+ nsCOMPtr<nsIObserverService> obsServ =
+ mozilla::services::GetObserverService();
+ obsServ->NotifyObservers(msgFolder, "folder-subscribed",
+ nullptr);
+ }
+ }
+ }
+ }
+ }
+ break;
+ case nsIImapUrl::nsImapExpungeFolder:
+ break;
+ default:
+ break;
+ }
+ }
+ // give base class a chance to send folder loaded notification...
+ rv = nsMsgDBFolder::OnStopRunningUrl(aUrl, aExitCode);
+ }
+ // if we're not running a url, we must not be getting new mail.
+ SetGettingNewMessages(false);
+
+ // If we're planning to inform another listener then do that now.
+ // Some folder methods can take a listener to inform when the operation
+ // is complete e.g. UpdateFolderWithListener(), DownloadAllForOffline(),
+ // GetNewMessages(). That listener is stashed in m_urlListener.
+ if (m_urlListener) {
+ nsCOMPtr<nsIUrlListener> saveListener = m_urlListener;
+ m_urlListener = nullptr;
+ saveListener->OnStopRunningUrl(aUrl, aExitCode);
+ }
+ return rv;
+}
+
+void nsImapMailFolder::UpdatePendingCounts() {
+ if (m_copyState) {
+ int32_t delta =
+ m_copyState->m_isCrossServerOp ? 1 : m_copyState->m_messages.Length();
+ if (!m_copyState->m_selectedState && m_copyState->m_messages.IsEmpty()) {
+ // special case from CopyFileMessage():
+ // - copied a single message in from a file
+ // - no previously-existing messages are involved
+ delta = 1;
+ }
+ ChangePendingTotal(delta);
+
+ // count the moves that were unread
+ int numUnread = m_copyState->m_unreadCount;
+ if (numUnread) {
+ m_numServerUnseenMessages +=
+ numUnread; // adjust last status count by this delta.
+ ChangeNumPendingUnread(numUnread);
+ }
+ SummaryChanged();
+ }
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::ClearFolderRights() {
+ SetFolderNeedsACLListed(false);
+ delete m_folderACL;
+ m_folderACL = new nsMsgIMAPFolderACL(this);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::AddFolderRights(const nsACString& userName,
+ const nsACString& rights) {
+ SetFolderNeedsACLListed(false);
+ GetFolderACL()->SetFolderRightsForUser(userName, rights);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::RefreshFolderRights() {
+ if (GetFolderACL()->GetIsFolderShared())
+ SetFlag(nsMsgFolderFlags::PersonalShared);
+ else
+ ClearFlag(nsMsgFolderFlags::PersonalShared);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::SetCopyResponseUid(const char* msgIdString,
+ nsIImapUrl* aUrl) { // CopyMessages() only
+ nsresult rv = NS_OK;
+ RefPtr<nsImapMoveCopyMsgTxn> msgTxn;
+ nsCOMPtr<nsISupports> copyState;
+
+ if (aUrl) aUrl->GetCopyState(getter_AddRefs(copyState));
+
+ if (copyState) {
+ nsCOMPtr<nsImapMailCopyState> mailCopyState =
+ do_QueryInterface(copyState, &rv);
+ if (NS_FAILED(rv)) return rv;
+ if (mailCopyState->m_undoMsgTxn) msgTxn = mailCopyState->m_undoMsgTxn;
+ } else if (aUrl && m_pendingOfflineMoves.Length()) {
+ nsCString urlSourceMsgIds, undoTxnSourceMsgIds;
+ aUrl->GetListOfMessageIds(urlSourceMsgIds);
+ RefPtr<nsImapMoveCopyMsgTxn> imapUndo = m_pendingOfflineMoves[0];
+ if (imapUndo) {
+ imapUndo->GetSrcMsgIds(undoTxnSourceMsgIds);
+ if (undoTxnSourceMsgIds.Equals(urlSourceMsgIds)) msgTxn = imapUndo;
+ // ### we should handle batched moves, but lets keep it simple for a2.
+ m_pendingOfflineMoves.Clear();
+ }
+ }
+ if (msgTxn) msgTxn->SetCopyResponseUid(msgIdString);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::StartMessage(nsIMsgMailNewsUrl* aUrl) {
+ nsCOMPtr<nsIImapUrl> imapUrl(do_QueryInterface(aUrl));
+ nsCOMPtr<nsISupports> copyState;
+ NS_ENSURE_TRUE(imapUrl, NS_ERROR_FAILURE);
+
+ imapUrl->GetCopyState(getter_AddRefs(copyState));
+ if (copyState) {
+ nsCOMPtr<nsICopyMessageStreamListener> listener =
+ do_QueryInterface(copyState);
+ if (listener) listener->StartMessage();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::EndMessage(nsIMsgMailNewsUrl* aUrl, nsMsgKey uidOfMessage) {
+ nsCOMPtr<nsIImapUrl> imapUrl(do_QueryInterface(aUrl));
+ nsCOMPtr<nsISupports> copyState;
+ NS_ENSURE_TRUE(imapUrl, NS_ERROR_FAILURE);
+ imapUrl->GetCopyState(getter_AddRefs(copyState));
+ if (copyState) {
+ nsCOMPtr<nsICopyMessageStreamListener> listener =
+ do_QueryInterface(copyState);
+ if (listener) listener->EndMessage(uidOfMessage);
+ }
+ return NS_OK;
+}
+
+#define WHITESPACE " \015\012" // token delimiter
+
+NS_IMETHODIMP
+nsImapMailFolder::NotifySearchHit(nsIMsgMailNewsUrl* aUrl,
+ const char* searchHitLine) {
+ NS_ENSURE_ARG_POINTER(aUrl);
+ nsresult rv = GetDatabase();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // expect search results in the form of "* SEARCH <hit> <hit> ..."
+ // expect search results in the form of "* SEARCH <hit> <hit> ..."
+ nsCString tokenString(searchHitLine);
+ char* currentPosition = PL_strcasestr(tokenString.get(), "SEARCH");
+ if (currentPosition) {
+ currentPosition += strlen("SEARCH");
+ bool shownUpdateAlert = false;
+ char* hitUidToken = NS_strtok(WHITESPACE, &currentPosition);
+ while (hitUidToken) {
+ long naturalLong; // %l is 64 bits on OSF1
+ sscanf(hitUidToken, "%ld", &naturalLong);
+ nsMsgKey hitUid = (nsMsgKey)naturalLong;
+
+ nsCOMPtr<nsIMsgDBHdr> hitHeader;
+ rv = mDatabase->GetMsgHdrForKey(hitUid, getter_AddRefs(hitHeader));
+ if (NS_SUCCEEDED(rv) && hitHeader) {
+ nsCOMPtr<nsIMsgSearchSession> searchSession;
+ nsCOMPtr<nsIMsgSearchAdapter> searchAdapter;
+ aUrl->GetSearchSession(getter_AddRefs(searchSession));
+ if (searchSession) {
+ searchSession->GetRunningAdapter(getter_AddRefs(searchAdapter));
+ if (searchAdapter) searchAdapter->AddResultElement(hitHeader);
+ }
+ } else if (!shownUpdateAlert) {
+ }
+
+ hitUidToken = NS_strtok(WHITESPACE, &currentPosition);
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::SetAppendMsgUid(nsMsgKey aKey, nsIImapUrl* aUrl) {
+ nsresult rv;
+ nsCOMPtr<nsISupports> copyState;
+ if (aUrl) aUrl->GetCopyState(getter_AddRefs(copyState));
+ if (copyState) {
+ nsCOMPtr<nsImapMailCopyState> mailCopyState =
+ do_QueryInterface(copyState, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ if (mailCopyState->m_undoMsgTxn) // CopyMessages()
+ {
+ RefPtr<nsImapMoveCopyMsgTxn> msgTxn;
+ msgTxn = mailCopyState->m_undoMsgTxn;
+ msgTxn->AddDstKey(aKey);
+ } else if (mailCopyState->m_listener) // CopyFileMessage();
+ // Draft/Template goes here
+ {
+ mailCopyState->m_appendUID = aKey;
+ mailCopyState->m_listener->SetMessageKey(aKey);
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::GetMessageId(nsIImapUrl* aUrl, nsACString& messageId) {
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsISupports> copyState;
+
+ if (aUrl) aUrl->GetCopyState(getter_AddRefs(copyState));
+ if (copyState) {
+ nsCOMPtr<nsImapMailCopyState> mailCopyState =
+ do_QueryInterface(copyState, &rv);
+ if (NS_FAILED(rv)) return rv;
+ if (mailCopyState->m_listener)
+ rv = mailCopyState->m_listener->GetMessageId(messageId);
+ }
+ if (NS_SUCCEEDED(rv) && messageId.Length() > 0) {
+ if (messageId.First() == '<') messageId.Cut(0, 1);
+ if (messageId.Last() == '>') messageId.SetLength(messageId.Length() - 1);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::HeaderFetchCompleted(nsIImapProtocol* aProtocol) {
+ nsCOMPtr<nsIMsgWindow>
+ msgWindow; // we might need this for the filter plugins.
+ if (mBackupDatabase) RemoveBackupMsgDatabase();
+
+ SetSizeOnDisk(mFolderSize);
+ int32_t numNewBiffMsgs = 0;
+ if (m_performingBiff) GetNumNewMessages(false, &numNewBiffMsgs);
+
+ bool pendingMoves = m_moveCoalescer && m_moveCoalescer->HasPendingMoves();
+ PlaybackCoalescedOperations();
+ if (aProtocol) {
+ // check if we should download message bodies because it's the inbox and
+ // the server is specified as one where where we download msg bodies
+ // automatically. Or if we autosyncing all offline folders.
+ nsCOMPtr<nsIImapIncomingServer> imapServer;
+ GetImapIncomingServer(getter_AddRefs(imapServer));
+
+ bool autoDownloadNewHeaders = false;
+ bool autoSyncOfflineStores = false;
+
+ if (imapServer) {
+ imapServer->GetAutoSyncOfflineStores(&autoSyncOfflineStores);
+ imapServer->GetDownloadBodiesOnGetNewMail(&autoDownloadNewHeaders);
+ if (m_filterListRequiresBody) autoDownloadNewHeaders = true;
+ }
+ bool notifiedBodies = false;
+ if (m_downloadingFolderForOfflineUse || autoSyncOfflineStores ||
+ autoDownloadNewHeaders) {
+ nsTArray<nsMsgKey> keysToDownload;
+ GetBodysToDownload(&keysToDownload);
+ if (!keysToDownload.IsEmpty() &&
+ (m_downloadingFolderForOfflineUse || autoDownloadNewHeaders)) {
+ // this is the case when DownloadAllForOffline is called.
+ notifiedBodies = true;
+ aProtocol->NotifyBodysToDownload(keysToDownload);
+ } else {
+ // create auto-sync state object lazily
+ InitAutoSyncState();
+ if (MOZ_LOG_TEST(gAutoSyncLog, mozilla::LogLevel::Debug)) {
+ int32_t flags = 0;
+ GetFlags((uint32_t*)&flags);
+ nsString folderName;
+ GetName(folderName);
+ nsCString utfLeafName;
+ CopyUTF16toUTF8(folderName, utfLeafName);
+ MOZ_LOG(gAutoSyncLog, mozilla::LogLevel::Debug,
+ ("%s: foldername=%s, flags=0x%X, "
+ "isOffline=%s, nsMsgFolderFlags::Offline=0x%X",
+ __func__, utfLeafName.get(), flags,
+ (flags & nsMsgFolderFlags::Offline) ? "true" : "false",
+ nsMsgFolderFlags::Offline));
+ MOZ_LOG(gAutoSyncLog, mozilla::LogLevel::Debug,
+ ("%s: created autosync obj, have keys to download=%s",
+ __func__, keysToDownload.IsEmpty() ? "false" : "true"));
+ }
+ // make enough room for new downloads
+ m_autoSyncStateObj->ManageStorageSpace(); // currently a no-op
+
+ m_autoSyncStateObj->SetServerCounts(
+ m_numServerTotalMessages, m_numServerRecentMessages,
+ m_numServerUnseenMessages, m_nextUID);
+ m_autoSyncStateObj->OnNewHeaderFetchCompleted(keysToDownload);
+ }
+ }
+ if (!notifiedBodies) {
+ nsTArray<nsMsgKey> noBodies;
+ aProtocol->NotifyBodysToDownload(noBodies);
+ }
+
+ nsCOMPtr<nsIURI> runningUri;
+ aProtocol->GetRunningUrl(getter_AddRefs(runningUri));
+ if (runningUri) {
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(runningUri);
+ if (mailnewsUrl) mailnewsUrl->GetMsgWindow(getter_AddRefs(msgWindow));
+ }
+ }
+
+ // delay calling plugins if filter application is also delayed
+ if (!m_filterListRequiresBody) {
+ bool filtersRun;
+ CallFilterPlugins(msgWindow, &filtersRun);
+ if (!filtersRun && m_performingBiff && mDatabase && numNewBiffMsgs > 0 &&
+ (!pendingMoves || !ShowPreviewText())) {
+ // If we are performing biff for this folder, tell the
+ // stand-alone biff about the new high water mark
+ // We must ensure that the server knows that we are performing biff.
+ // Otherwise the stand-alone biff won't fire.
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ if (NS_SUCCEEDED(GetServer(getter_AddRefs(server))) && server)
+ server->SetPerformingBiff(true);
+
+ SetBiffState(nsIMsgFolder::nsMsgBiffState_NewMail);
+ if (server) server->SetPerformingBiff(false);
+ m_performingBiff = false;
+ }
+
+ if (m_filterList) (void)m_filterList->FlushLogIfNecessary();
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::SetBiffStateAndUpdate(nsMsgBiffState biffState) {
+ SetBiffState(biffState);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::GetUidValidity(int32_t* uidValidity) {
+ NS_ENSURE_ARG(uidValidity);
+ if ((int32_t)m_uidValidity == kUidUnknown) {
+ nsCOMPtr<nsIMsgDatabase> db;
+ nsCOMPtr<nsIDBFolderInfo> dbFolderInfo;
+ (void)GetDBFolderInfoAndDB(getter_AddRefs(dbFolderInfo),
+ getter_AddRefs(db));
+ if (db) db->GetDBFolderInfo(getter_AddRefs(dbFolderInfo));
+
+ if (dbFolderInfo)
+ dbFolderInfo->GetImapUidValidity((int32_t*)&m_uidValidity);
+ }
+ *uidValidity = m_uidValidity;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::SetUidValidity(int32_t uidValidity) {
+ m_uidValidity = uidValidity;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::FillInFolderProps(nsIMsgImapFolderProps* aFolderProps) {
+ NS_ENSURE_ARG(aFolderProps);
+ const char* folderTypeStringID;
+ const char* folderTypeDescStringID = nullptr;
+ const char* folderQuotaStatusStringID;
+ nsString folderType;
+ nsString folderTypeDesc;
+ nsString folderQuotaStatusDesc;
+ nsCOMPtr<nsIStringBundle> bundle;
+ nsresult rv = IMAPGetStringBundle(getter_AddRefs(bundle));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIImapIncomingServer> imapServer;
+ rv = GetImapIncomingServer(getter_AddRefs(imapServer));
+ // if for some bizarre reason this fails, we'll still fall through to the
+ // normal sharing code
+ if (NS_SUCCEEDED(rv)) {
+ // get the latest committed imap capabilities bit mask.
+ eIMAPCapabilityFlags capability = kCapabilityUndefined;
+ imapServer->GetCapability(&capability);
+ bool haveACL = capability & kACLCapability;
+ bool haveQuota = capability & kQuotaCapability;
+
+ // Figure out what to display in the Quota tab of the folder properties.
+ // Does the server support quotas? This depends on the latest imap
+ // CAPABILITY response.
+ if (haveQuota) {
+ // Have quota capability. Have we asked the server for quota information?
+ if (m_folderQuotaCommandIssued) {
+ // Has the server replied with all the quota info?
+ if (m_folderQuotaDataIsValid) {
+ if (!m_folderQuota.IsEmpty()) {
+ // If so, set quota data to show in the quota tab
+ folderQuotaStatusStringID = nullptr;
+ aFolderProps->SetQuotaData(m_folderQuota);
+ } else {
+ // The server reported no quota limits on this folder.
+ folderQuotaStatusStringID = "imapQuotaStatusNoQuota2";
+ }
+ } else {
+ // The getquotaroot command was sent to the server but the complete
+ // response was not yet received when the folder properties were
+ // requested. This is rare. Request the folder properties again to
+ // obtain the quota data.
+ folderQuotaStatusStringID = "imapQuotaStatusInProgress";
+ }
+ } else {
+ // The folder is not open, so no quota information is available
+ folderQuotaStatusStringID = "imapQuotaStatusFolderNotOpen";
+ }
+ } else {
+ // Either the server doesn't support quotas, or we don't know if it does
+ // (e.g., because we don't have a connection yet). If the latter, we fall
+ // back to saying that no information is available because the folder is
+ // not yet open.
+ folderQuotaStatusStringID = (capability == kCapabilityUndefined)
+ ? "imapQuotaStatusFolderNotOpen"
+ : "imapQuotaStatusNotSupported";
+ }
+
+ if (!folderQuotaStatusStringID) {
+ // Display quota data
+ aFolderProps->ShowQuotaData(true);
+ } else {
+ // Hide quota data and show reason why it is not available
+ aFolderProps->ShowQuotaData(false);
+
+ rv = IMAPGetStringByName(folderQuotaStatusStringID,
+ getter_Copies(folderQuotaStatusDesc));
+ if (NS_SUCCEEDED(rv)) aFolderProps->SetQuotaStatus(folderQuotaStatusDesc);
+ }
+
+ // See if the server supports ACL.
+ // If not, just set the folder description to a string that says
+ // the server doesn't support sharing, and return.
+ if (!haveACL) {
+ rv = IMAPGetStringByName("imapServerDoesntSupportAcl",
+ getter_Copies(folderTypeDesc));
+ if (NS_SUCCEEDED(rv))
+ aFolderProps->SetFolderTypeDescription(folderTypeDesc);
+ aFolderProps->ServerDoesntSupportACL();
+ return NS_OK;
+ }
+ }
+ if (mFlags & nsMsgFolderFlags::ImapPublic) {
+ folderTypeStringID = "imapPublicFolderTypeName";
+ folderTypeDescStringID = "imapPublicFolderTypeDescription";
+ } else if (mFlags & nsMsgFolderFlags::ImapOtherUser) {
+ folderTypeStringID = "imapOtherUsersFolderTypeName";
+ nsCString owner;
+ nsString uniOwner;
+ GetFolderOwnerUserName(owner);
+ if (owner.IsEmpty()) {
+ IMAPGetStringByName(folderTypeStringID, getter_Copies(uniOwner));
+ // Another user's folder, for which we couldn't find an owner name
+ NS_ASSERTION(false, "couldn't get owner name for other user's folder");
+ } else {
+ CopyUTF8toUTF16(owner, uniOwner);
+ }
+ AutoTArray<nsString, 1> params = {uniOwner};
+ bundle->FormatStringFromName("imapOtherUsersFolderTypeDescription", params,
+ folderTypeDesc);
+ } else if (GetFolderACL()->GetIsFolderShared()) {
+ folderTypeStringID = "imapPersonalSharedFolderTypeName";
+ folderTypeDescStringID = "imapPersonalSharedFolderTypeDescription";
+ } else {
+ folderTypeStringID = "imapPersonalSharedFolderTypeName";
+ folderTypeDescStringID = "imapPersonalFolderTypeDescription";
+ }
+
+ rv = IMAPGetStringByName(folderTypeStringID, getter_Copies(folderType));
+ if (NS_SUCCEEDED(rv)) aFolderProps->SetFolderType(folderType);
+
+ if (folderTypeDesc.IsEmpty() && folderTypeDescStringID)
+ IMAPGetStringByName(folderTypeDescStringID, getter_Copies(folderTypeDesc));
+ if (!folderTypeDesc.IsEmpty())
+ aFolderProps->SetFolderTypeDescription(folderTypeDesc);
+
+ nsString rightsString;
+ rv = CreateACLRightsStringForFolder(rightsString);
+ if (NS_SUCCEEDED(rv)) aFolderProps->SetFolderPermissions(rightsString);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailFolder::SetAclFlags(uint32_t aclFlags) {
+ nsresult rv = NS_OK;
+ if (m_aclFlags != aclFlags) {
+ nsCOMPtr<nsIDBFolderInfo> dbFolderInfo;
+ bool dbWasOpen = (mDatabase != nullptr);
+ rv = GetDatabase();
+
+ m_aclFlags = aclFlags;
+ if (mDatabase) {
+ rv = mDatabase->GetDBFolderInfo(getter_AddRefs(dbFolderInfo));
+ if (NS_SUCCEEDED(rv) && dbFolderInfo)
+ dbFolderInfo->SetUint32Property("aclFlags", aclFlags);
+ // if setting the acl flags caused us to open the db, release the ref
+ // because on startup, we might get acl on all folders,which will
+ // leave a lot of db's open.
+ if (!dbWasOpen) {
+ mDatabase->Close(true /* commit changes */);
+ mDatabase = nullptr;
+ }
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapMailFolder::GetAclFlags(uint32_t* aclFlags) {
+ NS_ENSURE_ARG_POINTER(aclFlags);
+ nsresult rv;
+ ReadDBFolderInfo(false); // update cache first.
+ if (m_aclFlags == kAclInvalid) // -1 means invalid value, so get it from db.
+ {
+ nsCOMPtr<nsIDBFolderInfo> dbFolderInfo;
+ bool dbWasOpen = (mDatabase != nullptr);
+ rv = GetDatabase();
+
+ if (mDatabase) {
+ rv = mDatabase->GetDBFolderInfo(getter_AddRefs(dbFolderInfo));
+ if (NS_SUCCEEDED(rv) && dbFolderInfo) {
+ rv = dbFolderInfo->GetUint32Property("aclFlags", 0, aclFlags);
+ m_aclFlags = *aclFlags;
+ }
+ // if getting the acl flags caused us to open the db, release the ref
+ // because on startup, we might get acl on all folders,which will
+ // leave a lot of db's open.
+ if (!dbWasOpen) {
+ mDatabase->Close(true /* commit changes */);
+ mDatabase = nullptr;
+ }
+ }
+ } else
+ *aclFlags = m_aclFlags;
+ return NS_OK;
+}
+
+nsresult nsImapMailFolder::SetSupportedUserFlags(uint32_t userFlags) {
+ nsCOMPtr<nsIDBFolderInfo> dbFolderInfo;
+ nsresult rv = GetDatabase();
+
+ m_supportedUserFlags = userFlags;
+ if (mDatabase) {
+ rv = mDatabase->GetDBFolderInfo(getter_AddRefs(dbFolderInfo));
+ if (NS_SUCCEEDED(rv) && dbFolderInfo)
+ dbFolderInfo->SetUint32Property("imapFlags", userFlags);
+ }
+ return rv;
+}
+
+nsresult nsImapMailFolder::GetSupportedUserFlags(uint32_t* userFlags) {
+ NS_ENSURE_ARG_POINTER(userFlags);
+
+ nsresult rv = NS_OK;
+
+ ReadDBFolderInfo(false); // update cache first.
+ if (m_supportedUserFlags == 0) // 0 means invalid value, so get it from db.
+ {
+ nsCOMPtr<nsIDBFolderInfo> dbFolderInfo;
+ rv = GetDatabase();
+
+ if (mDatabase) {
+ rv = mDatabase->GetDBFolderInfo(getter_AddRefs(dbFolderInfo));
+ if (NS_SUCCEEDED(rv) && dbFolderInfo) {
+ rv = dbFolderInfo->GetUint32Property("imapFlags", 0, userFlags);
+ m_supportedUserFlags = *userFlags;
+ }
+ }
+ } else
+ *userFlags = m_supportedUserFlags;
+ return rv;
+}
+
+NS_IMETHODIMP nsImapMailFolder::GetCanOpenFolder(bool* aBool) {
+ NS_ENSURE_ARG_POINTER(aBool);
+ bool noSelect;
+ GetFlag(nsMsgFolderFlags::ImapNoselect, &noSelect);
+ *aBool = (noSelect) ? false : GetFolderACL()->GetCanIReadFolder();
+ return NS_OK;
+}
+
+///////// nsMsgIMAPFolderACL class ///////////////////////////////
+
+// This string is defined in the ACL RFC to be "anyone"
+#define IMAP_ACL_ANYONE_STRING "anyone"
+
+nsMsgIMAPFolderACL::nsMsgIMAPFolderACL(nsImapMailFolder* folder)
+ : m_rightsHash(24) {
+ NS_ASSERTION(folder, "need folder");
+ m_folder = folder;
+ m_aclCount = 0;
+ BuildInitialACLFromCache();
+}
+
+nsMsgIMAPFolderACL::~nsMsgIMAPFolderACL() {}
+
+// We cache most of our own rights in the MSG_FOLDER_PREF_* flags
+void nsMsgIMAPFolderACL::BuildInitialACLFromCache() {
+ nsAutoCString myrights;
+
+ uint32_t startingFlags;
+ m_folder->GetAclFlags(&startingFlags);
+
+ if (startingFlags & IMAP_ACL_READ_FLAG) myrights += "r";
+ if (startingFlags & IMAP_ACL_STORE_SEEN_FLAG) myrights += "s";
+ if (startingFlags & IMAP_ACL_WRITE_FLAG) myrights += "w";
+ if (startingFlags & IMAP_ACL_INSERT_FLAG) myrights += "i";
+ if (startingFlags & IMAP_ACL_POST_FLAG) myrights += "p";
+ if (startingFlags & IMAP_ACL_CREATE_SUBFOLDER_FLAG) myrights += "c";
+ if (startingFlags & IMAP_ACL_DELETE_FLAG) myrights += "dt";
+ if (startingFlags & IMAP_ACL_ADMINISTER_FLAG) myrights += "a";
+ if (startingFlags & IMAP_ACL_EXPUNGE_FLAG) myrights += "e";
+
+ if (!myrights.IsEmpty()) SetFolderRightsForUser(EmptyCString(), myrights);
+}
+
+void nsMsgIMAPFolderACL::UpdateACLCache() {
+ uint32_t startingFlags = 0;
+ m_folder->GetAclFlags(&startingFlags);
+
+ if (GetCanIReadFolder())
+ startingFlags |= IMAP_ACL_READ_FLAG;
+ else
+ startingFlags &= ~IMAP_ACL_READ_FLAG;
+
+ if (GetCanIStoreSeenInFolder())
+ startingFlags |= IMAP_ACL_STORE_SEEN_FLAG;
+ else
+ startingFlags &= ~IMAP_ACL_STORE_SEEN_FLAG;
+
+ if (GetCanIWriteFolder())
+ startingFlags |= IMAP_ACL_WRITE_FLAG;
+ else
+ startingFlags &= ~IMAP_ACL_WRITE_FLAG;
+
+ if (GetCanIInsertInFolder())
+ startingFlags |= IMAP_ACL_INSERT_FLAG;
+ else
+ startingFlags &= ~IMAP_ACL_INSERT_FLAG;
+
+ if (GetCanIPostToFolder())
+ startingFlags |= IMAP_ACL_POST_FLAG;
+ else
+ startingFlags &= ~IMAP_ACL_POST_FLAG;
+
+ if (GetCanICreateSubfolder())
+ startingFlags |= IMAP_ACL_CREATE_SUBFOLDER_FLAG;
+ else
+ startingFlags &= ~IMAP_ACL_CREATE_SUBFOLDER_FLAG;
+
+ if (GetCanIDeleteInFolder())
+ startingFlags |= IMAP_ACL_DELETE_FLAG;
+ else
+ startingFlags &= ~IMAP_ACL_DELETE_FLAG;
+
+ if (GetCanIAdministerFolder())
+ startingFlags |= IMAP_ACL_ADMINISTER_FLAG;
+ else
+ startingFlags &= ~IMAP_ACL_ADMINISTER_FLAG;
+
+ if (GetCanIExpungeFolder())
+ startingFlags |= IMAP_ACL_EXPUNGE_FLAG;
+ else
+ startingFlags &= ~IMAP_ACL_EXPUNGE_FLAG;
+
+ m_folder->SetAclFlags(startingFlags);
+}
+
+bool nsMsgIMAPFolderACL::SetFolderRightsForUser(const nsACString& userName,
+ const nsACString& rights) {
+ nsCString myUserName;
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ nsresult rv = m_folder->GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, false);
+
+ server->GetUsername(myUserName);
+
+ nsAutoCString ourUserName;
+ if (userName.IsEmpty())
+ ourUserName.Assign(myUserName);
+ else
+ ourUserName.Assign(userName);
+
+ if (ourUserName.IsEmpty()) return false;
+
+ ToLowerCase(ourUserName);
+ nsCString oldValue = m_rightsHash.Get(ourUserName);
+ if (!oldValue.IsEmpty()) {
+ m_rightsHash.Remove(ourUserName);
+ m_aclCount--;
+ NS_ASSERTION(m_aclCount >= 0, "acl count can't go negative");
+ }
+ m_aclCount++;
+ m_rightsHash.InsertOrUpdate(ourUserName, PromiseFlatCString(rights));
+
+ if (myUserName.Equals(ourUserName) ||
+ ourUserName.EqualsLiteral(IMAP_ACL_ANYONE_STRING))
+ // if this is setting an ACL for me, cache it in the folder pref flags
+ UpdateACLCache();
+
+ return true;
+}
+
+NS_IMETHODIMP nsImapMailFolder::GetOtherUsersWithAccess(
+ nsIUTF8StringEnumerator** aResult) {
+ return GetFolderACL()->GetOtherUsers(aResult);
+}
+
+nsresult nsMsgIMAPFolderACL::GetOtherUsers(nsIUTF8StringEnumerator** aResult) {
+ nsCString myUserName;
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ nsresult rv = m_folder->GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+ server->GetUsername(myUserName);
+
+ // We need to filter out myUserName from m_rightsHash.
+ nsTArray<nsCString>* resultArray = new nsTArray<nsCString>;
+ for (auto iter = m_rightsHash.Iter(); !iter.Done(); iter.Next()) {
+ if (!iter.Key().Equals(myUserName)) resultArray->AppendElement(iter.Key());
+ }
+
+ // enumerator will free resultArray
+ return NS_NewAdoptingUTF8StringEnumerator(aResult, resultArray);
+}
+
+nsresult nsImapMailFolder::GetPermissionsForUser(const nsACString& otherUser,
+ nsACString& aResult) {
+ nsCString str;
+ nsresult rv = GetFolderACL()->GetRightsStringForUser(otherUser, str);
+ NS_ENSURE_SUCCESS(rv, rv);
+ aResult = str;
+ return NS_OK;
+}
+
+nsresult nsMsgIMAPFolderACL::GetRightsStringForUser(
+ const nsACString& inUserName, nsCString& rights) {
+ nsCString userName;
+ userName.Assign(inUserName);
+ if (userName.IsEmpty()) {
+ nsCOMPtr<nsIMsgIncomingServer> server;
+
+ nsresult rv = m_folder->GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+ // we need the real user name to match with what the imap server returns
+ // in the acl response.
+ server->GetUsername(userName);
+ }
+ ToLowerCase(userName);
+ rights = m_rightsHash.Get(userName);
+ return NS_OK;
+}
+
+// First looks for individual user; then looks for 'anyone' if the user isn't
+// found. Returns defaultIfNotFound, if neither are found.
+bool nsMsgIMAPFolderACL::GetFlagSetInRightsForUser(const nsACString& userName,
+ char flag,
+ bool defaultIfNotFound) {
+ nsCString flags;
+ nsresult rv = GetRightsStringForUser(userName, flags);
+ NS_ENSURE_SUCCESS(rv, defaultIfNotFound);
+ if (flags.IsEmpty()) {
+ nsCString anyoneFlags;
+ GetRightsStringForUser(nsLiteralCString(IMAP_ACL_ANYONE_STRING),
+ anyoneFlags);
+ if (anyoneFlags.IsEmpty()) return defaultIfNotFound;
+ return (anyoneFlags.FindChar(flag) != kNotFound);
+ }
+ return (flags.FindChar(flag) != kNotFound);
+}
+
+bool nsMsgIMAPFolderACL::GetCanUserLookupFolder(const nsACString& userName) {
+ return GetFlagSetInRightsForUser(userName, 'l', false);
+}
+
+bool nsMsgIMAPFolderACL::GetCanUserReadFolder(const nsACString& userName) {
+ return GetFlagSetInRightsForUser(userName, 'r', false);
+}
+
+bool nsMsgIMAPFolderACL::GetCanUserStoreSeenInFolder(
+ const nsACString& userName) {
+ return GetFlagSetInRightsForUser(userName, 's', false);
+}
+
+bool nsMsgIMAPFolderACL::GetCanUserWriteFolder(const nsACString& userName) {
+ return GetFlagSetInRightsForUser(userName, 'w', false);
+}
+
+bool nsMsgIMAPFolderACL::GetCanUserInsertInFolder(const nsACString& userName) {
+ return GetFlagSetInRightsForUser(userName, 'i', false);
+}
+
+bool nsMsgIMAPFolderACL::GetCanUserPostToFolder(const nsACString& userName) {
+ return GetFlagSetInRightsForUser(userName, 'p', false);
+}
+
+bool nsMsgIMAPFolderACL::GetCanUserCreateSubfolder(const nsACString& userName) {
+ return GetFlagSetInRightsForUser(userName, 'c', false);
+}
+
+bool nsMsgIMAPFolderACL::GetCanUserDeleteInFolder(const nsACString& userName) {
+ return GetFlagSetInRightsForUser(userName, 'd', false) ||
+ GetFlagSetInRightsForUser(userName, 't', false);
+}
+
+bool nsMsgIMAPFolderACL::GetCanUserAdministerFolder(
+ const nsACString& userName) {
+ return GetFlagSetInRightsForUser(userName, 'a', false);
+}
+
+bool nsMsgIMAPFolderACL::GetCanILookupFolder() {
+ return GetFlagSetInRightsForUser(EmptyCString(), 'l', true);
+}
+
+bool nsMsgIMAPFolderACL::GetCanIReadFolder() {
+ return GetFlagSetInRightsForUser(EmptyCString(), 'r', true);
+}
+
+bool nsMsgIMAPFolderACL::GetCanIStoreSeenInFolder() {
+ return GetFlagSetInRightsForUser(EmptyCString(), 's', true);
+}
+
+bool nsMsgIMAPFolderACL::GetCanIWriteFolder() {
+ return GetFlagSetInRightsForUser(EmptyCString(), 'w', true);
+}
+
+bool nsMsgIMAPFolderACL::GetCanIInsertInFolder() {
+ return GetFlagSetInRightsForUser(EmptyCString(), 'i', true);
+}
+
+bool nsMsgIMAPFolderACL::GetCanIPostToFolder() {
+ return GetFlagSetInRightsForUser(EmptyCString(), 'p', true);
+}
+
+bool nsMsgIMAPFolderACL::GetCanICreateSubfolder() {
+ return GetFlagSetInRightsForUser(EmptyCString(), 'c', true);
+}
+
+bool nsMsgIMAPFolderACL::GetCanIDeleteInFolder() {
+ return GetFlagSetInRightsForUser(EmptyCString(), 'd', true) ||
+ GetFlagSetInRightsForUser(EmptyCString(), 't', true);
+}
+
+bool nsMsgIMAPFolderACL::GetCanIAdministerFolder() {
+ return GetFlagSetInRightsForUser(EmptyCString(), 'a', true);
+}
+
+bool nsMsgIMAPFolderACL::GetCanIExpungeFolder() {
+ return GetFlagSetInRightsForUser(EmptyCString(), 'e', true) ||
+ GetFlagSetInRightsForUser(EmptyCString(), 'd', true);
+}
+
+// We use this to see if the ACLs think a folder is shared or not.
+// We will define "Shared" in 5.0 to mean:
+// At least one user other than the currently authenticated user has at least
+// one explicitly-listed ACL right on that folder.
+bool nsMsgIMAPFolderACL::GetIsFolderShared() {
+ // If we have more than one ACL count for this folder, which means that
+ // someone other than ourself has rights on it, then it is "shared."
+ if (m_aclCount > 1) return true;
+
+ // Or, if "anyone" has rights to it, it is shared.
+ nsCString anyonesRights =
+ m_rightsHash.Get(nsLiteralCString(IMAP_ACL_ANYONE_STRING));
+ return (!anyonesRights.IsEmpty());
+}
+
+bool nsMsgIMAPFolderACL::GetDoIHaveFullRightsForFolder() {
+ return (GetCanIReadFolder() && GetCanIWriteFolder() &&
+ GetCanIInsertInFolder() && GetCanIAdministerFolder() &&
+ GetCanICreateSubfolder() && GetCanIDeleteInFolder() &&
+ GetCanILookupFolder() && GetCanIStoreSeenInFolder() &&
+ GetCanIExpungeFolder() && GetCanIPostToFolder());
+}
+
+// Returns a newly allocated string describing these rights
+nsresult nsMsgIMAPFolderACL::CreateACLRightsString(nsAString& aRightsString) {
+ nsString curRight;
+ nsCOMPtr<nsIStringBundle> bundle;
+ nsresult rv = IMAPGetStringBundle(getter_AddRefs(bundle));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (GetDoIHaveFullRightsForFolder()) {
+ nsAutoString result;
+ rv = bundle->GetStringFromName("imapAclFullRights", result);
+ aRightsString.Assign(result);
+ return rv;
+ }
+
+ if (GetCanIReadFolder()) {
+ bundle->GetStringFromName("imapAclReadRight", curRight);
+ aRightsString.Append(curRight);
+ }
+ if (GetCanIWriteFolder()) {
+ if (!aRightsString.IsEmpty()) aRightsString.AppendLiteral(", ");
+ bundle->GetStringFromName("imapAclWriteRight", curRight);
+ aRightsString.Append(curRight);
+ }
+ if (GetCanIInsertInFolder()) {
+ if (!aRightsString.IsEmpty()) aRightsString.AppendLiteral(", ");
+ bundle->GetStringFromName("imapAclInsertRight", curRight);
+ aRightsString.Append(curRight);
+ }
+ if (GetCanILookupFolder()) {
+ if (!aRightsString.IsEmpty()) aRightsString.AppendLiteral(", ");
+ bundle->GetStringFromName("imapAclLookupRight", curRight);
+ aRightsString.Append(curRight);
+ }
+ if (GetCanIStoreSeenInFolder()) {
+ if (!aRightsString.IsEmpty()) aRightsString.AppendLiteral(", ");
+ bundle->GetStringFromName("imapAclSeenRight", curRight);
+ aRightsString.Append(curRight);
+ }
+ if (GetCanIDeleteInFolder()) {
+ if (!aRightsString.IsEmpty()) aRightsString.AppendLiteral(", ");
+ bundle->GetStringFromName("imapAclDeleteRight", curRight);
+ aRightsString.Append(curRight);
+ }
+ if (GetCanIExpungeFolder()) {
+ if (!aRightsString.IsEmpty()) aRightsString.AppendLiteral(", ");
+ bundle->GetStringFromName("imapAclExpungeRight", curRight);
+ aRightsString.Append(curRight);
+ }
+ if (GetCanICreateSubfolder()) {
+ if (!aRightsString.IsEmpty()) aRightsString.AppendLiteral(", ");
+ bundle->GetStringFromName("imapAclCreateRight", curRight);
+ aRightsString.Append(curRight);
+ }
+ if (GetCanIPostToFolder()) {
+ if (!aRightsString.IsEmpty()) aRightsString.AppendLiteral(", ");
+ bundle->GetStringFromName("imapAclPostRight", curRight);
+ aRightsString.Append(curRight);
+ }
+ if (GetCanIAdministerFolder()) {
+ if (!aRightsString.IsEmpty()) aRightsString.AppendLiteral(", ");
+ bundle->GetStringFromName("imapAclAdministerRight", curRight);
+ aRightsString.Append(curRight);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapMailFolder::GetFilePath(nsIFile** aPathName) {
+ // this will return a copy of mPath, which is what we want.
+ // this will also initialize mPath using parseURI if it isn't already done
+ return nsMsgDBFolder::GetFilePath(aPathName);
+}
+
+NS_IMETHODIMP nsImapMailFolder::SetFilePath(nsIFile* aPathName) {
+ return nsMsgDBFolder::SetFilePath(
+ aPathName); // call base class so mPath will get set
+}
+
+nsresult nsImapMailFolder::DisplayStatusMsg(nsIImapUrl* aImapUrl,
+ const nsAString& msg) {
+ nsCOMPtr<nsIImapMockChannel> mockChannel;
+ aImapUrl->GetMockChannel(getter_AddRefs(mockChannel));
+ if (mockChannel) {
+ nsCOMPtr<nsIProgressEventSink> progressSink;
+ mockChannel->GetProgressEventSink(getter_AddRefs(progressSink));
+ if (progressSink) {
+ progressSink->OnStatus(mockChannel, NS_OK,
+ PromiseFlatString(msg).get()); // XXX i18n message
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::ProgressStatusString(nsIImapProtocol* aProtocol,
+ const char* aMsgName,
+ const char16_t* extraInfo) {
+ nsString progressMsg;
+
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ nsresult rv = GetServer(getter_AddRefs(server));
+ if (NS_SUCCEEDED(rv) && server) {
+ nsCOMPtr<nsIImapServerSink> serverSink = do_QueryInterface(server);
+ if (serverSink) serverSink->GetImapStringByName(aMsgName, progressMsg);
+ }
+ if (progressMsg.IsEmpty())
+ IMAPGetStringByName(aMsgName, getter_Copies(progressMsg));
+
+ if (aProtocol && !progressMsg.IsEmpty()) {
+ nsCOMPtr<nsIImapUrl> imapUrl;
+ aProtocol->GetRunningImapURL(getter_AddRefs(imapUrl));
+ if (imapUrl) {
+ if (extraInfo) {
+ nsString printfString;
+ nsTextFormatter::ssprintf(printfString, progressMsg.get(), extraInfo);
+ progressMsg = printfString;
+ }
+
+ DisplayStatusMsg(imapUrl, progressMsg);
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::PercentProgress(nsIImapProtocol* aProtocol,
+ nsACString const& aFmtStringName,
+ nsAString const& aMailboxName,
+ int64_t aCurrentProgress,
+ int64_t aMaxProgress) {
+ if (aProtocol) {
+ nsCOMPtr<nsIImapUrl> imapUrl;
+ aProtocol->GetRunningImapURL(getter_AddRefs(imapUrl));
+ if (imapUrl) {
+ nsCOMPtr<nsIImapMockChannel> mockChannel;
+ imapUrl->GetMockChannel(getter_AddRefs(mockChannel));
+ if (mockChannel) {
+ nsCOMPtr<nsIProgressEventSink> progressSink;
+ mockChannel->GetProgressEventSink(getter_AddRefs(progressSink));
+ if (progressSink) {
+ progressSink->OnProgress(mockChannel, aCurrentProgress, aMaxProgress);
+
+ if (!aFmtStringName.IsEmpty()) {
+ // There's a progress message to format (the progress messages are
+ // all localized and expect three params).
+ nsAutoString current;
+ current.AppendInt(aCurrentProgress);
+ nsAutoString expected;
+ expected.AppendInt(aMaxProgress);
+ nsAutoString mailbox(aMailboxName);
+ AutoTArray<nsString, 3> params = {current, expected, mailbox};
+
+ nsCOMPtr<nsIStringBundle> bundle;
+ nsresult rv = IMAPGetStringBundle(getter_AddRefs(bundle));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsString progressText;
+ rv = bundle->FormatStringFromName(
+ PromiseFlatCString(aFmtStringName).get(), params, progressText);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!progressText.IsEmpty()) {
+ progressSink->OnStatus(mockChannel, NS_OK, progressText.get());
+ }
+ }
+ }
+ }
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::CopyNextStreamMessage(bool copySucceeded,
+ nsISupports* copyState) {
+ // if copy has failed it could be either user interrupted it or for some other
+ // reason don't do any subsequent copies or delete src messages if it is move
+ if (!copySucceeded) return NS_OK;
+ nsresult rv;
+ nsCOMPtr<nsImapMailCopyState> mailCopyState =
+ do_QueryInterface(copyState, &rv);
+ if (NS_FAILED(rv)) {
+ MOZ_LOG(IMAP, mozilla::LogLevel::Info,
+ ("QI copyState failed: %" PRIx32, static_cast<uint32_t>(rv)));
+ return rv; // this can fail...
+ }
+
+ if (!mailCopyState->m_streamCopy) return NS_OK;
+
+ uint32_t idx = mailCopyState->m_curIndex;
+ if (mailCopyState->m_isMove && idx) {
+ nsCOMPtr<nsIMsgFolder> srcFolder(
+ do_QueryInterface(mailCopyState->m_srcSupport, &rv));
+ if (NS_SUCCEEDED(rv) && srcFolder) {
+ // Create "array" of one message header to delete
+ idx--;
+ if (idx < mailCopyState->m_messages.Length()) {
+ RefPtr<nsIMsgDBHdr> msg = mailCopyState->m_messages[idx];
+ srcFolder->DeleteMessages({msg}, nullptr, true, true, nullptr, false);
+ }
+ }
+ }
+
+ if (mailCopyState->m_curIndex < mailCopyState->m_messages.Length()) {
+ MOZ_LOG(IMAP, mozilla::LogLevel::Info,
+ ("CopyNextStreamMessage: %s %u of %u",
+ mailCopyState->m_isMove ? "Moving" : "Copying",
+ mailCopyState->m_curIndex,
+ (uint32_t)mailCopyState->m_messages.Length()));
+ nsIMsgDBHdr* message = mailCopyState->m_messages[mailCopyState->m_curIndex];
+ bool isRead;
+ message->GetIsRead(&isRead);
+ mailCopyState->m_unreadCount = (isRead) ? 0 : 1;
+ rv = CopyStreamMessage(message, this, mailCopyState->m_msgWindow,
+ mailCopyState->m_isMove);
+ } else {
+ // Notify of move/copy completion in case we have some source headers
+ nsCOMPtr<nsIMsgFolderNotificationService> notifier(
+ do_GetService("@mozilla.org/messenger/msgnotificationservice;1"));
+ if (notifier && !mailCopyState->m_messages.IsEmpty()) {
+ notifier->NotifyMsgsMoveCopyCompleted(
+ mailCopyState->m_isMove, mailCopyState->m_messages, this, {});
+ }
+ if (mailCopyState->m_isMove) {
+ nsCOMPtr<nsIMsgFolder> srcFolder(
+ do_QueryInterface(mailCopyState->m_srcSupport, &rv));
+ if (NS_SUCCEEDED(rv) && srcFolder) {
+ // we want to send this notification now that the source messages have
+ // been deleted.
+ nsCOMPtr<nsIMsgLocalMailFolder> popFolder(do_QueryInterface(srcFolder));
+ if (popFolder) // needed if move pop->imap to notify FE
+ srcFolder->NotifyFolderEvent(kDeleteOrMoveMsgCompleted);
+ }
+ }
+ }
+ if (NS_FAILED(rv)) (void)OnCopyCompleted(mailCopyState->m_srcSupport, rv);
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::SetUrlState(nsIImapProtocol* aProtocol,
+ nsIMsgMailNewsUrl* aUrl, bool isRunning,
+ bool aSuspend, nsresult statusCode) {
+ // If we have no path, then the folder has been shutdown, and there's
+ // no point in doing anything...
+ if (!mPath) return NS_OK;
+ if (!isRunning) {
+ ProgressStatusString(aProtocol, "imapDone", nullptr);
+ m_urlRunning = false;
+ // if no protocol, then we're reading from the mem or disk cache
+ // and we don't want to end the offline download just yet.
+ if (aProtocol) {
+ EndOfflineDownload();
+ m_downloadingFolderForOfflineUse = false;
+ }
+ nsCOMPtr<nsIImapUrl> imapUrl(do_QueryInterface(aUrl));
+ if (imapUrl) {
+ nsImapAction imapAction;
+ imapUrl->GetImapAction(&imapAction);
+ // if the server doesn't support copyUID, then SetCopyResponseUid won't
+ // get called, so we need to clear m_pendingOfflineMoves when the online
+ // move operation has finished.
+ if (imapAction == nsIImapUrl::nsImapOnlineMove)
+ m_pendingOfflineMoves.Clear();
+ }
+ }
+ if (aUrl && !aSuspend) return aUrl->SetUrlState(isRunning, statusCode);
+ return statusCode;
+}
+
+// used when copying from local mail folder, or other imap server)
+nsresult nsImapMailFolder::CopyMessagesWithStream(
+ nsIMsgFolder* srcFolder, nsTArray<RefPtr<nsIMsgDBHdr>> const& messages,
+ bool isMove, bool isCrossServerOp, nsIMsgWindow* msgWindow,
+ nsIMsgCopyServiceListener* listener, bool allowUndo) {
+ NS_ENSURE_ARG_POINTER(srcFolder);
+ nsresult rv;
+ rv = InitCopyState(srcFolder, messages, isMove, false, isCrossServerOp, 0,
+ EmptyCString(), listener, msgWindow, allowUndo);
+ if (NS_FAILED(rv)) return rv;
+
+ m_copyState->m_streamCopy = true;
+
+ // ** jt - needs to create server to server move/copy undo msg txn
+ if (m_copyState->m_allowUndo) {
+ nsAutoCString messageIds;
+ nsTArray<nsMsgKey> srcKeyArray;
+ rv = BuildIdsAndKeyArray(messages, messageIds, srcKeyArray);
+
+ RefPtr<nsImapMoveCopyMsgTxn> undoMsgTxn = new nsImapMoveCopyMsgTxn;
+
+ if (!undoMsgTxn ||
+ NS_FAILED(undoMsgTxn->Init(srcFolder, &srcKeyArray, messageIds.get(),
+ this, true, isMove)))
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ if (isMove) {
+ if (mFlags & nsMsgFolderFlags::Trash)
+ undoMsgTxn->SetTransactionType(nsIMessenger::eDeleteMsg);
+ else
+ undoMsgTxn->SetTransactionType(nsIMessenger::eMoveMsg);
+ } else
+ undoMsgTxn->SetTransactionType(nsIMessenger::eCopyMsg);
+ m_copyState->m_undoMsgTxn = undoMsgTxn;
+ }
+ if (NS_SUCCEEDED(rv)) CopyStreamMessage(messages[0], this, msgWindow, isMove);
+ return rv; // we are clearing copy state in CopyMessages on failure
+}
+
+nsresult nsImapMailFolder::GetClearedOriginalOp(
+ nsIMsgOfflineImapOperation* op, nsIMsgOfflineImapOperation** originalOp,
+ nsIMsgDatabase** originalDB) {
+ nsCOMPtr<nsIMsgOfflineImapOperation> returnOp;
+ nsOfflineImapOperationType opType;
+ op->GetOperation(&opType);
+ NS_ASSERTION(opType & nsIMsgOfflineImapOperation::kMoveResult,
+ "not an offline move op");
+
+ nsCString sourceFolderURI;
+ op->GetSourceFolderURI(sourceFolderURI);
+
+ nsresult rv;
+ nsCOMPtr<nsIMsgFolder> sourceFolder;
+ rv = GetOrCreateFolder(sourceFolderURI, getter_AddRefs(sourceFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIDBFolderInfo> folderInfo;
+ sourceFolder->GetDBFolderInfoAndDB(getter_AddRefs(folderInfo), originalDB);
+ if (*originalDB) {
+ nsCOMPtr<nsIMsgOfflineOpsDatabase> opsDb =
+ do_QueryInterface(*originalDB, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsMsgKey originalKey;
+ op->GetMessageKey(&originalKey);
+ rv =
+ opsDb->GetOfflineOpForKey(originalKey, false, getter_AddRefs(returnOp));
+ if (NS_SUCCEEDED(rv) && returnOp) {
+ nsCString moveDestination;
+ nsCString thisFolderURI;
+ GetURI(thisFolderURI);
+ returnOp->GetDestinationFolderURI(moveDestination);
+ if (moveDestination.Equals(thisFolderURI))
+ returnOp->ClearOperation(nsIMsgOfflineImapOperation::kMoveResult);
+ }
+ }
+ returnOp.forget(originalOp);
+ return rv;
+}
+
+nsresult nsImapMailFolder::GetOriginalOp(
+ nsIMsgOfflineImapOperation* op, nsIMsgOfflineImapOperation** originalOp,
+ nsIMsgDatabase** originalDB) {
+ nsCOMPtr<nsIMsgOfflineImapOperation> returnOp;
+ nsCString sourceFolderURI;
+ op->GetSourceFolderURI(sourceFolderURI);
+
+ nsresult rv;
+ nsCOMPtr<nsIMsgFolder> sourceFolder;
+ rv = GetOrCreateFolder(sourceFolderURI, getter_AddRefs(sourceFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIDBFolderInfo> folderInfo;
+ sourceFolder->GetDBFolderInfoAndDB(getter_AddRefs(folderInfo), originalDB);
+ if (*originalDB) {
+ nsCOMPtr<nsIMsgOfflineOpsDatabase> opsDb =
+ do_QueryInterface(*originalDB, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsMsgKey originalKey;
+ op->GetMessageKey(&originalKey);
+ rv =
+ opsDb->GetOfflineOpForKey(originalKey, false, getter_AddRefs(returnOp));
+ }
+ returnOp.forget(originalOp);
+ return rv;
+}
+
+nsresult nsImapMailFolder::FindOpenRange(nsMsgKey& fakeBase,
+ uint32_t srcCount) {
+ nsresult rv = GetDatabase();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsMsgKey newBase = fakeBase - 1;
+ uint32_t freeCount = 0;
+ while (freeCount != srcCount && newBase > 0) {
+ bool containsKey;
+ if (NS_SUCCEEDED(mDatabase->ContainsKey(newBase, &containsKey)) &&
+ !containsKey)
+ freeCount++;
+ else
+ freeCount = 0;
+ newBase--;
+ }
+ if (!newBase) return NS_ERROR_FAILURE;
+ fakeBase = newBase;
+ return NS_OK;
+}
+
+// Helper to synchronously copy a message from one msgStore to another.
+static nsresult CopyStoreMessage(nsIMsgDBHdr* srcHdr, nsIMsgDBHdr* destHdr,
+ uint64_t& bytesCopied) {
+ nsresult rv;
+
+ // Boilerplate setup.
+ nsCOMPtr<nsIMsgFolder> srcFolder;
+ rv = srcHdr->GetFolder(getter_AddRefs(srcFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIMsgFolder> destFolder;
+ rv = destHdr->GetFolder(getter_AddRefs(destFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIMsgPluggableStore> destStore;
+ rv = destFolder->GetMsgStore(getter_AddRefs(destStore));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Copy message into the msgStore.
+ nsCOMPtr<nsIInputStream> srcStream;
+ rv = srcFolder->GetLocalMsgStream(srcHdr, getter_AddRefs(srcStream));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIOutputStream> destStream;
+ rv = destFolder->GetOfflineStoreOutputStream(destHdr,
+ getter_AddRefs(destStream));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = SyncCopyStream(srcStream, destStream, bytesCopied);
+ if (NS_SUCCEEDED(rv)) {
+ rv = destStore->FinishNewMessage(destStream, destHdr);
+ } else {
+ destStore->DiscardNewMessage(destStream, destHdr);
+ }
+ return rv;
+}
+
+// This imap folder is the destination of an offline move/copy.
+// We are either offline, or doing a pseudo-offline delete (where we do an
+// offline delete, load the next message, then playback the offline delete).
+nsresult nsImapMailFolder::CopyMessagesOffline(
+ nsIMsgFolder* srcFolder, nsTArray<RefPtr<nsIMsgDBHdr>> const& messages,
+ bool isMove, nsIMsgWindow* msgWindow, nsIMsgCopyServiceListener* listener) {
+ nsresult rv;
+ nsresult stopit = NS_OK;
+ nsCOMPtr<nsIMsgDatabase> sourceMailDB;
+ nsCOMPtr<nsIDBFolderInfo> srcDbFolderInfo;
+ srcFolder->GetDBFolderInfoAndDB(getter_AddRefs(srcDbFolderInfo),
+ getter_AddRefs(sourceMailDB));
+ bool deleteToTrash = false;
+ bool deleteImmediately = false;
+ uint32_t srcCount = messages.Length();
+ nsCOMPtr<nsIImapIncomingServer> imapServer;
+ rv = GetImapIncomingServer(getter_AddRefs(imapServer));
+
+ nsTArray<RefPtr<nsIMsgDBHdr>> msgHdrsCopied;
+ nsTArray<RefPtr<nsIMsgDBHdr>> destMsgHdrs;
+
+ if (NS_SUCCEEDED(rv) && imapServer) {
+ nsMsgImapDeleteModel deleteModel;
+ imapServer->GetDeleteModel(&deleteModel);
+ deleteToTrash = (deleteModel == nsMsgImapDeleteModels::MoveToTrash);
+ deleteImmediately = (deleteModel == nsMsgImapDeleteModels::DeleteNoTrash);
+ }
+
+ // This array is used only when we are actually removing the messages from the
+ // source database.
+ nsTArray<nsMsgKey> keysToDelete(
+ (isMove && (deleteToTrash || deleteImmediately)) ? srcCount : 0);
+
+ if (sourceMailDB) {
+ // save the future ops in the source DB, if this is not a imap->local
+ // copy/move
+ nsCOMPtr<nsITransactionManager> txnMgr;
+ if (msgWindow) msgWindow->GetTransactionManager(getter_AddRefs(txnMgr));
+ if (txnMgr) txnMgr->BeginBatch(nullptr);
+ nsCOMPtr<nsIMsgDatabase> database;
+ GetMsgDatabase(getter_AddRefs(database));
+ if (database) {
+ // get the highest key in the dest db, so we can make up our fake keys
+ nsMsgKey fakeBase = 1;
+ nsCOMPtr<nsIDBFolderInfo> folderInfo;
+ rv = database->GetDBFolderInfo(getter_AddRefs(folderInfo));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsMsgKey highWaterMark = nsMsgKey_None;
+ folderInfo->GetHighWater(&highWaterMark);
+ fakeBase += highWaterMark;
+ nsMsgKey fakeTop = fakeBase + srcCount;
+ // Check that we have enough room for the fake headers. If fakeTop
+ // is <= highWaterMark, we've overflowed.
+ if (fakeTop <= highWaterMark || fakeTop == nsMsgKey_None) {
+ rv = FindOpenRange(fakeBase, srcCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ // N.B. We must not return out of the for loop - we need the matching
+ // end notifications to be sent.
+ // We don't need to acquire the semaphor since this is synchronous
+ // on the UI thread but we should check if the offline store is locked.
+ bool isLocked;
+ GetLocked(&isLocked);
+ nsTArray<nsMsgKey> addedKeys;
+ nsTArray<nsMsgKey> srcKeyArray;
+ nsCOMArray<nsIMsgDBHdr> addedHdrs;
+ nsCOMArray<nsIMsgDBHdr> srcMsgs;
+ nsOfflineImapOperationType moveCopyOpType;
+ nsOfflineImapOperationType deleteOpType =
+ nsIMsgOfflineImapOperation::kDeletedMsg;
+ if (!deleteToTrash)
+ deleteOpType = nsIMsgOfflineImapOperation::kMsgMarkedDeleted;
+ nsCString messageIds;
+ rv = BuildIdsAndKeyArray(messages, messageIds, srcKeyArray);
+ // put fake message in destination db, delete source if move
+ EnableNotifications(nsIMsgFolder::allMessageCountNotifications, false);
+ nsCString originalSrcFolderURI;
+ srcFolder->GetURI(originalSrcFolderURI);
+ nsCOMPtr<nsIMsgOfflineOpsDatabase> opsDb =
+ do_QueryInterface(sourceMailDB, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ for (uint32_t sourceKeyIndex = 0;
+ NS_SUCCEEDED(stopit) && (sourceKeyIndex < srcCount);
+ sourceKeyIndex++) {
+ bool messageReturningHome = false;
+ RefPtr<nsIMsgDBHdr> message = messages[sourceKeyIndex];
+ nsMsgKey originalKey;
+ if (message) {
+ rv = message->GetMessageKey(&originalKey);
+ } else {
+ NS_ERROR("bad msg in src array");
+ continue;
+ }
+ nsCOMPtr<nsIMsgOfflineImapOperation> sourceOp;
+ rv = opsDb->GetOfflineOpForKey(originalKey, true,
+ getter_AddRefs(sourceOp));
+ if (NS_SUCCEEDED(rv) && sourceOp) {
+ srcFolder->SetFlag(nsMsgFolderFlags::OfflineEvents);
+ nsCOMPtr<nsIMsgDatabase> originalDB;
+ nsOfflineImapOperationType opType;
+ sourceOp->GetOperation(&opType);
+ // if we already have an offline op for this key, then we need to see
+ // if it was moved into the source folder while offline
+ if (opType ==
+ nsIMsgOfflineImapOperation::kMoveResult) // offline move
+ {
+ // gracious me, we are moving something we already moved while
+ // offline! find the original operation and clear it!
+ nsCOMPtr<nsIMsgOfflineImapOperation> originalOp;
+ GetClearedOriginalOp(sourceOp, getter_AddRefs(originalOp),
+ getter_AddRefs(originalDB));
+ nsCOMPtr<nsIMsgOfflineOpsDatabase> opsDbOriginal =
+ do_QueryInterface(originalDB, &rv);
+ if (NS_SUCCEEDED(rv) && originalOp) {
+ nsCString srcFolderURI;
+ srcFolder->GetURI(srcFolderURI);
+ sourceOp->GetSourceFolderURI(originalSrcFolderURI);
+ sourceOp->GetMessageKey(&originalKey);
+ if (isMove) opsDb->RemoveOfflineOp(sourceOp);
+ sourceOp = originalOp;
+ if (originalSrcFolderURI.Equals(srcFolderURI)) {
+ messageReturningHome = true;
+ opsDbOriginal->RemoveOfflineOp(originalOp);
+ }
+ }
+ }
+ if (!messageReturningHome) {
+ nsCString folderURI;
+ GetURI(folderURI);
+ if (isMove) {
+ uint32_t msgSize;
+ uint32_t msgFlags;
+ imapMessageFlagsType newImapFlags = 0;
+ message->GetMessageSize(&msgSize);
+ message->GetFlags(&msgFlags);
+ sourceOp->SetDestinationFolderURI(folderURI); // offline move
+ sourceOp->SetOperation(nsIMsgOfflineImapOperation::kMsgMoved);
+ sourceOp->SetMsgSize(msgSize);
+ newImapFlags = msgFlags & 0x7;
+ if (msgFlags & nsMsgMessageFlags::Forwarded)
+ newImapFlags |= kImapMsgForwardedFlag;
+ sourceOp->SetNewFlags(newImapFlags);
+ } else {
+ sourceOp->AddMessageCopyOperation(folderURI); // offline copy
+ }
+
+ sourceOp->GetOperation(&moveCopyOpType);
+ srcMsgs.AppendObject(message);
+ }
+ } else {
+ stopit = NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIMsgDBHdr> mailHdr;
+ rv =
+ sourceMailDB->GetMsgHdrForKey(originalKey, getter_AddRefs(mailHdr));
+
+ if (NS_SUCCEEDED(rv) && mailHdr) {
+ // Copy the DB hdr into the destination folder.
+ bool successfulCopy = false;
+ nsMsgKey srcDBhighWaterMark;
+ srcDbFolderInfo->GetHighWater(&srcDBhighWaterMark);
+
+ nsCOMPtr<nsIMsgDBHdr> newMailHdr;
+ rv = database->CopyHdrFromExistingHdr(fakeBase + sourceKeyIndex,
+ mailHdr, true,
+ getter_AddRefs(newMailHdr));
+ if (!newMailHdr || NS_FAILED(rv)) {
+ NS_ASSERTION(false, "failed to copy hdr");
+ stopit = rv;
+ }
+
+ if (NS_SUCCEEDED(stopit)) {
+ bool hasMsgOffline = false;
+
+ destMsgHdrs.AppendElement(newMailHdr);
+ srcFolder->HasMsgOffline(originalKey, &hasMsgOffline);
+ newMailHdr->SetUint32Property("pseudoHdr", 1);
+
+ if (hasMsgOffline && !isLocked) {
+ uint64_t bytesCopied;
+ stopit = CopyStoreMessage(mailHdr, newMailHdr, bytesCopied);
+ if (NS_SUCCEEDED(stopit)) {
+ uint32_t unused;
+ newMailHdr->OrFlags(nsMsgMessageFlags::Offline, &unused);
+ newMailHdr->SetOfflineMessageSize(bytesCopied);
+ }
+ } else {
+ database->MarkOffline(fakeBase + sourceKeyIndex, false, nullptr);
+ }
+
+ nsCOMPtr<nsIMsgOfflineOpsDatabase> opsDb =
+ do_QueryInterface(database, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgOfflineImapOperation> destOp;
+ opsDb->GetOfflineOpForKey(fakeBase + sourceKeyIndex, true,
+ getter_AddRefs(destOp));
+ if (destOp) {
+ // check if this is a move back to the original mailbox, in which
+ // case we just delete the offline operation.
+ if (messageReturningHome) {
+ opsDb->RemoveOfflineOp(destOp);
+ } else {
+ SetFlag(nsMsgFolderFlags::OfflineEvents);
+ destOp->SetSourceFolderURI(originalSrcFolderURI);
+ destOp->SetSrcMessageKey(originalKey);
+ addedKeys.AppendElement(fakeBase + sourceKeyIndex);
+ addedHdrs.AppendObject(newMailHdr);
+ }
+ } else {
+ stopit = NS_ERROR_FAILURE;
+ }
+ }
+ successfulCopy = NS_SUCCEEDED(stopit);
+ nsMsgKey msgKey;
+ mailHdr->GetMessageKey(&msgKey);
+ if (isMove && successfulCopy) {
+ if (deleteToTrash || deleteImmediately)
+ keysToDelete.AppendElement(msgKey);
+ else
+ sourceMailDB->MarkImapDeleted(msgKey, true,
+ nullptr); // offline delete
+ }
+ if (successfulCopy) {
+ // This is for both moves and copies
+ msgHdrsCopied.AppendElement(mailHdr);
+ }
+ }
+ } // End message loop.
+ EnableNotifications(nsIMsgFolder::allMessageCountNotifications, true);
+ RefPtr<nsImapOfflineTxn> addHdrMsgTxn = new nsImapOfflineTxn(
+ this, &addedKeys, nullptr, this, isMove,
+ nsIMsgOfflineImapOperation::kAddedHeader, addedHdrs);
+ if (addHdrMsgTxn && txnMgr) txnMgr->DoTransaction(addHdrMsgTxn);
+ RefPtr<nsImapOfflineTxn> undoMsgTxn =
+ new nsImapOfflineTxn(srcFolder, &srcKeyArray, messageIds.get(), this,
+ isMove, moveCopyOpType, srcMsgs);
+ if (undoMsgTxn) {
+ if (isMove) {
+ undoMsgTxn->SetTransactionType(nsIMessenger::eMoveMsg);
+ nsCOMPtr<nsIMsgImapMailFolder> srcIsImap(
+ do_QueryInterface(srcFolder));
+ // remember this undo transaction so we can hook up the result
+ // msg ids in the undo transaction.
+ if (srcIsImap) {
+ nsImapMailFolder* srcImapFolder =
+ static_cast<nsImapMailFolder*>(srcFolder);
+ srcImapFolder->m_pendingOfflineMoves.AppendElement(undoMsgTxn);
+ }
+ } else {
+ undoMsgTxn->SetTransactionType(nsIMessenger::eCopyMsg);
+ }
+ // we're adding this undo action before the delete is successful. This
+ // is evil, but 4.5 did it as well.
+ if (txnMgr) txnMgr->DoTransaction(undoMsgTxn);
+ }
+ undoMsgTxn =
+ new nsImapOfflineTxn(srcFolder, &srcKeyArray, messageIds.get(), this,
+ isMove, deleteOpType, srcMsgs);
+ if (undoMsgTxn) {
+ if (isMove) {
+ if (mFlags & nsMsgFolderFlags::Trash) {
+ undoMsgTxn->SetTransactionType(nsIMessenger::eDeleteMsg);
+ } else {
+ undoMsgTxn->SetTransactionType(nsIMessenger::eMoveMsg);
+ }
+ } else {
+ undoMsgTxn->SetTransactionType(nsIMessenger::eCopyMsg);
+ }
+ if (txnMgr) txnMgr->DoTransaction(undoMsgTxn);
+ }
+
+ if (isMove) sourceMailDB->Commit(nsMsgDBCommitType::kLargeCommit);
+ database->Commit(nsMsgDBCommitType::kLargeCommit);
+ SummaryChanged();
+ srcFolder->SummaryChanged();
+ }
+ if (txnMgr) txnMgr->EndBatch(false);
+ }
+
+ if (!msgHdrsCopied.IsEmpty()) {
+ nsCOMPtr<nsIMsgFolderNotificationService> notifier(
+ do_GetService("@mozilla.org/messenger/msgnotificationservice;1"));
+ if (notifier) {
+ notifier->NotifyMsgsMoveCopyCompleted(isMove, msgHdrsCopied, this,
+ destMsgHdrs);
+ }
+ }
+
+ // NOTE (Bug 1787963):
+ // If we're performing a move, by rights we should be deleting the source
+ // message(s) here. But that would mean they won't be available when we try
+ // to run the offline move operation once we're back online. So we'll just
+ // leave things as they are:
+ // - the message(s) copied into the destination folder
+ // - the original message(s) left in the source folder
+ // - the offline move operation all queued up for when we go back online
+ // When we do go back online, the offline move op will be performed and
+ // the source message(s) will be deleted. For real.
+ // Would be nice to have some marker to hide or grey out messages which are
+ // in this state of impending doom... but it's a pretty obscure corner case
+ // and we've already got quite enough of those.
+ //
+ // BUT... CopyMessagesOffline() is also used when online (ha!), *if* we're
+ // copying between folders on the same nsIMsgIncomingServer, in order to
+ // support undo. In that case we _do_ want to go ahead with the delete now.
+
+ bool sameServer;
+ rv = IsOnSameServer(srcFolder, this, &sameServer);
+
+ if (NS_SUCCEEDED(rv) && sameServer && isMove &&
+ (deleteToTrash || deleteImmediately)) {
+ DeleteStoreMessages(keysToDelete, srcFolder);
+ srcFolder->EnableNotifications(nsIMsgFolder::allMessageCountNotifications,
+ false);
+ sourceMailDB->DeleteMessages(keysToDelete, nullptr);
+ srcFolder->EnableNotifications(nsIMsgFolder::allMessageCountNotifications,
+ true);
+ }
+ nsCOMPtr<nsISupports> srcSupport = do_QueryInterface(srcFolder);
+ OnCopyCompleted(srcSupport, rv);
+
+ if (isMove) {
+ srcFolder->NotifyFolderEvent(NS_SUCCEEDED(rv) ? kDeleteOrMoveMsgCompleted
+ : kDeleteOrMoveMsgFailed);
+ }
+ return rv;
+}
+
+void nsImapMailFolder::SetPendingAttributes(
+ const nsTArray<RefPtr<nsIMsgDBHdr>>& messages, bool aIsMove,
+ bool aSetOffline) {
+ GetDatabase();
+ if (!mDatabase) return;
+
+ uint32_t supportedUserFlags;
+ GetSupportedUserFlags(&supportedUserFlags);
+
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> prefBranch(
+ do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ nsCString dontPreserve;
+
+ // These preferences exist so that extensions can control which properties
+ // are preserved in the database when a message is moved or copied. All
+ // properties are preserved except those listed in these preferences
+ if (aIsMove)
+ prefBranch->GetCharPref("mailnews.database.summary.dontPreserveOnMove",
+ dontPreserve);
+ else
+ prefBranch->GetCharPref("mailnews.database.summary.dontPreserveOnCopy",
+ dontPreserve);
+
+ // We'll add spaces at beginning and end so we can search for space-name-space
+ nsCString dontPreserveEx(" "_ns);
+ dontPreserveEx.Append(dontPreserve);
+ dontPreserveEx.Append(' ');
+
+ // these properties are set as integers below, so don't set them again
+ // in the iteration through the properties
+ dontPreserveEx.AppendLiteral(
+ "offlineMsgSize msgOffset flags priority pseudoHdr ");
+
+ // these fields are either copied separately when the server does not support
+ // custom IMAP flags, or managed directly through the flags
+ dontPreserveEx.AppendLiteral("keywords label ");
+
+ // check if any msg hdr has special flags or properties set
+ // that we need to set on the dest hdr
+ for (auto msgDBHdr : messages) {
+ if (!(supportedUserFlags & kImapMsgSupportUserFlag)) {
+ nsCString keywords;
+ msgDBHdr->GetStringProperty("keywords", keywords);
+ if (!keywords.IsEmpty())
+ mDatabase->SetAttributeOnPendingHdr(msgDBHdr, "keywords",
+ keywords.get());
+ }
+
+ nsTArray<nsCString> properties;
+ nsresult rv = msgDBHdr->GetProperties(properties);
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ nsCString sourceString;
+ for (auto property : properties) {
+ nsAutoCString propertyEx(" "_ns);
+ propertyEx.Append(property);
+ propertyEx.Append(' ');
+ if (dontPreserveEx.Find(propertyEx) != kNotFound) continue;
+
+ nsCString sourceString;
+ msgDBHdr->GetStringProperty(property.get(), sourceString);
+ mDatabase->SetAttributeOnPendingHdr(msgDBHdr, property.get(),
+ sourceString.get());
+ }
+
+ // Carry over HasRe flag.
+ uint32_t flags;
+ uint32_t storeFlags = 0;
+ msgDBHdr->GetFlags(&flags);
+ if (flags & nsMsgMessageFlags::HasRe) {
+ storeFlags = nsMsgMessageFlags::HasRe;
+ mDatabase->SetUint32AttributeOnPendingHdr(msgDBHdr, "flags", storeFlags);
+ }
+
+ uint32_t messageSize;
+ uint64_t messageOffset;
+ nsCString storeToken;
+ msgDBHdr->GetMessageOffset(&messageOffset);
+ msgDBHdr->GetOfflineMessageSize(&messageSize);
+ msgDBHdr->GetStringProperty("storeToken", storeToken);
+ if (messageSize) {
+ mDatabase->SetUint32AttributeOnPendingHdr(msgDBHdr, "offlineMsgSize",
+ messageSize);
+ mDatabase->SetUint64AttributeOnPendingHdr(msgDBHdr, "msgOffset",
+ messageOffset);
+ // Not always setting "flags" attribute to nsMsgMessageFlags::Offline
+ // here because it can cause missing parts (inline or attachments)
+ // when messages are moved or copied manually or by filter action.
+ if (aSetOffline)
+ mDatabase->SetUint32AttributeOnPendingHdr(
+ msgDBHdr, "flags", storeFlags | nsMsgMessageFlags::Offline);
+ mDatabase->SetAttributeOnPendingHdr(msgDBHdr, "storeToken",
+ storeToken.get());
+ }
+ nsMsgPriorityValue priority;
+ msgDBHdr->GetPriority(&priority);
+ if (priority != 0) {
+ nsAutoCString priorityStr;
+ priorityStr.AppendInt(priority);
+ mDatabase->SetAttributeOnPendingHdr(msgDBHdr, "priority",
+ priorityStr.get());
+ }
+ }
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::CopyMessages(
+ nsIMsgFolder* srcFolder, nsTArray<RefPtr<nsIMsgDBHdr>> const& messages,
+ bool isMove, nsIMsgWindow* msgWindow, nsIMsgCopyServiceListener* listener,
+ bool isFolder, // isFolder for future use when we do cross-server folder
+ // move/copy
+ bool allowUndo) {
+ UpdateTimestamps(allowUndo);
+
+ nsresult rv;
+ nsCOMPtr<nsISupports> srcSupport = do_QueryInterface(srcFolder);
+
+ bool sameServer;
+ rv = IsOnSameServer(srcFolder, this, &sameServer);
+ if (NS_FAILED(rv)) goto done;
+
+ // in theory, if allowUndo is true, then this is a user initiated
+ // action, and we should do it pseudo-offline. If it's not
+ // user initiated (e.g., mail filters firing), then allowUndo is
+ // false, and we should just do the action.
+ if (!WeAreOffline() && sameServer && allowUndo) {
+ // complete the copy operation as in offline mode
+ rv = CopyMessagesOffline(srcFolder, messages, isMove, msgWindow, listener);
+
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "error offline copy");
+ // We'll warn if this fails, but we should still try to play back
+ // offline ops, because it's possible the copy got far enough to
+ // create the offline ops.
+
+ // We make sure that the source folder is an imap folder by limiting
+ // pseudo-offline operations to the same imap server. If we extend the code
+ // to cover non imap folders in the future (i.e. imap folder->local folder),
+ // then the following downcast will cause either a crash or compiler error.
+ // Do not forget to change it accordingly.
+ nsImapMailFolder* srcImapFolder = static_cast<nsImapMailFolder*>(srcFolder);
+
+ // if there is no pending request, create a new one, and set the timer.
+ // Otherwise use the existing one to reset the timer. it is callback
+ // function's responsibility to delete the new request object
+ if (!srcImapFolder->m_pendingPlaybackReq) {
+ srcImapFolder->m_pendingPlaybackReq =
+ new nsPlaybackRequest(srcImapFolder, msgWindow);
+ }
+
+ // Create and start a new playback one-shot timer. If there is already a
+ // timer created that has not timed out, cancel it.
+ if (srcImapFolder->m_playbackTimer)
+ srcImapFolder->m_playbackTimer->Cancel();
+ rv = NS_NewTimerWithFuncCallback(
+ getter_AddRefs(srcImapFolder->m_playbackTimer), PlaybackTimerCallback,
+ (void*)srcImapFolder->m_pendingPlaybackReq,
+ PLAYBACK_TIMER_INTERVAL_IN_MS, nsITimer::TYPE_ONE_SHOT,
+ "nsImapMailFolder::PlaybackTimerCallback", nullptr);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Could not start m_playbackTimer timer");
+ }
+ return rv;
+ } else {
+ // sort the message array by key
+
+ nsTArray<nsMsgKey> keyArray(messages.Length());
+ for (nsIMsgDBHdr* aMessage : messages) {
+ if (!aMessage) {
+ continue;
+ }
+ nsMsgKey key;
+ aMessage->GetMessageKey(&key);
+ keyArray.AppendElement(key);
+ }
+ keyArray.Sort();
+
+ nsTArray<RefPtr<nsIMsgDBHdr>> sortedMsgs;
+ rv = MessagesInKeyOrder(keyArray, srcFolder, sortedMsgs);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (WeAreOffline())
+ return CopyMessagesOffline(srcFolder, sortedMsgs, isMove, msgWindow,
+ listener);
+
+ nsCOMPtr<nsIImapService> imapService =
+ do_GetService("@mozilla.org/messenger/imapservice;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // 3rd parameter: Do not set offline flag.
+ SetPendingAttributes(sortedMsgs, isMove, false);
+
+ // if the folders aren't on the same server, do a stream base copy
+ if (!sameServer) {
+ rv = CopyMessagesWithStream(srcFolder, sortedMsgs, isMove, true,
+ msgWindow, listener, allowUndo);
+ goto done;
+ }
+
+ nsAutoCString messageIds;
+ rv = AllocateUidStringFromKeys(keyArray, messageIds);
+ if (NS_FAILED(rv)) goto done;
+
+ nsCOMPtr<nsIUrlListener> urlListener;
+ rv =
+ QueryInterface(NS_GET_IID(nsIUrlListener), getter_AddRefs(urlListener));
+ rv = InitCopyState(srcSupport, sortedMsgs, isMove, true, false, 0,
+ EmptyCString(), listener, msgWindow, allowUndo);
+ if (NS_FAILED(rv)) goto done;
+
+ m_copyState->m_curIndex = m_copyState->m_messages.Length();
+
+ if (isMove)
+ srcFolder->EnableNotifications(
+ allMessageCountNotifications,
+ false); // disable message count notification
+
+ nsCOMPtr<nsIURI> resultUrl;
+ nsCOMPtr<nsISupports> copySupport = do_QueryInterface(m_copyState);
+ rv = imapService->OnlineMessageCopy(
+ srcFolder, messageIds, this, true, isMove, urlListener,
+ getter_AddRefs(resultUrl), copySupport, msgWindow);
+ if (NS_SUCCEEDED(rv) && m_copyState->m_allowUndo) {
+ RefPtr<nsImapMoveCopyMsgTxn> undoMsgTxn = new nsImapMoveCopyMsgTxn;
+ if (!undoMsgTxn ||
+ NS_FAILED(undoMsgTxn->Init(srcFolder, &keyArray, messageIds.get(),
+ this, true, isMove)))
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ if (isMove) {
+ if (mFlags & nsMsgFolderFlags::Trash)
+ undoMsgTxn->SetTransactionType(nsIMessenger::eDeleteMsg);
+ else
+ undoMsgTxn->SetTransactionType(nsIMessenger::eMoveMsg);
+ } else
+ undoMsgTxn->SetTransactionType(nsIMessenger::eCopyMsg);
+ m_copyState->m_undoMsgTxn = undoMsgTxn;
+ }
+
+ } // endif
+
+done:
+ if (NS_FAILED(rv)) {
+ (void)OnCopyCompleted(srcSupport, rv);
+ if (isMove) {
+ srcFolder->EnableNotifications(
+ allMessageCountNotifications,
+ true); // enable message count notification
+ NotifyFolderEvent(kDeleteOrMoveMsgFailed);
+ }
+ }
+ return rv;
+}
+
+// This is used when copying an imap or local/pop3 folder to an imap server.
+// It does not allow completely moving an imap or local/pop3 folder to an imap
+// server since only the messages can be moved between servers.
+class nsImapFolderCopyState final : public nsIUrlListener,
+ public nsIMsgCopyServiceListener {
+ public:
+ nsImapFolderCopyState(nsIMsgFolder* destParent, nsIMsgFolder* srcFolder,
+ bool isMoveMessages, nsIMsgWindow* msgWindow,
+ nsIMsgCopyServiceListener* listener);
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIURLLISTENER
+ NS_DECL_NSIMSGCOPYSERVICELISTENER
+
+ nsresult StartNextCopy();
+ nsresult AdvanceToNextFolder(nsresult aStatus);
+
+ protected:
+ ~nsImapFolderCopyState();
+ RefPtr<nsImapMailFolder> m_newDestFolder;
+ nsCOMPtr<nsISupports> m_origSrcFolder;
+ nsCOMPtr<nsIMsgFolder> m_curDestParent;
+ nsCOMPtr<nsIMsgFolder> m_curSrcFolder;
+ bool m_isMoveMessages;
+ nsCOMPtr<nsIMsgCopyServiceListener> m_copySrvcListener;
+ nsCOMPtr<nsIMsgWindow> m_msgWindow;
+ int32_t m_childIndex;
+ nsCOMArray<nsIMsgFolder> m_srcChildFolders;
+ nsCOMArray<nsIMsgFolder> m_destParents;
+};
+
+NS_IMPL_ISUPPORTS(nsImapFolderCopyState, nsIUrlListener,
+ nsIMsgCopyServiceListener)
+
+nsImapFolderCopyState::nsImapFolderCopyState(
+ nsIMsgFolder* destParent, nsIMsgFolder* srcFolder, bool isMoveMessages,
+ nsIMsgWindow* msgWindow, nsIMsgCopyServiceListener* listener) {
+ m_origSrcFolder = do_QueryInterface(srcFolder);
+ m_curDestParent = destParent;
+ m_curSrcFolder = srcFolder;
+ m_isMoveMessages = isMoveMessages;
+ m_msgWindow = msgWindow;
+ m_copySrvcListener = listener;
+ m_childIndex = -1;
+ // NOTE: The nsImapMailFolder doesn't keep a reference to us, so we're
+ // relying on our use as a listener by nsImapService and nsMsgCopyService
+ // to keep our refcount from zeroing!
+ // Might be safer to add a kungfudeathgrip on ourselves for the duration
+ // of the operation? Would need to make sure we catch all error conditions.
+}
+
+nsImapFolderCopyState::~nsImapFolderCopyState() {}
+
+nsresult nsImapFolderCopyState::StartNextCopy() {
+ nsresult rv;
+ // Create the destination folder (our OnStopRunningUrl() will be called
+ // when done).
+ nsCOMPtr<nsIImapService> imapService =
+ do_GetService("@mozilla.org/messenger/imapservice;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsString folderName;
+ m_curSrcFolder->GetName(folderName);
+ return imapService->EnsureFolderExists(m_curDestParent, folderName,
+ m_msgWindow, this);
+}
+
+nsresult nsImapFolderCopyState::AdvanceToNextFolder(nsresult aStatus) {
+ nsresult rv = NS_OK;
+ m_childIndex++;
+ if (m_childIndex >= m_srcChildFolders.Count()) {
+ if (m_newDestFolder)
+ m_newDestFolder->OnCopyCompleted(m_origSrcFolder, aStatus);
+ } else {
+ m_curDestParent = m_destParents[m_childIndex];
+ m_curSrcFolder = m_srcChildFolders[m_childIndex];
+ rv = StartNextCopy();
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsImapFolderCopyState::OnStartRunningUrl(nsIURI* aUrl) {
+ NS_ASSERTION(aUrl, "sanity check - need to be be running non-null url");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapFolderCopyState::OnStopRunningUrl(nsIURI* aUrl, nsresult aExitCode) {
+ if (NS_FAILED(aExitCode)) {
+ if (m_copySrvcListener) m_copySrvcListener->OnStopCopy(aExitCode);
+ return aExitCode; // or NS_OK???
+ }
+ nsresult rv = NS_OK;
+ if (aUrl) {
+ nsCOMPtr<nsIImapUrl> imapUrl = do_QueryInterface(aUrl);
+ if (imapUrl) {
+ nsImapAction imapAction = nsIImapUrl::nsImapTest;
+ imapUrl->GetImapAction(&imapAction);
+
+ switch (imapAction) {
+ case nsIImapUrl::nsImapEnsureExistsFolder: {
+ // Our EnsureFolderExists() call has completed successfully,
+ // so our dest folder is ready.
+ nsCOMPtr<nsIMsgFolder> newMsgFolder;
+ nsString folderName;
+ nsCString utfLeafName;
+ m_curSrcFolder->GetName(folderName);
+ bool utf8AcceptEnabled;
+ nsCOMPtr<nsIMsgImapMailFolder> imapFolder =
+ do_QueryInterface(m_curDestParent);
+ rv = imapFolder->GetShouldUseUtf8FolderName(&utf8AcceptEnabled);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (utf8AcceptEnabled) {
+ CopyUTF16toUTF8(folderName, utfLeafName);
+ } else {
+ CopyUTF16toMUTF7(folderName, utfLeafName);
+ }
+ // Create the nsIMsgFolder object which represents the folder on
+ // the IMAP server.
+ rv = m_curDestParent->FindSubFolder(utfLeafName,
+ getter_AddRefs(newMsgFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+ // Save the first new folder so we can send a notification to the
+ // copy service when this whole process is done.
+ if (!m_newDestFolder)
+ m_newDestFolder =
+ static_cast<nsImapMailFolder*>(newMsgFolder.get());
+
+ // Check if the source folder has children. If it does, list them
+ // into m_srcChildFolders, and set m_destParents for the
+ // corresponding indexes to the newly created folder.
+ nsTArray<RefPtr<nsIMsgFolder>> subFolders;
+ rv = m_curSrcFolder->GetSubFolders(subFolders);
+ NS_ENSURE_SUCCESS(rv, rv);
+ uint32_t childIndex = 0;
+ for (nsIMsgFolder* folder : subFolders) {
+ m_srcChildFolders.InsertElementAt(m_childIndex + childIndex + 1,
+ folder);
+ m_destParents.InsertElementAt(m_childIndex + childIndex + 1,
+ newMsgFolder);
+ ++childIndex;
+ }
+
+ // Now kick off a copy (or move) of messages to the new folder.
+ nsCOMPtr<nsIMsgEnumerator> enumerator;
+ rv = m_curSrcFolder->GetMessages(getter_AddRefs(enumerator));
+ nsTArray<RefPtr<nsIMsgDBHdr>> msgArray;
+ bool hasMore = false;
+
+ if (enumerator) rv = enumerator->HasMoreElements(&hasMore);
+
+ // Early-out for empty folder.
+ if (!hasMore) return AdvanceToNextFolder(NS_OK);
+
+ while (NS_SUCCEEDED(rv) && hasMore) {
+ nsCOMPtr<nsIMsgDBHdr> hdr;
+ rv = enumerator->GetNext(getter_AddRefs(hdr));
+ NS_ENSURE_SUCCESS(rv, rv);
+ msgArray.AppendElement(hdr);
+ rv = enumerator->HasMoreElements(&hasMore);
+ }
+
+ nsCOMPtr<nsIMsgCopyService> copyService =
+ do_GetService("@mozilla.org/messenger/messagecopyservice;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = copyService->CopyMessages(m_curSrcFolder, msgArray, newMsgFolder,
+ m_isMoveMessages, this, m_msgWindow,
+ false /* allowUndo */);
+ } break;
+ }
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapFolderCopyState::OnStartCopy() { return NS_OK; }
+
+/* void OnProgress (in uint32_t aProgress, in uint32_t aProgressMax); */
+NS_IMETHODIMP nsImapFolderCopyState::OnProgress(uint32_t aProgress,
+ uint32_t aProgressMax) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/* void SetMessageKey (in nsMsgKey aKey); */
+NS_IMETHODIMP nsImapFolderCopyState::SetMessageKey(nsMsgKey aKey) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/* [noscript] void GetMessageId (in nsCString aMessageId); */
+NS_IMETHODIMP nsImapFolderCopyState::GetMessageId(nsACString& messageId) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/* void OnStopCopy (in nsresult aStatus); */
+NS_IMETHODIMP nsImapFolderCopyState::OnStopCopy(nsresult aStatus) {
+ if (NS_SUCCEEDED(aStatus)) return AdvanceToNextFolder(aStatus);
+ if (m_copySrvcListener) {
+ (void)m_copySrvcListener->OnStopCopy(aStatus);
+ m_copySrvcListener = nullptr;
+ }
+
+ return NS_OK;
+}
+
+// "this" is the destination (parent) imap folder that srcFolder is copied to.
+// srcFolder may be another imap or a local/pop3 folder.
+NS_IMETHODIMP
+nsImapMailFolder::CopyFolder(nsIMsgFolder* srcFolder, bool isMoveFolder,
+ nsIMsgWindow* msgWindow,
+ nsIMsgCopyServiceListener* listener) {
+ NS_ENSURE_ARG_POINTER(srcFolder);
+ nsresult rv;
+ bool sameServer;
+ rv = IsOnSameServer(this, srcFolder, &sameServer);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (sameServer && isMoveFolder) {
+ // Do a pure folder move within the same IMAP account/server, where
+ // "pure" means the folder AND messages are copied to the destination and
+ // then both are removed from source account.
+ uint32_t folderFlags = 0;
+ if (srcFolder) srcFolder->GetFlags(&folderFlags);
+
+ // if our source folder is a virtual folder
+ if (folderFlags & nsMsgFolderFlags::Virtual) {
+ nsCOMPtr<nsIMsgFolder> newMsgFolder;
+ nsString folderName;
+ srcFolder->GetName(folderName);
+
+ nsAutoString safeFolderName(folderName);
+ NS_MsgHashIfNecessary(safeFolderName);
+
+ srcFolder->ForceDBClosed();
+
+ nsCOMPtr<nsIFile> oldPathFile;
+ rv = srcFolder->GetFilePath(getter_AddRefs(oldPathFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> summaryFile;
+ GetSummaryFileLocation(oldPathFile, getter_AddRefs(summaryFile));
+
+ nsCOMPtr<nsIFile> newPathFile;
+ rv = GetFilePath(getter_AddRefs(newPathFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool isDirectory = false;
+ newPathFile->IsDirectory(&isDirectory);
+ if (!isDirectory) {
+ AddDirectorySeparator(newPathFile);
+ rv = newPathFile->Create(nsIFile::DIRECTORY_TYPE, 0700);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ rv = CheckIfFolderExists(folderName, this, msgWindow);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = summaryFile->CopyTo(newPathFile, EmptyString());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = AddSubfolder(safeFolderName, getter_AddRefs(newMsgFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ newMsgFolder->SetPrettyName(folderName);
+
+ uint32_t flags;
+ srcFolder->GetFlags(&flags);
+ newMsgFolder->SetFlags(flags);
+
+ NotifyFolderAdded(newMsgFolder);
+
+ // now remove the old folder
+ nsCOMPtr<nsIMsgFolder> msgParent;
+ srcFolder->GetParent(getter_AddRefs(msgParent));
+ srcFolder->SetParent(nullptr);
+ if (msgParent) {
+ // The files have already been moved, so delete storage false.
+ msgParent->PropagateDelete(srcFolder, false);
+ oldPathFile->Remove(false); // berkeley mailbox
+ srcFolder->DeleteStorage();
+
+ nsCOMPtr<nsIFile> parentPathFile;
+ rv = msgParent->GetFilePath(getter_AddRefs(parentPathFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ AddDirectorySeparator(parentPathFile);
+ nsCOMPtr<nsIDirectoryEnumerator> children;
+ parentPathFile->GetDirectoryEntries(getter_AddRefs(children));
+ bool more;
+ // checks if the directory is empty or not
+ if (children && NS_SUCCEEDED(children->HasMoreElements(&more)) && !more)
+ parentPathFile->Remove(true);
+ }
+ } else // non-virtual folder
+ {
+ nsCOMPtr<nsIImapService> imapService =
+ do_GetService("@mozilla.org/messenger/imapservice;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsISupports> srcSupport = do_QueryInterface(srcFolder);
+ bool match = false;
+ bool confirmed = false;
+ if (mFlags & nsMsgFolderFlags::Trash) {
+ rv = srcFolder->MatchOrChangeFilterDestination(nullptr, false, &match);
+ if (match) {
+ srcFolder->ConfirmFolderDeletionForFilter(msgWindow, &confirmed);
+ // should we return an error to copy service?
+ // or send a notification?
+ if (!confirmed) return NS_OK;
+ }
+ }
+ rv = InitCopyState(srcSupport, {}, false, false, false, 0, EmptyCString(),
+ listener, msgWindow, false);
+ if (NS_FAILED(rv)) return OnCopyCompleted(srcSupport, rv);
+
+ rv = imapService->MoveFolder(srcFolder, this, this, msgWindow);
+ }
+ } else {
+ // !sameServer OR it's a copy. Unit tests expect a successful folder
+ // copy within the same IMAP server even though the UI forbids copy and
+ // only allows moves inside the same server. folderCopier, set below,
+ // handles the folder copy within an IMAP server (needed by unit tests) and
+ // the folder move or copy from another account or server into an IMAP
+ // account/server. The folder move from another account is "impure" since
+ // just the messages are moved and the source folder remains in place.
+ RefPtr<nsImapFolderCopyState> folderCopier = new nsImapFolderCopyState(
+ this, srcFolder,
+ isMoveFolder, // Always copy folders; if true only move the messages
+ msgWindow, listener);
+ // NOTE: the copystate object must hold itself in existence until complete,
+ // as we're not keeping hold of it here.
+ rv = folderCopier->StartNextCopy();
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::CopyFileMessage(nsIFile* file, nsIMsgDBHdr* msgToReplace,
+ bool isDraftOrTemplate, uint32_t aNewMsgFlags,
+ const nsACString& aNewMsgKeywords,
+ nsIMsgWindow* msgWindow,
+ nsIMsgCopyServiceListener* listener) {
+ nsresult rv = NS_ERROR_NULL_POINTER;
+ nsMsgKey key = nsMsgKey_None;
+ nsAutoCString messageId;
+ nsTArray<RefPtr<nsIMsgDBHdr>> messages;
+
+ nsCOMPtr<nsIImapService> imapService =
+ do_GetService("@mozilla.org/messenger/imapservice;1", &rv);
+ if (NS_FAILED(rv)) return OnCopyCompleted(file, rv);
+
+ if (msgToReplace) {
+ rv = msgToReplace->GetMessageKey(&key);
+ if (NS_SUCCEEDED(rv)) {
+ messageId.AppendInt((int32_t)key);
+ // We have an existing message to replace because the user has deleted or
+ // detached one or more attachments. So tell SetPendingAttributes() to
+ // not set several pending offline items (offset, message size, etc.) for
+ // the message to be replaced by setting message size temporarily to zero.
+ // The original message is not actually "replaced" but is imap deleted
+ // and a new message with the same body but with some deleted or detached
+ // attachments is imap appended from the file to the folder.
+ uint32_t saveMsgSize;
+ msgToReplace->GetOfflineMessageSize(&saveMsgSize);
+ msgToReplace->SetOfflineMessageSize(0);
+ SetPendingAttributes({msgToReplace}, false, false);
+ msgToReplace->SetOfflineMessageSize(saveMsgSize);
+ messages.AppendElement(msgToReplace);
+ }
+ }
+
+ bool isMove = (msgToReplace ? true : false);
+ rv = InitCopyState(file, messages, isMove, isDraftOrTemplate, false,
+ aNewMsgFlags, aNewMsgKeywords, listener, msgWindow, false);
+ if (NS_FAILED(rv)) return OnCopyCompleted(file, rv);
+
+ m_copyState->m_streamCopy = true;
+ rv = imapService->AppendMessageFromFile(file, this, messageId, true,
+ isDraftOrTemplate, this, m_copyState,
+ msgWindow);
+ if (NS_FAILED(rv)) return OnCopyCompleted(file, rv);
+
+ return rv;
+}
+
+nsresult nsImapMailFolder::CopyStreamMessage(
+ nsIMsgDBHdr* message,
+ nsIMsgFolder* dstFolder, // should be this
+ nsIMsgWindow* aMsgWindow, bool isMove) {
+ NS_ENSURE_ARG_POINTER(message);
+ if (!m_copyState)
+ MOZ_LOG(IMAP, mozilla::LogLevel::Info,
+ ("CopyStreamMessage failed with null m_copyState"));
+ NS_ENSURE_TRUE(m_copyState, NS_ERROR_NULL_POINTER);
+ nsresult rv;
+ nsCOMPtr<nsICopyMessageStreamListener> copyStreamListener = do_CreateInstance(
+ "@mozilla.org/messenger/copymessagestreamlistener;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsICopyMessageListener> copyListener(
+ do_QueryInterface(dstFolder, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgFolder> srcFolder(
+ do_QueryInterface(m_copyState->m_srcSupport, &rv));
+ if (NS_FAILED(rv))
+ MOZ_LOG(IMAP, mozilla::LogLevel::Info,
+ ("CopyStreaMessage failed with null m_copyState->m_srcSupport"));
+ if (NS_FAILED(rv)) return rv;
+ rv = copyStreamListener->Init(copyListener);
+ if (NS_FAILED(rv))
+ MOZ_LOG(IMAP, mozilla::LogLevel::Info,
+ ("CopyStreaMessage failed in copyStreamListener->Init"));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCString uri;
+ srcFolder->GetUriForMsg(message, uri);
+
+ if (!m_copyState->m_msgService)
+ rv = GetMessageServiceFromURI(uri,
+ getter_AddRefs(m_copyState->m_msgService));
+
+ if (NS_SUCCEEDED(rv) && m_copyState->m_msgService) {
+ nsCOMPtr<nsIStreamListener> streamListener(
+ do_QueryInterface(copyStreamListener, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // put up status message here, if copying more than one message.
+ if (m_copyState->m_messages.Length() > 1) {
+ nsString dstFolderName, progressText;
+ GetName(dstFolderName);
+ nsAutoString curMsgString;
+ nsAutoString totalMsgString;
+ totalMsgString.AppendInt((int32_t)m_copyState->m_messages.Length());
+ curMsgString.AppendInt(m_copyState->m_curIndex + 1);
+
+ AutoTArray<nsString, 3> formatStrings = {curMsgString, totalMsgString,
+ dstFolderName};
+
+ nsCOMPtr<nsIStringBundle> bundle;
+ rv = IMAPGetStringBundle(getter_AddRefs(bundle));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = bundle->FormatStringFromName("imapCopyingMessageOf2", formatStrings,
+ progressText);
+ nsCOMPtr<nsIMsgStatusFeedback> statusFeedback;
+ if (m_copyState->m_msgWindow)
+ m_copyState->m_msgWindow->GetStatusFeedback(
+ getter_AddRefs(statusFeedback));
+ if (statusFeedback) {
+ statusFeedback->ShowStatusString(progressText);
+ int32_t percent;
+ percent = (100 * m_copyState->m_curIndex) /
+ (int32_t)m_copyState->m_messages.Length();
+ statusFeedback->ShowProgress(percent);
+ }
+ }
+ rv = m_copyState->m_msgService->CopyMessage(
+ uri, streamListener, isMove && !m_copyState->m_isCrossServerOp, nullptr,
+ aMsgWindow);
+ if (NS_FAILED(rv))
+ MOZ_LOG(IMAP, mozilla::LogLevel::Info,
+ ("CopyMessage failed: uri %s", uri.get()));
+ }
+ return rv;
+}
+
+nsImapMailCopyState::nsImapMailCopyState()
+ : m_isMove(false),
+ m_selectedState(false),
+ m_isCrossServerOp(false),
+ m_curIndex(0),
+ m_streamCopy(false),
+ m_dataBuffer(nullptr),
+ m_dataBufferSize(0),
+ m_leftOver(0),
+ m_allowUndo(false),
+ m_eatLF(false),
+ m_newMsgFlags(0),
+ m_appendUID(nsMsgKey_None) {}
+
+nsImapMailCopyState::~nsImapMailCopyState() {
+ PR_Free(m_dataBuffer);
+ if (m_tmpFile) m_tmpFile->Remove(false);
+}
+
+NS_IMPL_ISUPPORTS(nsImapMailCopyState, nsImapMailCopyState)
+
+nsresult nsImapMailFolder::InitCopyState(
+ nsISupports* srcSupport, nsTArray<RefPtr<nsIMsgDBHdr>> const& messages,
+ bool isMove, bool selectedState, bool acrossServers, uint32_t newMsgFlags,
+ const nsACString& newMsgKeywords, nsIMsgCopyServiceListener* listener,
+ nsIMsgWindow* msgWindow, bool allowUndo) {
+ NS_ENSURE_ARG_POINTER(srcSupport);
+
+ NS_ENSURE_TRUE(!m_copyState, NS_ERROR_FAILURE);
+
+ m_copyState = new nsImapMailCopyState();
+
+ m_copyState->m_isCrossServerOp = acrossServers;
+ m_copyState->m_srcSupport = srcSupport;
+
+ m_copyState->m_messages = messages.Clone();
+ if (!m_copyState->m_isCrossServerOp) {
+ uint32_t numUnread = 0;
+ for (nsIMsgDBHdr* message : m_copyState->m_messages) {
+ // if the message is not there, then assume what the caller tells us to.
+ bool isRead = false;
+ uint32_t flags;
+ if (message) {
+ message->GetFlags(&flags);
+ isRead = flags & nsMsgMessageFlags::Read;
+ }
+ if (!isRead) numUnread++;
+ }
+ m_copyState->m_unreadCount = numUnread;
+ } else {
+ nsIMsgDBHdr* message = m_copyState->m_messages[m_copyState->m_curIndex];
+ // if the key is not there, then assume what the caller tells us to.
+ bool isRead = false;
+ uint32_t flags;
+ if (message) {
+ message->GetFlags(&flags);
+ isRead = flags & nsMsgMessageFlags::Read;
+ }
+ m_copyState->m_unreadCount = (isRead) ? 0 : 1;
+ }
+
+ m_copyState->m_isMove = isMove;
+ m_copyState->m_newMsgFlags = newMsgFlags;
+ m_copyState->m_newMsgKeywords = newMsgKeywords;
+ m_copyState->m_allowUndo = allowUndo;
+ m_copyState->m_selectedState = selectedState;
+ m_copyState->m_msgWindow = msgWindow;
+ if (listener) m_copyState->m_listener = listener;
+ return NS_OK;
+}
+
+nsresult nsImapMailFolder::CopyFileToOfflineStore(nsIFile* srcFile,
+ nsMsgKey msgKey) {
+ nsresult rv = GetDatabase();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool storeOffline = (mFlags & nsMsgFolderFlags::Offline) && !WeAreOffline();
+
+ if (msgKey == nsMsgKey_None) {
+ // To support send filters, we need to store the message in the database
+ // when it is copied to the FCC folder. In that case, we know the UID of the
+ // message and therefore have the correct msgKey. In other cases, where
+ // we don't need the offline message copied, don't add to db.
+ if (!storeOffline) return NS_OK;
+
+ mDatabase->GetNextFakeOfflineMsgKey(&msgKey);
+ }
+
+ nsCOMPtr<nsIMsgDBHdr> fakeHdr;
+ rv = mDatabase->CreateNewHdr(msgKey, getter_AddRefs(fakeHdr));
+ NS_ENSURE_SUCCESS(rv, rv);
+ fakeHdr->SetUint32Property("pseudoHdr", 1);
+
+ // Should we add this to the offline store?
+ nsCOMPtr<nsIOutputStream> offlineStore;
+ if (storeOffline) {
+ rv = GetOfflineStoreOutputStream(fakeHdr, getter_AddRefs(offlineStore));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // We set an offline kMoveResult because in any case we want to update this
+ // msgHdr with one downloaded from the server, with possible additional
+ // headers added.
+ nsCOMPtr<nsIMsgOfflineImapOperation> op;
+ nsCOMPtr<nsIMsgOfflineOpsDatabase> opsDb = do_QueryInterface(mDatabase, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = opsDb->GetOfflineOpForKey(msgKey, true, getter_AddRefs(op));
+ if (NS_SUCCEEDED(rv) && op) {
+ nsCString destFolderUri;
+ GetURI(destFolderUri);
+ op->SetOperation(nsIMsgOfflineImapOperation::kMoveResult);
+ op->SetDestinationFolderURI(destFolderUri);
+ SetFlag(nsMsgFolderFlags::OfflineEvents);
+ }
+
+ nsCOMPtr<nsIInputStream> inputStream;
+ nsCOMPtr<nsIMsgParseMailMsgState> msgParser =
+ do_CreateInstance("@mozilla.org/messenger/messagestateparser;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ msgParser->SetMailDB(mDatabase);
+
+ rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream), srcFile);
+ if (NS_SUCCEEDED(rv) && inputStream) {
+ // Now, parse the temp file to (optionally) copy to
+ // the offline store for the cur folder.
+ RefPtr<nsMsgLineStreamBuffer> inputStreamBuffer =
+ new nsMsgLineStreamBuffer(FILE_IO_BUFFER_SIZE, true, false);
+ int64_t fileSize;
+ srcFile->GetFileSize(&fileSize);
+ uint32_t bytesWritten;
+ rv = NS_OK;
+ msgParser->SetState(nsIMsgParseMailMsgState::ParseHeadersState);
+ msgParser->SetNewMsgHdr(fakeHdr);
+ bool needMoreData = false;
+ char* newLine = nullptr;
+ uint32_t numBytesInLine = 0;
+ if (offlineStore) {
+ const char* envelope = "From " CRLF;
+ offlineStore->Write(envelope, strlen(envelope), &bytesWritten);
+ fileSize += bytesWritten;
+ }
+ do {
+ newLine = inputStreamBuffer->ReadNextLine(inputStream, numBytesInLine,
+ needMoreData);
+ if (newLine) {
+ msgParser->ParseAFolderLine(newLine, numBytesInLine);
+ if (offlineStore)
+ rv = offlineStore->Write(newLine, numBytesInLine, &bytesWritten);
+
+ free(newLine);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ } while (newLine);
+
+ msgParser->FinishHeader();
+ uint32_t resultFlags;
+ if (offlineStore)
+ fakeHdr->OrFlags(nsMsgMessageFlags::Offline | nsMsgMessageFlags::Read,
+ &resultFlags);
+ else
+ fakeHdr->OrFlags(nsMsgMessageFlags::Read, &resultFlags);
+ if (offlineStore) fakeHdr->SetOfflineMessageSize(fileSize);
+ mDatabase->AddNewHdrToDB(fakeHdr, true /* notify */);
+
+ // Call FinishNewMessage before setting pending attributes, as in
+ // maildir it copies from tmp to cur and may change the storeToken
+ // to get a unique filename.
+ if (offlineStore) {
+ nsCOMPtr<nsIMsgPluggableStore> msgStore;
+ GetMsgStore(getter_AddRefs(msgStore));
+ if (msgStore) msgStore->FinishNewMessage(offlineStore, fakeHdr);
+ }
+
+ // We are copying from a file to offline store so set offline flag.
+ SetPendingAttributes({&*fakeHdr}, false, true);
+
+ // Gloda needs this notification to index the fake message.
+ nsCOMPtr<nsIMsgFolderNotificationService> notifier(
+ do_GetService("@mozilla.org/messenger/msgnotificationservice;1"));
+ if (notifier) notifier->NotifyMsgsClassified({&*fakeHdr}, false, false);
+ inputStream->Close();
+ inputStream = nullptr;
+ }
+ if (offlineStore) offlineStore->Close();
+ return rv;
+}
+
+nsresult nsImapMailFolder::OnCopyCompleted(nsISupports* srcSupport,
+ nsresult rv) {
+ // if it's a file, and the copy succeeded, then fcc the offline
+ // store, and add a kMoveResult offline op.
+ if (NS_SUCCEEDED(rv) && m_copyState) {
+ nsCOMPtr<nsIFile> srcFile(do_QueryInterface(srcSupport));
+ if (srcFile)
+ (void)CopyFileToOfflineStore(srcFile, m_copyState->m_appendUID);
+ }
+ m_copyState = nullptr;
+ nsresult result;
+ nsCOMPtr<nsIMsgCopyService> copyService =
+ do_GetService("@mozilla.org/messenger/messagecopyservice;1", &result);
+ NS_ENSURE_SUCCESS(result, result);
+ return copyService->NotifyCompletion(srcSupport, this, rv);
+}
+
+nsresult nsImapMailFolder::CreateBaseMessageURI(const nsACString& aURI) {
+ return nsCreateImapBaseMessageURI(aURI, mBaseMessageURI);
+}
+
+NS_IMETHODIMP nsImapMailFolder::GetFolderURL(nsACString& aFolderURL) {
+ nsCOMPtr<nsIMsgFolder> rootFolder;
+ nsresult rv = GetRootFolder(getter_AddRefs(rootFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rootFolder->GetURI(aFolderURL);
+ if (rootFolder == this) return NS_OK;
+
+ NS_ASSERTION(mURI.Length() > aFolderURL.Length(),
+ "Should match with a folder name!");
+ nsCString escapedName;
+ MsgEscapeString(Substring(mURI, aFolderURL.Length()),
+ nsINetUtil::ESCAPE_URL_PATH, escapedName);
+ if (escapedName.IsEmpty()) return NS_ERROR_OUT_OF_MEMORY;
+ aFolderURL.Append(escapedName);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailFolder::GetFolderNeedsSubscribing(bool* bVal) {
+ NS_ENSURE_ARG_POINTER(bVal);
+ *bVal = m_folderNeedsSubscribing;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailFolder::SetFolderNeedsSubscribing(bool bVal) {
+ m_folderNeedsSubscribing = bVal;
+ return NS_OK;
+}
+
+nsMsgIMAPFolderACL* nsImapMailFolder::GetFolderACL() {
+ if (!m_folderACL) m_folderACL = new nsMsgIMAPFolderACL(this);
+ return m_folderACL;
+}
+
+nsresult nsImapMailFolder::CreateACLRightsStringForFolder(
+ nsAString& rightsString) {
+ GetFolderACL(); // lazy create
+ NS_ENSURE_TRUE(m_folderACL, NS_ERROR_NULL_POINTER);
+ return m_folderACL->CreateACLRightsString(rightsString);
+}
+
+NS_IMETHODIMP nsImapMailFolder::GetFolderNeedsACLListed(bool* bVal) {
+ NS_ENSURE_ARG_POINTER(bVal);
+ bool dontNeedACLListed = !m_folderNeedsACLListed;
+ // if we haven't acl listed, and it's not a no select folder or the inbox,
+ // then we'll list the acl if it's not a namespace.
+ if (m_folderNeedsACLListed &&
+ !(mFlags & (nsMsgFolderFlags::ImapNoselect | nsMsgFolderFlags::Inbox)))
+ GetIsNamespace(&dontNeedACLListed);
+ *bVal = !dontNeedACLListed;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailFolder::SetFolderNeedsACLListed(bool bVal) {
+ m_folderNeedsACLListed = bVal;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailFolder::GetIsNamespace(bool* aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+ nsresult rv = NS_OK;
+ if (!m_namespace) {
+#ifdef DEBUG_bienvenu
+ // Make sure this isn't causing us to open the database
+ NS_ASSERTION(m_hierarchyDelimiter != kOnlineHierarchySeparatorUnknown,
+ "hierarchy delimiter not set");
+#endif
+
+ nsCString onlineName, serverKey;
+ GetServerKey(serverKey);
+ GetOnlineName(onlineName);
+ char hierarchyDelimiter;
+ GetHierarchyDelimiter(&hierarchyDelimiter);
+
+ nsCOMPtr<nsIImapHostSessionList> hostSession =
+ do_GetService(kCImapHostSessionList, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ m_namespace = nsImapNamespaceList::GetNamespaceForFolder(
+ serverKey.get(), onlineName.get(), hierarchyDelimiter);
+ if (m_namespace == nullptr) {
+ if (mFlags & nsMsgFolderFlags::ImapOtherUser)
+ rv = hostSession->GetDefaultNamespaceOfTypeForHost(
+ serverKey.get(), kOtherUsersNamespace, m_namespace);
+ else if (mFlags & nsMsgFolderFlags::ImapPublic)
+ rv = hostSession->GetDefaultNamespaceOfTypeForHost(
+ serverKey.get(), kPublicNamespace, m_namespace);
+ else
+ rv = hostSession->GetDefaultNamespaceOfTypeForHost(
+ serverKey.get(), kPersonalNamespace, m_namespace);
+ }
+ NS_ASSERTION(m_namespace, "failed to get namespace");
+ if (m_namespace) {
+ nsImapNamespaceList::SuggestHierarchySeparatorForNamespace(
+ m_namespace, hierarchyDelimiter);
+ m_folderIsNamespace = nsImapNamespaceList::GetFolderIsNamespace(
+ serverKey.get(), onlineName.get(), hierarchyDelimiter, m_namespace);
+ }
+ }
+ *aResult = m_folderIsNamespace;
+ return rv;
+}
+
+NS_IMETHODIMP nsImapMailFolder::SetIsNamespace(bool isNamespace) {
+ m_folderIsNamespace = isNamespace;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailFolder::ResetNamespaceReferences() {
+ nsCString serverKey;
+ nsCString onlineName;
+ GetServerKey(serverKey);
+ GetOnlineName(onlineName);
+ char hierarchyDelimiter;
+ GetHierarchyDelimiter(&hierarchyDelimiter);
+ m_namespace = nsImapNamespaceList::GetNamespaceForFolder(
+ serverKey.get(), onlineName.get(), hierarchyDelimiter);
+ m_folderIsNamespace = m_namespace ? nsImapNamespaceList::GetFolderIsNamespace(
+ serverKey.get(), onlineName.get(),
+ hierarchyDelimiter, m_namespace)
+ : false;
+
+ nsTArray<RefPtr<nsIMsgFolder>> subFolders;
+ nsresult rv = GetSubFolders(subFolders);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (nsIMsgFolder* f : subFolders) {
+ nsCOMPtr<nsIMsgImapMailFolder> imapFolder(do_QueryInterface(f, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = imapFolder->ResetNamespaceReferences();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailFolder::FindOnlineSubFolder(
+ const nsACString& targetOnlineName, nsIMsgImapMailFolder** aResultFolder) {
+ *aResultFolder = nullptr;
+ nsresult rv = NS_OK;
+
+ nsCString onlineName;
+ GetOnlineName(onlineName);
+
+ if (onlineName.Equals(targetOnlineName)) {
+ return QueryInterface(NS_GET_IID(nsIMsgImapMailFolder),
+ (void**)aResultFolder);
+ }
+
+ nsTArray<RefPtr<nsIMsgFolder>> subFolders;
+ rv = GetSubFolders(subFolders);
+ NS_ENSURE_SUCCESS(rv, rv);
+ for (nsIMsgFolder* f : subFolders) {
+ nsCOMPtr<nsIMsgImapMailFolder> imapFolder(do_QueryInterface(f, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = imapFolder->FindOnlineSubFolder(targetOnlineName, aResultFolder);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (*aResultFolder) {
+ return NS_OK; // Found it!
+ }
+ }
+ return NS_OK; // Not found.
+}
+
+NS_IMETHODIMP nsImapMailFolder::GetFolderNeedsAdded(bool* bVal) {
+ NS_ENSURE_ARG_POINTER(bVal);
+ *bVal = m_folderNeedsAdded;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailFolder::SetFolderNeedsAdded(bool bVal) {
+ m_folderNeedsAdded = bVal;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailFolder::GetFolderQuotaCommandIssued(bool* aCmdIssued) {
+ NS_ENSURE_ARG_POINTER(aCmdIssued);
+ *aCmdIssued = m_folderQuotaCommandIssued;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailFolder::SetFolderQuotaCommandIssued(bool aCmdIssued) {
+ m_folderQuotaCommandIssued = aCmdIssued;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailFolder::SetFolderQuotaData(
+ uint32_t aAction, const nsACString& aFolderQuotaRoot,
+ uint64_t aFolderQuotaUsage, uint64_t aFolderQuotaLimit) {
+ switch (aAction) {
+ case kInvalidateQuota:
+ // Reset to initialize evaluation of a new quotaroot imap response. This
+ // clears any previous array data and marks the quota data for this folder
+ // invalid.
+ m_folderQuotaDataIsValid = false;
+ m_folderQuota.Clear();
+ break;
+ case kStoreQuota:
+ // Store folder's quota data to an array. This will occur zero or more
+ // times for a folder.
+ m_folderQuota.AppendElement(new nsMsgQuota(
+ aFolderQuotaRoot, aFolderQuotaUsage, aFolderQuotaLimit));
+ break;
+ case kValidateQuota:
+ // GETQUOTAROOT command was successful and OK response has occurred. This
+ // indicates that all the untagged QUOTA responses have occurred so mark
+ // as valid.
+ m_folderQuotaDataIsValid = true;
+ break;
+ default:
+ // Called with undefined aAction parameter.
+ NS_ASSERTION(false, "undefined action");
+ }
+ return NS_OK;
+}
+
+// Provide the quota array for status bar notification.
+NS_IMETHODIMP nsImapMailFolder::GetQuota(
+ nsTArray<RefPtr<nsIMsgQuota>>& aArray) {
+ if (m_folderQuotaDataIsValid) {
+ aArray = m_folderQuota.Clone();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailFolder::PerformExpand(nsIMsgWindow* aMsgWindow) {
+ nsresult rv;
+ bool usingSubscription = false;
+ nsCOMPtr<nsIImapIncomingServer> imapServer;
+ rv = GetImapIncomingServer(getter_AddRefs(imapServer));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = imapServer->GetUsingSubscription(&usingSubscription);
+ if (NS_SUCCEEDED(rv) && !usingSubscription) {
+ nsCOMPtr<nsIImapService> imapService =
+ do_GetService("@mozilla.org/messenger/imapservice;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = imapService->DiscoverChildren(this, this, m_onlineFolderName);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapMailFolder::RenameClient(nsIMsgWindow* msgWindow,
+ nsIMsgFolder* msgFolder,
+ const nsACString& oldName,
+ const nsACString& newName) {
+ nsresult rv;
+ nsCOMPtr<nsIFile> pathFile;
+ rv = GetFilePath(getter_AddRefs(pathFile));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIMsgImapMailFolder> oldImapFolder =
+ do_QueryInterface(msgFolder, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ char hierarchyDelimiter = '/';
+ oldImapFolder->GetHierarchyDelimiter(&hierarchyDelimiter);
+ int32_t boxflags = 0;
+ oldImapFolder->GetBoxFlags(&boxflags);
+
+ nsAutoString newLeafName;
+ NS_ConvertUTF8toUTF16 newNameString(newName);
+ NS_ENSURE_SUCCESS(rv, rv);
+ newLeafName = newNameString;
+ nsAutoString folderNameStr;
+ int32_t folderStart = newLeafName.RFindChar(
+ '/'); // internal use of hierarchyDelimiter is always '/'
+ if (folderStart > 0) {
+ newLeafName = Substring(newNameString, folderStart + 1);
+ CreateDirectoryForFolder(
+ getter_AddRefs(pathFile)); // needed when we move a folder to a folder
+ // with no subfolders.
+ }
+
+ // if we get here, it's really a leaf, and "this" is the parent.
+ folderNameStr = newLeafName;
+
+ // Create an empty database for this mail folder, set its name from the user
+ nsCOMPtr<nsIMsgDatabase> mailDBFactory;
+ nsCOMPtr<nsIMsgFolder> child;
+ nsCOMPtr<nsIMsgImapMailFolder> imapFolder;
+
+ nsCOMPtr<nsIMsgDBService> msgDBService =
+ do_GetService("@mozilla.org/msgDatabase/msgDBService;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgDatabase> unusedDB;
+ nsCOMPtr<nsIFile> dbFile;
+
+ // warning, path will be changed
+ rv = CreateFileForDB(folderNameStr, pathFile, getter_AddRefs(dbFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Use openMailDBFromFile() and not OpenFolderDB() here, since we don't use
+ // the DB.
+ rv = msgDBService->OpenMailDBFromFile(dbFile, nullptr, true, true,
+ getter_AddRefs(unusedDB));
+ if (NS_SUCCEEDED(rv) && unusedDB) {
+ // need to set the folder name
+ nsCOMPtr<nsIDBFolderInfo> folderInfo;
+ rv = unusedDB->GetDBFolderInfo(getter_AddRefs(folderInfo));
+
+ // Now let's create the actual new folder
+ rv = AddSubfolderWithPath(folderNameStr, dbFile, getter_AddRefs(child));
+ if (!child || NS_FAILED(rv)) return rv;
+ nsAutoString unicodeName;
+ rv = CopyFolderNameToUTF16(NS_ConvertUTF16toUTF8(folderNameStr),
+ unicodeName);
+ if (NS_SUCCEEDED(rv)) child->SetPrettyName(unicodeName);
+ imapFolder = do_QueryInterface(child);
+ if (imapFolder) {
+ nsAutoCString onlineName(m_onlineFolderName);
+
+ if (!onlineName.IsEmpty()) onlineName.Append(hierarchyDelimiter);
+ onlineName.Append(NS_ConvertUTF16toUTF8(folderNameStr));
+ imapFolder->SetVerifiedAsOnlineFolder(true);
+ imapFolder->SetOnlineName(onlineName);
+ imapFolder->SetHierarchyDelimiter(hierarchyDelimiter);
+ imapFolder->SetBoxFlags(boxflags);
+ // store the online name as the mailbox name in the db folder info
+ // I don't think anyone uses the mailbox name, so we'll use it
+ // to restore the online name when blowing away an imap db.
+ if (folderInfo) {
+ nsAutoString unicodeOnlineName;
+ CopyUTF8toUTF16(onlineName, unicodeOnlineName);
+ folderInfo->SetMailboxName(unicodeOnlineName);
+ }
+ bool changed = false;
+ msgFolder->MatchOrChangeFilterDestination(
+ child, false /*caseInsensitive*/, &changed);
+ if (changed) msgFolder->AlertFilterChanged(msgWindow);
+ }
+ unusedDB->SetSummaryValid(true);
+ unusedDB->Commit(nsMsgDBCommitType::kLargeCommit);
+ unusedDB->Close(true);
+ child->RenameSubFolders(msgWindow, msgFolder);
+ nsCOMPtr<nsIMsgFolder> msgParent;
+ msgFolder->GetParent(getter_AddRefs(msgParent));
+ msgFolder->SetParent(nullptr);
+ // Reset online status now that the folder is renamed.
+ nsCOMPtr<nsIMsgImapMailFolder> oldImapFolder = do_QueryInterface(msgFolder);
+ if (oldImapFolder) oldImapFolder->SetVerifiedAsOnlineFolder(false);
+ nsCOMPtr<nsIMsgFolderNotificationService> notifier(
+ do_GetService("@mozilla.org/messenger/msgnotificationservice;1"));
+ if (notifier) notifier->NotifyFolderRenamed(msgFolder, child);
+
+ // Do not propagate the deletion until after we have (synchronously)
+ // notified all listeners about the rename. This allows them to access
+ // properties on the source folder without experiencing failures.
+ if (msgParent) msgParent->PropagateDelete(msgFolder, true);
+ NotifyFolderAdded(child);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapMailFolder::RenameSubFolders(nsIMsgWindow* msgWindow,
+ nsIMsgFolder* oldFolder) {
+ m_initialized = true;
+ nsTArray<RefPtr<nsIMsgFolder>> subFolders;
+ nsresult rv = oldFolder->GetSubFolders(subFolders);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (nsIMsgFolder* msgFolder : subFolders) {
+ nsCOMPtr<nsIMsgImapMailFolder> folder(do_QueryInterface(msgFolder, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ char hierarchyDelimiter = '/';
+ folder->GetHierarchyDelimiter(&hierarchyDelimiter);
+
+ int32_t boxflags;
+ folder->GetBoxFlags(&boxflags);
+
+ bool verified;
+ folder->GetVerifiedAsOnlineFolder(&verified);
+
+ nsCOMPtr<nsIFile> oldPathFile;
+ rv = msgFolder->GetFilePath(getter_AddRefs(oldPathFile));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIFile> newParentPathFile;
+ rv = GetFilePath(getter_AddRefs(newParentPathFile));
+ if (NS_FAILED(rv)) return rv;
+
+ rv = AddDirectorySeparator(newParentPathFile);
+ nsAutoCString oldLeafName;
+ oldPathFile->GetNativeLeafName(oldLeafName);
+ newParentPathFile->AppendNative(oldLeafName);
+
+ nsCOMPtr<nsIFile> newPathFile =
+ do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ newPathFile->InitWithFile(newParentPathFile);
+
+ nsCOMPtr<nsIFile> dbFilePath = newPathFile;
+
+ nsCOMPtr<nsIMsgFolder> child;
+
+ nsString folderName;
+ rv = msgFolder->GetName(folderName);
+ if (folderName.IsEmpty() || NS_FAILED(rv)) return rv;
+
+ nsCString utfLeafName;
+ bool utf8AcceptEnabled;
+ nsCOMPtr<nsIMsgImapMailFolder> imapFolder = do_QueryInterface(msgFolder);
+ rv = imapFolder->GetShouldUseUtf8FolderName(&utf8AcceptEnabled);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (utf8AcceptEnabled) {
+ CopyUTF16toUTF8(folderName, utfLeafName);
+ } else {
+ CopyUTF16toMUTF7(folderName, utfLeafName);
+ }
+
+ // XXX : Fix this non-sense by fixing AddSubfolderWithPath
+ nsAutoString unicodeLeafName;
+ CopyUTF8toUTF16(utfLeafName, unicodeLeafName);
+
+ rv = AddSubfolderWithPath(unicodeLeafName, dbFilePath,
+ getter_AddRefs(child));
+ if (!child || NS_FAILED(rv)) return rv;
+
+ child->SetName(folderName);
+ imapFolder = do_QueryInterface(child);
+ nsCString onlineName;
+ GetOnlineName(onlineName);
+ nsAutoCString onlineCName(onlineName);
+ onlineCName.Append(hierarchyDelimiter);
+ onlineCName.Append(utfLeafName);
+ if (imapFolder) {
+ imapFolder->SetVerifiedAsOnlineFolder(verified);
+ imapFolder->SetOnlineName(onlineCName);
+ imapFolder->SetHierarchyDelimiter(hierarchyDelimiter);
+ imapFolder->SetBoxFlags(boxflags);
+
+ bool changed = false;
+ msgFolder->MatchOrChangeFilterDestination(
+ child, false /*caseInsensitive*/, &changed);
+ if (changed) msgFolder->AlertFilterChanged(msgWindow);
+ child->RenameSubFolders(msgWindow, msgFolder);
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapMailFolder::IsCommandEnabled(const nsACString& command,
+ bool* result) {
+ NS_ENSURE_ARG_POINTER(result);
+ *result = !(WeAreOffline() && (command.EqualsLiteral("cmd_renameFolder") ||
+ command.EqualsLiteral("cmd_compactFolder") ||
+ command.EqualsLiteral("button_compact") ||
+ command.EqualsLiteral("cmd_delete") ||
+ command.EqualsLiteral("button_delete")));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::GetCanFileMessages(bool* aCanFileMessages) {
+ nsresult rv;
+ *aCanFileMessages = true;
+
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = GetServer(getter_AddRefs(server));
+ if (NS_SUCCEEDED(rv) && server)
+ rv = server->GetCanFileMessagesOnServer(aCanFileMessages);
+
+ if (*aCanFileMessages)
+ rv = nsMsgDBFolder::GetCanFileMessages(aCanFileMessages);
+
+ if (*aCanFileMessages) {
+ bool noSelect;
+ GetFlag(nsMsgFolderFlags::ImapNoselect, &noSelect);
+ *aCanFileMessages =
+ (noSelect) ? false : GetFolderACL()->GetCanIInsertInFolder();
+ return NS_OK;
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::GetCanDeleteMessages(bool* aCanDeleteMessages) {
+ NS_ENSURE_ARG_POINTER(aCanDeleteMessages);
+ *aCanDeleteMessages = GetFolderACL()->GetCanIDeleteInFolder();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::GetPerformingBiff(bool* aPerformingBiff) {
+ NS_ENSURE_ARG_POINTER(aPerformingBiff);
+ *aPerformingBiff = m_performingBiff;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::SetPerformingBiff(bool aPerformingBiff) {
+ m_performingBiff = aPerformingBiff;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::SetFilterList(nsIMsgFilterList* aMsgFilterList) {
+ m_filterList = aMsgFilterList;
+ return nsMsgDBFolder::SetFilterList(aMsgFilterList);
+}
+
+nsresult nsImapMailFolder::GetMoveCoalescer() {
+ if (!m_moveCoalescer)
+ m_moveCoalescer = new nsImapMoveCoalescer(this, nullptr /* msgWindow */);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::StoreCustomKeywords(nsIMsgWindow* aMsgWindow,
+ const nsACString& aFlagsToAdd,
+ const nsACString& aFlagsToSubtract,
+ const nsTArray<nsMsgKey>& aKeysToStore,
+ nsIURI** _retval) {
+ if (aKeysToStore.IsEmpty()) return NS_OK;
+ nsresult rv = NS_OK;
+ if (WeAreOffline()) {
+ GetDatabase();
+ if (!mDatabase) return NS_ERROR_UNEXPECTED;
+ nsCOMPtr<nsIMsgOfflineOpsDatabase> opsDb =
+ do_QueryInterface(mDatabase, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ for (auto key : aKeysToStore) {
+ nsCOMPtr<nsIMsgOfflineImapOperation> op;
+ nsresult rv2 = opsDb->GetOfflineOpForKey(key, true, getter_AddRefs(op));
+ if (NS_FAILED(rv2)) rv = rv2;
+ SetFlag(nsMsgFolderFlags::OfflineEvents);
+ if (NS_SUCCEEDED(rv2) && op) {
+ if (!aFlagsToAdd.IsEmpty())
+ op->AddKeywordToAdd(PromiseFlatCString(aFlagsToAdd).get());
+ if (!aFlagsToSubtract.IsEmpty())
+ op->AddKeywordToRemove(PromiseFlatCString(aFlagsToSubtract).get());
+ }
+ }
+ mDatabase->Commit(nsMsgDBCommitType::kLargeCommit); // flush offline ops
+ return rv;
+ }
+
+ nsCOMPtr<nsIImapService> imapService(
+ do_GetService("@mozilla.org/messenger/imapservice;1", &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsAutoCString msgIds;
+ AllocateUidStringFromKeys(aKeysToStore, msgIds);
+ nsCOMPtr<nsIURI> retUri;
+ rv = imapService->StoreCustomKeywords(this, aMsgWindow, aFlagsToAdd,
+ aFlagsToSubtract, msgIds,
+ getter_AddRefs(retUri));
+ if (_retval) {
+ retUri.forget(_retval);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapMailFolder::NotifyIfNewMail() {
+ return PerformBiffNotifications();
+}
+
+bool nsImapMailFolder::ShowPreviewText() {
+ bool showPreviewText = false;
+ nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID));
+ if (prefBranch)
+ prefBranch->GetBoolPref("mail.biff.alert.show_preview", &showPreviewText);
+ return showPreviewText;
+}
+
+nsresult nsImapMailFolder::PlaybackCoalescedOperations() {
+ if (m_moveCoalescer) {
+ nsTArray<nsMsgKey>* junkKeysToClassify = m_moveCoalescer->GetKeyBucket(0);
+ if (junkKeysToClassify && !junkKeysToClassify->IsEmpty())
+ StoreCustomKeywords(m_moveCoalescer->GetMsgWindow(), "Junk"_ns,
+ EmptyCString(), *junkKeysToClassify, nullptr);
+ junkKeysToClassify->Clear();
+ nsTArray<nsMsgKey>* nonJunkKeysToClassify =
+ m_moveCoalescer->GetKeyBucket(1);
+ if (nonJunkKeysToClassify && !nonJunkKeysToClassify->IsEmpty())
+ StoreCustomKeywords(m_moveCoalescer->GetMsgWindow(), "NonJunk"_ns,
+ EmptyCString(), *nonJunkKeysToClassify, nullptr);
+ nonJunkKeysToClassify->Clear();
+ return m_moveCoalescer->PlaybackMoves(ShowPreviewText());
+ }
+ return NS_OK; // must not be any coalesced operations
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::SetJunkScoreForMessages(
+ const nsTArray<RefPtr<nsIMsgDBHdr>>& aMessages,
+ const nsACString& aJunkScore) {
+ nsresult rv = nsMsgDBFolder::SetJunkScoreForMessages(aMessages, aJunkScore);
+ if (NS_SUCCEEDED(rv)) {
+ nsAutoCString messageIds;
+ nsTArray<nsMsgKey> keys;
+ nsresult rv = BuildIdsAndKeyArray(aMessages, messageIds, keys);
+ NS_ENSURE_SUCCESS(rv, rv);
+ StoreCustomKeywords(
+ nullptr, aJunkScore.EqualsLiteral("0") ? "NonJunk"_ns : "Junk"_ns,
+ aJunkScore.EqualsLiteral("0") ? "Junk"_ns : "NonJunk"_ns, keys,
+ nullptr);
+ if (mDatabase) mDatabase->Commit(nsMsgDBCommitType::kLargeCommit);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::OnMessageClassified(const nsACString& aMsgURI,
+ nsMsgJunkStatus aClassification,
+ uint32_t aJunkPercent) {
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ nsresult rv = GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!aMsgURI.IsEmpty()) // not end of batch
+ {
+ 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) {
+ nsMsgDBFolder::OnMessageClassified(aMsgURI, aClassification,
+ aJunkPercent);
+
+ GetMoveCoalescer();
+ if (m_moveCoalescer) {
+ nsTArray<nsMsgKey>* keysToClassify = m_moveCoalescer->GetKeyBucket(
+ (aClassification == nsIJunkMailPlugin::JUNK) ? 0 : 1);
+ NS_ASSERTION(keysToClassify, "error getting key bucket");
+ if (keysToClassify) keysToClassify->AppendElement(msgKey);
+ }
+ if (aClassification == nsIJunkMailPlugin::JUNK) {
+ nsCOMPtr<nsISpamSettings> spamSettings;
+ rv = server->GetSpamSettings(getter_AddRefs(spamSettings));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool markAsReadOnSpam;
+ (void)spamSettings->GetMarkAsReadOnSpam(&markAsReadOnSpam);
+ if (markAsReadOnSpam) {
+ m_junkMessagesToMarkAsRead.AppendElement(msgHdr);
+ }
+
+ bool willMoveMessage = false;
+
+ // don't do the move when we are opening up
+ // the junk mail folder or the trash folder
+ // or when manually classifying messages in those folders
+ if (!(mFlags & nsMsgFolderFlags::Junk ||
+ mFlags & nsMsgFolderFlags::Trash)) {
+ bool moveOnSpam;
+ (void)spamSettings->GetMoveOnSpam(&moveOnSpam);
+ if (moveOnSpam) {
+ nsCString spamFolderURI;
+ rv = spamSettings->GetSpamFolderURI(spamFolderURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!spamFolderURI.IsEmpty()) {
+ rv = FindFolder(spamFolderURI, getter_AddRefs(mSpamFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (mSpamFolder) {
+ rv = mSpamFolder->SetFlag(nsMsgFolderFlags::Junk);
+ NS_ENSURE_SUCCESS(rv, rv);
+ mSpamKeysToMove.AppendElement(msgKey);
+ willMoveMessage = true;
+ } else {
+ // XXX TODO
+ // JUNK MAIL RELATED
+ // the listener should do
+ // rv = folder->SetFlag(nsMsgFolderFlags::Junk);
+ // NS_ENSURE_SUCCESS(rv,rv);
+ // if (NS_SUCCEEDED(GetMoveCoalescer())) {
+ // m_moveCoalescer->AddMove(folder, msgKey);
+ // willMoveMessage = true;
+ // }
+ rv = GetOrCreateJunkFolder(spamFolderURI,
+ nullptr /* aListener */);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "GetOrCreateJunkFolder failed");
+ }
+ }
+ }
+ }
+ rv = spamSettings->LogJunkHit(msgHdr, willMoveMessage);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+ }
+
+ else // end of batch
+ {
+ // Parent will apply post bayes filters.
+ nsMsgDBFolder::OnMessageClassified(EmptyCString(),
+ nsIJunkMailPlugin::UNCLASSIFIED, 0);
+
+ if (!m_junkMessagesToMarkAsRead.IsEmpty()) {
+ rv = MarkMessagesRead(m_junkMessagesToMarkAsRead, true);
+ NS_ENSURE_SUCCESS(rv, rv);
+ m_junkMessagesToMarkAsRead.Clear();
+ }
+ if (!mSpamKeysToMove.IsEmpty()) {
+ GetMoveCoalescer();
+ for (uint32_t keyIndex = 0; keyIndex < mSpamKeysToMove.Length();
+ keyIndex++) {
+ // If an upstream filter moved this message, don't move it here.
+ nsMsgKey msgKey = mSpamKeysToMove.ElementAt(keyIndex);
+ nsMsgProcessingFlagType processingFlags;
+ GetProcessingFlags(msgKey, &processingFlags);
+ if (!(processingFlags & nsMsgProcessingFlags::FilterToMove)) {
+ if (m_moveCoalescer && mSpamFolder)
+ m_moveCoalescer->AddMove(mSpamFolder, msgKey);
+ } else {
+ // We don't need the FilterToMove flag anymore.
+ AndProcessingFlags(msgKey, ~nsMsgProcessingFlags::FilterToMove);
+ }
+ }
+ mSpamKeysToMove.Clear();
+ }
+
+ // Let's not hold onto the spam folder reference longer than necessary.
+ mSpamFolder = nullptr;
+
+ bool pendingMoves = m_moveCoalescer && m_moveCoalescer->HasPendingMoves();
+ PlaybackCoalescedOperations();
+ // If we are performing biff for this folder, tell the server object
+ if ((!pendingMoves || !ShowPreviewText()) && m_performingBiff) {
+ // we don't need to adjust the num new messages in this folder because
+ // the playback moves code already did that.
+ (void)PerformBiffNotifications();
+ server->SetPerformingBiff(false);
+ m_performingBiff = false;
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::GetShouldDownloadAllHeaders(bool* aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = false;
+ // for just the inbox, we check if the filter list has arbitrary headers.
+ // for all folders, check if we have a spam plugin that requires all headers
+ if (mFlags & nsMsgFolderFlags::Inbox) {
+ nsCOMPtr<nsIMsgFilterList> filterList;
+ nsresult rv = GetFilterList(nullptr, getter_AddRefs(filterList));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = filterList->GetShouldDownloadAllHeaders(aResult);
+ if (*aResult) return rv;
+ }
+ nsCOMPtr<nsIMsgFilterPlugin> filterPlugin;
+ nsCOMPtr<nsIMsgIncomingServer> server;
+
+ if (NS_SUCCEEDED(GetServer(getter_AddRefs(server))))
+ server->GetSpamFilterPlugin(getter_AddRefs(filterPlugin));
+
+ return (filterPlugin) ? filterPlugin->GetShouldDownloadAllHeaders(aResult)
+ : NS_OK;
+}
+
+void nsImapMailFolder::GetTrashFolderName(nsAString& aFolderName) {
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ nsCOMPtr<nsIImapIncomingServer> imapServer;
+ nsresult rv;
+ rv = GetServer(getter_AddRefs(server));
+ if (NS_FAILED(rv)) return;
+ imapServer = do_QueryInterface(server, &rv);
+ if (NS_FAILED(rv)) return;
+ imapServer->GetTrashFolderName(aFolderName);
+ return;
+}
+NS_IMETHODIMP nsImapMailFolder::FetchMsgPreviewText(
+ nsTArray<nsMsgKey> const& aKeysToFetch, nsIUrlListener* aUrlListener,
+ bool* aAsyncResults) {
+ NS_ENSURE_ARG_POINTER(aAsyncResults);
+
+ nsTArray<nsMsgKey> keysToFetchFromServer;
+
+ *aAsyncResults = false;
+ nsresult rv = NS_OK;
+
+ nsCOMPtr<nsIMsgMessageService> msgService =
+ do_GetService("@mozilla.org/messenger/messageservice;1?type=imap", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (uint32_t i = 0; i < aKeysToFetch.Length(); i++) {
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ nsCString prevBody;
+ rv = GetMessageHeader(aKeysToFetch[i], getter_AddRefs(msgHdr));
+ NS_ENSURE_SUCCESS(rv, rv);
+ // ignore messages that already have a preview body.
+ msgHdr->GetStringProperty("preview", prevBody);
+ if (!prevBody.IsEmpty()) continue;
+
+ /* check if message is in memory cache or offline store. */
+ nsCOMPtr<nsIURI> url;
+ nsCOMPtr<nsIInputStream> inputStream;
+ nsCString messageUri;
+ rv = GetUriForMsg(msgHdr, messageUri);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = msgService->GetUrlForUri(messageUri, nullptr, getter_AddRefs(url));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Lets look in the offline store.
+ uint32_t msgFlags;
+ msgHdr->GetFlags(&msgFlags);
+ if (msgFlags & nsMsgMessageFlags::Offline) {
+ rv = GetLocalMsgStream(msgHdr, getter_AddRefs(inputStream));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = GetMsgPreviewTextFromStream(msgHdr, inputStream);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ nsMsgKey msgKey;
+ msgHdr->GetMessageKey(&msgKey);
+ keysToFetchFromServer.AppendElement(msgKey);
+ }
+ }
+ if (!keysToFetchFromServer.IsEmpty()) {
+ uint32_t msgCount = keysToFetchFromServer.Length();
+ nsAutoCString messageIds;
+ AllocateImapUidString(keysToFetchFromServer.Elements(), msgCount, nullptr,
+ messageIds);
+ nsCOMPtr<nsIImapService> imapService =
+ do_GetService("@mozilla.org/messenger/imapservice;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIURI> outUri;
+ rv = imapService->GetBodyStart(this, aUrlListener, messageIds, 2048,
+ getter_AddRefs(outUri));
+ *aAsyncResults = true; // the preview text will be available async...
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailFolder::AddKeywordsToMessages(
+ const nsTArray<RefPtr<nsIMsgDBHdr>>& aMessages,
+ const nsACString& aKeywords) {
+ nsresult rv = nsMsgDBFolder::AddKeywordsToMessages(aMessages, aKeywords);
+ if (NS_SUCCEEDED(rv)) {
+ nsAutoCString messageIds;
+ nsTArray<nsMsgKey> keys;
+ rv = BuildIdsAndKeyArray(aMessages, messageIds, keys);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = StoreCustomKeywords(nullptr, aKeywords, EmptyCString(), keys, nullptr);
+ if (mDatabase) mDatabase->Commit(nsMsgDBCommitType::kLargeCommit);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapMailFolder::RemoveKeywordsFromMessages(
+ const nsTArray<RefPtr<nsIMsgDBHdr>>& aMessages,
+ const nsACString& aKeywords) {
+ nsresult rv = nsMsgDBFolder::RemoveKeywordsFromMessages(aMessages, aKeywords);
+ if (NS_SUCCEEDED(rv)) {
+ nsAutoCString messageIds;
+ nsTArray<nsMsgKey> keys;
+ nsresult rv = BuildIdsAndKeyArray(aMessages, messageIds, keys);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = StoreCustomKeywords(nullptr, EmptyCString(), aKeywords, keys, nullptr);
+ if (mDatabase) mDatabase->Commit(nsMsgDBCommitType::kLargeCommit);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapMailFolder::GetCustomIdentity(nsIMsgIdentity** aIdentity) {
+ NS_ENSURE_ARG_POINTER(aIdentity);
+ if (mFlags & nsMsgFolderFlags::ImapOtherUser) {
+ nsresult rv;
+ bool delegateOtherUsersFolders = false;
+ nsCOMPtr<nsIPrefBranch> prefBranch(
+ do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ prefBranch->GetBoolPref("mail.imap.delegateOtherUsersFolders",
+ &delegateOtherUsersFolders);
+ // if we're automatically delegating other user's folders, we need to
+ // cons up an e-mail address for the other user. We do that by
+ // taking the other user's name and the current user's domain name,
+ // assuming they'll be the same. So, <otherUsersName>@<ourDomain>
+ if (delegateOtherUsersFolders) {
+ nsCOMPtr<nsIMsgIncomingServer> server = do_QueryReferent(mServer, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIMsgAccountManager> accountManager =
+ do_GetService("@mozilla.org/messenger/account-manager;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIMsgIdentity> ourIdentity;
+ nsCOMPtr<nsIMsgIdentity> retIdentity;
+ nsCOMPtr<nsIMsgAccount> account;
+ nsCString foldersUserName;
+ nsCString ourEmailAddress;
+
+ accountManager->FindAccountForServer(server, getter_AddRefs(account));
+ NS_ENSURE_SUCCESS(rv, rv);
+ account->GetDefaultIdentity(getter_AddRefs(ourIdentity));
+ NS_ENSURE_SUCCESS(rv, rv);
+ ourIdentity->GetEmail(ourEmailAddress);
+ int32_t atPos = ourEmailAddress.FindChar('@');
+ if (atPos != kNotFound) {
+ nsCString otherUsersEmailAddress;
+ GetFolderOwnerUserName(otherUsersEmailAddress);
+ otherUsersEmailAddress.Append(
+ Substring(ourEmailAddress, atPos, ourEmailAddress.Length()));
+ nsTArray<RefPtr<nsIMsgIdentity>> identities;
+ rv = accountManager->GetIdentitiesForServer(server, identities);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (auto identity : identities) {
+ if (!identity) continue;
+ nsCString identityEmail;
+ identity->GetEmail(identityEmail);
+ if (identityEmail.Equals(otherUsersEmailAddress)) {
+ retIdentity = identity;
+ break;
+ }
+ }
+ if (!retIdentity) {
+ // create the identity
+ rv = accountManager->CreateIdentity(getter_AddRefs(retIdentity));
+ NS_ENSURE_SUCCESS(rv, rv);
+ retIdentity->SetEmail(otherUsersEmailAddress);
+ nsCOMPtr<nsIMsgAccount> account;
+ accountManager->FindAccountForServer(server, getter_AddRefs(account));
+ NS_ENSURE_SUCCESS(rv, rv);
+ account->AddIdentity(retIdentity);
+ }
+ }
+ if (retIdentity) {
+ retIdentity.forget(aIdentity);
+ return NS_OK;
+ }
+ }
+ }
+ return nsMsgDBFolder::GetCustomIdentity(aIdentity);
+}
+
+NS_IMETHODIMP nsImapMailFolder::ChangePendingTotal(int32_t aDelta) {
+ ChangeNumPendingTotalMessages(aDelta);
+ if (aDelta > 0) NotifyHasPendingMsgs();
+ return NS_OK;
+}
+
+void nsImapMailFolder::NotifyHasPendingMsgs() {
+ InitAutoSyncState();
+ nsresult rv;
+ nsCOMPtr<nsIAutoSyncManager> autoSyncMgr =
+ do_GetService(NS_AUTOSYNCMANAGER_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv)) autoSyncMgr->OnFolderHasPendingMsgs(m_autoSyncStateObj);
+}
+
+/* void changePendingUnread (in long aDelta); */
+NS_IMETHODIMP nsImapMailFolder::ChangePendingUnread(int32_t aDelta) {
+ ChangeNumPendingUnread(aDelta);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailFolder::GetServerRecent(int32_t* aServerRecent) {
+ NS_ENSURE_ARG_POINTER(aServerRecent);
+ *aServerRecent = m_numServerRecentMessages;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailFolder::GetServerTotal(int32_t* aServerTotal) {
+ NS_ENSURE_ARG_POINTER(aServerTotal);
+ *aServerTotal = m_numServerTotalMessages;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailFolder::GetServerUnseen(int32_t* aServerUnseen) {
+ NS_ENSURE_ARG_POINTER(aServerUnseen);
+ *aServerUnseen = m_numServerUnseenMessages;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailFolder::GetServerNextUID(int32_t* aNextUID) {
+ NS_ENSURE_ARG_POINTER(aNextUID);
+ *aNextUID = m_nextUID;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailFolder::GetAutoSyncStateObj(
+ nsIAutoSyncState** autoSyncStateObj) {
+ NS_ENSURE_ARG_POINTER(autoSyncStateObj);
+
+ // create auto-sync state object lazily
+ InitAutoSyncState();
+
+ NS_IF_ADDREF(*autoSyncStateObj = m_autoSyncStateObj);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailFolder::InitiateAutoSync(nsIUrlListener* aUrlListener) {
+ nsCString folderName;
+ GetURI(folderName);
+ MOZ_LOG(gAutoSyncLog, mozilla::LogLevel::Debug,
+ ("%s: Updating folder: %s", __func__, folderName.get()));
+
+ // HACK: if UpdateFolder finds out that it can't open
+ // the folder, it doesn't set the url listener and returns
+ // no error. In this case, we return success from this call
+ // but the caller never gets a notification on its url listener.
+ bool canOpenThisFolder = true;
+ GetCanOpenFolder(&canOpenThisFolder);
+
+ if (!canOpenThisFolder) {
+ MOZ_LOG(gAutoSyncLog, mozilla::LogLevel::Debug,
+ ("%s: Cannot update folder: %s", __func__, folderName.get()));
+ return NS_ERROR_FAILURE;
+ }
+
+ // create auto-sync state object lazily
+ InitAutoSyncState();
+
+ // make sure we get the counts from the folder cache.
+ ReadDBFolderInfo(false);
+
+ nsresult rv = m_autoSyncStateObj->ManageStorageSpace();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t syncState;
+ m_autoSyncStateObj->GetState(&syncState);
+ if (syncState == nsAutoSyncState::stUpdateNeeded)
+ return m_autoSyncStateObj->UpdateFolder();
+
+ // We only want to init the autosyncStateObj server counts the first time
+ // we update, and update it when the STATUS call finishes. This deals with
+ // the case where biff is doing a STATUS on a non-inbox folder, which
+ // can make autosync think the counts aren't changing.
+ PRTime lastUpdateTime;
+ m_autoSyncStateObj->GetLastUpdateTime(&lastUpdateTime);
+ if (!lastUpdateTime)
+ m_autoSyncStateObj->SetServerCounts(m_numServerTotalMessages,
+ m_numServerRecentMessages,
+ m_numServerUnseenMessages, m_nextUID);
+ // Issue a STATUS command and see if any counts changed.
+ m_autoSyncStateObj->SetState(nsAutoSyncState::stStatusIssued);
+ // The OnStopRunningUrl method of the autosync state obj
+ // will check if the counts or next uid have changed,
+ // and if so, will issue an UpdateFolder().
+ rv = UpdateStatus(m_autoSyncStateObj, nullptr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // record the last update time
+ m_autoSyncStateObj->SetLastUpdateTime(PR_Now());
+
+ return NS_OK;
+}
+
+/* static */
+void nsImapMailFolder::PlaybackTimerCallback(nsITimer* aTimer, void* aClosure) {
+ nsPlaybackRequest* request = static_cast<nsPlaybackRequest*>(aClosure);
+
+ NS_ASSERTION(request->SrcFolder->m_pendingPlaybackReq == request,
+ "wrong playback request pointer");
+
+ RefPtr<nsImapOfflineSync> offlineSync = new nsImapOfflineSync();
+ offlineSync->Init(request->MsgWindow, nullptr, request->SrcFolder, true);
+ if (offlineSync) {
+ mozilla::DebugOnly<nsresult> rv = offlineSync->ProcessNextOperation();
+ NS_ASSERTION(NS_SUCCEEDED(rv), "pseudo-offline playback is not successful");
+ }
+
+ // release request struct and timer
+ request->SrcFolder->m_pendingPlaybackReq = nullptr;
+ request->SrcFolder->m_playbackTimer = nullptr; // Just to flag timed out
+ delete request;
+}
+
+void nsImapMailFolder::InitAutoSyncState() {
+ if (!m_autoSyncStateObj) m_autoSyncStateObj = new nsAutoSyncState(this);
+}
+
+NS_IMETHODIMP nsImapMailFolder::HasMsgOffline(nsMsgKey msgKey, bool* _retval) {
+ NS_ENSURE_ARG_POINTER(_retval);
+ *_retval = false;
+ nsCOMPtr<nsIMsgFolder> msgFolder;
+ nsresult rv = GetOfflineMsgFolder(msgKey, getter_AddRefs(msgFolder));
+ if (NS_SUCCEEDED(rv) && msgFolder) *_retval = true;
+ return NS_OK;
+}
+
+nsresult nsImapMailFolder::GetOfflineMsgFolder(nsMsgKey msgKey,
+ nsIMsgFolder** aMsgFolder) {
+ // Check if we have the message in the current folder.
+ NS_ENSURE_ARG_POINTER(aMsgFolder);
+ nsCOMPtr<nsIMsgFolder> subMsgFolder;
+ nsresult rv = GetDatabase();
+ NS_ENSURE_SUCCESS(rv, 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)) {
+ NS_IF_ADDREF(*aMsgFolder = this);
+ return NS_OK;
+ }
+ }
+
+ if (!*aMsgFolder) {
+ // Checking the existence of message in other folders in case of GMail
+ // Server
+ bool isGMail;
+ nsCOMPtr<nsIImapIncomingServer> imapServer;
+ rv = GetImapIncomingServer(getter_AddRefs(imapServer));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = imapServer->GetIsGMailServer(&isGMail);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (isGMail) {
+ nsCString labels;
+ nsTArray<nsCString> labelNames;
+ hdr->GetStringProperty("X-GM-LABELS", labels);
+ ParseString(labels, ' ', labelNames);
+ nsCOMPtr<nsIMsgFolder> rootFolder;
+ nsCOMPtr<nsIMsgImapMailFolder> subFolder;
+ for (uint32_t i = 0; i < labelNames.Length(); i++) {
+ rv = GetRootFolder(getter_AddRefs(rootFolder));
+ if (NS_SUCCEEDED(rv) && (rootFolder)) {
+ nsCOMPtr<nsIMsgImapMailFolder> imapRootFolder =
+ do_QueryInterface(rootFolder);
+ if (labelNames[i].EqualsLiteral("\"\\\\Draft\""))
+ rv = rootFolder->GetFolderWithFlags(nsMsgFolderFlags::Drafts,
+ getter_AddRefs(subMsgFolder));
+ if (labelNames[i].EqualsLiteral("\"\\\\Inbox\""))
+ rv = rootFolder->GetFolderWithFlags(nsMsgFolderFlags::Inbox,
+ getter_AddRefs(subMsgFolder));
+ if (labelNames[i].EqualsLiteral("\"\\\\All Mail\""))
+ rv = rootFolder->GetFolderWithFlags(nsMsgFolderFlags::Archive,
+ getter_AddRefs(subMsgFolder));
+ if (labelNames[i].EqualsLiteral("\"\\\\Trash\""))
+ rv = rootFolder->GetFolderWithFlags(nsMsgFolderFlags::Trash,
+ getter_AddRefs(subMsgFolder));
+ if (labelNames[i].EqualsLiteral("\"\\\\Spam\""))
+ rv = rootFolder->GetFolderWithFlags(nsMsgFolderFlags::Junk,
+ getter_AddRefs(subMsgFolder));
+ if (labelNames[i].EqualsLiteral("\"\\\\Sent\""))
+ rv = rootFolder->GetFolderWithFlags(nsMsgFolderFlags::SentMail,
+ getter_AddRefs(subMsgFolder));
+ if (FindInReadable("[Imap]/"_ns, labelNames[i],
+ nsCaseInsensitiveCStringComparator)) {
+ labelNames[i].ReplaceSubstring("[Imap]/", "");
+ imapRootFolder->FindOnlineSubFolder(labelNames[i],
+ getter_AddRefs(subFolder));
+ subMsgFolder = do_QueryInterface(subFolder);
+ }
+ if (!subMsgFolder) {
+ imapRootFolder->FindOnlineSubFolder(labelNames[i],
+ getter_AddRefs(subFolder));
+ subMsgFolder = do_QueryInterface(subFolder);
+ }
+ if (subMsgFolder) {
+ nsCOMPtr<nsIMsgDatabase> db;
+ subMsgFolder->GetMsgDatabase(getter_AddRefs(db));
+ if (db) {
+ nsCOMPtr<nsIMsgDBHdr> retHdr;
+ nsCString gmMsgID;
+ hdr->GetStringProperty("X-GM-MSGID", gmMsgID);
+ rv = db->GetMsgHdrForGMMsgID(gmMsgID.get(),
+ getter_AddRefs(retHdr));
+ if (NS_FAILED(rv)) return rv;
+ if (retHdr) {
+ uint32_t gmFlags = 0;
+ retHdr->GetFlags(&gmFlags);
+ if ((gmFlags & nsMsgMessageFlags::Offline)) {
+ subMsgFolder.forget(aMsgFolder);
+ // Focus on first positive result.
+ return NS_OK;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ return NS_OK;
+}
+
+nsresult nsImapMailFolder::GetOfflineFileStream(nsMsgKey msgKey,
+ uint64_t* offset,
+ uint32_t* size,
+ nsIInputStream** aFileStream) {
+ NS_ENSURE_ARG(aFileStream);
+ nsCOMPtr<nsIMsgFolder> offlineFolder;
+ nsresult rv = GetOfflineMsgFolder(msgKey, getter_AddRefs(offlineFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!offlineFolder) return NS_ERROR_FAILURE;
+
+ rv = GetDatabase();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (offlineFolder == this) {
+ return nsMsgDBFolder::GetOfflineFileStream(msgKey, offset, size,
+ aFileStream);
+ }
+
+ // The message we want is stored in a different folder (hackery for gmail).
+ nsCOMPtr<nsIMsgDBHdr> hdr;
+ rv = mDatabase->GetMsgHdrForKey(msgKey, getter_AddRefs(hdr));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCString gmMsgID;
+ hdr->GetStringProperty("X-GM-MSGID", gmMsgID);
+ nsCOMPtr<nsIMsgDatabase> db;
+ offlineFolder->GetMsgDatabase(getter_AddRefs(db));
+ rv = db->GetMsgHdrForGMMsgID(gmMsgID.get(), getter_AddRefs(hdr));
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!hdr) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsMsgKey newMsgKey;
+ hdr->GetMessageKey(&newMsgKey);
+
+ // We _know_ it's a nsImapMailFolder.
+ nsImapMailFolder* other = static_cast<nsImapMailFolder*>(offlineFolder.get());
+ return other->GetOfflineFileStream(newMsgKey, offset, size, aFileStream);
+}
+
+NS_IMETHODIMP nsImapMailFolder::GetLocalMsgStream(nsIMsgDBHdr* hdr,
+ nsIInputStream** stream) {
+ nsMsgKey key;
+ hdr->GetMessageKey(&key);
+
+ uint64_t offset = 0;
+ uint32_t size = 0;
+ nsCOMPtr<nsIInputStream> rawStream;
+ nsresult rv =
+ GetOfflineFileStream(key, &offset, &size, getter_AddRefs(rawStream));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<SlicedInputStream> slicedStream =
+ new SlicedInputStream(rawStream.forget(), offset, uint64_t(size));
+ slicedStream.forget(stream);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailFolder::GetIncomingServerType(nsACString& serverType) {
+ serverType.AssignLiteral("imap");
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailFolder::GetShouldUseUtf8FolderName(bool* aUseUTF8) {
+ *aUseUTF8 = false;
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ nsresult rv = GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIImapIncomingServer> imapServer = do_QueryInterface(server, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ imapServer->GetUtf8AcceptEnabled(aUseUTF8);
+ return NS_OK;
+}
+
+void nsImapMailFolder::DeleteStoreMessages(
+ const nsTArray<RefPtr<nsIMsgDBHdr>>& aMessages) {
+ // Delete messages for pluggable stores that do not support compaction.
+ nsCOMPtr<nsIMsgPluggableStore> offlineStore;
+ (void)GetMsgStore(getter_AddRefs(offlineStore));
+
+ if (offlineStore) {
+ bool supportsCompaction;
+ offlineStore->GetSupportsCompaction(&supportsCompaction);
+ if (!supportsCompaction) offlineStore->DeleteMessages(aMessages);
+ }
+}
+
+void nsImapMailFolder::DeleteStoreMessages(
+ const nsTArray<nsMsgKey>& aMessages) {
+ DeleteStoreMessages(aMessages, this);
+}
+
+void nsImapMailFolder::DeleteStoreMessages(const nsTArray<nsMsgKey>& aMessages,
+ nsIMsgFolder* aFolder) {
+ // Delete messages for pluggable stores that do not support compaction.
+ NS_ASSERTION(aFolder, "Missing Source Folder");
+ nsCOMPtr<nsIMsgPluggableStore> offlineStore;
+ (void)aFolder->GetMsgStore(getter_AddRefs(offlineStore));
+ if (offlineStore) {
+ bool supportsCompaction;
+ offlineStore->GetSupportsCompaction(&supportsCompaction);
+ if (!supportsCompaction) {
+ nsCOMPtr<nsIMsgDatabase> db;
+ aFolder->GetMsgDatabase(getter_AddRefs(db));
+ nsresult rv = NS_ERROR_FAILURE;
+ nsTArray<RefPtr<nsIMsgDBHdr>> messages;
+ if (db) rv = MsgGetHeadersFromKeys(db, aMessages, messages);
+ if (NS_SUCCEEDED(rv))
+ offlineStore->DeleteMessages(messages);
+ else
+ NS_WARNING("Failed to get database");
+ }
+ }
+}
diff --git a/comm/mailnews/imap/src/nsImapMailFolder.h b/comm/mailnews/imap/src/nsImapMailFolder.h
new file mode 100644
index 0000000000..0f7f6ce406
--- /dev/null
+++ b/comm/mailnews/imap/src/nsImapMailFolder.h
@@ -0,0 +1,599 @@
+/* -*- 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 nsImapMailFolder_h__
+#define nsImapMailFolder_h__
+
+#include "mozilla/Attributes.h"
+#include "nsImapCore.h" // so that consumers including ImapMailFolder.h also get the kImapMsg* constants
+#include "nsMsgDBFolder.h"
+#include "nsIImapMailFolderSink.h"
+#include "nsIImapMessageSink.h"
+#include "nsICopyMessageListener.h"
+#include "nsIUrlListener.h"
+#include "nsIImapIncomingServer.h" // we need this for its IID
+#include "nsIMsgParseMailMsgState.h"
+#include "nsImapUndoTxn.h"
+#include "nsIMsgMessageService.h"
+#include "nsIMsgFilterHitNotify.h"
+#include "nsIMsgFilterList.h"
+#include "prmon.h"
+#include "nsIMsgImapMailFolder.h"
+#include "nsIMsgThread.h"
+#include "nsIImapMailFolderSink.h"
+#include "nsIMsgFilterPlugin.h"
+#include "nsISimpleEnumerator.h"
+#include "nsIStringEnumerator.h"
+#include "nsTHashMap.h"
+#include "nsITimer.h"
+#include "nsCOMArray.h"
+#include "nsAutoSyncState.h"
+
+class nsImapMoveCoalescer;
+class nsIMsgIdentity;
+class nsIMsgOfflineImapOperation;
+
+#define COPY_BUFFER_SIZE 16384
+
+#define NS_IMAPMAILCOPYSTATE_IID \
+ { \
+ 0xb64534f0, 0x3d53, 0x11d3, { \
+ 0xac, 0x2a, 0x00, 0x80, 0x5f, 0x8a, 0xc9, 0x68 \
+ } \
+ }
+
+class nsImapMailCopyState : public nsISupports {
+ public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_IMAPMAILCOPYSTATE_IID)
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ nsImapMailCopyState();
+
+ nsCOMPtr<nsISupports> m_srcSupport; // source file spec or folder
+ nsTArray<RefPtr<nsIMsgDBHdr>> m_messages; // array of source messages
+ RefPtr<nsImapMoveCopyMsgTxn>
+ m_undoMsgTxn; // undo object with this copy operation
+ nsCOMPtr<nsIMsgCopyServiceListener> m_listener; // listener of this copy
+ // operation
+ nsCOMPtr<nsIFile> m_tmpFile; // temp file spec for copy operation
+ nsCOMPtr<nsIMsgWindow> m_msgWindow; // msg window for copy operation
+
+ nsCOMPtr<nsIMsgMessageService>
+ m_msgService; // source folder message service; can
+ // be Nntp, Mailbox, or Imap
+ bool m_isMove; // is a move
+ bool m_selectedState; // needs to be in selected state; append msg
+ bool m_isCrossServerOp; // are we copying between imap servers?
+ uint32_t m_curIndex; // message index to the message array which we are
+ // copying
+ uint32_t m_unreadCount; // num unread messages we're moving
+ bool m_streamCopy;
+ char* m_dataBuffer; // temporary buffer for this copy operation
+ nsCOMPtr<nsIOutputStream> m_msgFileStream; // temporary file (processed mail)
+ uint32_t m_dataBufferSize;
+ uint32_t m_leftOver;
+ bool m_allowUndo;
+ bool m_eatLF;
+ uint32_t m_newMsgFlags; // only used if m_messages is empty
+ nsCString m_newMsgKeywords; // ditto
+ // If the server supports UIDPLUS, this is the UID for the append,
+ // if we're doing an append.
+ nsMsgKey m_appendUID;
+
+ private:
+ virtual ~nsImapMailCopyState();
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsImapMailCopyState, NS_IMAPMAILCOPYSTATE_IID)
+
+// ACLs for this folder.
+// Generally, we will try to always query this class when performing
+// an operation on the folder.
+// If the server doesn't support ACLs, none of this data will be filled in.
+// Therefore, we can assume that if we look up ourselves and don't find
+// any info (and also look up "anyone") then we have full rights, that is, ACLs
+// don't exist.
+class nsImapMailFolder;
+
+// clang-format off
+#define IMAP_ACL_READ_FLAG 0x0000001 // SELECT, CHECK, FETCH, PARTIAL, SEARCH, COPY from folder
+#define IMAP_ACL_STORE_SEEN_FLAG 0x0000002 // STORE SEEN flag
+#define IMAP_ACL_WRITE_FLAG 0x0000004 // STORE flags other than SEEN and DELETED
+#define IMAP_ACL_INSERT_FLAG 0x0000008 // APPEND, COPY into folder
+#define IMAP_ACL_POST_FLAG 0x0000010 // Can I send mail to the submission address for folder?
+#define IMAP_ACL_CREATE_SUBFOLDER_FLAG 0x0000020 // Can I CREATE a subfolder of this folder?
+#define IMAP_ACL_DELETE_FLAG 0x0000040 // STORE DELETED flag
+#define IMAP_ACL_ADMINISTER_FLAG 0x0000080 // perform SETACL
+#define IMAP_ACL_RETRIEVED_FLAG 0x0000100 // ACL info for this folder has been initialized
+#define IMAP_ACL_EXPUNGE_FLAG 0x0000200 // can EXPUNGE or do implicit EXPUNGE on CLOSE
+#define IMAP_ACL_DELETE_FOLDER 0x0000400 // can DELETE/RENAME folder
+// clang-format on
+
+class nsMsgIMAPFolderACL {
+ public:
+ explicit nsMsgIMAPFolderACL(nsImapMailFolder* folder);
+ ~nsMsgIMAPFolderACL();
+
+ bool SetFolderRightsForUser(const nsACString& userName,
+ const nsACString& rights);
+
+ public:
+ // generic for any user, although we might not use them in
+ // DO NOT use these for looking up information about the currently
+ // authenticated user. (There are some different checks and defaults we do).
+ // Instead, use the functions below, GetICan....()
+ // clang-format off
+ bool GetCanUserLookupFolder(const nsACString& userName); // Is folder visible to LIST/LSUB?
+ bool GetCanUserReadFolder(const nsACString& userName); // SELECT, CHECK, FETCH, PARTIAL, SEARCH, COPY from folder?
+ bool GetCanUserStoreSeenInFolder(const nsACString& userName); // STORE SEEN flag?
+ bool GetCanUserWriteFolder(const nsACString& userName); // STORE flags other than SEEN and DELETED?
+ bool GetCanUserInsertInFolder(const nsACString& userName); // APPEND, COPY into folder?
+ bool GetCanUserPostToFolder(const nsACString& userName); // Can I send mail to the submission address for folder?
+ bool GetCanUserCreateSubfolder(const nsACString& userName); // Can I CREATE a subfolder of this folder?
+ bool GetCanUserDeleteInFolder(const nsACString& userName); // STORE DELETED flag, perform EXPUNGE?
+ bool GetCanUserAdministerFolder(const nsACString& userName); // perform SETACL?
+
+ // Functions to find out rights for the currently authenticated user.
+
+ bool GetCanILookupFolder(); // Is folder visible to LIST/LSUB?
+ bool GetCanIReadFolder(); // SELECT, CHECK, FETCH, PARTIAL, SEARCH, COPY from folder?
+ bool GetCanIStoreSeenInFolder(); // STORE SEEN flag?
+ bool GetCanIWriteFolder(); // STORE flags other than SEEN and DELETED?
+ bool GetCanIInsertInFolder(); // APPEND, COPY into folder?
+ bool GetCanIPostToFolder(); // Can I send mail to the submission address for folder?
+ bool GetCanICreateSubfolder(); // Can I CREATE a subfolder of this folder?
+ bool GetCanIDeleteInFolder(); // STORE DELETED flag?
+ bool GetCanIAdministerFolder(); // perform SETACL?
+ bool GetCanIExpungeFolder(); // perform EXPUNGE?
+ // clang-format on
+
+ bool GetDoIHaveFullRightsForFolder(); // Returns TRUE if I have full rights
+ // on this folder (all of the above
+ // return TRUE)
+
+ bool GetIsFolderShared(); // We use this to see if the ACLs think a folder is
+ // shared or not.
+ // We will define "Shared" in 5.0 to mean:
+ // At least one user other than the currently authenticated user has at least
+ // one explicitly-listed ACL right on that folder.
+
+ // Returns a newly allocated string describing these rights
+ nsresult CreateACLRightsString(nsAString& rightsString);
+
+ nsresult GetRightsStringForUser(const nsACString& userName,
+ nsCString& rights);
+
+ nsresult GetOtherUsers(nsIUTF8StringEnumerator** aResult);
+
+ protected:
+ bool GetFlagSetInRightsForUser(const nsACString& userName, char flag,
+ bool defaultIfNotFound);
+ void BuildInitialACLFromCache();
+ void UpdateACLCache();
+
+ protected:
+ nsTHashMap<nsCStringHashKey, nsCString>
+ m_rightsHash; // Hash table, mapping username strings to rights strings.
+ nsImapMailFolder* m_folder;
+ int32_t m_aclCount;
+};
+
+/**
+ * Encapsulates parameters required to playback offline ops
+ * on given folder.
+ */
+struct nsPlaybackRequest {
+ explicit nsPlaybackRequest(nsImapMailFolder* srcFolder,
+ nsIMsgWindow* msgWindow)
+ : SrcFolder(srcFolder), MsgWindow(msgWindow) {}
+ nsImapMailFolder* SrcFolder;
+ nsCOMPtr<nsIMsgWindow> MsgWindow;
+};
+
+class nsMsgQuota final : public nsIMsgQuota {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMSGQUOTA
+
+ nsMsgQuota(const nsACString& aName, const uint64_t& aUsage,
+ const uint64_t& aLimit);
+
+ protected:
+ ~nsMsgQuota();
+
+ nsCString mName;
+ uint64_t mUsage, mLimit;
+};
+
+class nsImapMailFolder : public nsMsgDBFolder,
+ public nsIMsgImapMailFolder,
+ public nsIImapMailFolderSink,
+ public nsIImapMessageSink,
+ public nsICopyMessageListener,
+ public nsIMsgFilterHitNotify {
+ static const uint32_t PLAYBACK_TIMER_INTERVAL_IN_MS = 500;
+
+ public:
+ nsImapMailFolder();
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+ // nsIMsgFolder methods:
+ NS_IMETHOD GetSubFolders(nsTArray<RefPtr<nsIMsgFolder>>& folders) override;
+
+ NS_IMETHOD UpdateFolder(nsIMsgWindow* aWindow) override;
+
+ NS_IMETHOD CreateSubfolder(const nsAString& folderName,
+ nsIMsgWindow* msgWindow) override;
+ NS_IMETHOD AddSubfolder(const nsAString& aName,
+ nsIMsgFolder** aChild) override;
+ NS_IMETHODIMP CreateStorageIfMissing(nsIUrlListener* urlListener) override;
+
+ NS_IMETHOD Compact(nsIUrlListener* aListener,
+ nsIMsgWindow* aMsgWindow) override;
+ NS_IMETHOD CompactAll(nsIUrlListener* aListener,
+ nsIMsgWindow* aMsgWindow) override;
+ NS_IMETHOD EmptyTrash(nsIUrlListener* aListener) override;
+ NS_IMETHOD CopyDataToOutputStreamForAppend(
+ nsIInputStream* aIStream, int32_t aLength,
+ nsIOutputStream* outputStream) override;
+ NS_IMETHOD CopyDataDone() override;
+ NS_IMETHOD DeleteStorage() override;
+ NS_IMETHOD Rename(const nsAString& newName, nsIMsgWindow* msgWindow) override;
+ NS_IMETHOD RenameSubFolders(nsIMsgWindow* msgWindow,
+ nsIMsgFolder* oldFolder) override;
+ NS_IMETHOD GetNoSelect(bool* aResult) override;
+
+ NS_IMETHOD GetPrettyName(nsAString& prettyName)
+ override; // Override of the base, for top-level mail folder
+
+ NS_IMETHOD GetFolderURL(nsACString& url) override;
+
+ NS_IMETHOD UpdateSummaryTotals(bool force) override;
+
+ NS_IMETHOD GetDeletable(bool* deletable) override;
+
+ NS_IMETHOD GetSizeOnDisk(int64_t* size) override;
+
+ NS_IMETHOD GetCanCreateSubfolders(bool* aResult) override;
+ NS_IMETHOD GetCanSubscribe(bool* aResult) override;
+
+ NS_IMETHOD ApplyRetentionSettings() override;
+
+ NS_IMETHOD AddMessageDispositionState(
+ nsIMsgDBHdr* aMessage, nsMsgDispositionState aDispositionFlag) override;
+ NS_IMETHOD MarkMessagesRead(const nsTArray<RefPtr<nsIMsgDBHdr>>& messages,
+ bool markRead) override;
+ NS_IMETHOD MarkAllMessagesRead(nsIMsgWindow* aMsgWindow) override;
+ NS_IMETHOD MarkMessagesFlagged(const nsTArray<RefPtr<nsIMsgDBHdr>>& messages,
+ bool markFlagged) override;
+ NS_IMETHOD MarkThreadRead(nsIMsgThread* thread) override;
+ NS_IMETHOD SetJunkScoreForMessages(
+ const nsTArray<RefPtr<nsIMsgDBHdr>>& aMessages,
+ const nsACString& aJunkScore) override;
+ NS_IMETHOD DeleteSelf(nsIMsgWindow* msgWindow) override;
+ NS_IMETHOD ReadFromFolderCacheElem(
+ nsIMsgFolderCacheElement* element) override;
+ NS_IMETHOD WriteToFolderCacheElem(nsIMsgFolderCacheElement* element) override;
+
+ NS_IMETHOD GetDBFolderInfoAndDB(nsIDBFolderInfo** folderInfo,
+ nsIMsgDatabase** db) override;
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD
+ DeleteMessages(nsTArray<RefPtr<nsIMsgDBHdr>> const& msgHeaders,
+ nsIMsgWindow* msgWindow, bool deleteStorage, bool isMove,
+ nsIMsgCopyServiceListener* listener, bool allowUndo) override;
+ NS_IMETHOD CopyMessages(nsIMsgFolder* srcFolder,
+ nsTArray<RefPtr<nsIMsgDBHdr>> const& messages,
+ bool isMove, nsIMsgWindow* msgWindow,
+ nsIMsgCopyServiceListener* listener, bool isFolder,
+ bool allowUndo) override;
+ NS_IMETHOD CopyFolder(nsIMsgFolder* srcFolder, bool isMove,
+ nsIMsgWindow* msgWindow,
+ nsIMsgCopyServiceListener* listener) override;
+ NS_IMETHOD CopyFileMessage(nsIFile* file, nsIMsgDBHdr* msgToReplace,
+ bool isDraftOrTemplate, uint32_t aNewMsgFlags,
+ const nsACString& aNewMsgKeywords,
+ nsIMsgWindow* msgWindow,
+ nsIMsgCopyServiceListener* listener) override;
+ NS_IMETHOD GetNewMessages(nsIMsgWindow* aWindow,
+ nsIUrlListener* aListener) override;
+
+ NS_IMETHOD GetFilePath(nsIFile** aPathName) override;
+ NS_IMETHOD SetFilePath(nsIFile* aPath) override;
+
+ NS_IMETHOD Shutdown(bool shutdownChildren) override;
+
+ NS_IMETHOD DownloadMessagesForOffline(
+ nsTArray<RefPtr<nsIMsgDBHdr>> const& messages,
+ nsIMsgWindow* msgWindow) override;
+
+ NS_IMETHOD DownloadAllForOffline(nsIUrlListener* listener,
+ nsIMsgWindow* msgWindow) override;
+ NS_IMETHOD GetCanFileMessages(bool* aCanFileMessages) override;
+ NS_IMETHOD GetCanDeleteMessages(bool* aCanDeleteMessages) override;
+ NS_IMETHOD FetchMsgPreviewText(nsTArray<nsMsgKey> const& aKeysToFetch,
+ nsIUrlListener* aUrlListener,
+ bool* aAsyncResults) override;
+
+ NS_IMETHOD AddKeywordsToMessages(
+ const nsTArray<RefPtr<nsIMsgDBHdr>>& aMessages,
+ const nsACString& aKeywords) override;
+ NS_IMETHOD RemoveKeywordsFromMessages(
+ const nsTArray<RefPtr<nsIMsgDBHdr>>& aMessages,
+ const nsACString& aKeywords) override;
+
+ NS_IMETHOD NotifyCompactCompleted() override;
+
+ // overrides nsMsgDBFolder::HasMsgOffline()
+ NS_IMETHOD HasMsgOffline(nsMsgKey msgKey, bool* _retval) override;
+ NS_IMETHOD GetLocalMsgStream(nsIMsgDBHdr* hdr,
+ nsIInputStream** stream) override;
+
+ NS_DECL_NSIMSGIMAPMAILFOLDER
+ NS_DECL_NSIIMAPMAILFOLDERSINK
+ NS_DECL_NSIIMAPMESSAGESINK
+ NS_DECL_NSICOPYMESSAGELISTENER
+
+ // nsIUrlListener methods
+ NS_IMETHOD OnStartRunningUrl(nsIURI* aUrl) override;
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD
+ OnStopRunningUrl(nsIURI* aUrl, nsresult aExitCode) override;
+
+ NS_DECL_NSIMSGFILTERHITNOTIFY
+ NS_DECL_NSIJUNKMAILCLASSIFICATIONLISTENER
+
+ NS_IMETHOD IsCommandEnabled(const nsACString& command, bool* result) override;
+ NS_IMETHOD SetFilterList(nsIMsgFilterList* aMsgFilterList) override;
+ NS_IMETHOD GetCustomIdentity(nsIMsgIdentity** aIdentity) override;
+
+ NS_IMETHOD GetIncomingServerType(nsACString& serverType) override;
+
+ nsresult AddSubfolderWithPath(nsAString& name, nsIFile* dbPath,
+ nsIMsgFolder** child, bool brandNew = false);
+ nsresult MoveIncorporatedMessage(nsIMsgDBHdr* mailHdr,
+ nsIMsgDatabase* sourceDB,
+ const nsACString& destFolder,
+ nsIMsgFilter* filter,
+ nsIMsgWindow* msgWindow);
+
+ // send notification to copy service listener.
+ nsresult OnCopyCompleted(nsISupports* srcSupport, nsresult exitCode);
+
+ static nsresult AllocateUidStringFromKeys(const nsTArray<nsMsgKey>& keys,
+ nsCString& msgIds);
+ static nsresult BuildIdsAndKeyArray(
+ const nsTArray<RefPtr<nsIMsgDBHdr>>& messages, nsCString& msgIds,
+ nsTArray<nsMsgKey>& keyArray);
+
+ // these might end up as an nsIImapMailFolder attribute.
+ nsresult SetSupportedUserFlags(uint32_t userFlags);
+ nsresult GetSupportedUserFlags(uint32_t* userFlags);
+
+ // Find the start of a range of msgKeys that can hold srcCount headers.
+ nsresult FindOpenRange(nsMsgKey& fakeBase, uint32_t srcCount);
+
+ protected:
+ virtual ~nsImapMailFolder();
+ // Helper methods
+
+ nsresult ExpungeAndCompact(nsIUrlListener* aListener,
+ nsIMsgWindow* aMsgWindow);
+ virtual nsresult CreateChildFromURI(const nsACString& uri,
+ nsIMsgFolder** folder) override;
+ void FindKeysToAdd(const nsTArray<nsMsgKey>& existingKeys,
+ nsTArray<nsMsgKey>& keysToFetch, uint32_t& numNewUnread,
+ nsIImapFlagAndUidState* flagState);
+ void FindKeysToDelete(const nsTArray<nsMsgKey>& existingKeys,
+ nsTArray<nsMsgKey>& keysToFetch,
+ nsIImapFlagAndUidState* flagState, uint32_t boxFlags);
+ void PrepareToAddHeadersToMailDB(nsIImapProtocol* aProtocol);
+ void TweakHeaderFlags(nsIImapProtocol* aProtocol, nsIMsgDBHdr* tweakMe);
+
+ nsresult SyncFlags(nsIImapFlagAndUidState* flagState);
+ nsresult HandleCustomFlags(nsMsgKey uidOfMessage, nsIMsgDBHdr* dbHdr,
+ uint16_t userFlags, nsCString& keywords);
+ nsresult NotifyMessageFlagsFromHdr(nsIMsgDBHdr* dbHdr, nsMsgKey msgKey,
+ uint32_t flags);
+
+ nsresult SetupHeaderParseStream(uint32_t size, const nsACString& content_type,
+ nsIMailboxSpec* boxSpec);
+ nsresult ParseAdoptedHeaderLine(const char* messageLine, nsMsgKey msgKey);
+ nsresult NormalEndHeaderParseStream(nsIImapProtocol* aProtocol,
+ nsIImapUrl* imapUrl);
+
+ void EndOfflineDownload();
+
+ /**
+ * At the end of a file-to-folder copy operation, copy the file to the
+ * offline store and/or add to the message database, (if needed).
+ *
+ * @param srcFile file containing the message key
+ * @param msgKey key to use for the new messages
+ */
+ nsresult CopyFileToOfflineStore(nsIFile* srcFile, nsMsgKey msgKey);
+
+ nsresult MarkMessagesImapDeleted(nsTArray<nsMsgKey>* keyArray, bool deleted,
+ nsIMsgDatabase* db);
+
+ // Notifies imap autosync that it should update this folder when it
+ // gets a chance.
+ void NotifyHasPendingMsgs();
+ void UpdatePendingCounts();
+ void SetIMAPDeletedFlag(nsIMsgDatabase* mailDB,
+ const nsTArray<nsMsgKey>& msgids, bool markDeleted);
+ virtual bool ShowDeletedMessages();
+ virtual bool DeleteIsMoveToTrash();
+ nsresult GetFolder(const nsACString& name, nsIMsgFolder** pFolder);
+ nsresult GetTrashFolder(nsIMsgFolder** pTrashFolder);
+ bool TrashOrDescendentOfTrash(nsIMsgFolder* folder);
+ static bool ShouldCheckAllFolders(nsIImapIncomingServer* imapServer);
+ nsresult GetServerKey(nsACString& serverKey);
+ nsresult DisplayStatusMsg(nsIImapUrl* aImapUrl, const nsAString& msg);
+
+ // nsresult RenameLocal(const char *newName);
+ nsresult AddDirectorySeparator(nsIFile* path);
+ nsresult CreateSubFolders(nsIFile* path);
+ nsresult GetDatabase() override;
+
+ nsresult GetFolderOwnerUserName(nsACString& userName);
+ nsImapNamespace* GetNamespaceForFolder();
+ void SetNamespaceForFolder(nsImapNamespace* ns);
+
+ nsMsgIMAPFolderACL* GetFolderACL();
+ nsresult CreateACLRightsStringForFolder(nsAString& rightsString);
+ nsresult GetBodysToDownload(nsTArray<nsMsgKey>* keysOfMessagesToDownload);
+ // Uber message copy service
+ nsresult CopyMessagesWithStream(nsIMsgFolder* srcFolder,
+ nsTArray<RefPtr<nsIMsgDBHdr>> const& messages,
+ bool isMove, bool isCrossServerOp,
+ nsIMsgWindow* msgWindow,
+ nsIMsgCopyServiceListener* listener,
+ bool allowUndo);
+ nsresult CopyStreamMessage(nsIMsgDBHdr* message, nsIMsgFolder* dstFolder,
+ nsIMsgWindow* msgWindow, bool isMove);
+ nsresult InitCopyState(nsISupports* srcSupport,
+ nsTArray<RefPtr<nsIMsgDBHdr>> const& messages,
+ bool isMove, bool selectedState, bool acrossServers,
+ uint32_t newMsgFlags, const nsACString& newMsgKeywords,
+ nsIMsgCopyServiceListener* listener,
+ nsIMsgWindow* msgWindow, bool allowUndo);
+ nsresult GetMoveCoalescer();
+ nsresult PlaybackCoalescedOperations();
+ virtual nsresult CreateBaseMessageURI(const nsACString& aURI) override;
+ // offline-ish methods
+ nsresult GetClearedOriginalOp(nsIMsgOfflineImapOperation* op,
+ nsIMsgOfflineImapOperation** originalOp,
+ nsIMsgDatabase** originalDB);
+ nsresult GetOriginalOp(nsIMsgOfflineImapOperation* op,
+ nsIMsgOfflineImapOperation** originalOp,
+ nsIMsgDatabase** originalDB);
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY nsresult CopyMessagesOffline(
+ nsIMsgFolder* srcFolder, nsTArray<RefPtr<nsIMsgDBHdr>> const& messages,
+ bool isMove, nsIMsgWindow* msgWindow,
+ nsIMsgCopyServiceListener* listener);
+ void SetPendingAttributes(const nsTArray<RefPtr<nsIMsgDBHdr>>& messages,
+ bool aIsMove, bool aSetOffline);
+
+ nsresult CopyOfflineMsgBody(nsIMsgFolder* srcFolder, nsIMsgDBHdr* destHdr,
+ nsIMsgDBHdr* origHdr, nsIInputStream* inputStream,
+ nsIOutputStream* outputStream);
+
+ void GetTrashFolderName(nsAString& aFolderName);
+ bool ShowPreviewText();
+
+ // Pseudo-Offline operation playback timer
+ static void PlaybackTimerCallback(nsITimer* aTimer, void* aClosure);
+
+ // Allocate and initialize associated auto-sync state object.
+ void InitAutoSyncState();
+
+ virtual nsresult GetOfflineFileStream(nsMsgKey msgKey, uint64_t* offset,
+ uint32_t* size,
+ nsIInputStream** aFileStream) override;
+
+ bool m_initialized;
+ bool m_haveDiscoveredAllFolders;
+ nsCOMPtr<nsIMsgParseMailMsgState> m_msgParser;
+ nsCOMPtr<nsIMsgFilterList> m_filterList;
+ nsCOMPtr<nsIMsgFilterPlugin> m_filterPlugin; // XXX should be a list
+ // used with filter plugins to know when we've finished classifying and can
+ // playback moves
+ bool m_msgMovedByFilter;
+ RefPtr<nsImapMoveCoalescer>
+ m_moveCoalescer; // strictly owned by the nsImapMailFolder
+ nsTArray<RefPtr<nsIMsgDBHdr>> m_junkMessagesToMarkAsRead;
+ /// list of keys to be moved to the junk folder
+ nsTArray<nsMsgKey> mSpamKeysToMove;
+ /// the junk destination folder
+ nsCOMPtr<nsIMsgFolder> mSpamFolder;
+ nsMsgKey m_curMsgUid;
+ uint32_t m_uidValidity;
+
+ // These three vars are used to store counts from STATUS or SELECT command
+ // They include deleted messages, so they can differ from the generic
+ // folder total and unread counts.
+ int32_t m_numServerRecentMessages;
+ int32_t m_numServerUnseenMessages;
+ int32_t m_numServerTotalMessages;
+ // if server supports UIDNEXT, we store it here.
+ int32_t m_nextUID;
+
+ int32_t m_nextMessageByteLength;
+ nsCOMPtr<nsIUrlListener> m_urlListener;
+ bool m_urlRunning;
+
+ // undo move/copy transaction support
+ RefPtr<nsMsgTxn> m_pendingUndoTxn;
+ RefPtr<nsImapMailCopyState> m_copyState;
+ char m_hierarchyDelimiter;
+ int32_t m_boxFlags;
+ nsCString m_onlineFolderName;
+ nsCString m_ownerUserName; // username of the "other user," as in
+ // "Other Users' Mailboxes"
+
+ nsCString m_adminUrl; // url to run to set admin privileges for this folder
+ nsImapNamespace* m_namespace;
+ bool m_verifiedAsOnlineFolder;
+ bool m_explicitlyVerify; // whether or not we need to explicitly verify this
+ // through LIST
+ bool m_folderIsNamespace;
+ bool m_folderNeedsSubscribing;
+ bool m_folderNeedsAdded;
+ bool m_folderNeedsACLListed;
+ bool m_performingBiff;
+ bool m_updatingFolder;
+ bool m_applyIncomingFilters; // apply filters to this folder, even if not the
+ // inbox
+ nsMsgIMAPFolderACL* m_folderACL;
+ uint32_t m_aclFlags;
+ uint32_t m_supportedUserFlags;
+
+ // determines if we are on GMail server
+ bool m_isGmailServer;
+ // offline imap support
+ bool m_downloadingFolderForOfflineUse;
+ bool m_filterListRequiresBody;
+
+ // auto-sync (automatic message download) support
+ RefPtr<nsAutoSyncState> m_autoSyncStateObj;
+
+ // Quota support.
+ nsTArray<RefPtr<nsIMsgQuota>> m_folderQuota;
+ bool m_folderQuotaCommandIssued;
+ bool m_folderQuotaDataIsValid;
+
+ // Pseudo-Offline Playback support
+ nsPlaybackRequest* m_pendingPlaybackReq;
+ nsCOMPtr<nsITimer> m_playbackTimer;
+ nsTArray<RefPtr<nsImapMoveCopyMsgTxn>> m_pendingOfflineMoves;
+ // hash table of mapping between messageids and message keys
+ // for pseudo hdrs.
+ nsTHashMap<nsCStringHashKey, nsMsgKey> m_pseudoHdrs;
+
+ nsTArray<nsMsgKey> m_keysToFetch;
+ uint32_t m_totalKeysToFetch;
+
+ /**
+ * delete if appropriate local storage for messages in this folder
+ *
+ * @parm aMessages array (of nsIMsgDBHdr) of messages to delete
+ * (or an array of message keys)
+ * @parm aSrcFolder the folder containing the messages (optional)
+ */
+ void DeleteStoreMessages(const nsTArray<RefPtr<nsIMsgDBHdr>>& aMessages);
+ void DeleteStoreMessages(const nsTArray<nsMsgKey>& aMessages);
+ static void DeleteStoreMessages(const nsTArray<nsMsgKey>& aMessages,
+ nsIMsgFolder* aFolder);
+ /**
+ * This method is used to locate a folder where a msg could be present, not
+ * just the folder where the message first arrives, this method searches for
+ * the existence of msg in all the folders/labels that we retrieve from
+ * X-GM-LABELS also.
+ * @param msgKey key of the msg for which we are trying to get the folder;
+ * @param aMsgFolder required folder;
+ */
+ nsresult GetOfflineMsgFolder(nsMsgKey msgKey, nsIMsgFolder** aMsgFolder);
+};
+#endif
diff --git a/comm/mailnews/imap/src/nsImapNamespace.cpp b/comm/mailnews/imap/src/nsImapNamespace.cpp
new file mode 100644
index 0000000000..4fd2dcf7db
--- /dev/null
+++ b/comm/mailnews/imap/src/nsImapNamespace.cpp
@@ -0,0 +1,513 @@
+/* -*- 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 "nsImapCore.h"
+#include "nsImapNamespace.h"
+#include "../public/nsIImapHostSessionList.h"
+#include "nsImapUrl.h"
+#include "nsString.h"
+#include "nsServiceManagerUtils.h"
+
+//////////////////// nsImapNamespace
+////////////////////////////////////////////////////////////////
+
+#define NS_IIMAPHOSTSESSIONLIST_CID \
+ { \
+ 0x479ce8fc, 0xe725, 0x11d2, { \
+ 0xa5, 0x05, 0x00, 0x60, 0xb0, 0xfc, 0x04, 0xb7 \
+ } \
+ }
+static NS_DEFINE_CID(kCImapHostSessionListCID, NS_IIMAPHOSTSESSIONLIST_CID);
+
+nsImapNamespace::nsImapNamespace(EIMAPNamespaceType type, const char* prefix,
+ char delimiter, bool from_prefs) {
+ m_namespaceType = type;
+ m_prefix = PL_strdup(prefix);
+ m_fromPrefs = from_prefs;
+
+ m_delimiter = delimiter;
+ m_delimiterFilledIn =
+ !m_fromPrefs; // if it's from the prefs, we can't be sure about the
+ // delimiter until we list it.
+}
+
+nsImapNamespace::~nsImapNamespace() { PR_FREEIF(m_prefix); }
+
+void nsImapNamespace::SetDelimiter(char delimiter, bool delimiterFilledIn) {
+ m_delimiter = delimiter;
+ m_delimiterFilledIn = delimiterFilledIn;
+}
+
+// returns -1 if this box is not part of this namespace,
+// or the length of the prefix if it is part of this namespace
+int nsImapNamespace::MailboxMatchesNamespace(const char* boxname) {
+ if (!boxname) return -1;
+
+ // If the namespace is part of the boxname
+ if (!m_prefix || !*m_prefix) return 0;
+
+ if (PL_strstr(boxname, m_prefix) == boxname) return PL_strlen(m_prefix);
+
+ // If the boxname is part of the prefix
+ // (Used for matching Personal mailbox with Personal/ namespace, etc.)
+ if (PL_strstr(m_prefix, boxname) == m_prefix) return PL_strlen(boxname);
+ return -1;
+}
+
+nsImapNamespaceList* nsImapNamespaceList::CreatensImapNamespaceList() {
+ nsImapNamespaceList* rv = new nsImapNamespaceList();
+ return rv;
+}
+
+nsImapNamespaceList::nsImapNamespaceList() {}
+
+int nsImapNamespaceList::GetNumberOfNamespaces() {
+ return m_NamespaceList.Length();
+}
+
+nsresult nsImapNamespaceList::InitFromString(const char* nameSpaceString,
+ EIMAPNamespaceType nstype) {
+ nsresult rv = NS_OK;
+ if (nameSpaceString) {
+ int numNamespaces = UnserializeNamespaces(nameSpaceString, nullptr, 0);
+ char** prefixes = (char**)PR_CALLOC(numNamespaces * sizeof(char*));
+ if (prefixes) {
+ int len = UnserializeNamespaces(nameSpaceString, prefixes, numNamespaces);
+ for (int i = 0; i < len; i++) {
+ char* thisns = prefixes[i];
+ char delimiter = '/'; // a guess
+ if (PL_strlen(thisns) >= 1) delimiter = thisns[PL_strlen(thisns) - 1];
+ nsImapNamespace* ns =
+ new nsImapNamespace(nstype, thisns, delimiter, true);
+ if (ns) AddNewNamespace(ns);
+ PR_FREEIF(thisns);
+ }
+ PR_Free(prefixes);
+ }
+ }
+
+ return rv;
+}
+
+nsresult nsImapNamespaceList::OutputToString(nsCString& string) {
+ nsresult rv = NS_OK;
+ return rv;
+}
+
+int nsImapNamespaceList::GetNumberOfNamespaces(EIMAPNamespaceType type) {
+ int nodeIndex = 0, count = 0;
+ for (nodeIndex = m_NamespaceList.Length() - 1; nodeIndex >= 0; nodeIndex--) {
+ nsImapNamespace* nspace = m_NamespaceList.ElementAt(nodeIndex);
+ if (nspace->GetType() == type) {
+ count++;
+ }
+ }
+ return count;
+}
+
+int nsImapNamespaceList::AddNewNamespace(nsImapNamespace* ns) {
+ // If the namespace is from the NAMESPACE response, then we should see if
+ // there are any namespaces previously set by the preferences, or the default
+ // namespace. If so, remove these.
+
+ if (!ns->GetIsNamespaceFromPrefs()) {
+ int nodeIndex;
+ // iterate backwards because we delete elements
+ for (nodeIndex = m_NamespaceList.Length() - 1; nodeIndex >= 0;
+ nodeIndex--) {
+ nsImapNamespace* nspace = m_NamespaceList.ElementAt(nodeIndex);
+ // if we find existing namespace(s) that matches the
+ // new one, we'll just remove the old ones and let the
+ // new one get added when we've finished checking for
+ // matching namespaces or namespaces that came from prefs.
+ if (nspace && (nspace->GetIsNamespaceFromPrefs() ||
+ (!PL_strcmp(ns->GetPrefix(), nspace->GetPrefix()) &&
+ ns->GetType() == nspace->GetType() &&
+ ns->GetDelimiter() == nspace->GetDelimiter()))) {
+ m_NamespaceList.RemoveElementAt(nodeIndex);
+ delete nspace;
+ }
+ }
+ }
+
+ // Add the new namespace to the list. This must come after the removing code,
+ // or else we could never add the initial kDefaultNamespace type to the list.
+ m_NamespaceList.AppendElement(ns);
+
+ return 0;
+}
+
+// chrisf - later, fix this to know the real concept of "default" namespace of a
+// given type
+nsImapNamespace* nsImapNamespaceList::GetDefaultNamespaceOfType(
+ EIMAPNamespaceType type) {
+ nsImapNamespace *rv = 0, *firstOfType = 0;
+
+ int nodeIndex, count = m_NamespaceList.Length();
+ for (nodeIndex = 0; nodeIndex < count && !rv; nodeIndex++) {
+ nsImapNamespace* ns = m_NamespaceList.ElementAt(nodeIndex);
+ if (ns->GetType() == type) {
+ if (!firstOfType) firstOfType = ns;
+ if (!(*(ns->GetPrefix()))) {
+ // This namespace's prefix is ""
+ // Therefore it is the default
+ rv = ns;
+ }
+ }
+ }
+ if (!rv) rv = firstOfType;
+ return rv;
+}
+
+nsImapNamespaceList::~nsImapNamespaceList() {
+ ClearNamespaces(true, true, true);
+}
+
+// ClearNamespaces removes and deletes the namespaces specified, and if there
+// are no namespaces left,
+void nsImapNamespaceList::ClearNamespaces(bool deleteFromPrefsNamespaces,
+ bool deleteServerAdvertisedNamespaces,
+ bool reallyDelete) {
+ int nodeIndex;
+
+ // iterate backwards because we delete elements
+ for (nodeIndex = m_NamespaceList.Length() - 1; nodeIndex >= 0; nodeIndex--) {
+ nsImapNamespace* ns = m_NamespaceList.ElementAt(nodeIndex);
+ if (ns->GetIsNamespaceFromPrefs()) {
+ if (deleteFromPrefsNamespaces) {
+ m_NamespaceList.RemoveElementAt(nodeIndex);
+ if (reallyDelete) delete ns;
+ }
+ } else if (deleteServerAdvertisedNamespaces) {
+ m_NamespaceList.RemoveElementAt(nodeIndex);
+ if (reallyDelete) delete ns;
+ }
+ }
+}
+
+nsImapNamespace* nsImapNamespaceList::GetNamespaceNumber(int nodeIndex) {
+ NS_ASSERTION(nodeIndex >= 0 && nodeIndex < GetNumberOfNamespaces(),
+ "invalid IMAP namespace node index");
+ if (nodeIndex < 0) nodeIndex = 0;
+
+ // XXX really could be just ElementAt; that's why we have the assertion
+ return m_NamespaceList.SafeElementAt(nodeIndex);
+}
+
+nsImapNamespace* nsImapNamespaceList::GetNamespaceNumber(
+ int nodeIndex, EIMAPNamespaceType type) {
+ int nodeCount, count = 0;
+ for (nodeCount = m_NamespaceList.Length() - 1; nodeCount >= 0; nodeCount--) {
+ nsImapNamespace* nspace = m_NamespaceList.ElementAt(nodeCount);
+ if (nspace->GetType() == type) {
+ count++;
+ if (count == nodeIndex) return nspace;
+ }
+ }
+ return nullptr;
+}
+
+nsImapNamespace* nsImapNamespaceList::GetNamespaceForMailbox(
+ const char* boxname) {
+ // We want to find the LONGEST substring that matches the beginning of this
+ // mailbox's path. This accounts for nested namespaces (i.e. "Public/" and
+ // "Public/Users/")
+
+ // Also, we want to match the namespace's mailbox to that namespace also:
+ // The Personal box will match the Personal/ namespace, etc.
+
+ // these lists shouldn't be too long (99% chance there won't be more than 3 or
+ // 4) so just do a linear search
+
+ int lengthMatched = -1;
+ int currentMatchedLength = -1;
+ nsImapNamespace* rv = nullptr;
+ int nodeIndex = 0;
+
+ if (!PL_strcasecmp(boxname, "INBOX"))
+ return GetDefaultNamespaceOfType(kPersonalNamespace);
+
+ for (nodeIndex = m_NamespaceList.Length() - 1; nodeIndex >= 0; nodeIndex--) {
+ nsImapNamespace* nspace = m_NamespaceList.ElementAt(nodeIndex);
+ currentMatchedLength = nspace->MailboxMatchesNamespace(boxname);
+ if (currentMatchedLength > lengthMatched) {
+ rv = nspace;
+ lengthMatched = currentMatchedLength;
+ }
+ }
+
+ return rv;
+}
+
+#define SERIALIZER_SEPARATORS ","
+
+/**
+ * If len is one, copies the first element of prefixes into
+ * serializedNamespaces. If len > 1, copies len strings from prefixes into
+ * serializedNamespaces as a comma-separated list of quoted strings.
+ */
+nsresult nsImapNamespaceList::SerializeNamespaces(
+ char** prefixes, int len, nsCString& serializedNamespaces) {
+ if (len <= 0) return NS_OK;
+
+ if (len == 1) {
+ serializedNamespaces.Assign(prefixes[0]);
+ return NS_OK;
+ }
+
+ for (int i = 0; i < len; i++) {
+ if (i > 0) serializedNamespaces.Append(',');
+
+ serializedNamespaces.Append('"');
+ serializedNamespaces.Append(prefixes[i]);
+ serializedNamespaces.Append('"');
+ }
+ return NS_OK;
+}
+
+/* str is the string which needs to be unserialized.
+ If prefixes is NULL, simply returns the number of namespaces in str. (len is
+ ignored) If prefixes is not NULL, it should be an array of length len which
+ is to be filled in with newly-allocated string. Returns the number of
+ strings filled in.
+*/
+int nsImapNamespaceList::UnserializeNamespaces(const char* str, char** prefixes,
+ int len) {
+ if (!str) return 0;
+ if (!prefixes) {
+ if (str[0] != '"') return 1;
+
+ int count = 0;
+ char* ourstr = PL_strdup(str);
+ char* origOurStr = ourstr;
+ if (ourstr) {
+ char* token = NS_strtok(SERIALIZER_SEPARATORS, &ourstr);
+ while (token != nullptr) {
+ token = NS_strtok(SERIALIZER_SEPARATORS, &ourstr);
+ count++;
+ }
+ PR_Free(origOurStr);
+ }
+ return count;
+ }
+
+ if ((str[0] != '"') && (len >= 1)) {
+ prefixes[0] = PL_strdup(str);
+ return 1;
+ }
+
+ int count = 0;
+ char* ourstr = PL_strdup(str);
+ char* origOurStr = ourstr;
+ if (ourstr) {
+ char* token = NS_strtok(SERIALIZER_SEPARATORS, &ourstr);
+ while ((count < len) && (token != nullptr)) {
+ char *current = PL_strdup(token), *where = current;
+ if (where[0] == '"') where++;
+ if (where[PL_strlen(where) - 1] == '"') where[PL_strlen(where) - 1] = 0;
+ prefixes[count] = PL_strdup(where);
+ PR_FREEIF(current);
+ token = NS_strtok(SERIALIZER_SEPARATORS, &ourstr);
+ count++;
+ }
+ PR_Free(origOurStr);
+ }
+ return count;
+}
+
+// static
+nsCString nsImapNamespaceList::AllocateCanonicalFolderName(
+ const char* onlineFolderName, char delimiter) {
+ nsCString canonicalPath;
+ if (delimiter) {
+ char* tmp =
+ nsImapUrl::ReplaceCharsInCopiedString(onlineFolderName, delimiter, '/');
+ canonicalPath.Assign(tmp);
+ PR_Free(tmp);
+ } else {
+ canonicalPath.Assign(onlineFolderName);
+ }
+ canonicalPath.ReplaceSubstring("\\/", "/");
+ return canonicalPath;
+}
+
+nsImapNamespace* nsImapNamespaceList::GetNamespaceForFolder(
+ const char* hostName, const char* canonicalFolderName, char delimiter) {
+ if (!hostName || !canonicalFolderName) return nullptr;
+
+ nsImapNamespace* resultNamespace = nullptr;
+ nsresult rv;
+ char* convertedFolderName = nsImapNamespaceList::AllocateServerFolderName(
+ canonicalFolderName, delimiter);
+
+ if (convertedFolderName) {
+ nsCOMPtr<nsIImapHostSessionList> hostSessionList =
+ do_GetService(kCImapHostSessionListCID, &rv);
+ if (NS_FAILED(rv)) return nullptr;
+ hostSessionList->GetNamespaceForMailboxForHost(
+ hostName, convertedFolderName, resultNamespace);
+ PR_Free(convertedFolderName);
+ } else {
+ NS_ASSERTION(false, "couldn't get converted folder name");
+ }
+
+ return resultNamespace;
+}
+
+/* static */
+char* nsImapNamespaceList::AllocateServerFolderName(
+ const char* canonicalFolderName, char delimiter) {
+ if (delimiter)
+ return nsImapUrl::ReplaceCharsInCopiedString(canonicalFolderName, '/',
+ delimiter);
+ return NS_xstrdup(canonicalFolderName);
+}
+
+/*
+ GetFolderOwnerNameFromPath takes as inputs a folder name
+ in canonical form, and a namespace for that folder.
+ The namespace MUST be of type kOtherUsersNamespace, hence the folder MUST be
+ owned by another user. This function extracts the folder owner's name from
+ the canonical name of the folder, and returns the owner's name.
+*/
+/* static */
+nsCString nsImapNamespaceList::GetFolderOwnerNameFromPath(
+ nsImapNamespace* namespaceForFolder, const char* canonicalFolderName) {
+ if (!namespaceForFolder || !canonicalFolderName) {
+ NS_ERROR("null namespace or canonical folder name");
+ return ""_ns;
+ }
+
+ // Convert the canonical path to the online path.
+ nsAutoCString convertedFolderName(canonicalFolderName);
+ char delimiter = namespaceForFolder->GetDelimiter();
+ if (delimiter) {
+ convertedFolderName.ReplaceChar('/', delimiter);
+ }
+
+ // Trim off the prefix.
+ uint32_t prefixLen =
+ nsDependentCString(namespaceForFolder->GetPrefix()).Length();
+ if (convertedFolderName.Length() <= prefixLen) {
+ NS_ERROR("server folder name invalid");
+ return ""_ns;
+ }
+
+ // Trim off anything after the owner name.
+ nsCString owner(Substring(convertedFolderName, prefixLen));
+ int32_t i = owner.FindChar(delimiter);
+ if (i != kNotFound) {
+ owner.Truncate(i);
+ }
+ return owner;
+}
+
+/*
+GetFolderIsNamespace returns TRUE if the given folder is the folder representing
+a namespace.
+*/
+
+bool nsImapNamespaceList::GetFolderIsNamespace(
+ const char* hostName, const char* canonicalFolderName, char delimiter,
+ nsImapNamespace* namespaceForFolder) {
+ NS_ASSERTION(namespaceForFolder, "null namespace");
+
+ bool rv = false;
+
+ const char* prefix = namespaceForFolder->GetPrefix();
+ NS_ASSERTION(prefix, "namespace has no prefix");
+ if (!prefix || !*prefix) // empty namespace prefix
+ return false;
+
+ char* convertedFolderName =
+ AllocateServerFolderName(canonicalFolderName, delimiter);
+ if (convertedFolderName) {
+ bool lastCharIsDelimiter = (prefix[strlen(prefix) - 1] == delimiter);
+
+ if (lastCharIsDelimiter) {
+ rv = ((strncmp(convertedFolderName, prefix,
+ strlen(convertedFolderName)) == 0) &&
+ (strlen(convertedFolderName) == strlen(prefix) - 1));
+ } else {
+ rv = (strcmp(convertedFolderName, prefix) == 0);
+ }
+
+ PR_Free(convertedFolderName);
+ } else {
+ NS_ASSERTION(false, "couldn't allocate server folder name");
+ }
+
+ return rv;
+}
+
+/*
+ SuggestHierarchySeparatorForNamespace takes a namespace from libmsg
+ and a hierarchy delimiter. If the namespace has not been filled in from
+ online NAMESPACE command yet, it fills in the suggested delimiter to be
+ used from then on (until it is overridden by an online response).
+*/
+
+void nsImapNamespaceList::SuggestHierarchySeparatorForNamespace(
+ nsImapNamespace* namespaceForFolder, char delimiterFromFolder) {
+ NS_ASSERTION(namespaceForFolder, "need namespace");
+ if (namespaceForFolder && !namespaceForFolder->GetIsDelimiterFilledIn())
+ namespaceForFolder->SetDelimiter(delimiterFromFolder, false);
+}
+
+/*
+ GenerateFullFolderNameWithDefaultNamespace takes a folder name in canonical
+ form, converts to online form and calculates the full online server name
+ including the namespace prefix of the default namespace of the
+ given type, in the form: PR_smprintf("%s%s", prefix, onlineServerName) if
+ there is a NULL owner PR_smprintf("%s%s%c%s", prefix, owner, delimiter,
+ onlineServerName) if there is an owner. It then converts this back to
+ canonical form and returns it.
+ It returns empty string if there is no namespace of the given type.
+ If nsUsed is not passed in as NULL, then *nsUsed is filled in and returned; it
+ is the namespace used for generating the folder name.
+*/
+nsCString nsImapNamespaceList::GenerateFullFolderNameWithDefaultNamespace(
+ const char* hostName, const char* canonicalFolderName, const char* owner,
+ EIMAPNamespaceType nsType, nsImapNamespace** nsUsed) {
+ nsresult rv = NS_OK;
+
+ nsCOMPtr<nsIImapHostSessionList> hostSession =
+ do_GetService(kCImapHostSessionListCID, &rv);
+ NS_ENSURE_SUCCESS(rv, ""_ns);
+ nsImapNamespace* ns;
+ nsCString fullFolderName;
+ rv = hostSession->GetDefaultNamespaceOfTypeForHost(hostName, nsType, ns);
+ NS_ENSURE_SUCCESS(rv, ""_ns);
+ if (ns) {
+ if (nsUsed) *nsUsed = ns;
+ const char* prefix = ns->GetPrefix();
+ char* convertedFolderName =
+ AllocateServerFolderName(canonicalFolderName, ns->GetDelimiter());
+ if (convertedFolderName) {
+ char* convertedReturnName = nullptr;
+ if (owner) {
+ convertedReturnName = PR_smprintf(
+ "%s%s%c%s", prefix, owner, ns->GetDelimiter(), convertedFolderName);
+ } else {
+ convertedReturnName = PR_smprintf("%s%s", prefix, convertedFolderName);
+ }
+
+ if (convertedReturnName) {
+ fullFolderName = AllocateCanonicalFolderName(convertedReturnName,
+ ns->GetDelimiter());
+ PR_Free(convertedReturnName);
+ }
+ PR_Free(convertedFolderName);
+ } else {
+ NS_ASSERTION(false, "couldn't allocate server folder name");
+ }
+ } else {
+ // Could not find other users namespace on the given host
+ NS_WARNING("couldn't find namespace for given host");
+ }
+ return fullFolderName;
+}
diff --git a/comm/mailnews/imap/src/nsImapNamespace.h b/comm/mailnews/imap/src/nsImapNamespace.h
new file mode 100644
index 0000000000..5d11d73120
--- /dev/null
+++ b/comm/mailnews/imap/src/nsImapNamespace.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 _nsImapNamespace_H_
+#define _nsImapNamespace_H_
+
+#include "nsImapCore.h"
+#include "nsTArray.h"
+
+class nsImapNamespace {
+ public:
+ nsImapNamespace(EIMAPNamespaceType type, const char* prefix, char delimiter,
+ bool from_prefs);
+
+ ~nsImapNamespace();
+
+ EIMAPNamespaceType GetType() { return m_namespaceType; }
+ const char* GetPrefix() { return m_prefix; }
+ char GetDelimiter() { return m_delimiter; }
+ void SetDelimiter(char delimiter, bool delimiterFilledIn);
+ bool GetIsDelimiterFilledIn() { return m_delimiterFilledIn; }
+ bool GetIsNamespaceFromPrefs() { return m_fromPrefs; }
+
+ // returns -1 if this box is not part of this namespace,
+ // or the length of the prefix if it is part of this namespace
+ int MailboxMatchesNamespace(const char* boxname);
+
+ protected:
+ EIMAPNamespaceType m_namespaceType;
+ char* m_prefix;
+ char m_delimiter;
+ bool m_fromPrefs;
+ bool m_delimiterFilledIn;
+};
+
+// represents an array of namespaces for a given host
+class nsImapNamespaceList {
+ public:
+ ~nsImapNamespaceList();
+
+ static nsImapNamespaceList* CreatensImapNamespaceList();
+
+ nsresult InitFromString(const char* nameSpaceString,
+ EIMAPNamespaceType nstype);
+ nsresult OutputToString(nsCString& OutputString);
+ int UnserializeNamespaces(const char* str, char** prefixes, int len);
+ nsresult SerializeNamespaces(char** prefixes, int len,
+ nsCString& serializedNamespace);
+
+ void ClearNamespaces(bool deleteFromPrefsNamespaces,
+ bool deleteServerAdvertisedNamespaces,
+ bool reallyDelete);
+ int GetNumberOfNamespaces();
+ int GetNumberOfNamespaces(EIMAPNamespaceType);
+ nsImapNamespace* GetNamespaceNumber(int nodeIndex);
+ nsImapNamespace* GetNamespaceNumber(int nodeIndex, EIMAPNamespaceType);
+
+ nsImapNamespace* GetDefaultNamespaceOfType(EIMAPNamespaceType type);
+ int AddNewNamespace(nsImapNamespace* ns);
+ nsImapNamespace* GetNamespaceForMailbox(const char* boxname);
+ static nsImapNamespace* GetNamespaceForFolder(const char* hostName,
+ const char* canonicalFolderName,
+ char delimiter);
+ static bool GetFolderIsNamespace(const char* hostName,
+ const char* canonicalFolderName,
+ char delimiter,
+ nsImapNamespace* namespaceForFolder);
+ static nsCString GetFolderOwnerNameFromPath(
+ nsImapNamespace* namespaceForFolder, const char* canonicalFolderName);
+ static void SuggestHierarchySeparatorForNamespace(
+ nsImapNamespace* namespaceForFolder, char delimiterFromFolder);
+ static nsCString GenerateFullFolderNameWithDefaultNamespace(
+ const char* hostName, const char* canonicalFolderName, const char* owner,
+ EIMAPNamespaceType nsType, nsImapNamespace** nsUsed);
+
+ protected:
+ static char* AllocateServerFolderName(const char* canonicalFolderName,
+ char delimiter);
+ static nsCString AllocateCanonicalFolderName(const char* onlineFolderName,
+ char delimiter);
+ nsImapNamespaceList(); // use CreatensImapNamespaceList to create one
+
+ nsTArray<nsImapNamespace*> m_NamespaceList;
+};
+#endif
diff --git a/comm/mailnews/imap/src/nsImapOfflineSync.cpp b/comm/mailnews/imap/src/nsImapOfflineSync.cpp
new file mode 100644
index 0000000000..6b43a106bb
--- /dev/null
+++ b/comm/mailnews/imap/src/nsImapOfflineSync.cpp
@@ -0,0 +1,1175 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "msgCore.h"
+#include "netCore.h"
+#include "nsNetUtil.h"
+#include "nsImapOfflineSync.h"
+#include "nsImapMailFolder.h"
+#include "nsMsgFolderFlags.h"
+#include "nsMsgMessageFlags.h"
+#include "nsIMsgMailNewsUrl.h"
+#include "nsIMsgAccountManager.h"
+#include "nsINntpIncomingServer.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsISeekableStream.h"
+#include "nsIMsgCopyService.h"
+#include "nsImapProtocol.h"
+#include "nsMsgUtils.h"
+#include "nsIAutoSyncManager.h"
+#include "mozilla/Unused.h"
+
+NS_IMPL_ISUPPORTS(nsImapOfflineSync, nsIUrlListener, nsIMsgCopyServiceListener,
+ nsIDBChangeListener, nsIImapOfflineSync)
+
+nsImapOfflineSync::nsImapOfflineSync() {
+ m_singleFolderToUpdate = nullptr;
+ m_window = nullptr;
+ mCurrentPlaybackOpType = nsIMsgOfflineImapOperation::kFlagsChanged;
+ m_mailboxupdatesStarted = false;
+ m_mailboxupdatesFinished = false;
+ m_createdOfflineFolders = false;
+ m_pseudoOffline = false;
+ m_KeyIndex = 0;
+ mCurrentUIDValidity = nsMsgKey_None;
+ m_listener = nullptr;
+}
+
+NS_IMETHODIMP
+nsImapOfflineSync::Init(nsIMsgWindow* window, nsIUrlListener* listener,
+ nsIMsgFolder* singleFolderOnly, bool isPseudoOffline) {
+ m_window = window;
+ m_listener = listener;
+ m_singleFolderToUpdate = singleFolderOnly;
+ m_pseudoOffline = isPseudoOffline;
+
+ // not the perfect place for this, but I think it will work.
+ if (m_window) m_window->SetStopped(false);
+
+ return NS_OK;
+}
+
+nsImapOfflineSync::~nsImapOfflineSync() {}
+
+void nsImapOfflineSync::SetWindow(nsIMsgWindow* window) { m_window = window; }
+
+NS_IMETHODIMP nsImapOfflineSync::OnStartRunningUrl(nsIURI* url) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapOfflineSync::OnStopRunningUrl(nsIURI* url, nsresult exitCode) {
+ nsresult rv = exitCode;
+
+ // where do we make sure this gets cleared when we start running urls?
+ bool stopped = false;
+ if (m_window) m_window->GetStopped(&stopped);
+
+ if (m_curTempFile) {
+ m_curTempFile->Remove(false);
+ m_curTempFile = nullptr;
+ }
+ // 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 (stopped) {
+ if (m_listener) m_listener->OnStopRunningUrl(url, NS_BINDING_ABORTED);
+ return NS_OK;
+ }
+ nsCOMPtr<nsIImapUrl> imapUrl = do_QueryInterface(url);
+
+ if (imapUrl)
+ nsImapProtocol::LogImapUrl(NS_SUCCEEDED(rv) ? "offline imap url succeeded "
+ : "offline imap url failed ",
+ imapUrl);
+
+ // If we succeeded, or it was an imap move/copy that timed out, clear the
+ // operation.
+ bool moveCopy =
+ mCurrentPlaybackOpType == nsIMsgOfflineImapOperation::kMsgCopy ||
+ mCurrentPlaybackOpType == nsIMsgOfflineImapOperation::kMsgMoved;
+ if (NS_SUCCEEDED(exitCode) || exitCode == NS_MSG_ERROR_IMAP_COMMAND_FAILED ||
+ (moveCopy && exitCode == NS_ERROR_NET_TIMEOUT)) {
+ ClearCurrentOps();
+ rv = ProcessNextOperation();
+ }
+ // else if it's a non-stop error, and we're doing multiple folders,
+ // go to the next folder.
+ else if (!m_singleFolderToUpdate) {
+ if (AdvanceToNextFolder())
+ rv = ProcessNextOperation();
+ else if (m_listener)
+ m_listener->OnStopRunningUrl(url, rv);
+ }
+
+ return rv;
+}
+
+/**
+ * Leaves m_currentServer at the next imap or local mail "server" that
+ * might have offline events to playback, and m_folderQueue holding
+ * a (reversed) list of all the folders to consider for that server.
+ * If no more servers, m_currentServer will be left at nullptr and the
+ * function returns false.
+ */
+bool nsImapOfflineSync::AdvanceToNextServer() {
+ nsresult rv = NS_OK;
+
+ if (m_allServers.IsEmpty()) {
+ NS_ASSERTION(!m_currentServer, "this shouldn't be set");
+ m_currentServer = nullptr;
+ nsCOMPtr<nsIMsgAccountManager> accountManager =
+ do_GetService("@mozilla.org/messenger/account-manager;1", &rv);
+ NS_ASSERTION(accountManager && NS_SUCCEEDED(rv),
+ "couldn't get account mgr");
+ if (!accountManager || NS_FAILED(rv)) return false;
+
+ rv = accountManager->GetAllServers(m_allServers);
+ NS_ENSURE_SUCCESS(rv, false);
+ }
+ size_t serverIndex = 0;
+ if (m_currentServer) {
+ serverIndex = m_allServers.IndexOf(m_currentServer);
+ if (serverIndex == m_allServers.NoIndex) {
+ serverIndex = 0;
+ } else {
+ // Move to the next server
+ ++serverIndex;
+ }
+ }
+ m_currentServer = nullptr;
+ nsCOMPtr<nsIMsgFolder> rootFolder;
+
+ while (serverIndex < m_allServers.Length()) {
+ nsCOMPtr<nsIMsgIncomingServer> server(m_allServers[serverIndex]);
+ serverIndex++;
+
+ nsCOMPtr<nsINntpIncomingServer> newsServer = do_QueryInterface(server);
+ if (newsServer) // news servers aren't involved in offline imap
+ continue;
+
+ if (server) {
+ m_currentServer = server;
+ server->GetRootFolder(getter_AddRefs(rootFolder));
+ if (rootFolder) {
+ rv = rootFolder->GetDescendants(m_folderQueue);
+ if (NS_SUCCEEDED(rv)) {
+ if (!m_folderQueue.IsEmpty()) {
+ // We'll be popping folders off the end as they are processed.
+ m_folderQueue.Reverse();
+ return true;
+ }
+ }
+ }
+ }
+ }
+ return false;
+}
+
+/**
+ * Sets m_currentFolder to the next folder to process.
+ *
+ * @return True if next folder to process was found, otherwise false.
+ */
+bool nsImapOfflineSync::AdvanceToNextFolder() {
+ // we always start by changing flags
+ mCurrentPlaybackOpType = nsIMsgOfflineImapOperation::kFlagsChanged;
+
+ if (m_currentFolder) {
+ m_currentFolder->SetMsgDatabase(nullptr);
+ m_currentFolder = nullptr;
+ }
+
+ bool hasMore = false;
+ if (m_currentServer) {
+ hasMore = !m_folderQueue.IsEmpty();
+ }
+ if (!hasMore) {
+ hasMore = AdvanceToNextServer();
+ }
+ if (hasMore) {
+ m_currentFolder = m_folderQueue.PopLastElement();
+ }
+ ClearDB();
+ return m_currentFolder;
+}
+
+void nsImapOfflineSync::AdvanceToFirstIMAPFolder() {
+ m_currentServer = nullptr;
+ nsCOMPtr<nsIMsgImapMailFolder> imapFolder;
+ while (!imapFolder && AdvanceToNextFolder()) {
+ imapFolder = do_QueryInterface(m_currentFolder);
+ }
+}
+
+void nsImapOfflineSync::ProcessFlagOperation(nsIMsgOfflineImapOperation* op) {
+ nsCOMPtr<nsIMsgOfflineImapOperation> currentOp = op;
+ nsTArray<nsMsgKey> matchingFlagKeys;
+ uint32_t currentKeyIndex = m_KeyIndex;
+
+ imapMessageFlagsType matchingFlags;
+ currentOp->GetNewFlags(&matchingFlags);
+ bool flagsMatch = true;
+ do { // loop for all messages with the same flags
+ if (flagsMatch) {
+ nsMsgKey curKey;
+ currentOp->GetMessageKey(&curKey);
+ matchingFlagKeys.AppendElement(curKey);
+ currentOp->SetPlayingBack(true);
+ m_currentOpsToClear.AppendObject(currentOp);
+ }
+ currentOp = nullptr;
+ imapMessageFlagsType newFlags = kNoImapMsgFlag;
+ imapMessageFlagsType flagOperation = kNoImapMsgFlag;
+ if (++currentKeyIndex < m_CurrentKeys.Length())
+ m_currentDB->GetOfflineOpForKey(m_CurrentKeys[currentKeyIndex], false,
+ getter_AddRefs(currentOp));
+ if (currentOp) {
+ currentOp->GetFlagOperation(&flagOperation);
+ currentOp->GetNewFlags(&newFlags);
+ }
+ flagsMatch = (flagOperation & nsIMsgOfflineImapOperation::kFlagsChanged) &&
+ (newFlags == matchingFlags);
+ } while (currentOp);
+
+ if (!matchingFlagKeys.IsEmpty()) {
+ nsAutoCString uids;
+ nsImapMailFolder::AllocateUidStringFromKeys(matchingFlagKeys, uids);
+ uint32_t curFolderFlags;
+ m_currentFolder->GetFlags(&curFolderFlags);
+
+ if (uids.get() && (curFolderFlags & nsMsgFolderFlags::ImapBox)) {
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIMsgImapMailFolder> imapFolder =
+ do_QueryInterface(m_currentFolder);
+ nsCOMPtr<nsIURI> uriToSetFlags;
+ if (imapFolder) {
+ rv = imapFolder->SetImapFlags(uids.get(), matchingFlags,
+ getter_AddRefs(uriToSetFlags));
+ if (NS_SUCCEEDED(rv) && uriToSetFlags) {
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl =
+ do_QueryInterface(uriToSetFlags);
+ if (mailnewsUrl) mailnewsUrl->RegisterListener(this);
+ }
+ }
+ }
+ } else
+ ProcessNextOperation();
+}
+
+void nsImapOfflineSync::ProcessKeywordOperation(
+ nsIMsgOfflineImapOperation* op) {
+ nsCOMPtr<nsIMsgOfflineImapOperation> currentOp = op;
+ nsTArray<nsMsgKey> matchingKeywordKeys;
+ uint32_t currentKeyIndex = m_KeyIndex;
+
+ nsAutoCString keywords;
+ if (mCurrentPlaybackOpType == nsIMsgOfflineImapOperation::kAddKeywords)
+ currentOp->GetKeywordsToAdd(getter_Copies(keywords));
+ else
+ currentOp->GetKeywordsToRemove(getter_Copies(keywords));
+ bool keywordsMatch = true;
+ do { // loop for all messages with the same keywords
+ if (keywordsMatch) {
+ nsMsgKey curKey;
+ currentOp->GetMessageKey(&curKey);
+ matchingKeywordKeys.AppendElement(curKey);
+ currentOp->SetPlayingBack(true);
+ m_currentOpsToClear.AppendObject(currentOp);
+ }
+ currentOp = nullptr;
+ if (++currentKeyIndex < m_CurrentKeys.Length())
+ m_currentDB->GetOfflineOpForKey(m_CurrentKeys[currentKeyIndex], false,
+ getter_AddRefs(currentOp));
+ if (currentOp) {
+ nsAutoCString curOpKeywords;
+ nsOfflineImapOperationType operation;
+ currentOp->GetOperation(&operation);
+ if (mCurrentPlaybackOpType == nsIMsgOfflineImapOperation::kAddKeywords)
+ currentOp->GetKeywordsToAdd(getter_Copies(curOpKeywords));
+ else
+ currentOp->GetKeywordsToRemove(getter_Copies(curOpKeywords));
+ keywordsMatch = (operation & mCurrentPlaybackOpType) &&
+ (curOpKeywords.Equals(keywords));
+ }
+ } while (currentOp);
+
+ if (!matchingKeywordKeys.IsEmpty()) {
+ uint32_t curFolderFlags;
+ m_currentFolder->GetFlags(&curFolderFlags);
+
+ if (curFolderFlags & nsMsgFolderFlags::ImapBox) {
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIMsgImapMailFolder> imapFolder =
+ do_QueryInterface(m_currentFolder);
+ nsCOMPtr<nsIURI> uriToStoreCustomKeywords;
+ if (imapFolder) {
+ rv = imapFolder->StoreCustomKeywords(
+ m_window,
+ (mCurrentPlaybackOpType == nsIMsgOfflineImapOperation::kAddKeywords)
+ ? keywords
+ : EmptyCString(),
+ (mCurrentPlaybackOpType ==
+ nsIMsgOfflineImapOperation::kRemoveKeywords)
+ ? keywords
+ : EmptyCString(),
+ matchingKeywordKeys, getter_AddRefs(uriToStoreCustomKeywords));
+ if (NS_SUCCEEDED(rv) && uriToStoreCustomKeywords) {
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl =
+ do_QueryInterface(uriToStoreCustomKeywords);
+ if (mailnewsUrl) mailnewsUrl->RegisterListener(this);
+ }
+ }
+ }
+ } else
+ ProcessNextOperation();
+}
+
+// XXX This should not be void but return an error to indicate which low
+// level routine failed.
+void nsImapOfflineSync::ProcessAppendMsgOperation(
+ nsIMsgOfflineImapOperation* currentOp, int32_t opType) {
+ nsMsgKey msgKey;
+ currentOp->GetMessageKey(&msgKey);
+ nsCOMPtr<nsIMsgDBHdr> mailHdr;
+ nsresult rv = m_currentDB->GetMsgHdrForKey(msgKey, getter_AddRefs(mailHdr));
+ if (NS_FAILED(rv) || !mailHdr) {
+ m_currentDB->RemoveOfflineOp(currentOp);
+ ProcessNextOperation();
+ return;
+ }
+
+ uint64_t messageOffset;
+ uint32_t messageSize;
+ mailHdr->GetMessageOffset(&messageOffset);
+ mailHdr->GetOfflineMessageSize(&messageSize);
+ nsCOMPtr<nsIFile> tmpFile;
+
+ if (NS_WARN_IF(NS_FAILED(GetSpecialDirectoryWithFileName(
+ NS_OS_TEMP_DIR, "nscpmsg.txt", getter_AddRefs(tmpFile)))))
+ return;
+
+ if (NS_WARN_IF(
+ NS_FAILED(tmpFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 00600))))
+ return;
+
+ nsCOMPtr<nsIOutputStream> outputStream;
+ rv = MsgNewBufferedFileOutputStream(getter_AddRefs(outputStream), tmpFile,
+ PR_WRONLY | PR_CREATE_FILE, 00600);
+ if (NS_WARN_IF(NS_FAILED(rv) || !outputStream)) return;
+
+ // We break out of the loop to get to the clean-up code.
+ bool setPlayingBack = false;
+ do {
+ nsCString moveDestination;
+ currentOp->GetDestinationFolderURI(moveDestination);
+
+ nsCOMPtr<nsIMsgFolder> destFolder;
+ rv = GetOrCreateFolder(moveDestination, getter_AddRefs(destFolder));
+ if (NS_WARN_IF(NS_FAILED(rv))) break;
+
+ nsCOMPtr<nsIInputStream> offlineStoreInputStream;
+ rv = destFolder->GetMsgInputStream(mailHdr,
+ getter_AddRefs(offlineStoreInputStream));
+ if (NS_WARN_IF((NS_FAILED(rv) || !offlineStoreInputStream))) break;
+
+ nsCOMPtr<nsISeekableStream> seekStream =
+ do_QueryInterface(offlineStoreInputStream);
+ MOZ_ASSERT(seekStream, "non seekable stream - can't read from offline msg");
+ if (!seekStream) break;
+
+ // From this point onwards, we need to set "playing back".
+ setPlayingBack = true;
+
+ rv = seekStream->Seek(PR_SEEK_SET, messageOffset);
+ if (NS_WARN_IF(NS_FAILED(rv))) break;
+
+ // Copy the dest folder offline store msg to the temp file.
+ int32_t inputBufferSize = FILE_IO_BUFFER_SIZE;
+ char* inputBuffer = (char*)PR_Malloc(inputBufferSize);
+ int32_t bytesLeft;
+ uint32_t bytesRead, bytesWritten;
+
+ bytesLeft = messageSize;
+ rv = inputBuffer ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
+ while (bytesLeft > 0 && NS_SUCCEEDED(rv)) {
+ int32_t bytesToRead = std::min(inputBufferSize, bytesLeft);
+ rv = offlineStoreInputStream->Read(inputBuffer, bytesToRead, &bytesRead);
+ if (NS_WARN_IF(NS_FAILED(rv)) || bytesRead == 0) break;
+ rv = outputStream->Write(inputBuffer, bytesRead, &bytesWritten);
+ if (NS_WARN_IF(NS_FAILED(rv))) break;
+ MOZ_ASSERT(bytesWritten == bytesRead,
+ "wrote out incorrect number of bytes");
+ bytesLeft -= bytesRead;
+ }
+ PR_FREEIF(inputBuffer);
+
+ // rv could have an error from Read/Write.
+ nsresult rv2 = outputStream->Close();
+ if (NS_FAILED(rv2)) {
+ NS_WARNING("ouputStream->Close() failed");
+ }
+ outputStream = nullptr; // Don't try to close it again below.
+
+ // rv: Read/Write, rv2: Close
+ if (NS_FAILED(rv) || NS_FAILED(rv2)) {
+ // This Remove() will fail under Windows if the output stream
+ // fails to close above.
+ mozilla::Unused << NS_WARN_IF(NS_FAILED(tmpFile->Remove(false)));
+ break;
+ }
+
+ nsCOMPtr<nsIFile> cloneTmpFile;
+ // clone the tmp file to defeat nsIFile's stat/size caching.
+ tmpFile->Clone(getter_AddRefs(cloneTmpFile));
+ m_curTempFile = cloneTmpFile;
+ nsCOMPtr<nsIMsgCopyService> copyService =
+ do_GetService("@mozilla.org/messenger/messagecopyservice;1");
+
+ // CopyFileMessage returns error async to this->OnStopCopy
+ // if copyService is null, let's crash here and now.
+ rv = copyService->CopyFileMessage(cloneTmpFile, destFolder,
+ nullptr, // nsIMsgDBHdr* msgToReplace
+ true, // isDraftOrTemplate
+ 0, // new msg flags
+ EmptyCString(), this, m_window);
+ MOZ_ASSERT(NS_SUCCEEDED(rv),
+ "CopyFileMessage() failed. Fatal. Error in call setup?");
+ } while (false);
+
+ if (setPlayingBack) {
+ currentOp->SetPlayingBack(true);
+ m_currentOpsToClear.AppendObject(currentOp);
+ m_currentDB->DeleteHeader(mailHdr, nullptr, true, true);
+ }
+
+ // Close the output stream if it's not already closed.
+ if (outputStream)
+ mozilla::Unused << NS_WARN_IF(NS_FAILED(outputStream->Close()));
+}
+
+void nsImapOfflineSync::ClearCurrentOps() {
+ int32_t opCount = m_currentOpsToClear.Count();
+ for (int32_t i = opCount - 1; i >= 0; i--) {
+ m_currentOpsToClear[i]->SetPlayingBack(false);
+ m_currentOpsToClear[i]->ClearOperation(mCurrentPlaybackOpType);
+ m_currentOpsToClear.RemoveObjectAt(i);
+ }
+}
+
+void nsImapOfflineSync::ProcessMoveOperation(nsIMsgOfflineImapOperation* op) {
+ nsTArray<nsMsgKey> matchingFlagKeys;
+ uint32_t currentKeyIndex = m_KeyIndex;
+ nsCString moveDestination;
+ op->GetDestinationFolderURI(moveDestination);
+ bool moveMatches = true;
+ nsCOMPtr<nsIMsgOfflineImapOperation> currentOp = op;
+ do { // loop for all messages with the same destination
+ if (moveMatches) {
+ nsMsgKey curKey;
+ currentOp->GetMessageKey(&curKey);
+ matchingFlagKeys.AppendElement(curKey);
+ currentOp->SetPlayingBack(true);
+ m_currentOpsToClear.AppendObject(currentOp);
+ }
+ currentOp = nullptr;
+
+ if (++currentKeyIndex < m_CurrentKeys.Length()) {
+ nsCString nextDestination;
+ nsresult rv = m_currentDB->GetOfflineOpForKey(
+ m_CurrentKeys[currentKeyIndex], false, getter_AddRefs(currentOp));
+ moveMatches = false;
+ if (NS_SUCCEEDED(rv) && currentOp) {
+ nsOfflineImapOperationType opType;
+ currentOp->GetOperation(&opType);
+ if (opType & nsIMsgOfflineImapOperation::kMsgMoved) {
+ currentOp->GetDestinationFolderURI(nextDestination);
+ moveMatches = moveDestination.Equals(nextDestination);
+ }
+ }
+ }
+ } while (currentOp);
+
+ nsCOMPtr<nsIMsgFolder> destFolder;
+ FindFolder(moveDestination, getter_AddRefs(destFolder));
+ // if the dest folder doesn't really exist, these operations are
+ // going to fail, so clear them out and move on.
+ if (!destFolder) {
+ NS_WARNING("trying to playing back move to non-existent folder");
+ ClearCurrentOps();
+ ProcessNextOperation();
+ return;
+ }
+ nsCOMPtr<nsIMsgImapMailFolder> imapFolder =
+ do_QueryInterface(m_currentFolder);
+ if (imapFolder && DestFolderOnSameServer(destFolder)) {
+ uint32_t curFolderFlags;
+ m_currentFolder->GetFlags(&curFolderFlags);
+ bool curFolderOffline = curFolderFlags & nsMsgFolderFlags::Offline;
+ imapFolder->ReplayOfflineMoveCopy(matchingFlagKeys, true, destFolder, this,
+ m_window, curFolderOffline);
+ } else {
+ nsresult rv;
+ nsTArray<RefPtr<nsIMsgDBHdr>> messages;
+ for (uint32_t keyIndex = 0; keyIndex < matchingFlagKeys.Length();
+ keyIndex++) {
+ nsCOMPtr<nsIMsgDBHdr> mailHdr = nullptr;
+ rv = m_currentFolder->GetMessageHeader(
+ matchingFlagKeys.ElementAt(keyIndex), getter_AddRefs(mailHdr));
+ if (NS_SUCCEEDED(rv) && mailHdr) {
+ uint32_t msgSize;
+ // in case of a move, the header has already been deleted,
+ // so we've really got a fake header. We need to get its flags and
+ // size from the offline op to have any chance of doing the move.
+ mailHdr->GetMessageSize(&msgSize);
+ if (!msgSize) {
+ imapMessageFlagsType newImapFlags;
+ uint32_t msgFlags = 0;
+ op->GetMsgSize(&msgSize);
+ op->GetNewFlags(&newImapFlags);
+ // first three bits are the same
+ msgFlags |= (newImapFlags & 0x07);
+ if (newImapFlags & kImapMsgForwardedFlag)
+ msgFlags |= nsMsgMessageFlags::Forwarded;
+ mailHdr->SetFlags(msgFlags);
+ mailHdr->SetMessageSize(msgSize);
+ }
+ messages.AppendElement(mailHdr);
+ }
+ }
+ nsCOMPtr<nsIMsgCopyService> copyService =
+ do_GetService("@mozilla.org/messenger/messagecopyservice;1", &rv);
+ if (copyService) {
+ copyService->CopyMessages(m_currentFolder, messages, destFolder, true,
+ this, m_window, false);
+ }
+ }
+}
+
+// I'm tempted to make this a method on nsIMsgFolder, but that interface
+// is already so huge, and there are only a few places in the code that do this.
+// If there end up to be more places that need this, then we can reconsider.
+bool nsImapOfflineSync::DestFolderOnSameServer(nsIMsgFolder* destFolder) {
+ nsCOMPtr<nsIMsgIncomingServer> srcServer;
+ nsCOMPtr<nsIMsgIncomingServer> dstServer;
+
+ bool sameServer = false;
+ if (NS_SUCCEEDED(m_currentFolder->GetServer(getter_AddRefs(srcServer))) &&
+ NS_SUCCEEDED(destFolder->GetServer(getter_AddRefs(dstServer))))
+ dstServer->Equals(srcServer, &sameServer);
+ return sameServer;
+}
+
+void nsImapOfflineSync::ProcessCopyOperation(
+ nsIMsgOfflineImapOperation* aCurrentOp) {
+ nsCOMPtr<nsIMsgOfflineImapOperation> currentOp = aCurrentOp;
+
+ nsTArray<nsMsgKey> matchingFlagKeys;
+ uint32_t currentKeyIndex = m_KeyIndex;
+ nsCString copyDestination;
+ currentOp->GetCopyDestination(0, getter_Copies(copyDestination));
+ bool copyMatches = true;
+ nsresult rv;
+
+ do { // loop for all messages with the same destination
+ if (copyMatches) {
+ nsMsgKey curKey;
+ currentOp->GetMessageKey(&curKey);
+ matchingFlagKeys.AppendElement(curKey);
+ currentOp->SetPlayingBack(true);
+ m_currentOpsToClear.AppendObject(currentOp);
+ }
+ currentOp = nullptr;
+
+ if (++currentKeyIndex < m_CurrentKeys.Length()) {
+ nsCString nextDestination;
+ rv = m_currentDB->GetOfflineOpForKey(m_CurrentKeys[currentKeyIndex],
+ false, getter_AddRefs(currentOp));
+ copyMatches = false;
+ if (NS_SUCCEEDED(rv) && currentOp) {
+ nsOfflineImapOperationType opType;
+ currentOp->GetOperation(&opType);
+ if (opType & nsIMsgOfflineImapOperation::kMsgCopy) {
+ currentOp->GetCopyDestination(0, getter_Copies(nextDestination));
+ copyMatches = copyDestination.Equals(nextDestination);
+ }
+ }
+ }
+ } while (currentOp);
+
+ nsAutoCString uids;
+ nsCOMPtr<nsIMsgFolder> destFolder;
+ FindFolder(copyDestination, getter_AddRefs(destFolder));
+ // if the dest folder doesn't really exist, these operations are
+ // going to fail, so clear them out and move on.
+ if (!destFolder) {
+ NS_ERROR("trying to playing back copy to non-existent folder");
+ ClearCurrentOps();
+ ProcessNextOperation();
+ return;
+ }
+ nsCOMPtr<nsIMsgImapMailFolder> imapFolder =
+ do_QueryInterface(m_currentFolder);
+ if (imapFolder && DestFolderOnSameServer(destFolder)) {
+ uint32_t curFolderFlags;
+ m_currentFolder->GetFlags(&curFolderFlags);
+ bool curFolderOffline = curFolderFlags & nsMsgFolderFlags::Offline;
+ rv = imapFolder->ReplayOfflineMoveCopy(matchingFlagKeys, false, destFolder,
+ this, m_window, curFolderOffline);
+ } else {
+ nsTArray<RefPtr<nsIMsgDBHdr>> messages;
+ for (uint32_t keyIndex = 0; keyIndex < matchingFlagKeys.Length();
+ keyIndex++) {
+ nsCOMPtr<nsIMsgDBHdr> mailHdr = nullptr;
+ rv = m_currentFolder->GetMessageHeader(
+ matchingFlagKeys.ElementAt(keyIndex), getter_AddRefs(mailHdr));
+ if (NS_SUCCEEDED(rv) && mailHdr) messages.AppendElement(mailHdr);
+ }
+ nsCOMPtr<nsIMsgCopyService> copyService =
+ do_GetService("@mozilla.org/messenger/messagecopyservice;1", &rv);
+ if (copyService)
+ copyService->CopyMessages(m_currentFolder, messages, destFolder, false,
+ this, m_window, false);
+ }
+}
+
+void nsImapOfflineSync::ProcessEmptyTrash() {
+ m_currentFolder->EmptyTrash(this);
+ ClearDB(); // EmptyTrash closes and deletes the trash db.
+}
+
+// returns true if we found a folder to create, false if we're done creating
+// folders.
+bool nsImapOfflineSync::CreateOfflineFolders() {
+ while (m_currentFolder) {
+ uint32_t flags;
+ m_currentFolder->GetFlags(&flags);
+ bool offlineCreate = (flags & nsMsgFolderFlags::CreatedOffline) != 0;
+ if (offlineCreate) {
+ if (CreateOfflineFolder(m_currentFolder)) return true;
+ }
+ AdvanceToNextFolder();
+ }
+ return false;
+}
+
+bool nsImapOfflineSync::CreateOfflineFolder(nsIMsgFolder* folder) {
+ nsCOMPtr<nsIMsgFolder> parent;
+ folder->GetParent(getter_AddRefs(parent));
+
+ nsCOMPtr<nsIMsgImapMailFolder> imapFolder = do_QueryInterface(parent);
+ nsCOMPtr<nsIURI> createFolderURI;
+ nsCString onlineName;
+ imapFolder->GetOnlineName(onlineName);
+
+ NS_ConvertASCIItoUTF16 folderName(onlineName);
+ nsresult rv = imapFolder->PlaybackOfflineFolderCreate(
+ folderName, nullptr, getter_AddRefs(createFolderURI));
+ if (createFolderURI && NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl =
+ do_QueryInterface(createFolderURI);
+ if (mailnewsUrl) mailnewsUrl->RegisterListener(this);
+ }
+ return NS_SUCCEEDED(rv) ? true
+ : false; // this is asynch, we have to return and be
+ // called again by the OfflineOpExitFunction
+}
+
+int32_t nsImapOfflineSync::GetCurrentUIDValidity() {
+ if (m_currentFolder) {
+ nsCOMPtr<nsIImapMailFolderSink> imapFolderSink =
+ do_QueryInterface(m_currentFolder);
+ if (imapFolderSink) imapFolderSink->GetUidValidity(&mCurrentUIDValidity);
+ }
+ return mCurrentUIDValidity;
+}
+
+/**
+ * Playing back offline operations is one giant state machine that runs through
+ * ProcessNextOperation.
+ * The first state is creating online any folders created offline (we do this
+ * first, so we can play back any operations in them in the next pass)
+ */
+NS_IMETHODIMP
+nsImapOfflineSync::ProcessNextOperation() {
+ nsresult rv = NS_OK;
+
+ // if we haven't created offline folders, and we're updating all folders,
+ // first, find offline folders to create.
+ if (!m_createdOfflineFolders) {
+ if (m_singleFolderToUpdate) {
+ if (!m_pseudoOffline) {
+ AdvanceToFirstIMAPFolder();
+ if (CreateOfflineFolders()) return NS_OK;
+ }
+ } else {
+ if (CreateOfflineFolders()) return NS_OK;
+ m_currentServer = nullptr;
+ AdvanceToNextFolder();
+ }
+ m_createdOfflineFolders = true;
+ }
+ // if updating one folder only, restore m_currentFolder to that folder
+ if (m_singleFolderToUpdate) m_currentFolder = m_singleFolderToUpdate;
+
+ uint32_t folderFlags;
+ nsCOMPtr<nsIDBFolderInfo> folderInfo;
+ while (m_currentFolder && !m_currentDB) {
+ m_currentFolder->GetFlags(&folderFlags);
+ // need to check if folder has offline events, /* or is configured for
+ // offline */ shouldn't need to check if configured for offline use, since
+ // any folder with events should have nsMsgFolderFlags::OfflineEvents set.
+ if (folderFlags &
+ (nsMsgFolderFlags::OfflineEvents /* | nsMsgFolderFlags::Offline */)) {
+ nsCOMPtr<nsIMsgDatabase> db;
+ m_currentFolder->GetDBFolderInfoAndDB(getter_AddRefs(folderInfo),
+ getter_AddRefs(db));
+ if (db) {
+ m_currentDB = do_QueryInterface(db, &rv);
+ m_currentDB->AddListener(this);
+ }
+ }
+
+ if (m_currentDB) {
+ m_CurrentKeys.Clear();
+ m_KeyIndex = 0;
+ if (NS_FAILED(m_currentDB->ListAllOfflineOpIds(m_CurrentKeys)) ||
+ m_CurrentKeys.IsEmpty()) {
+ ClearDB();
+ folderInfo = nullptr; // can't hold onto folderInfo longer than db
+ m_currentFolder->ClearFlag(nsMsgFolderFlags::OfflineEvents);
+ } else {
+ // trash any ghost msgs
+ bool deletedGhostMsgs = false;
+ for (uint32_t fakeIndex = 0; fakeIndex < m_CurrentKeys.Length();
+ fakeIndex++) {
+ nsCOMPtr<nsIMsgOfflineImapOperation> currentOp;
+ m_currentDB->GetOfflineOpForKey(m_CurrentKeys[fakeIndex], false,
+ getter_AddRefs(currentOp));
+ if (currentOp) {
+ nsOfflineImapOperationType opType;
+ currentOp->GetOperation(&opType);
+
+ if (opType == nsIMsgOfflineImapOperation::kMoveResult) {
+ nsMsgKey curKey;
+ currentOp->GetMessageKey(&curKey);
+ m_currentDB->RemoveOfflineOp(currentOp);
+ deletedGhostMsgs = true;
+
+ // Remember the pseudo headers before we delete them,
+ // and when we download new headers, tell listeners about the
+ // message key change between the pseudo headers and the real
+ // downloaded headers. Note that we're not currently sending
+ // a msgsDeleted notification for these headers, but the
+ // db listeners are notified about the deletion.
+ // for imap folders, we should adjust the pending counts, because
+ // we have a header that we know about, but don't have in the db.
+ nsCOMPtr<nsIMsgImapMailFolder> imapFolder =
+ do_QueryInterface(m_currentFolder);
+ if (imapFolder) {
+ bool hdrIsRead;
+ m_currentDB->IsRead(curKey, &hdrIsRead);
+ imapFolder->ChangePendingTotal(1);
+ if (!hdrIsRead) imapFolder->ChangePendingUnread(1);
+ imapFolder->AddMoveResultPseudoKey(curKey);
+ }
+ m_currentDB->DeleteMessage(curKey, nullptr, false);
+ }
+ }
+ }
+
+ if (deletedGhostMsgs) m_currentFolder->SummaryChanged();
+
+ m_CurrentKeys.Clear();
+ if (NS_FAILED(m_currentDB->ListAllOfflineOpIds(m_CurrentKeys)) ||
+ m_CurrentKeys.IsEmpty()) {
+ ClearDB();
+ } else if (folderFlags & nsMsgFolderFlags::ImapBox) {
+ // if pseudo offline, falls through to playing ops back.
+ if (!m_pseudoOffline) {
+ // there are operations to playback so check uid validity
+ SetCurrentUIDValidity(0); // force initial invalid state
+ // do a lite select here and hook ourselves up as a listener.
+ nsCOMPtr<nsIMsgImapMailFolder> imapFolder =
+ do_QueryInterface(m_currentFolder, &rv);
+ if (imapFolder) rv = imapFolder->LiteSelect(this, m_window);
+ // this is async, we will be called again by OnStopRunningUrl.
+ return rv;
+ }
+ }
+ }
+ }
+
+ if (!m_currentDB) {
+ // only advance if we are doing all folders
+ if (!m_singleFolderToUpdate)
+ AdvanceToNextFolder();
+ else
+ m_currentFolder = nullptr; // force update of this folder now.
+ }
+ }
+
+ if (m_currentFolder) m_currentFolder->GetFlags(&folderFlags);
+ // do the current operation
+ if (m_currentDB) {
+ bool currentFolderFinished = false;
+ if (!folderInfo) m_currentDB->GetDBFolderInfo(getter_AddRefs(folderInfo));
+ // user canceled the lite select! if GetCurrentUIDValidity() == 0
+ if (folderInfo && (m_KeyIndex < m_CurrentKeys.Length()) &&
+ (m_pseudoOffline || (GetCurrentUIDValidity() != 0) ||
+ !(folderFlags & nsMsgFolderFlags::ImapBox))) {
+ int32_t curFolderUidValidity;
+ folderInfo->GetImapUidValidity(&curFolderUidValidity);
+ bool uidvalidityChanged =
+ (!m_pseudoOffline && folderFlags & nsMsgFolderFlags::ImapBox) &&
+ (GetCurrentUIDValidity() != curFolderUidValidity);
+ nsCOMPtr<nsIMsgOfflineImapOperation> currentOp;
+ if (uidvalidityChanged)
+ DeleteAllOfflineOpsForCurrentDB();
+ else
+ m_currentDB->GetOfflineOpForKey(m_CurrentKeys[m_KeyIndex], false,
+ getter_AddRefs(currentOp));
+
+ if (currentOp) {
+ nsOfflineImapOperationType opType;
+ currentOp->GetOperation(&opType);
+ // loop until we find the next db record that matches the current
+ // playback operation
+ while (currentOp && !(opType & mCurrentPlaybackOpType)) {
+ // remove operations with no type.
+ if (!opType) m_currentDB->RemoveOfflineOp(currentOp);
+ currentOp = nullptr;
+ ++m_KeyIndex;
+ if (m_KeyIndex < m_CurrentKeys.Length())
+ m_currentDB->GetOfflineOpForKey(m_CurrentKeys[m_KeyIndex], false,
+ getter_AddRefs(currentOp));
+ if (currentOp) currentOp->GetOperation(&opType);
+ }
+ // if we did not find a db record that matches the current playback
+ // operation, then move to the next playback operation and recurse.
+ if (!currentOp) {
+ // we are done with the current type
+ if (mCurrentPlaybackOpType ==
+ nsIMsgOfflineImapOperation::kFlagsChanged) {
+ mCurrentPlaybackOpType = nsIMsgOfflineImapOperation::kAddKeywords;
+ // recurse to deal with next type of operation
+ m_KeyIndex = 0;
+ ProcessNextOperation();
+ } else if (mCurrentPlaybackOpType ==
+ nsIMsgOfflineImapOperation::kAddKeywords) {
+ mCurrentPlaybackOpType =
+ nsIMsgOfflineImapOperation::kRemoveKeywords;
+ // recurse to deal with next type of operation
+ m_KeyIndex = 0;
+ ProcessNextOperation();
+ } else if (mCurrentPlaybackOpType ==
+ nsIMsgOfflineImapOperation::kRemoveKeywords) {
+ mCurrentPlaybackOpType = nsIMsgOfflineImapOperation::kMsgCopy;
+ // recurse to deal with next type of operation
+ m_KeyIndex = 0;
+ ProcessNextOperation();
+ } else if (mCurrentPlaybackOpType ==
+ nsIMsgOfflineImapOperation::kMsgCopy) {
+ mCurrentPlaybackOpType = nsIMsgOfflineImapOperation::kMsgMoved;
+ // recurse to deal with next type of operation
+ m_KeyIndex = 0;
+ ProcessNextOperation();
+ } else if (mCurrentPlaybackOpType ==
+ nsIMsgOfflineImapOperation::kMsgMoved) {
+ mCurrentPlaybackOpType = nsIMsgOfflineImapOperation::kAppendDraft;
+ // recurse to deal with next type of operation
+ m_KeyIndex = 0;
+ ProcessNextOperation();
+ } else if (mCurrentPlaybackOpType ==
+ nsIMsgOfflineImapOperation::kAppendDraft) {
+ mCurrentPlaybackOpType =
+ nsIMsgOfflineImapOperation::kAppendTemplate;
+ // recurse to deal with next type of operation
+ m_KeyIndex = 0;
+ ProcessNextOperation();
+ } else if (mCurrentPlaybackOpType ==
+ nsIMsgOfflineImapOperation::kAppendTemplate) {
+ mCurrentPlaybackOpType = nsIMsgOfflineImapOperation::kDeleteAllMsgs;
+ m_KeyIndex = 0;
+ ProcessNextOperation();
+ } else {
+ DeleteAllOfflineOpsForCurrentDB();
+ currentFolderFinished = true;
+ }
+
+ } else {
+ if (mCurrentPlaybackOpType ==
+ nsIMsgOfflineImapOperation::kFlagsChanged)
+ ProcessFlagOperation(currentOp);
+ else if (mCurrentPlaybackOpType ==
+ nsIMsgOfflineImapOperation::kAddKeywords ||
+ mCurrentPlaybackOpType ==
+ nsIMsgOfflineImapOperation::kRemoveKeywords)
+ ProcessKeywordOperation(currentOp);
+ else if (mCurrentPlaybackOpType ==
+ nsIMsgOfflineImapOperation::kMsgCopy)
+ ProcessCopyOperation(currentOp);
+ else if (mCurrentPlaybackOpType ==
+ nsIMsgOfflineImapOperation::kMsgMoved)
+ ProcessMoveOperation(currentOp);
+ else if (mCurrentPlaybackOpType ==
+ nsIMsgOfflineImapOperation::kAppendDraft)
+ ProcessAppendMsgOperation(currentOp,
+ nsIMsgOfflineImapOperation::kAppendDraft);
+ else if (mCurrentPlaybackOpType ==
+ nsIMsgOfflineImapOperation::kAppendTemplate)
+ ProcessAppendMsgOperation(
+ currentOp, nsIMsgOfflineImapOperation::kAppendTemplate);
+ else if (mCurrentPlaybackOpType ==
+ nsIMsgOfflineImapOperation::kDeleteAllMsgs) {
+ // empty trash is going to delete the db, so we'd better release the
+ // reference to the offline operation first.
+ currentOp = nullptr;
+ ProcessEmptyTrash();
+ } else
+ NS_WARNING("invalid playback op type");
+ }
+ } else
+ currentFolderFinished = true;
+ } else
+ currentFolderFinished = true;
+
+ if (currentFolderFinished) {
+ ClearDB();
+ if (!m_singleFolderToUpdate) {
+ AdvanceToNextFolder();
+ ProcessNextOperation();
+ return NS_OK;
+ }
+ m_currentFolder = nullptr;
+ }
+ }
+
+ if (!m_currentFolder && !m_mailboxupdatesStarted) {
+ m_mailboxupdatesStarted = true;
+
+ // if we are updating more than one folder then we need the iterator
+ if (!m_singleFolderToUpdate) {
+ m_currentServer = nullptr;
+ AdvanceToNextFolder();
+ }
+ if (m_singleFolderToUpdate) {
+ m_singleFolderToUpdate->ClearFlag(nsMsgFolderFlags::OfflineEvents);
+ m_singleFolderToUpdate->UpdateFolder(m_window);
+ }
+ }
+ // if we get here, then I *think* we're done. Not sure, though.
+#ifdef DEBUG_bienvenu
+ printf("done with offline imap sync\n");
+#endif
+ nsCOMPtr<nsIUrlListener> saveListener = m_listener;
+ m_listener = nullptr;
+
+ if (saveListener)
+ saveListener->OnStopRunningUrl(nullptr /* don't know url */, rv);
+ return rv;
+}
+
+void nsImapOfflineSync::DeleteAllOfflineOpsForCurrentDB() {
+ m_KeyIndex = 0;
+ nsCOMPtr<nsIMsgOfflineImapOperation> currentOp;
+ m_currentDB->GetOfflineOpForKey(m_CurrentKeys[m_KeyIndex], false,
+ getter_AddRefs(currentOp));
+ while (currentOp) {
+ // NS_ASSERTION(currentOp->GetOperationFlags() == 0);
+ // delete any ops that have already played back
+ m_currentDB->RemoveOfflineOp(currentOp);
+ currentOp = nullptr;
+
+ if (++m_KeyIndex < m_CurrentKeys.Length())
+ m_currentDB->GetOfflineOpForKey(m_CurrentKeys[m_KeyIndex], false,
+ getter_AddRefs(currentOp));
+ }
+ m_currentDB->Commit(nsMsgDBCommitType::kLargeCommit);
+ // turn off nsMsgFolderFlags::OfflineEvents
+ if (m_currentFolder)
+ m_currentFolder->ClearFlag(nsMsgFolderFlags::OfflineEvents);
+}
+
+nsImapOfflineDownloader::nsImapOfflineDownloader(nsIMsgWindow* aMsgWindow,
+ nsIUrlListener* aListener)
+ : nsImapOfflineSync() {
+ Init(aMsgWindow, aListener, nullptr, false);
+ // pause auto-sync service
+ nsresult rv;
+ nsCOMPtr<nsIAutoSyncManager> autoSyncMgr =
+ do_GetService(NS_AUTOSYNCMANAGER_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv)) autoSyncMgr->Pause();
+}
+
+nsImapOfflineDownloader::~nsImapOfflineDownloader() {}
+
+NS_IMETHODIMP
+nsImapOfflineDownloader::ProcessNextOperation() {
+ nsresult rv = NS_OK;
+ m_mailboxupdatesStarted = true;
+
+ if (!m_mailboxupdatesFinished) {
+ if (AdvanceToNextServer()) {
+ nsCOMPtr<nsIMsgFolder> rootMsgFolder;
+ m_currentServer->GetRootFolder(getter_AddRefs(rootMsgFolder));
+ nsCOMPtr<nsIMsgFolder> inbox;
+ if (rootMsgFolder) {
+ // Update the INBOX first so the updates on the remaining
+ // folders pickup the results of any filter moves.
+ rootMsgFolder->GetFolderWithFlags(nsMsgFolderFlags::Inbox,
+ getter_AddRefs(inbox));
+ if (inbox) {
+ nsCOMPtr<nsIMsgFolder> offlineImapFolder;
+ nsCOMPtr<nsIMsgImapMailFolder> imapInbox = do_QueryInterface(inbox);
+ if (imapInbox) {
+ rootMsgFolder->GetFolderWithFlags(
+ nsMsgFolderFlags::Offline, getter_AddRefs(offlineImapFolder));
+ if (!offlineImapFolder) {
+ // no imap folders configured for offline use - check if the
+ // account is set up so that we always download inbox msg bodies
+ // for offline use
+ nsCOMPtr<nsIImapIncomingServer> imapServer =
+ do_QueryInterface(m_currentServer);
+ if (imapServer) {
+ bool downloadBodiesOnGetNewMail = false;
+ imapServer->GetDownloadBodiesOnGetNewMail(
+ &downloadBodiesOnGetNewMail);
+ if (downloadBodiesOnGetNewMail) offlineImapFolder = inbox;
+ }
+ }
+ }
+ // if this isn't an imap inbox, or we have an offline imap sub-folder,
+ // then update the inbox. otherwise, it's an imap inbox for an account
+ // with no folders configured for offline use, so just advance to the
+ // next server.
+ if (!imapInbox || offlineImapFolder) {
+ // here we should check if this a pop3 server/inbox, and the user
+ // doesn't want to download pop3 mail for offline use.
+ if (!imapInbox) {
+ }
+ rv = inbox->GetNewMessages(m_window, this);
+ if (NS_SUCCEEDED(rv)) return rv; // otherwise, fall through.
+ }
+ }
+ }
+ return ProcessNextOperation(); // recurse and do next server.
+ }
+ m_allServers.Clear();
+ m_mailboxupdatesFinished = true;
+ }
+
+ while (AdvanceToNextFolder()) {
+ uint32_t folderFlags;
+
+ ClearDB();
+ nsCOMPtr<nsIMsgImapMailFolder> imapFolder;
+ if (m_currentFolder) imapFolder = do_QueryInterface(m_currentFolder);
+ m_currentFolder->GetFlags(&folderFlags);
+ // need to check if folder has offline events, or is configured for offline
+ if (imapFolder && folderFlags & nsMsgFolderFlags::Offline &&
+ !(folderFlags & nsMsgFolderFlags::Virtual)) {
+ rv = m_currentFolder->DownloadAllForOffline(this, m_window);
+ if (NS_SUCCEEDED(rv) || rv == NS_BINDING_ABORTED) return rv;
+ // if this fails and the user didn't cancel/stop, fall through to code
+ // that advances to next folder
+ }
+ }
+ if (m_listener) m_listener->OnStopRunningUrl(nullptr, NS_OK);
+ return rv;
+}
+
+NS_IMETHODIMP nsImapOfflineSync::OnStartCopy() {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/* void OnProgress (in uint32_t aProgress, in uint32_t aProgressMax); */
+NS_IMETHODIMP nsImapOfflineSync::OnProgress(uint32_t aProgress,
+ uint32_t aProgressMax) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/* void SetMessageKey (in uint32_t aKey); */
+NS_IMETHODIMP nsImapOfflineSync::SetMessageKey(uint32_t aKey) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/* [noscript] void GetMessageId (in nsCString aMessageId); */
+NS_IMETHODIMP nsImapOfflineSync::GetMessageId(nsACString& messageId) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/* void OnStopCopy (in nsresult aStatus); */
+NS_IMETHODIMP nsImapOfflineSync::OnStopCopy(nsresult aStatus) {
+ return OnStopRunningUrl(nullptr, aStatus);
+}
+
+void nsImapOfflineSync::ClearDB() {
+ m_currentOpsToClear.Clear();
+ if (m_currentDB) m_currentDB->RemoveListener(this);
+ m_currentDB = nullptr;
+}
+
+NS_IMETHODIMP
+nsImapOfflineSync::OnHdrPropertyChanged(nsIMsgDBHdr* aHdrToChange,
+ const nsACString& property,
+ bool aPreChange, uint32_t* aStatus,
+ nsIDBChangeListener* aInstigator) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapOfflineSync::OnHdrFlagsChanged(nsIMsgDBHdr* aHdrChanged,
+ uint32_t aOldFlags, uint32_t aNewFlags,
+ nsIDBChangeListener* aInstigator) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapOfflineSync::OnHdrDeleted(nsIMsgDBHdr* aHdrChanged, nsMsgKey aParentKey,
+ int32_t aFlags,
+ nsIDBChangeListener* aInstigator) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapOfflineSync::OnHdrAdded(nsIMsgDBHdr* aHdrAdded, nsMsgKey aParentKey,
+ int32_t aFlags,
+ nsIDBChangeListener* aInstigator) {
+ return NS_OK;
+}
+
+/* void OnParentChanged (in nsMsgKey aKeyChanged, in nsMsgKey oldParent, in
+ * nsMsgKey newParent, in nsIDBChangeListener aInstigator); */
+NS_IMETHODIMP
+nsImapOfflineSync::OnParentChanged(nsMsgKey aKeyChanged, nsMsgKey oldParent,
+ nsMsgKey newParent,
+ nsIDBChangeListener* aInstigator) {
+ return NS_OK;
+}
+
+/* void OnAnnouncerGoingAway (in nsIDBChangeAnnouncer instigator); */
+NS_IMETHODIMP
+nsImapOfflineSync::OnAnnouncerGoingAway(nsIDBChangeAnnouncer* instigator) {
+ ClearDB();
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapOfflineSync::OnEvent(nsIMsgDatabase* aDB,
+ const char* aEvent) {
+ return NS_OK;
+}
+
+/* void OnReadChanged (in nsIDBChangeListener instigator); */
+NS_IMETHODIMP
+nsImapOfflineSync::OnReadChanged(nsIDBChangeListener* instigator) {
+ return NS_OK;
+}
+
+/* void OnJunkScoreChanged (in nsIDBChangeListener instigator); */
+NS_IMETHODIMP
+nsImapOfflineSync::OnJunkScoreChanged(nsIDBChangeListener* instigator) {
+ return NS_OK;
+}
diff --git a/comm/mailnews/imap/src/nsImapOfflineSync.h b/comm/mailnews/imap/src/nsImapOfflineSync.h
new file mode 100644
index 0000000000..6a8ccc8f99
--- /dev/null
+++ b/comm/mailnews/imap/src/nsImapOfflineSync.h
@@ -0,0 +1,95 @@
+/* -*- 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 _nsImapOfflineSync_H_
+#define _nsImapOfflineSync_H_
+
+#include "mozilla/Attributes.h"
+#include "nsIMsgDatabase.h"
+#include "nsIUrlListener.h"
+#include "nsIMsgOfflineImapOperation.h"
+#include "nsIMsgWindow.h"
+#include "nsIMsgFolder.h"
+#include "nsCOMArray.h"
+#include "nsIDBChangeListener.h"
+#include "nsIImapOfflineSync.h"
+
+class nsImapOfflineSync : public nsIUrlListener,
+ public nsIMsgCopyServiceListener,
+ public nsIDBChangeListener,
+ public nsIImapOfflineSync {
+ public: // set to one folder to playback one folder only
+ nsImapOfflineSync();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIURLLISTENER
+ NS_DECL_NSIMSGCOPYSERVICELISTENER
+ NS_DECL_NSIDBCHANGELISTENER
+ NS_DECL_NSIIMAPOFFLINESYNC
+
+ int32_t GetCurrentUIDValidity();
+ void SetCurrentUIDValidity(int32_t uidvalidity) {
+ mCurrentUIDValidity = uidvalidity;
+ }
+
+ void SetPseudoOffline(bool pseudoOffline) { m_pseudoOffline = pseudoOffline; }
+ bool ProcessingStaleFolderUpdate() {
+ return m_singleFolderToUpdate != nullptr;
+ }
+
+ bool CreateOfflineFolder(nsIMsgFolder* folder);
+ void SetWindow(nsIMsgWindow* window);
+
+ protected:
+ virtual ~nsImapOfflineSync();
+
+ bool CreateOfflineFolders();
+ bool DestFolderOnSameServer(nsIMsgFolder* destFolder);
+ bool AdvanceToNextServer();
+ bool AdvanceToNextFolder();
+ void AdvanceToFirstIMAPFolder();
+ void DeleteAllOfflineOpsForCurrentDB();
+ void ClearCurrentOps();
+ // Clears m_currentDB, and unregister listener.
+ void ClearDB();
+ void ProcessFlagOperation(nsIMsgOfflineImapOperation* currentOp);
+ void ProcessKeywordOperation(nsIMsgOfflineImapOperation* op);
+ void ProcessMoveOperation(nsIMsgOfflineImapOperation* currentOp);
+ void ProcessCopyOperation(nsIMsgOfflineImapOperation* currentOp);
+ void ProcessEmptyTrash();
+ void ProcessAppendMsgOperation(nsIMsgOfflineImapOperation* currentOp,
+ nsOfflineImapOperationType opType);
+
+ nsCOMPtr<nsIMsgFolder> m_currentFolder;
+ nsCOMPtr<nsIMsgFolder> m_singleFolderToUpdate;
+ nsCOMPtr<nsIMsgWindow> m_window;
+ nsTArray<RefPtr<nsIMsgIncomingServer>> m_allServers;
+ nsCOMPtr<nsIMsgIncomingServer> m_currentServer;
+ // Folders left to consider on m_currentServer.
+ nsTArray<RefPtr<nsIMsgFolder>> m_folderQueue;
+
+ nsCOMPtr<nsIFile> m_curTempFile;
+
+ nsTArray<nsMsgKey> m_CurrentKeys;
+ nsCOMArray<nsIMsgOfflineImapOperation> m_currentOpsToClear;
+ uint32_t m_KeyIndex;
+ nsCOMPtr<nsIMsgOfflineOpsDatabase> m_currentDB;
+ nsCOMPtr<nsIUrlListener> m_listener;
+ int32_t mCurrentUIDValidity;
+ int32_t mCurrentPlaybackOpType; // kFlagsChanged -> kMsgCopy -> kMsgMoved
+ bool m_mailboxupdatesStarted;
+ bool m_mailboxupdatesFinished;
+ bool m_pseudoOffline; // for queueing online events in offline db
+ bool m_createdOfflineFolders;
+};
+
+class nsImapOfflineDownloader : public nsImapOfflineSync {
+ public:
+ nsImapOfflineDownloader(nsIMsgWindow* window, nsIUrlListener* listener);
+ virtual ~nsImapOfflineDownloader();
+ NS_IMETHOD ProcessNextOperation() override; // this kicks off download
+};
+
+#endif
diff --git a/comm/mailnews/imap/src/nsImapProtocol.cpp b/comm/mailnews/imap/src/nsImapProtocol.cpp
new file mode 100644
index 0000000000..b0a9214749
--- /dev/null
+++ b/comm/mailnews/imap/src/nsImapProtocol.cpp
@@ -0,0 +1,9915 @@
+/* -*- 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/. */
+
+// as does this
+#include "msgCore.h" // for pre-compiled headers
+#include "nsMsgUtils.h"
+
+#include "nsImapStringBundle.h"
+#include "nsVersionComparator.h"
+
+#include "nsThreadUtils.h"
+#include "nsIMsgStatusFeedback.h"
+#include "nsImapCore.h"
+#include "nsImapProtocol.h"
+#include "nsIMsgMailNewsUrl.h"
+#include "../public/nsIImapHostSessionList.h"
+#include "nsImapMailFolder.h"
+#include "nsIMsgAccountManager.h"
+#include "nsImapServerResponseParser.h"
+#include "nspr.h"
+#include "plbase64.h"
+#include "nsIEventTarget.h"
+#include "nsIImapService.h"
+#include "nsISocketTransportService.h"
+#include "nsIStreamListenerTee.h"
+#include "nsIInputStreamPump.h"
+#include "nsNetUtil.h"
+#include "nsIDBFolderInfo.h"
+#include "nsIPipe.h"
+#include "nsIMsgFolder.h"
+#include "nsMsgMessageFlags.h"
+#include "nsTextFormatter.h"
+#include "nsTransportUtils.h"
+#include "nsIMsgHdr.h"
+#include "nsMsgI18N.h"
+// for the memory cache...
+#include "nsICacheEntry.h"
+#include "nsICacheStorage.h"
+#include "nsICacheEntryOpenCallback.h"
+#include "CacheObserver.h"
+#include "nsIURIMutator.h"
+
+#include "nsIDocShell.h"
+#include "nsILoadInfo.h"
+#include "nsCOMPtr.h"
+#include "nsMimeTypes.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsXPCOMCIDInternal.h"
+#include "nsIXULAppInfo.h"
+#include "nsSocketTransportService2.h"
+#include "nsSyncRunnableHelpers.h"
+#include "nsICancelable.h"
+
+// netlib required files
+#include "nsIStreamListener.h"
+#include "nsIMsgIncomingServer.h"
+#include "nsIImapIncomingServer.h"
+#include "nsIPrefBranch.h"
+#include "nsIPrefService.h"
+#include "nsIPrefLocalizedString.h"
+#include "nsImapUtils.h"
+#include "nsIStreamConverterService.h"
+#include "nsIProxyInfo.h"
+#include "nsITLSSocketControl.h"
+#include "nsITransportSecurityInfo.h"
+#include "nsProxyRelease.h"
+#include "nsDebug.h"
+#include "nsMsgCompressIStream.h"
+#include "nsMsgCompressOStream.h"
+#include "mozilla/Logging.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/SlicedInputStream.h"
+#include "nsIPrincipal.h"
+#include "nsContentSecurityManager.h"
+
+// imap event sinks
+#include "nsIImapMailFolderSink.h"
+#include "nsIImapServerSink.h"
+#include "nsIImapMessageSink.h"
+
+#include "mozilla/dom/InternalResponse.h"
+#include "mozilla/NullPrincipal.h"
+
+// TLS alerts
+#include "NSSErrorsService.h"
+
+#include "mozilla/SyncRunnable.h"
+
+using namespace mozilla;
+
+LazyLogModule IMAP("IMAP");
+LazyLogModule IMAP_CS("IMAP_CS");
+LazyLogModule IMAPCache("IMAPCache");
+
+#define ONE_SECOND ((uint32_t)1000) // one second
+
+#define OUTPUT_BUFFER_SIZE (4096 * 2)
+
+#define IMAP_ENV_HEADERS "From To Cc Bcc Subject Date Message-ID "
+#define IMAP_DB_HEADERS \
+ "Priority X-Priority References Newsgroups In-Reply-To Content-Type " \
+ "Reply-To"
+#define IMAP_ENV_AND_DB_HEADERS IMAP_ENV_HEADERS IMAP_DB_HEADERS
+static const PRIntervalTime kImapSleepTime = PR_MillisecondsToInterval(60000);
+static int32_t gPromoteNoopToCheckCount = 0;
+static const uint32_t kFlagChangesBeforeCheck = 10;
+static const int32_t kMaxSecondsBeforeCheck = 600;
+
+class AutoProxyReleaseMsgWindow {
+ public:
+ AutoProxyReleaseMsgWindow() : mMsgWindow() {}
+ ~AutoProxyReleaseMsgWindow() {
+ NS_ReleaseOnMainThread("AutoProxyReleaseMsgWindow::mMsgWindow",
+ dont_AddRef(mMsgWindow));
+ }
+ nsIMsgWindow** StartAssignment() {
+ MOZ_ASSERT(!mMsgWindow);
+ return &mMsgWindow;
+ }
+ operator nsIMsgWindow*() { return mMsgWindow; }
+
+ private:
+ nsIMsgWindow* mMsgWindow;
+};
+
+nsIMsgWindow** getter_AddRefs(AutoProxyReleaseMsgWindow& aSmartPtr) {
+ return aSmartPtr.StartAssignment();
+}
+
+NS_IMPL_ISUPPORTS(nsMsgImapHdrXferInfo, nsIImapHeaderXferInfo)
+
+nsMsgImapHdrXferInfo::nsMsgImapHdrXferInfo() : m_hdrInfos(kNumHdrsToXfer) {
+ m_nextFreeHdrInfo = 0;
+}
+
+nsMsgImapHdrXferInfo::~nsMsgImapHdrXferInfo() {}
+
+NS_IMETHODIMP nsMsgImapHdrXferInfo::GetNumHeaders(int32_t* aNumHeaders) {
+ *aNumHeaders = m_nextFreeHdrInfo;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgImapHdrXferInfo::GetHeader(int32_t hdrIndex,
+ nsIImapHeaderInfo** aResult) {
+ // If the header index is more than (or equal to) our next free pointer, then
+ // its a header we haven't really got and the caller has done something
+ // wrong.
+ NS_ENSURE_TRUE(hdrIndex < m_nextFreeHdrInfo, NS_ERROR_NULL_POINTER);
+
+ NS_IF_ADDREF(*aResult = m_hdrInfos.SafeObjectAt(hdrIndex));
+ if (!*aResult) return NS_ERROR_NULL_POINTER;
+ return NS_OK;
+}
+
+static const int32_t kInitLineHdrCacheSize = 512; // should be about right
+
+nsIImapHeaderInfo* nsMsgImapHdrXferInfo::StartNewHdr() {
+ if (m_nextFreeHdrInfo >= kNumHdrsToXfer) return nullptr;
+
+ nsIImapHeaderInfo* result = m_hdrInfos.SafeObjectAt(m_nextFreeHdrInfo++);
+ if (result) return result;
+
+ nsMsgImapLineDownloadCache* lineCache = new nsMsgImapLineDownloadCache();
+ if (!lineCache) return nullptr;
+
+ lineCache->GrowBuffer(kInitLineHdrCacheSize);
+
+ m_hdrInfos.AppendObject(lineCache);
+
+ return lineCache;
+}
+
+// maybe not needed...
+void nsMsgImapHdrXferInfo::FinishCurrentHdr() {
+ // nothing to do?
+}
+
+void nsMsgImapHdrXferInfo::ResetAll() {
+ int32_t count = m_hdrInfos.Count();
+ for (int32_t i = 0; i < count; i++) {
+ nsIImapHeaderInfo* hdrInfo = m_hdrInfos[i];
+ if (hdrInfo) hdrInfo->ResetCache();
+ }
+ m_nextFreeHdrInfo = 0;
+}
+
+void nsMsgImapHdrXferInfo::ReleaseAll() {
+ m_hdrInfos.Clear();
+ m_nextFreeHdrInfo = 0;
+}
+
+NS_IMPL_ISUPPORTS(nsMsgImapLineDownloadCache, nsIImapHeaderInfo)
+
+// **** helper class for downloading line ****
+nsMsgImapLineDownloadCache::nsMsgImapLineDownloadCache() {
+ fLineInfo = (msg_line_info*)PR_CALLOC(sizeof(msg_line_info));
+ fLineInfo->uidOfMessage = nsMsgKey_None;
+ m_msgSize = 0;
+}
+
+nsMsgImapLineDownloadCache::~nsMsgImapLineDownloadCache() {
+ PR_Free(fLineInfo);
+}
+
+uint32_t nsMsgImapLineDownloadCache::CurrentUID() {
+ return fLineInfo->uidOfMessage;
+}
+
+uint32_t nsMsgImapLineDownloadCache::SpaceAvailable() {
+ MOZ_ASSERT(kDownLoadCacheSize >= m_bufferPos);
+ if (kDownLoadCacheSize <= m_bufferPos) return 0;
+ return kDownLoadCacheSize - m_bufferPos;
+}
+
+msg_line_info* nsMsgImapLineDownloadCache::GetCurrentLineInfo() {
+ AppendBuffer("", 1); // null terminate the buffer
+ fLineInfo->adoptedMessageLine = GetBuffer();
+ return fLineInfo;
+}
+
+NS_IMETHODIMP nsMsgImapLineDownloadCache::ResetCache() {
+ ResetWritePos();
+ return NS_OK;
+}
+
+bool nsMsgImapLineDownloadCache::CacheEmpty() { return m_bufferPos == 0; }
+
+NS_IMETHODIMP nsMsgImapLineDownloadCache::CacheLine(const char* line,
+ uint32_t uid) {
+ fLineInfo->uidOfMessage = uid;
+ return AppendString(line);
+}
+
+/* attribute nsMsgKey msgUid; */
+NS_IMETHODIMP nsMsgImapLineDownloadCache::GetMsgUid(nsMsgKey* aMsgUid) {
+ *aMsgUid = fLineInfo->uidOfMessage;
+ return NS_OK;
+}
+NS_IMETHODIMP nsMsgImapLineDownloadCache::SetMsgUid(nsMsgKey aMsgUid) {
+ fLineInfo->uidOfMessage = aMsgUid;
+ return NS_OK;
+}
+
+/* attribute long msgSize; */
+NS_IMETHODIMP nsMsgImapLineDownloadCache::GetMsgSize(int32_t* aMsgSize) {
+ *aMsgSize = m_msgSize;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgImapLineDownloadCache::SetMsgSize(int32_t aMsgSize) {
+ m_msgSize = aMsgSize;
+ return NS_OK;
+}
+
+/* readonly attribute ACString msgHdrs; */
+NS_IMETHODIMP nsMsgImapLineDownloadCache::GetMsgHdrs(nsACString& aMsgHdrs) {
+ AppendBuffer("", 1); // null terminate the buffer
+ aMsgHdrs.Assign(GetBuffer());
+ return NS_OK;
+}
+
+// The following macros actually implement addref, release and query interface
+// for our component.
+NS_IMPL_ADDREF_INHERITED(nsImapProtocol, nsMsgProtocol)
+NS_IMPL_RELEASE_INHERITED(nsImapProtocol, nsMsgProtocol)
+
+NS_INTERFACE_MAP_BEGIN(nsImapProtocol)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIImapProtocol)
+ NS_INTERFACE_MAP_ENTRY(nsIImapProtocol)
+ NS_INTERFACE_MAP_ENTRY(nsIProtocolProxyCallback)
+ NS_INTERFACE_MAP_ENTRY(nsIInputStreamCallback)
+ NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+ NS_INTERFACE_MAP_ENTRY(nsIImapProtocolSink)
+ NS_INTERFACE_MAP_ENTRY(nsIMsgAsyncPromptListener)
+NS_INTERFACE_MAP_END
+
+static int32_t gTooFastTime = 2;
+static int32_t gIdealTime = 4;
+static int32_t gChunkAddSize = 16384;
+static int32_t gChunkSize = 250000;
+static int32_t gChunkThreshold = gChunkSize + gChunkSize / 2;
+static bool gChunkSizeDirty = false;
+static bool gFetchByChunks = true;
+static bool gInitialized = false;
+static bool gHideUnusedNamespaces = true;
+static bool gHideOtherUsersFromList = false;
+static bool gUseEnvelopeCmd = false;
+static bool gUseLiteralPlus = true;
+static bool gExpungeAfterDelete = false;
+static bool gCheckDeletedBeforeExpunge = false; // bug 235004
+static int32_t gResponseTimeout = 100;
+static int32_t gAppendTimeout = gResponseTimeout / 5;
+static nsImapProtocol::TCPKeepalive gTCPKeepalive;
+static bool gUseDiskCache2 = true; // Use disk cache instead of memory cache
+static nsCOMPtr<nsICacheStorage> gCache2Storage;
+
+// let delete model control expunging, i.e., don't ever expunge when the
+// user chooses the imap delete model, otherwise, expunge when over the
+// threshold. This is the normal TB behavior.
+static const int32_t kAutoExpungeDeleteModel = 0; // default
+// Expunge whenever the folder is opened regardless of delete model or number
+// of marked deleted messages present in the folder.
+static const int32_t kAutoExpungeAlways = 1;
+// Expunge when over the threshold, independent of the delete model.
+static const int32_t kAutoExpungeOnThreshold = 2;
+// Set mail.imap.expunge_option to kAutoExpungeNever to NEVER do an auto-
+// expunge. This is useful when doing a bulk transfer of folders and messages
+// between imap servers.
+static const int32_t kAutoExpungeNever = 3;
+
+static int32_t gExpungeOption = kAutoExpungeDeleteModel;
+static int32_t gExpungeThreshold = 20;
+
+const int32_t kAppBufSize = 100;
+// can't use static nsCString because it shows up as a leak.
+static char gAppName[kAppBufSize];
+static char gAppVersion[kAppBufSize];
+
+nsresult nsImapProtocol::GlobalInitialization(nsIPrefBranch* aPrefBranch) {
+ gInitialized = true;
+
+ aPrefBranch->GetIntPref("mail.imap.chunk_fast",
+ &gTooFastTime); // secs we read too little too fast
+ aPrefBranch->GetIntPref("mail.imap.chunk_ideal",
+ &gIdealTime); // secs we read enough in good time
+ aPrefBranch->GetIntPref(
+ "mail.imap.chunk_add",
+ &gChunkAddSize); // buffer size to add when wasting time
+ aPrefBranch->GetIntPref("mail.imap.chunk_size", &gChunkSize);
+ aPrefBranch->GetIntPref("mail.imap.min_chunk_size_threshold",
+ &gChunkThreshold);
+ aPrefBranch->GetBoolPref("mail.imap.hide_other_users",
+ &gHideOtherUsersFromList);
+ aPrefBranch->GetBoolPref("mail.imap.hide_unused_namespaces",
+ &gHideUnusedNamespaces);
+ aPrefBranch->GetIntPref("mail.imap.noop_check_count",
+ &gPromoteNoopToCheckCount);
+ aPrefBranch->GetBoolPref("mail.imap.use_envelope_cmd", &gUseEnvelopeCmd);
+ aPrefBranch->GetBoolPref("mail.imap.use_literal_plus", &gUseLiteralPlus);
+ aPrefBranch->GetBoolPref("mail.imap.expunge_after_delete",
+ &gExpungeAfterDelete);
+ aPrefBranch->GetBoolPref("mail.imap.use_disk_cache2", &gUseDiskCache2);
+ aPrefBranch->GetBoolPref("mail.imap.check_deleted_before_expunge",
+ &gCheckDeletedBeforeExpunge);
+ aPrefBranch->GetIntPref("mail.imap.expunge_option", &gExpungeOption);
+ aPrefBranch->GetIntPref("mail.imap.expunge_threshold_number",
+ &gExpungeThreshold);
+ aPrefBranch->GetIntPref("mailnews.tcptimeout", &gResponseTimeout);
+ gAppendTimeout = gResponseTimeout / 5;
+
+ gTCPKeepalive.enabled.store(false, std::memory_order_relaxed);
+ gTCPKeepalive.idleTimeS.store(-1, std::memory_order_relaxed);
+ gTCPKeepalive.retryIntervalS.store(-1, std::memory_order_relaxed);
+
+ nsCOMPtr<nsIXULAppInfo> appInfo(do_GetService(XULAPPINFO_SERVICE_CONTRACTID));
+
+ if (appInfo) {
+ nsCString appName, appVersion;
+ appInfo->GetName(appName);
+ appInfo->GetVersion(appVersion);
+ PL_strncpyz(gAppName, appName.get(), kAppBufSize);
+ PL_strncpyz(gAppVersion, appVersion.get(), kAppBufSize);
+ }
+ return NS_OK;
+}
+
+class nsImapTransportEventSink final : public nsITransportEventSink {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSITRANSPORTEVENTSINK
+
+ private:
+ friend class nsImapProtocol;
+
+ virtual ~nsImapTransportEventSink() = default;
+ nsresult ApplyTCPKeepalive(nsISocketTransport* aTransport);
+
+ nsCOMPtr<nsITransportEventSink> m_proxy;
+};
+
+NS_IMPL_ISUPPORTS(nsImapTransportEventSink, nsITransportEventSink)
+
+NS_IMETHODIMP
+nsImapTransportEventSink::OnTransportStatus(nsITransport* aTransport,
+ nsresult aStatus, int64_t aProgress,
+ int64_t aProgressMax) {
+ if (aStatus == NS_NET_STATUS_CONNECTED_TO) {
+ nsCOMPtr<nsISocketTransport> sockTrans(do_QueryInterface(aTransport));
+ if (!NS_WARN_IF(!sockTrans)) ApplyTCPKeepalive(sockTrans);
+ }
+
+ if (NS_WARN_IF(!m_proxy)) return NS_OK;
+
+ return m_proxy->OnTransportStatus(aTransport, aStatus, aProgress,
+ aProgressMax);
+}
+
+nsresult nsImapTransportEventSink::ApplyTCPKeepalive(
+ nsISocketTransport* aTransport) {
+ nsresult rv;
+
+ bool kaEnabled = gTCPKeepalive.enabled.load(std::memory_order_relaxed);
+ if (kaEnabled) {
+ // TCP keepalive idle time, don't mistake with IMAP IDLE.
+ int32_t kaIdleTime =
+ gTCPKeepalive.idleTimeS.load(std::memory_order_relaxed);
+ int32_t kaRetryInterval =
+ gTCPKeepalive.retryIntervalS.load(std::memory_order_relaxed);
+
+ if (kaIdleTime < 0 || kaRetryInterval < 0) {
+ if (NS_WARN_IF(!net::gSocketTransportService))
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+ if (kaIdleTime < 0) {
+ rv = net::gSocketTransportService->GetKeepaliveIdleTime(&kaIdleTime);
+ if (NS_FAILED(rv)) {
+ MOZ_LOG(IMAP, LogLevel::Error,
+ ("GetKeepaliveIdleTime() failed, %" PRIx32,
+ static_cast<uint32_t>(rv)));
+ return rv;
+ }
+ }
+ if (kaRetryInterval < 0) {
+ rv = net::gSocketTransportService->GetKeepaliveRetryInterval(
+ &kaRetryInterval);
+ if (NS_FAILED(rv)) {
+ MOZ_LOG(IMAP, LogLevel::Error,
+ ("GetKeepaliveRetryInterval() failed, %" PRIx32,
+ static_cast<uint32_t>(rv)));
+ return rv;
+ }
+ }
+
+ MOZ_ASSERT(kaIdleTime > 0);
+ MOZ_ASSERT(kaRetryInterval > 0);
+ rv = aTransport->SetKeepaliveVals(kaIdleTime, kaRetryInterval);
+ if (NS_FAILED(rv)) {
+ MOZ_LOG(IMAP, LogLevel::Error,
+ ("SetKeepaliveVals(%" PRId32 ", %" PRId32 ") failed, %" PRIx32,
+ kaIdleTime, kaRetryInterval, static_cast<uint32_t>(rv)));
+ return rv;
+ }
+ }
+
+ rv = aTransport->SetKeepaliveEnabled(kaEnabled);
+ if (NS_FAILED(rv)) {
+ MOZ_LOG(IMAP, LogLevel::Error,
+ ("SetKeepaliveEnabled(%s) failed, %" PRIx32,
+ kaEnabled ? "true" : "false", static_cast<uint32_t>(rv)));
+ return rv;
+ }
+ return NS_OK;
+}
+
+// This runnable runs on IMAP thread.
+class nsImapProtocolMainLoopRunnable final : public mozilla::Runnable {
+ public:
+ explicit nsImapProtocolMainLoopRunnable(nsImapProtocol* aProtocol)
+ : mozilla::Runnable("nsImapProtocolEventLoopRunnable"),
+ mProtocol(aProtocol) {}
+
+ NS_IMETHOD Run() {
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ if (!mProtocol->RunImapThreadMainLoop()) {
+ // We already run another IMAP event loop.
+ return NS_OK;
+ }
+
+ // Release protocol object on the main thread to avoid destruction of
+ // nsImapProtocol on the IMAP thread, which causes grief for weak
+ // references.
+ NS_ReleaseOnMainThread("nsImapProtocol::this", mProtocol.forget());
+
+ // shutdown this thread, but do it from the main thread
+ nsCOMPtr<nsIThread> imapThread(do_GetCurrentThread());
+ if (NS_FAILED(NS_DispatchToMainThread(
+ NS_NewRunnableFunction("nsImapProtorolMainLoopRunnable::Run",
+ [imapThread = std::move(imapThread)]() {
+ imapThread->Shutdown();
+ })))) {
+ NS_WARNING("Failed to dispatch nsImapThreadShutdownEvent");
+ }
+ return NS_OK;
+ }
+
+ private:
+ RefPtr<nsImapProtocol> mProtocol;
+};
+
+nsImapProtocol::nsImapProtocol()
+ : nsMsgProtocol(nullptr),
+ m_dataAvailableMonitor("imapDataAvailable"),
+ m_urlReadyToRunMonitor("imapUrlReadyToRun"),
+ m_pseudoInterruptMonitor("imapPseudoInterrupt"),
+ m_dataMemberMonitor("imapDataMember"),
+ m_threadDeathMonitor("imapThreadDeath"),
+ m_waitForBodyIdsMonitor("imapWaitForBodyIds"),
+ m_fetchBodyListMonitor("imapFetchBodyList"),
+ m_passwordReadyMonitor("imapPasswordReady"),
+ mLock("nsImapProtocol.mLock"),
+ m_parser(*this) {
+ m_urlInProgress = false;
+ m_idle = false;
+ m_retryUrlOnError = false;
+ m_useIdle = true; // by default, use it
+ m_useCondStore = true;
+ m_useCompressDeflate = true;
+ m_ignoreExpunges = false;
+ m_prefAuthMethods = kCapabilityUndefined;
+ m_failedAuthMethods = 0;
+ m_currentAuthMethod = kCapabilityUndefined;
+ m_socketType = nsMsgSocketType::trySTARTTLS;
+ m_connectionStatus = NS_OK;
+ m_safeToCloseConnection = false;
+ m_hostSessionList = nullptr;
+ m_isGmailServer = false;
+ m_fetchingWholeMessage = false;
+ m_allowUTF8Accept = false;
+
+ nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID));
+ NS_ASSERTION(prefBranch, "FAILED to create the preference service");
+
+ // read in the accept languages preference
+ if (prefBranch) {
+ if (!gInitialized) GlobalInitialization(prefBranch);
+
+ nsCOMPtr<nsIPrefLocalizedString> prefString;
+ prefBranch->GetComplexValue("intl.accept_languages",
+ NS_GET_IID(nsIPrefLocalizedString),
+ getter_AddRefs(prefString));
+ if (prefString) prefString->ToString(getter_Copies(mAcceptLanguages));
+
+ nsCString customDBHeaders;
+ prefBranch->GetCharPref("mailnews.customDBHeaders", customDBHeaders);
+
+ ParseString(customDBHeaders, ' ', mCustomDBHeaders);
+ prefBranch->GetBoolPref("mailnews.display.prefer_plaintext",
+ &m_preferPlainText);
+
+ nsAutoCString customHeaders;
+ prefBranch->GetCharPref("mailnews.customHeaders", customHeaders);
+ customHeaders.StripWhitespace();
+ ParseString(customHeaders, ':', mCustomHeaders);
+
+ nsresult rv;
+ bool bVal = false;
+ rv = prefBranch->GetBoolPref("mail.imap.tcp_keepalive.enabled", &bVal);
+ if (NS_SUCCEEDED(rv))
+ gTCPKeepalive.enabled.store(bVal, std::memory_order_relaxed);
+
+ if (bVal) {
+ int32_t val;
+ // TCP keepalive idle time, don't mistake with IMAP IDLE.
+ rv = prefBranch->GetIntPref("mail.imap.tcp_keepalive.idle_time", &val);
+ if (NS_SUCCEEDED(rv) && val >= 0)
+ gTCPKeepalive.idleTimeS.store(
+ std::min<int32_t>(std::max(val, 1), net::kMaxTCPKeepIdle),
+ std::memory_order_relaxed);
+
+ rv = prefBranch->GetIntPref("mail.imap.tcp_keepalive.retry_interval",
+ &val);
+ if (NS_SUCCEEDED(rv) && val >= 0)
+ gTCPKeepalive.retryIntervalS.store(
+ std::min<int32_t>(std::max(val, 1), net::kMaxTCPKeepIntvl),
+ std::memory_order_relaxed);
+ }
+ }
+
+ // ***** Thread support *****
+ m_thread = nullptr;
+ m_imapThreadIsRunning = false;
+ m_currentServerCommandTagNumber = 0;
+ m_active = false;
+ m_folderNeedsSubscribing = false;
+ m_folderNeedsACLRefreshed = false;
+ m_threadShouldDie = false;
+ m_inThreadShouldDie = false;
+ m_pseudoInterrupted = false;
+ m_nextUrlReadyToRun = false;
+ m_idleResponseReadyToHandle = false;
+ m_trackingTime = false;
+ m_curFetchSize = 0;
+ m_startTime = 0;
+ m_endTime = 0;
+ m_lastActiveTime = 0;
+ m_lastProgressTime = 0;
+ ResetProgressInfo();
+
+ m_tooFastTime = 0;
+ m_idealTime = 0;
+ m_chunkAddSize = 0;
+ m_chunkStartSize = 0;
+ m_fetchByChunks = true;
+ m_sendID = true;
+ m_chunkSize = 0;
+ m_chunkThreshold = 0;
+ m_fromHeaderSeen = false;
+ m_closeNeededBeforeSelect = false;
+ m_needNoop = false;
+ m_noopCount = 0;
+ m_fetchBodyListIsNew = false;
+ m_flagChangeCount = 0;
+ m_lastCheckTime = PR_Now();
+
+ m_hierarchyNameState = kNoOperationInProgress;
+ m_discoveryStatus = eContinue;
+
+ // m_dataOutputBuf is used by Send Data
+ m_dataOutputBuf = (char*)PR_CALLOC(sizeof(char) * OUTPUT_BUFFER_SIZE);
+
+ // used to buffer incoming data by ReadNextLine
+ m_inputStreamBuffer = new nsMsgLineStreamBuffer(
+ OUTPUT_BUFFER_SIZE, true /* allocate new lines */,
+ false /* leave CRLFs on the returned string */);
+ m_currentBiffState = nsIMsgFolder::nsMsgBiffState_Unknown;
+ m_progressStringName.Truncate();
+ m_stringIndex = IMAP_EMPTY_STRING_INDEX;
+ m_progressExpectedNumber = 0;
+ memset(m_progressCurrentNumber, 0, sizeof m_progressCurrentNumber);
+
+ // since these are embedded in the nsImapProtocol object, but passed
+ // through proxied xpcom methods, just AddRef them here.
+ m_hdrDownloadCache = new nsMsgImapHdrXferInfo();
+ m_downloadLineCache = new nsMsgImapLineDownloadCache();
+
+ // subscription
+ m_autoSubscribe = true;
+ m_autoUnsubscribe = true;
+ m_autoSubscribeOnOpen = true;
+ m_deletableChildren = nullptr;
+
+ mFolderLastModSeq = 0;
+
+ Configure(gTooFastTime, gIdealTime, gChunkAddSize, gChunkSize,
+ gChunkThreshold, gFetchByChunks);
+ m_forceSelect = false;
+ m_capabilityResponseOccurred = true;
+
+ m_imapAction = 0;
+ m_bytesToChannel = 0;
+ m_passwordStatus = NS_OK;
+ m_passwordObtained = false;
+ mFolderTotalMsgCount = 0;
+ mFolderHighestUID = 0;
+ m_notifySearchHit = false;
+ m_preferPlainText = false;
+ m_uidValidity = kUidUnknown;
+}
+
+nsresult nsImapProtocol::Configure(int32_t TooFastTime, int32_t IdealTime,
+ int32_t ChunkAddSize, int32_t ChunkSize,
+ int32_t ChunkThreshold, bool FetchByChunks) {
+ m_tooFastTime = TooFastTime; // secs we read too little too fast
+ m_idealTime = IdealTime; // secs we read enough in good time
+ m_chunkAddSize = ChunkAddSize; // buffer size to add when wasting time
+ m_chunkStartSize = m_chunkSize = ChunkSize;
+ m_chunkThreshold = ChunkThreshold;
+ m_fetchByChunks = FetchByChunks;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapProtocol::Initialize(nsIImapHostSessionList* aHostSessionList,
+ nsIImapIncomingServer* aServer) {
+ NS_ASSERTION(
+ aHostSessionList && aServer,
+ "oops...trying to initialize with a null host session list or server!");
+ if (!aHostSessionList || !aServer) return NS_ERROR_NULL_POINTER;
+
+ nsresult rv = m_downloadLineCache->GrowBuffer(kDownLoadCacheSize);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ m_flagState = new nsImapFlagAndUidState(kImapFlagAndUidStateSize);
+ if (!m_flagState) return NS_ERROR_OUT_OF_MEMORY;
+
+ aServer->GetUseIdle(&m_useIdle);
+ aServer->GetForceSelect(&m_forceSelect);
+ aServer->GetUseCondStore(&m_useCondStore);
+ aServer->GetUseCompressDeflate(&m_useCompressDeflate);
+ aServer->GetAllowUTF8Accept(&m_allowUTF8Accept);
+
+ m_hostSessionList = aHostSessionList;
+ m_parser.SetHostSessionList(aHostSessionList);
+ m_parser.SetFlagState(m_flagState);
+
+ // Initialize the empty mime part string on the main thread.
+ nsCOMPtr<nsIStringBundle> bundle;
+ rv = IMAPGetStringBundle(getter_AddRefs(bundle));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = bundle->GetStringFromName("imapEmptyMimePart", m_emptyMimePartString);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Now initialize the thread for the connection
+ if (m_thread == nullptr) {
+ nsCOMPtr<nsIThread> imapThread;
+ nsresult rv = NS_NewNamedThread("IMAP", getter_AddRefs(imapThread));
+ if (NS_FAILED(rv)) {
+ NS_ASSERTION(imapThread, "Unable to create imap thread.");
+ return rv;
+ }
+ RefPtr<nsImapProtocolMainLoopRunnable> runnable =
+ new nsImapProtocolMainLoopRunnable(this);
+ imapThread->Dispatch(runnable.forget(), NS_DISPATCH_NORMAL);
+ imapThread->GetPRThread(&m_thread);
+ }
+ return NS_OK;
+}
+
+nsImapProtocol::~nsImapProtocol() {
+ PR_Free(m_dataOutputBuf);
+
+ // **** We must be out of the thread main loop function
+ NS_ASSERTION(!m_imapThreadIsRunning, "Oops, thread is still running.");
+ MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread());
+}
+
+const nsCString& nsImapProtocol::GetImapHostName() {
+ if (m_runningUrl && m_hostName.IsEmpty()) {
+ nsCOMPtr<nsIURI> url = do_QueryInterface(m_runningUrl);
+ url->GetAsciiHost(m_hostName);
+ }
+
+ return m_hostName;
+}
+
+const nsCString& nsImapProtocol::GetImapUserName() {
+ if (m_userName.IsEmpty() && m_imapServerSink) {
+ m_imapServerSink->GetOriginalUsername(m_userName);
+ }
+ return m_userName;
+}
+
+const char* nsImapProtocol::GetImapServerKey() {
+ if (m_serverKey.IsEmpty() && m_imapServerSink) {
+ m_imapServerSink->GetServerKey(m_serverKey);
+ }
+ return m_serverKey.get();
+}
+
+nsresult nsImapProtocol::SetupSinkProxy() {
+ if (!m_runningUrl) return NS_OK;
+ nsresult res;
+ bool newFolderSink = false;
+ if (!m_imapMailFolderSink) {
+ nsCOMPtr<nsIImapMailFolderSink> aImapMailFolderSink;
+ (void)m_runningUrl->GetImapMailFolderSink(
+ getter_AddRefs(aImapMailFolderSink));
+ if (aImapMailFolderSink) {
+ m_imapMailFolderSink = new ImapMailFolderSinkProxy(aImapMailFolderSink);
+ newFolderSink = true;
+ }
+ }
+ if (newFolderSink) Log("SetupSinkProxy", nullptr, "got m_imapMailFolderSink");
+
+ if (!m_imapMessageSink) {
+ nsCOMPtr<nsIImapMessageSink> aImapMessageSink;
+ (void)m_runningUrl->GetImapMessageSink(getter_AddRefs(aImapMessageSink));
+ if (aImapMessageSink) {
+ m_imapMessageSink = new ImapMessageSinkProxy(aImapMessageSink);
+ } else {
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+ }
+ if (!m_imapServerSink) {
+ nsCOMPtr<nsIImapServerSink> aImapServerSink;
+ res = m_runningUrl->GetImapServerSink(getter_AddRefs(aImapServerSink));
+ if (aImapServerSink) {
+ m_imapServerSink = new ImapServerSinkProxy(aImapServerSink);
+ m_imapServerSinkLatest = m_imapServerSink;
+ } else {
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+ }
+ if (!m_imapProtocolSink) {
+ nsCOMPtr<nsIImapProtocolSink> anImapProxyHelper(
+ do_QueryInterface(NS_ISUPPORTS_CAST(nsIImapProtocolSink*, this), &res));
+ m_imapProtocolSink = new ImapProtocolSinkProxy(anImapProxyHelper);
+ }
+ return NS_OK;
+}
+
+static void SetSecurityCallbacksFromChannel(nsISocketTransport* aTrans,
+ nsIChannel* aChannel) {
+ nsCOMPtr<nsIInterfaceRequestor> callbacks;
+ aChannel->GetNotificationCallbacks(getter_AddRefs(callbacks));
+
+ nsCOMPtr<nsILoadGroup> loadGroup;
+ aChannel->GetLoadGroup(getter_AddRefs(loadGroup));
+
+ nsCOMPtr<nsIInterfaceRequestor> securityCallbacks;
+ NS_NewNotificationCallbacksAggregation(callbacks, loadGroup,
+ getter_AddRefs(securityCallbacks));
+ if (securityCallbacks) aTrans->SetSecurityCallbacks(securityCallbacks);
+}
+
+// Setup With Url is intended to set up data which is held on a PER URL basis
+// and not a per connection basis. If you have data which is independent of the
+// url we are currently running, then you should put it in Initialize(). This is
+// only ever called from the UI thread. It is called from LoadImapUrl, right
+// before the url gets run - i.e., the url is next in line to run.
+// See also ReleaseUrlState(), which frees a bunch of the things set up in here.
+nsresult nsImapProtocol::SetupWithUrl(nsIURI* aURL, nsISupports* aConsumer) {
+ nsresult rv = NS_ERROR_FAILURE;
+ NS_ASSERTION(aURL, "null URL passed into Imap Protocol");
+ if (aURL) {
+ nsCOMPtr<nsIImapUrl> imapURL = do_QueryInterface(aURL, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ m_runningUrl = imapURL;
+ m_runningUrlLatest = m_runningUrl;
+
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_runningUrl);
+ nsCOMPtr<nsIMsgIncomingServer> server = do_QueryReferent(m_server);
+ if (!server) {
+ rv = mailnewsUrl->GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+ m_server = do_GetWeakReference(server);
+ }
+ nsCOMPtr<nsIMsgFolder> folder;
+ mailnewsUrl->GetFolder(getter_AddRefs(folder));
+ mFolderLastModSeq = 0;
+ mFolderTotalMsgCount = 0;
+ mFolderHighestUID = 0;
+ m_uidValidity = kUidUnknown;
+ if (folder) {
+ nsCOMPtr<nsIMsgDatabase> folderDB;
+ nsCOMPtr<nsIDBFolderInfo> folderInfo;
+ folder->GetDBFolderInfoAndDB(getter_AddRefs(folderInfo),
+ getter_AddRefs(folderDB));
+ if (folderInfo) {
+ nsCString modSeqStr;
+ folderInfo->GetCharProperty(kModSeqPropertyName, modSeqStr);
+ mFolderLastModSeq = ParseUint64Str(modSeqStr.get());
+ folderInfo->GetNumMessages(&mFolderTotalMsgCount);
+ folderInfo->GetUint32Property(kHighestRecordedUIDPropertyName, 0,
+ &mFolderHighestUID);
+ folderInfo->GetImapUidValidity(&m_uidValidity);
+ }
+ }
+ nsCOMPtr<nsIImapIncomingServer> imapServer = do_QueryInterface(server);
+ nsCOMPtr<nsIStreamListener> aRealStreamListener =
+ do_QueryInterface(aConsumer);
+ m_runningUrl->GetMockChannel(getter_AddRefs(m_mockChannel));
+ imapServer->GetIsGMailServer(&m_isGmailServer);
+ if (!m_mockChannel) {
+ nsCOMPtr<nsIPrincipal> nullPrincipal =
+ NullPrincipal::CreateWithoutOriginAttributes();
+
+ // there are several imap operations that aren't initiated via a
+ // nsIChannel::AsyncOpen call on the mock channel. such as selecting a
+ // folder. nsImapProtocol now insists on a mock channel when processing a
+ // url.
+ nsCOMPtr<nsIChannel> channel;
+ rv =
+ NS_NewChannel(getter_AddRefs(channel), aURL, nullPrincipal,
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER);
+ m_mockChannel = do_QueryInterface(channel);
+ NS_ASSERTION(m_mockChannel,
+ "failed to get a mock channel in nsImapProtocol");
+
+ // Certain imap operations (not initiated by the IO Service via AsyncOpen)
+ // can be interrupted by the stop button on the toolbar. We do this by
+ // using the loadgroup of the docshell for the message pane. We really
+ // shouldn't be doing this.. See the comment in
+ // nsMsgMailNewsUrl::GetLoadGroup.
+ nsCOMPtr<nsILoadGroup> loadGroup;
+ mailnewsUrl->GetLoadGroup(
+ getter_AddRefs(loadGroup)); // get the message pane load group
+ if (loadGroup)
+ loadGroup->AddRequest(m_mockChannel, nullptr /* context isupports */);
+ }
+
+ if (m_mockChannel) {
+ m_mockChannel->SetImapProtocol(this);
+ // if we have a listener from a mock channel, over-ride the consumer that
+ // was passed in
+ nsCOMPtr<nsIStreamListener> channelListener;
+ m_mockChannel->GetChannelListener(getter_AddRefs(channelListener));
+ if (channelListener) // only over-ride if we have a non null channel
+ // listener
+ aRealStreamListener = channelListener;
+ nsCOMPtr<nsIMsgWindow> msgWindow;
+ GetMsgWindow(getter_AddRefs(msgWindow));
+ if (!msgWindow) GetTopmostMsgWindow(getter_AddRefs(msgWindow));
+ if (msgWindow) {
+ // Set up the MockChannel to attempt nsIProgressEventSink callbacks on
+ // the messageWindow, with fallback to the docShell (and the
+ // loadgroup).
+ nsCOMPtr<nsIDocShell> docShell;
+ msgWindow->GetMessageWindowDocShell(getter_AddRefs(docShell));
+ nsCOMPtr<nsIInterfaceRequestor> ir(do_QueryInterface(docShell));
+ nsCOMPtr<nsIInterfaceRequestor> interfaceRequestor;
+ msgWindow->GetNotificationCallbacks(getter_AddRefs(interfaceRequestor));
+ nsCOMPtr<nsIInterfaceRequestor> aggregateIR;
+ NS_NewInterfaceRequestorAggregation(interfaceRequestor, ir,
+ getter_AddRefs(aggregateIR));
+ m_mockChannel->SetNotificationCallbacks(aggregateIR);
+ }
+ }
+
+ // since we'll be making calls directly from the imap thread to the channel
+ // listener, we need to turn it into a proxy object....we'll assume that the
+ // listener is on the same thread as the event sink queue
+ if (aRealStreamListener) {
+ NS_ASSERTION(!m_channelListener,
+ "shouldn't already have a channel listener");
+ m_channelListener = new StreamListenerProxy(aRealStreamListener);
+ }
+
+ server->GetHostName(m_hostName);
+ int32_t authMethod;
+ (void)server->GetAuthMethod(&authMethod);
+ InitPrefAuthMethods(authMethod, server);
+ (void)server->GetSocketType(&m_socketType);
+ bool shuttingDown;
+ (void)imapServer->GetShuttingDown(&shuttingDown);
+ if (!shuttingDown)
+ (void)imapServer->GetUseIdle(&m_useIdle);
+ else
+ m_useIdle = false;
+ imapServer->GetFetchByChunks(&m_fetchByChunks);
+ imapServer->GetSendID(&m_sendID);
+
+ nsAutoString trashFolderPath;
+ if (NS_SUCCEEDED(imapServer->GetTrashFolderName(trashFolderPath))) {
+ if (m_allowUTF8Accept)
+ CopyUTF16toUTF8(trashFolderPath, m_trashFolderPath);
+ else
+ CopyUTF16toMUTF7(trashFolderPath, m_trashFolderPath);
+ }
+
+ nsCOMPtr<nsIPrefBranch> prefBranch(
+ do_GetService(NS_PREFSERVICE_CONTRACTID));
+ if (prefBranch) {
+ bool preferPlainText;
+ prefBranch->GetBoolPref("mailnews.display.prefer_plaintext",
+ &preferPlainText);
+ // If the pref has changed since the last time we ran a url,
+ // clear the shell cache for this host. (bodyshell no longer exists.)
+ if (preferPlainText != m_preferPlainText) {
+ m_preferPlainText = preferPlainText;
+ }
+ }
+ // If enabled, retrieve the clientid so that we can use it later.
+ bool clientidEnabled = false;
+ if (NS_SUCCEEDED(server->GetClientidEnabled(&clientidEnabled)) &&
+ clientidEnabled)
+ server->GetClientid(m_clientId);
+ else {
+ m_clientId.Truncate();
+ }
+
+ bool proxyCallback = false;
+ if (m_runningUrl && !m_transport /* and we don't have a transport yet */) {
+ if (m_mockChannel) {
+ rv = MsgExamineForProxyAsync(m_mockChannel, this,
+ getter_AddRefs(m_proxyRequest));
+ if (NS_FAILED(rv)) {
+ rv = SetupWithUrlCallback(nullptr);
+ } else {
+ proxyCallback = true;
+ }
+ }
+ }
+
+ if (!proxyCallback) rv = LoadImapUrlInternal();
+ }
+
+ return rv;
+}
+
+// nsIProtocolProxyCallback
+NS_IMETHODIMP
+nsImapProtocol::OnProxyAvailable(nsICancelable* aRequest, nsIChannel* aChannel,
+ nsIProxyInfo* aProxyInfo, nsresult aStatus) {
+ // If we're called with NS_BINDING_ABORTED, the IMAP thread already died,
+ // so we can't carry on. Otherwise, no checking of 'aStatus' here, see
+ // nsHttpChannel::OnProxyAvailable(). Status is non-fatal and we just kick on.
+ if (aStatus == NS_BINDING_ABORTED) return NS_ERROR_FAILURE;
+
+ nsresult rv = SetupWithUrlCallback(aProxyInfo);
+ if (NS_FAILED(rv)) {
+ // Cancel the protocol and be done.
+ if (m_mockChannel) m_mockChannel->Cancel(rv);
+ return rv;
+ }
+
+ rv = LoadImapUrlInternal();
+ if (NS_FAILED(rv)) {
+ if (m_mockChannel) m_mockChannel->Cancel(rv);
+ }
+
+ return rv;
+}
+
+nsresult nsImapProtocol::SetupWithUrlCallback(nsIProxyInfo* aProxyInfo) {
+ m_proxyRequest = nullptr;
+
+ nsresult rv;
+
+ nsCOMPtr<nsISocketTransportService> socketService =
+ do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ Log("SetupWithUrlCallback", nullptr, "clearing IMAP_CONNECTION_IS_OPEN");
+ ClearFlag(IMAP_CONNECTION_IS_OPEN);
+ const char* connectionType = nullptr;
+
+ if (m_socketType == nsMsgSocketType::SSL)
+ connectionType = "ssl";
+ else if (m_socketType == nsMsgSocketType::alwaysSTARTTLS)
+ connectionType = "starttls";
+ // This can go away once we think everyone is migrated
+ // away from the trySTARTTLS socket type.
+ else if (m_socketType == nsMsgSocketType::trySTARTTLS)
+ connectionType = "starttls";
+
+ int32_t port = -1;
+ nsCOMPtr<nsIURI> uri = do_QueryInterface(m_runningUrl, &rv);
+ if (NS_FAILED(rv)) return rv;
+ uri->GetPort(&port);
+
+ AutoTArray<nsCString, 1> connectionTypeArray;
+ if (connectionType) connectionTypeArray.AppendElement(connectionType);
+ // NOTE: Some errors won't show up until the first read attempt (SSL bad
+ // certificate errors, for example).
+ rv = socketService->CreateTransport(connectionTypeArray, m_hostName, port,
+ aProxyInfo, nullptr,
+ getter_AddRefs(m_transport));
+ if (NS_FAILED(rv) && m_socketType == nsMsgSocketType::trySTARTTLS) {
+ connectionType = nullptr;
+ m_socketType = nsMsgSocketType::plain;
+ rv = socketService->CreateTransport(connectionTypeArray, m_hostName, port,
+ aProxyInfo, nullptr,
+ getter_AddRefs(m_transport));
+ }
+
+ // remember so we can know whether we can issue a start tls or not...
+ m_connectionType = connectionType;
+ if (m_transport && m_mockChannel) {
+ uint8_t qos;
+ rv = GetQoSBits(&qos);
+ if (NS_SUCCEEDED(rv)) m_transport->SetQoSBits(qos);
+
+ // Ensure that the socket can get the notification callbacks
+ SetSecurityCallbacksFromChannel(m_transport, m_mockChannel);
+
+ // open buffered, blocking input stream
+ rv = m_transport->OpenInputStream(nsITransport::OPEN_BLOCKING, 0, 0,
+ getter_AddRefs(m_inputStream));
+ if (NS_FAILED(rv)) return 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;
+ SetFlag(IMAP_CONNECTION_IS_OPEN);
+ }
+
+ return rv;
+}
+
+// when the connection is done processing the current state, free any per url
+// state data...
+void nsImapProtocol::ReleaseUrlState(bool rerunning) {
+ // clear out the socket's reference to the notification callbacks for this
+ // transaction
+ {
+ MutexAutoLock mon(mLock);
+ if (m_transport) {
+ m_transport->SetSecurityCallbacks(nullptr);
+ m_transport->SetEventSink(nullptr, nullptr);
+ }
+ }
+
+ if (m_mockChannel && !rerunning) {
+ // Proxy the close of the channel to the ui thread.
+ if (m_imapMailFolderSink)
+ m_imapMailFolderSink->CloseMockChannel(m_mockChannel);
+ else
+ m_mockChannel->Close();
+
+ {
+ // grab a lock so m_mockChannel doesn't get cleared out
+ // from under us.
+ MutexAutoLock mon(mLock);
+ if (m_mockChannel) {
+ // Proxy the release of the channel to the main thread. This is
+ // something that the xpcom proxy system should do for us!
+ NS_ReleaseOnMainThread("nsImapProtocol::m_mockChannel",
+ m_mockChannel.forget());
+ }
+ }
+ }
+
+ m_imapMessageSink = nullptr;
+
+ // Proxy the release of the listener to the main thread. This is something
+ // that the xpcom proxy system should do for us!
+ {
+ // grab a lock so the m_channelListener doesn't get cleared.
+ MutexAutoLock mon(mLock);
+ if (m_channelListener) {
+ NS_ReleaseOnMainThread("nsImapProtocol::m_channelListener",
+ m_channelListener.forget());
+ }
+ }
+ m_channelInputStream = nullptr;
+ m_channelOutputStream = nullptr;
+
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsurl;
+ nsCOMPtr<nsIImapMailFolderSink> saveFolderSink;
+
+ {
+ MutexAutoLock mon(mLock);
+ if (m_runningUrl) {
+ mailnewsurl = do_QueryInterface(m_runningUrl);
+ // It is unclear what 'saveFolderSink' is used for, most likely to hold
+ // a reference for a little longer. See bug 1324893 and bug 391259.
+ saveFolderSink = m_imapMailFolderSink;
+
+ m_runningUrl =
+ nullptr; // force us to release our last reference on the url
+ m_urlInProgress = false;
+ }
+ }
+ // Need to null this out whether we have an m_runningUrl or not
+ m_imapMailFolderSink = nullptr;
+
+ // we want to make sure the imap protocol's last reference to the url gets
+ // released back on the UI thread. This ensures that the objects the imap url
+ // hangs on to properly get released back on the UI thread.
+ if (mailnewsurl) {
+ NS_ReleaseOnMainThread("nsImapProtocol::m_runningUrl",
+ mailnewsurl.forget());
+ }
+ saveFolderSink = nullptr;
+}
+
+class nsImapCancelProxy : public mozilla::Runnable {
+ public:
+ explicit nsImapCancelProxy(nsICancelable* aProxyRequest)
+ : mozilla::Runnable("nsImapCancelProxy"), mRequest(aProxyRequest) {}
+ NS_IMETHOD Run() {
+ if (mRequest) mRequest->Cancel(NS_BINDING_ABORTED);
+ return NS_OK;
+ }
+
+ private:
+ nsCOMPtr<nsICancelable> mRequest;
+};
+
+bool nsImapProtocol::RunImapThreadMainLoop() {
+ PR_CEnterMonitor(this);
+ NS_ASSERTION(!m_imapThreadIsRunning,
+ "Oh. oh. thread is already running. What's wrong here?");
+ if (m_imapThreadIsRunning) {
+ PR_CExitMonitor(this);
+ return false;
+ }
+
+ m_imapThreadIsRunning = true;
+ PR_CExitMonitor(this);
+
+ // call the platform specific main loop ....
+ ImapThreadMainLoop();
+
+ if (m_proxyRequest) {
+ // Cancel proxy on main thread.
+ RefPtr<nsImapCancelProxy> cancelProxy =
+ new nsImapCancelProxy(m_proxyRequest);
+ NS_DispatchAndSpinEventLoopUntilComplete(
+ "nsImapProtocol::RunImapThreadMainLoop"_ns,
+ GetMainThreadSerialEventTarget(), cancelProxy.forget());
+ m_proxyRequest = nullptr;
+ }
+
+ if (m_runningUrl) {
+ NS_ReleaseOnMainThread("nsImapProtocol::m_runningUrl",
+ m_runningUrl.forget());
+ }
+
+ // close streams via UI thread if it's not already done
+ if (m_imapProtocolSink) m_imapProtocolSink->CloseStreams();
+
+ m_imapMailFolderSink = nullptr;
+ m_imapMailFolderSinkSelected = nullptr;
+ m_imapMessageSink = nullptr;
+
+ return true;
+}
+
+//
+// Must be called from UI thread only
+//
+NS_IMETHODIMP nsImapProtocol::CloseStreams() {
+ // make sure that it is called by the UI thread
+ MOZ_ASSERT(NS_IsMainThread(),
+ "CloseStreams() should not be called from an off UI thread");
+
+ {
+ MutexAutoLock mon(mLock);
+ if (m_transport) {
+ // make sure the transport closes (even if someone is still indirectly
+ // referencing it).
+ m_transport->Close(NS_ERROR_ABORT);
+ m_transport = nullptr;
+ }
+ m_inputStream = nullptr;
+ m_outputStream = nullptr;
+ m_channelListener = nullptr;
+ if (m_mockChannel) {
+ m_mockChannel->Close();
+ m_mockChannel = nullptr;
+ }
+ m_channelInputStream = nullptr;
+ m_channelOutputStream = nullptr;
+
+ // Close scope because we must let go of the monitor before calling
+ // RemoveConnection to unblock anyone who tries to get a monitor to the
+ // protocol object while holding onto a monitor to the server.
+ }
+ nsCOMPtr<nsIMsgIncomingServer> me_server = do_QueryReferent(m_server);
+ if (me_server) {
+ nsresult result;
+ nsCOMPtr<nsIImapIncomingServer> aImapServer(
+ do_QueryInterface(me_server, &result));
+ if (NS_SUCCEEDED(result)) aImapServer->RemoveConnection(this);
+ me_server = nullptr;
+ }
+ m_server = nullptr;
+ // take this opportunity of being on the UI thread to
+ // persist chunk prefs if they've changed
+ if (gChunkSizeDirty) {
+ nsCOMPtr<nsIPrefBranch> prefBranch(
+ do_GetService(NS_PREFSERVICE_CONTRACTID));
+ if (prefBranch) {
+ prefBranch->SetIntPref("mail.imap.chunk_size", gChunkSize);
+ prefBranch->SetIntPref("mail.imap.min_chunk_size_threshold",
+ gChunkThreshold);
+ gChunkSizeDirty = false;
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapProtocol::GetUrlWindow(nsIMsgMailNewsUrl* aUrl,
+ nsIMsgWindow** aMsgWindow) {
+ NS_ENSURE_ARG_POINTER(aUrl);
+ NS_ENSURE_ARG_POINTER(aMsgWindow);
+ return aUrl->GetMsgWindow(aMsgWindow);
+}
+
+NS_IMETHODIMP nsImapProtocol::SetupMainThreadProxies() {
+ return SetupSinkProxy();
+}
+
+NS_IMETHODIMP nsImapProtocol::OnInputStreamReady(nsIAsyncInputStream* inStr) {
+ // should we check if it's a close vs. data available?
+ if (m_idle) {
+ uint64_t bytesAvailable = 0;
+ (void)inStr->Available(&bytesAvailable);
+ // check if data available - might be a close
+ if (bytesAvailable != 0) {
+ ReentrantMonitorAutoEnter mon(m_urlReadyToRunMonitor);
+ m_lastActiveTime = PR_Now();
+ m_idleResponseReadyToHandle = true;
+ mon.Notify();
+ }
+ }
+ return NS_OK;
+}
+
+// this is to be called from the UI thread. It sets m_threadShouldDie,
+// and then signals the imap thread, which, when it wakes up, should exit.
+// The imap thread cleanup code will check m_safeToCloseConnection.
+NS_IMETHODIMP
+nsImapProtocol::TellThreadToDie(bool aIsSafeToClose) {
+ MOZ_DIAGNOSTIC_ASSERT(
+ NS_IsMainThread(),
+ "TellThreadToDie(aIsSafeToClose) should only be called from UI thread");
+ MutexAutoLock mon(mLock);
+
+ nsCOMPtr<nsIMsgIncomingServer> me_server = do_QueryReferent(m_server);
+ if (me_server) {
+ nsresult rv;
+ nsCOMPtr<nsIImapIncomingServer> aImapServer(
+ do_QueryInterface(me_server, &rv));
+ if (NS_SUCCEEDED(rv)) aImapServer->RemoveConnection(this);
+ m_server = nullptr;
+ me_server = nullptr;
+ }
+ {
+ ReentrantMonitorAutoEnter deathMon(m_threadDeathMonitor);
+ m_safeToCloseConnection = aIsSafeToClose;
+ m_threadShouldDie = true;
+ }
+ ReentrantMonitorAutoEnter readyMon(m_urlReadyToRunMonitor);
+ m_nextUrlReadyToRun = true;
+ readyMon.Notify();
+ return NS_OK;
+}
+
+/**
+ * Dispatch socket thread to to determine if connection is alive.
+ */
+nsresult nsImapProtocol::IsTransportAlive(bool* alive) {
+ nsresult rv;
+ auto GetIsAlive = [transport = nsCOMPtr{m_transport}, &rv, alive]() mutable {
+ rv = transport->IsAlive(alive);
+ };
+ nsCOMPtr<nsIEventTarget> socketThread(
+ do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID));
+ if (socketThread) {
+ mozilla::SyncRunnable::DispatchToThread(
+ socketThread,
+ NS_NewRunnableFunction("nsImapProtocol::IsTransportAlive", GetIsAlive));
+ } else {
+ rv = NS_ERROR_NOT_AVAILABLE;
+ }
+ return rv;
+}
+
+/**
+ * Dispatch socket thread to initiate STARTTLS handshakes.
+ */
+nsresult nsImapProtocol::TransportStartTLS() {
+ nsresult rv = NS_ERROR_NOT_AVAILABLE;
+ nsCOMPtr<nsITLSSocketControl> tlsSocketControl;
+ if (m_transport &&
+ NS_SUCCEEDED(
+ m_transport->GetTlsSocketControl(getter_AddRefs(tlsSocketControl))) &&
+ tlsSocketControl) {
+ auto CallStartTLS = [sockCon = nsCOMPtr{tlsSocketControl}, &rv]() mutable {
+ rv = sockCon->StartTLS();
+ };
+ nsCOMPtr<nsIEventTarget> socketThread(
+ do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID));
+ if (socketThread) {
+ mozilla::SyncRunnable::DispatchToThread(
+ socketThread, NS_NewRunnableFunction(
+ "nsImapProtocol::TransportStartTLS", CallStartTLS));
+ } else {
+ rv = NS_ERROR_NOT_AVAILABLE;
+ }
+ }
+ return rv;
+}
+
+/**
+ * Dispatch socket thread to obtain transport security information.
+ */
+void nsImapProtocol::GetTransportSecurityInfo(
+ nsITransportSecurityInfo** aSecurityInfo) {
+ *aSecurityInfo = nullptr;
+ nsCOMPtr<nsIEventTarget> socketThread(
+ do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID));
+ nsCOMPtr<nsITLSSocketControl> tlsSocketControl;
+ if (socketThread &&
+ NS_SUCCEEDED(
+ m_transport->GetTlsSocketControl(getter_AddRefs(tlsSocketControl))) &&
+ tlsSocketControl) {
+ if (socketThread) {
+ nsCOMPtr<nsITransportSecurityInfo> secInfo;
+ auto GetSecurityInfo = [&tlsSocketControl, &secInfo]() mutable {
+ tlsSocketControl->GetSecurityInfo(getter_AddRefs(secInfo));
+ };
+ mozilla::SyncRunnable::DispatchToThread(
+ socketThread,
+ NS_NewRunnableFunction("nsImapProtocol::GetTransportSecurityInfo",
+ GetSecurityInfo));
+ NS_IF_ADDREF(*aSecurityInfo = secInfo);
+ }
+ }
+}
+
+void nsImapProtocol::TellThreadToDie() {
+ nsresult rv = NS_OK;
+ MOZ_DIAGNOSTIC_ASSERT(
+ !NS_IsMainThread(),
+ "TellThreadToDie() should not be called from UI thread");
+
+ // prevent re-entering this method because it may lock the UI.
+ if (m_inThreadShouldDie) return;
+ m_inThreadShouldDie = true;
+
+ // This routine is called only from the imap protocol thread.
+ // The UI thread causes this to be called by calling TellThreadToDie.
+ // In that case, m_safeToCloseConnection will be FALSE if it's dropping a
+ // timed out connection, true when closing a cached connection.
+ // We're using PR_CEnter/ExitMonitor because Monitors don't like having
+ // us to hold one monitor and call code that gets a different monitor. And
+ // some of the methods we call here use Monitors.
+ PR_CEnterMonitor(this);
+
+ m_urlInProgress = true; // let's say it's busy so no one tries to use
+ // this about to die connection.
+ bool urlWritingData = false;
+ bool connectionIdle = !m_runningUrl;
+
+ if (!connectionIdle)
+ urlWritingData = m_imapAction == nsIImapUrl::nsImapAppendMsgFromFile ||
+ m_imapAction == nsIImapUrl::nsImapAppendDraftFromFile;
+
+ bool closeNeeded = GetServerStateParser().GetIMAPstate() ==
+ nsImapServerResponseParser::kFolderSelected &&
+ m_safeToCloseConnection;
+ nsCString command;
+ // if a url is writing data, we can't even logout, so we're just
+ // going to close the connection as if the user pressed stop.
+ if (m_currentServerCommandTagNumber > 0 && !urlWritingData) {
+ bool isAlive = false;
+ if (m_transport) rv = IsTransportAlive(&isAlive);
+
+ if (TestFlag(IMAP_CONNECTION_IS_OPEN) && m_idle && isAlive) EndIdle(false);
+
+ if (NS_SUCCEEDED(rv) && isAlive && closeNeeded &&
+ GetDeleteIsMoveToTrash() && TestFlag(IMAP_CONNECTION_IS_OPEN) &&
+ m_outputStream)
+ ImapClose(true, connectionIdle);
+
+ if (NS_SUCCEEDED(rv) && isAlive && TestFlag(IMAP_CONNECTION_IS_OPEN) &&
+ NS_SUCCEEDED(GetConnectionStatus()) && m_outputStream)
+ Logout(true, connectionIdle);
+ }
+ PR_CExitMonitor(this);
+ // close streams via UI thread
+ if (m_imapProtocolSink) {
+ m_imapProtocolSink->CloseStreams();
+ m_imapProtocolSink = nullptr;
+ }
+ Log("TellThreadToDie", nullptr, "close socket connection");
+
+ {
+ ReentrantMonitorAutoEnter mon(m_threadDeathMonitor);
+ m_threadShouldDie = true;
+ }
+ {
+ ReentrantMonitorAutoEnter dataMon(m_dataAvailableMonitor);
+ dataMon.Notify();
+ }
+ ReentrantMonitorAutoEnter urlReadyMon(m_urlReadyToRunMonitor);
+ urlReadyMon.NotifyAll();
+}
+
+NS_IMETHODIMP
+nsImapProtocol::GetLastActiveTimeStamp(PRTime* aTimeStamp) {
+ if (aTimeStamp) *aTimeStamp = m_lastActiveTime;
+ return NS_OK;
+}
+
+static void DoomCacheEntry(nsIMsgMailNewsUrl* url);
+NS_IMETHODIMP
+nsImapProtocol::PseudoInterruptMsgLoad(nsIMsgFolder* aImapFolder,
+ nsIMsgWindow* aMsgWindow,
+ bool* interrupted) {
+ NS_ENSURE_ARG(interrupted);
+
+ *interrupted = false;
+
+ PR_CEnterMonitor(this);
+
+ if (m_runningUrl && !TestFlag(IMAP_CLEAN_UP_URL_STATE)) {
+ nsImapAction imapAction;
+ m_runningUrl->GetImapAction(&imapAction);
+
+ if (imapAction == nsIImapUrl::nsImapMsgFetch) {
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIImapUrl> runningImapURL;
+
+ rv = GetRunningImapURL(getter_AddRefs(runningImapURL));
+ if (NS_SUCCEEDED(rv) && runningImapURL) {
+ nsCOMPtr<nsIMsgFolder> runningImapFolder;
+ nsCOMPtr<nsIMsgWindow> msgWindow;
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl =
+ do_QueryInterface(runningImapURL);
+ mailnewsUrl->GetMsgWindow(getter_AddRefs(msgWindow));
+ mailnewsUrl->GetFolder(getter_AddRefs(runningImapFolder));
+ if (aImapFolder == runningImapFolder && msgWindow == aMsgWindow) {
+ MOZ_LOG(IMAPCache, LogLevel::Debug,
+ ("%s: Set PseudoInterrupt", __func__));
+ PseudoInterrupt(true);
+ *interrupted = true;
+ }
+ // If we're pseudo-interrupted, doom any incomplete cache entry.
+ // But if mock channel indicates fetch is complete so cache write is
+ // done, then don't doom the cache entry.
+ bool cacheWriteInProgress = true;
+ if (m_mockChannel)
+ m_mockChannel->GetWritingToCache(&cacheWriteInProgress);
+ if (cacheWriteInProgress) {
+ MOZ_LOG(IMAPCache, LogLevel::Debug,
+ ("%s: Call DoomCacheEntry()", __func__));
+ DoomCacheEntry(mailnewsUrl);
+ }
+ }
+ }
+ }
+ PR_CExitMonitor(this);
+#ifdef DEBUG_bienvenu
+ printf("interrupt msg load : %s\n", (*interrupted) ? "TRUE" : "FALSE");
+#endif
+ return NS_OK;
+}
+
+void nsImapProtocol::ImapThreadMainLoop() {
+ MOZ_LOG(IMAP, LogLevel::Debug,
+ ("ImapThreadMainLoop entering [this=%p]", this));
+
+ PRIntervalTime sleepTime = kImapSleepTime;
+ bool idlePending = false;
+ while (!DeathSignalReceived()) {
+ nsresult rv = NS_OK;
+ bool urlReadyToRun;
+
+ // wait for a URL or idle response to process...
+ {
+ ReentrantMonitorAutoEnter mon(m_urlReadyToRunMonitor);
+
+ while (NS_SUCCEEDED(rv) && !DeathSignalReceived() &&
+ !m_nextUrlReadyToRun && !m_idleResponseReadyToHandle &&
+ !m_threadShouldDie) {
+ rv = mon.Wait(sleepTime);
+ if (idlePending) break;
+ }
+
+ urlReadyToRun = m_nextUrlReadyToRun;
+ m_nextUrlReadyToRun = false;
+ }
+ // This will happen if the UI thread signals us to die
+ if (m_threadShouldDie) {
+ TellThreadToDie();
+ break;
+ }
+
+ if (NS_FAILED(rv) && PR_PENDING_INTERRUPT_ERROR == PR_GetError()) {
+ printf("error waiting for monitor\n");
+ break;
+ }
+
+ // If an idle response has occurred, handle only it on this pass through
+ // the loop.
+ if (m_idleResponseReadyToHandle && !m_threadShouldDie) {
+ m_idleResponseReadyToHandle = false;
+ HandleIdleResponses();
+ if (urlReadyToRun) {
+ // A URL is also ready. Process it on next loop.
+ m_nextUrlReadyToRun = true;
+ urlReadyToRun = false;
+ }
+ }
+
+ if (urlReadyToRun && m_runningUrl) {
+ if (m_currentServerCommandTagNumber && m_transport) {
+ bool isAlive;
+ rv = IsTransportAlive(&isAlive);
+ // if the transport is not alive, and we've ever sent a command with
+ // this connection, kill it. otherwise, we've probably just not finished
+ // setting it so don't kill it!
+ if (NS_FAILED(rv) || !isAlive) {
+ // This says we never started running the url, which is the case.
+ m_runningUrl->SetRerunningUrl(false);
+ RetryUrl();
+ return;
+ }
+ }
+ //
+ // NOTE: Although we cleared m_nextUrlReadyToRun above, it may now be set
+ // again by LoadImapUrlInternal(), which runs on the main thread.
+ // Because of this, we must not clear m_nextUrlReadyToRun here.
+ //
+ if (ProcessCurrentURL()) {
+ // Another URL has been setup to run. Process it on next loop.
+ m_nextUrlReadyToRun = true;
+ m_imapMailFolderSink = nullptr;
+ } else {
+ // No more URLs setup to run. Set idle pending if user has configured
+ // idle and if a URL is not in progress and if the server has IDLE
+ // capability. Just set idlePending since want to wait a short time
+ // to see if more URLs occurs before actually entering idle.
+ if (!idlePending && m_useIdle && !m_urlInProgress &&
+ GetServerStateParser().GetCapabilityFlag() & kHasIdleCapability &&
+ GetServerStateParser().GetIMAPstate() ==
+ nsImapServerResponseParser::kFolderSelected) {
+ // Set-up short wait time in milliseconds
+ static const PRIntervalTime kIdleWait =
+ PR_MillisecondsToInterval(2000);
+ sleepTime = kIdleWait;
+ idlePending = true;
+ Log("ImapThreadMainLoop", nullptr, "idlePending set");
+ } else {
+ // if not idle used, don't need to remember folder sink
+ m_imapMailFolderSink = nullptr;
+ }
+ }
+ } else {
+ // No URL to run detected on wake up.
+ if (idlePending) {
+ // Have seen no URLs for the short time (kIdleWait) so go into idle mode
+ // and set the loop sleep time back to its original longer time.
+ Idle();
+ if (!m_idle) {
+ // Server rejected IDLE. Treat like IDLE not enabled or available.
+ m_imapMailFolderSink = nullptr;
+ }
+ idlePending = false;
+ sleepTime = kImapSleepTime;
+ }
+ }
+ if (!GetServerStateParser().Connected()) break;
+#ifdef DEBUG_bienvenu
+ else
+ printf("ready to run but no url and not idle\n");
+#endif
+ // This can happen if the UI thread closes cached connections in the
+ // OnStopRunningUrl notification.
+ if (m_threadShouldDie) TellThreadToDie();
+ }
+ m_imapThreadIsRunning = false;
+
+ MOZ_LOG(IMAP, LogLevel::Debug,
+ ("ImapThreadMainLoop leaving [this=%p]", this));
+}
+
+// This handles the response to Idle() command and handles responses sent by
+// the server after in idle mode. Returns true if a BAD or NO response is
+// not seen which is needed when called from Idle().
+bool nsImapProtocol::HandleIdleResponses() {
+ bool rvIdleOk = true;
+ bool untagged = false;
+ NS_ASSERTION(PL_strstr(m_currentCommand.get(), " IDLE"), "not IDLE!");
+ do {
+ ParseIMAPandCheckForNewMail();
+ rvIdleOk = rvIdleOk && !GetServerStateParser().CommandFailed();
+ untagged = untagged || GetServerStateParser().UntaggedResponse();
+ } while (m_inputStreamBuffer->NextLineAvailable() &&
+ GetServerStateParser().Connected());
+
+ // If still connected and rvIdleOk is true and an untagged response was
+ // detected and have the sink pointer, OnNewIdleMessage will invoke a URL to
+ // update the folder. Otherwise just setup so we get notified of idle response
+ // data on the socket transport thread by OnInputStreamReady() above, which
+ // will trigger the imap thread main loop to run and call this function again.
+ if (GetServerStateParser().Connected() && rvIdleOk) {
+ if (m_imapMailFolderSinkSelected && untagged) {
+ Log("HandleIdleResponses", nullptr, "idle response");
+ m_imapMailFolderSinkSelected->OnNewIdleMessages();
+ } else {
+ // Enable async wait mode. Occurs when Idle() called.
+ nsCOMPtr<nsIAsyncInputStream> asyncInputStream =
+ do_QueryInterface(m_inputStream);
+ if (asyncInputStream) {
+ asyncInputStream->AsyncWait(this, 0, 0, nullptr);
+ Log("HandleIdleResponses", nullptr, "idle mode async waiting");
+ }
+ }
+ }
+ return rvIdleOk;
+}
+
+void nsImapProtocol::EstablishServerConnection() {
+#define ESC_LENGTH(x) (sizeof(x) - 1)
+#define ESC_OK "* OK"
+#define ESC_OK_LEN ESC_LENGTH(ESC_OK)
+#define ESC_PREAUTH "* PREAUTH"
+#define ESC_PREAUTH_LEN ESC_LENGTH(ESC_PREAUTH)
+#define ESC_CAPABILITY_STAR "* "
+#define ESC_CAPABILITY_STAR_LEN ESC_LENGTH(ESC_CAPABILITY_STAR)
+#define ESC_CAPABILITY_OK "* OK ["
+#define ESC_CAPABILITY_OK_LEN ESC_LENGTH(ESC_CAPABILITY_OK)
+#define ESC_CAPABILITY_GREETING (ESC_CAPABILITY_OK "CAPABILITY")
+#define ESC_CAPABILITY_GREETING_LEN ESC_LENGTH(ESC_CAPABILITY_GREETING)
+
+ char* serverResponse = CreateNewLineFromSocket(); // read in the greeting
+ // record the fact that we've received a greeting for this connection so we
+ // don't ever try to do it again..
+ if (serverResponse) SetFlag(IMAP_RECEIVED_GREETING);
+
+ if (!PL_strncasecmp(serverResponse, ESC_OK, ESC_OK_LEN)) {
+ SetConnectionStatus(NS_OK);
+
+ if (!PL_strncasecmp(serverResponse, ESC_CAPABILITY_GREETING,
+ ESC_CAPABILITY_GREETING_LEN)) {
+ nsAutoCString tmpstr(serverResponse);
+ int32_t endIndex = tmpstr.FindChar(']', ESC_CAPABILITY_GREETING_LEN);
+ if (endIndex >= 0) {
+ // Allocate the new buffer here. This buffer will be passed to
+ // ParseIMAPServerResponse() where it will be used to fill the
+ // fCurrentLine field and will be freed by the next call to
+ // ResetLexAnalyzer().
+ char* fakeServerResponse = (char*)PR_Malloc(PL_strlen(serverResponse));
+ // Munge the greeting into something that would pass for an IMAP
+ // server's response to a "CAPABILITY" command.
+ strcpy(fakeServerResponse, ESC_CAPABILITY_STAR);
+ strcat(fakeServerResponse, serverResponse + ESC_CAPABILITY_OK_LEN);
+ fakeServerResponse[endIndex - ESC_CAPABILITY_OK_LEN +
+ ESC_CAPABILITY_STAR_LEN] = '\0';
+ // Tell the response parser that we just issued a "CAPABILITY" and
+ // got the following back.
+ GetServerStateParser().ParseIMAPServerResponse("1 CAPABILITY", true,
+ fakeServerResponse);
+ }
+ }
+ } else if (!PL_strncasecmp(serverResponse, ESC_PREAUTH, ESC_PREAUTH_LEN)) {
+ // PREAUTH greeting received. We've been pre-authenticated by the server.
+ // We can skip sending a password and transition right into the
+ // kAuthenticated state; but we won't if the user has configured STARTTLS.
+ // (STARTTLS can only occur with the server in non-authenticated state.)
+ if (!(m_socketType == nsMsgSocketType::alwaysSTARTTLS ||
+ m_socketType == nsMsgSocketType::trySTARTTLS)) {
+ GetServerStateParser().PreauthSetAuthenticatedState();
+
+ if (GetServerStateParser().GetCapabilityFlag() == kCapabilityUndefined)
+ Capability();
+
+ if (!(GetServerStateParser().GetCapabilityFlag() &
+ (kIMAP4Capability | kIMAP4rev1Capability | kIMAP4other))) {
+ // AlertUserEventUsingId(MK_MSG_IMAP_SERVER_NOT_IMAP4);
+ SetConnectionStatus(NS_ERROR_FAILURE); // stop netlib
+ } else {
+ // let's record the user as authenticated.
+ m_imapServerSink->SetUserAuthenticated(true);
+ m_hostSessionList->SetPasswordVerifiedOnline(GetImapServerKey());
+
+ ProcessAfterAuthenticated();
+ // the connection was a success
+ SetConnectionStatus(NS_OK);
+ }
+ } else {
+ // STARTTLS is configured so don't transition to authenticated state. Just
+ // alert the user, log the error and drop the connection. This may
+ // indicate a man-in-the middle attack if the user is not expecting
+ // PREAUTH. The user must change the connection security setting to other
+ // than STARTTLS to allow PREAUTH to be accepted on subsequent IMAP
+ // connections.
+ AlertUserEventUsingName("imapServerDisconnected");
+ const nsCString& hostName = GetImapHostName();
+ MOZ_LOG(
+ IMAP, LogLevel::Error,
+ ("PREAUTH received from IMAP server %s because STARTTLS selected. "
+ "Connection dropped",
+ hostName.get()));
+ SetConnectionStatus(NS_ERROR_FAILURE); // stop netlib
+ }
+ }
+
+ PR_Free(serverResponse); // we don't care about the greeting yet...
+
+#undef ESC_LENGTH
+#undef ESC_OK
+#undef ESC_OK_LEN
+#undef ESC_PREAUTH
+#undef ESC_PREAUTH_LEN
+#undef ESC_CAPABILITY_STAR
+#undef ESC_CAPABILITY_STAR_LEN
+#undef ESC_CAPABILITY_OK
+#undef ESC_CAPABILITY_OK_LEN
+#undef ESC_CAPABILITY_GREETING
+#undef ESC_CAPABILITY_GREETING_LEN
+}
+
+// This can get called from the UI thread or an imap thread.
+// It makes sure we don't get left with partial messages in
+// the memory cache.
+static void DoomCacheEntry(nsIMsgMailNewsUrl* url) {
+ bool readingFromMemCache = false;
+ nsCOMPtr<nsIImapUrl> imapUrl = do_QueryInterface(url);
+ imapUrl->GetMsgLoadingFromCache(&readingFromMemCache);
+ if (!readingFromMemCache) {
+ nsCOMPtr<nsICacheEntry> cacheEntry;
+ url->GetMemCacheEntry(getter_AddRefs(cacheEntry));
+ if (cacheEntry) {
+ MOZ_LOG(IMAPCache, LogLevel::Debug,
+ ("%s: Call AsyncDoom(), url=%s", __func__,
+ url->GetSpecOrDefault().get()));
+ cacheEntry->AsyncDoom(nullptr);
+ }
+ }
+}
+
+/**
+ * ProcessCurrentURL() runs the current URL (m_runningUrl).
+ * Things to remember:
+ * - IMAP protocol URLs don't correspond directly to IMAP commands. A single
+ * URL might cause multiple IMAP commands to be issued.
+ * - This is all synchronous. But that's OK, because we're running in our
+ * own thread.
+ *
+ * @return true if another url was run, false otherwise.
+ */
+bool nsImapProtocol::ProcessCurrentURL() {
+ nsresult rv = NS_OK;
+ if (m_idle) EndIdle();
+
+ if (m_retryUrlOnError) {
+ // we clear this flag if we're re-running immediately, because that
+ // means we never sent a start running url notification, and later we
+ // don't send start running notification if we think we're rerunning
+ // the url (see first call to SetUrlState below). This means we won't
+ // send a start running notification, which means our stop running
+ // notification will be ignored because we don't think we were running.
+ m_runningUrl->SetRerunningUrl(false);
+ return RetryUrl();
+ }
+ Log("ProcessCurrentURL", nullptr, "entering");
+ (void)GetImapHostName(); // force m_hostName to get set.
+
+ bool logonFailed = false;
+ bool anotherUrlRun = false;
+ bool rerunningUrl = false;
+ bool isExternalUrl;
+ bool validUrl = true;
+
+ PseudoInterrupt(false); // clear this if left over from previous url.
+
+ m_runningUrl->GetRerunningUrl(&rerunningUrl);
+ m_runningUrl->GetExternalLinkUrl(&isExternalUrl);
+ m_runningUrl->GetValidUrl(&validUrl);
+ m_runningUrl->GetImapAction(&m_imapAction);
+
+ if (isExternalUrl) {
+ if (m_imapAction == nsIImapUrl::nsImapSelectFolder) {
+ // we need to send a start request so that the doc loader
+ // will call HandleContent on the imap service so we
+ // can abort this url, and run a new url in a new msg window
+ // to run the folder load url and get off this crazy merry-go-round.
+ if (m_channelListener) {
+ m_channelListener->OnStartRequest(m_mockChannel);
+ }
+ return false;
+ }
+ }
+
+ if (!m_imapMailFolderSink && m_imapProtocolSink) {
+ // This occurs when running another URL in the main thread loop
+ rv = m_imapProtocolSink->SetupMainThreadProxies();
+ NS_ENSURE_SUCCESS(rv, false);
+ }
+
+ // Reinitialize the parser
+ GetServerStateParser().InitializeState();
+ GetServerStateParser().SetConnected(true);
+
+ // acknowledge that we are running the url now..
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsurl =
+ do_QueryInterface(m_runningUrl, &rv);
+ nsAutoCString urlSpec;
+ rv = mailnewsurl->GetSpec(urlSpec);
+ NS_ENSURE_SUCCESS(rv, false);
+ Log("ProcessCurrentURL", urlSpec.get(),
+ (validUrl) ? " = currentUrl" : " is not valid");
+ if (!validUrl) return false;
+
+ if (NS_SUCCEEDED(rv) && mailnewsurl && m_imapMailFolderSink && !rerunningUrl)
+ m_imapMailFolderSink->SetUrlState(this, mailnewsurl, true, false, 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 (m_channelListener) // ### not sure we want to do this if rerunning url...
+ {
+ m_channelListener->OnStartRequest(m_mockChannel);
+ }
+ // If we haven't received the greeting yet, we need to make sure we strip
+ // it out of the input before we start to do useful things...
+ if (!TestFlag(IMAP_RECEIVED_GREETING)) EstablishServerConnection();
+
+ // Step 1: If we have not moved into the authenticated state yet then do so
+ // by attempting to logon.
+ if (!DeathSignalReceived() && NS_SUCCEEDED(GetConnectionStatus()) &&
+ (GetServerStateParser().GetIMAPstate() ==
+ nsImapServerResponseParser::kNonAuthenticated)) {
+ /* if we got here, the server's greeting should not have been PREAUTH */
+ // If greeting did not contain a capability response and if user has not
+ // configured STARTTLS, request capabilities. If STARTTLS configured,
+ // capabilities will be requested after TLS handshakes are complete.
+ if ((GetServerStateParser().GetCapabilityFlag() == kCapabilityUndefined) &&
+ (m_socketType != nsMsgSocketType::alwaysSTARTTLS)) {
+ Capability();
+ }
+
+ // If capability response has yet to occur and STARTTLS is not
+ // configured then drop the connection since this should not happen. Also
+ // drop the connection if capability response has occurred and
+ // the imap version is unacceptable. Show alert only for wrong version.
+ if (((GetServerStateParser().GetCapabilityFlag() == kCapabilityUndefined) &&
+ (m_socketType != nsMsgSocketType::alwaysSTARTTLS)) ||
+ (GetServerStateParser().GetCapabilityFlag() &&
+ !(GetServerStateParser().GetCapabilityFlag() &
+ (kIMAP4Capability | kIMAP4rev1Capability | kIMAP4other)))) {
+ if (!DeathSignalReceived() && NS_SUCCEEDED(GetConnectionStatus()) &&
+ GetServerStateParser().GetCapabilityFlag())
+ AlertUserEventUsingName("imapServerNotImap4");
+
+ SetConnectionStatus(NS_ERROR_FAILURE); // stop netlib
+ } else {
+ if ((m_connectionType.EqualsLiteral("starttls") &&
+ (m_socketType == nsMsgSocketType::trySTARTTLS &&
+ (GetServerStateParser().GetCapabilityFlag() &
+ kHasStartTLSCapability))) ||
+ m_socketType == nsMsgSocketType::alwaysSTARTTLS) {
+ StartTLS(); // Send imap STARTTLS command
+ if (GetServerStateParser().LastCommandSuccessful()) {
+ NS_ENSURE_TRUE(m_transport, false);
+ MOZ_ASSERT(!NS_IsMainThread());
+ rv = TransportStartTLS(); // Initiate STARTTLS handshakes
+ if (NS_SUCCEEDED(rv)) {
+ // Transition to secure state is now enabled but handshakes and
+ // negotiation has not yet occurred. Make sure that
+ // the stream input response buffer is drained to avoid false
+ // responses to subsequent commands (capability, login etc),
+ // i.e., due to possible MitM attack doing pre-TLS response
+ // injection. We are discarding any possible malicious data
+ // stored prior to TransportStartTLS().
+ // Note: If any non-TLS related data arrives while transitioning
+ // to secure state (after TransportStartTLS()), it will
+ // cause the TLS negotiation to fail so any injected data is never
+ // accessed since the transport connection will be dropped.
+ char discardBuf[80];
+ uint64_t numBytesInStream = 0;
+ uint32_t numBytesRead;
+ rv = m_inputStream->Available(&numBytesInStream);
+ nsCOMPtr<nsIInputStream> kungFuGrip = m_inputStream;
+ // Read and discard any data available in socket buffer.
+ while (numBytesInStream > 0 && NS_SUCCEEDED(rv)) {
+ rv = m_inputStream->Read(
+ discardBuf,
+ std::min(uint64_t(sizeof discardBuf), numBytesInStream),
+ &numBytesRead);
+ numBytesInStream -= numBytesRead;
+ }
+ kungFuGrip = nullptr;
+
+ // Discard any data lines previously read from socket buffer.
+ m_inputStreamBuffer->ClearBuffer();
+
+ // Force re-issue of "capability", because servers may
+ // enable other auth features (e.g. remove LOGINDISABLED
+ // and add AUTH=PLAIN). Sending imap data here first triggers
+ // the TLS negotiation handshakes.
+ Capability();
+
+ // If user has set pref mail.server.serverX.socketType to 1
+ // (trySTARTTLS, now deprecated in UI) and Capability()
+ // succeeds, indicating TLS handshakes succeeded, set and
+ // latch the socketType to 2 (alwaysSTARTTLS) for this server.
+ if ((m_socketType == nsMsgSocketType::trySTARTTLS) &&
+ GetServerStateParser().LastCommandSuccessful())
+ m_imapServerSink->UpdateTrySTARTTLSPref(true);
+
+ // Courier imap doesn't return STARTTLS capability if we've done
+ // a STARTTLS! But we need to remember this capability so we'll
+ // try to use STARTTLS next time.
+ // Update: This may not be a problem since "next time" will be
+ // on a new connection that is not yet in secure state. So the
+ // capability greeting *will* contain STARTTLS. I observed and
+ // tested this on Courier imap server. But keep this to be sure.
+ eIMAPCapabilityFlags capabilityFlag =
+ GetServerStateParser().GetCapabilityFlag();
+ if (!(capabilityFlag & kHasStartTLSCapability)) {
+ capabilityFlag |= kHasStartTLSCapability;
+ GetServerStateParser().SetCapabilityFlag(capabilityFlag);
+ CommitCapability();
+ }
+ }
+ if (NS_FAILED(rv)) {
+ nsAutoCString logLine("Enable of STARTTLS failed. Error 0x");
+ logLine.AppendInt(static_cast<uint32_t>(rv), 16);
+ Log("ProcessCurrentURL", nullptr, logLine.get());
+ if (m_socketType == nsMsgSocketType::alwaysSTARTTLS) {
+ SetConnectionStatus(rv); // stop netlib
+ if (m_transport) m_transport->Close(rv);
+ } else if (m_socketType == nsMsgSocketType::trySTARTTLS)
+ m_imapServerSink->UpdateTrySTARTTLSPref(false);
+ }
+ } else if (m_socketType == nsMsgSocketType::alwaysSTARTTLS) {
+ SetConnectionStatus(NS_ERROR_FAILURE); // stop netlib
+ if (m_transport) m_transport->Close(rv);
+ } else if (m_socketType == nsMsgSocketType::trySTARTTLS) {
+ // STARTTLS failed, so downgrade socket type
+ m_imapServerSink->UpdateTrySTARTTLSPref(false);
+ }
+ } else if (m_socketType == nsMsgSocketType::trySTARTTLS) {
+ // we didn't know the server supported TLS when we created
+ // the socket, so we're going to retry with a STARTTLS socket
+ if (GetServerStateParser().GetCapabilityFlag() &
+ kHasStartTLSCapability) {
+ ClearFlag(IMAP_CONNECTION_IS_OPEN);
+ TellThreadToDie();
+ SetConnectionStatus(NS_ERROR_FAILURE);
+ return RetryUrl();
+ }
+ // trySTARTTLS set, but server doesn't have TLS capability,
+ // so downgrade socket type
+ m_imapServerSink->UpdateTrySTARTTLSPref(false);
+ m_socketType = nsMsgSocketType::plain;
+ }
+ if (!DeathSignalReceived() && (NS_SUCCEEDED(GetConnectionStatus()))) {
+ logonFailed = !TryToLogon();
+ }
+ if (m_retryUrlOnError) return RetryUrl();
+ }
+ } // if death signal not received
+
+ // We assume one IMAP thread is used for exactly one server, only.
+ if (m_transport && !m_securityInfo) {
+ MOZ_ASSERT(!NS_IsMainThread());
+ GetTransportSecurityInfo(getter_AddRefs(m_securityInfo));
+ }
+
+ if (!DeathSignalReceived() && (NS_SUCCEEDED(GetConnectionStatus()))) {
+ // if the server supports a language extension then we should
+ // attempt to issue the language extension.
+ if (GetServerStateParser().GetCapabilityFlag() & kHasLanguageCapability)
+ Language();
+
+ if (m_runningUrl) {
+ bool foundMailboxesAlready = false;
+ m_hostSessionList->GetHaveWeEverDiscoveredFoldersForHost(
+ GetImapServerKey(), foundMailboxesAlready);
+ if (!foundMailboxesAlready) FindMailboxesIfNecessary();
+ }
+
+ nsImapState imapState = nsIImapUrl::ImapStatusNone;
+ if (m_runningUrl) m_runningUrl->GetRequiredImapState(&imapState);
+
+ if (imapState == nsIImapUrl::nsImapAuthenticatedState)
+ ProcessAuthenticatedStateURL();
+ else // must be a url that requires us to be in the selected state
+ ProcessSelectedStateURL();
+
+ if (m_retryUrlOnError) return RetryUrl();
+
+ // The URL has now been processed
+ if ((!logonFailed && NS_FAILED(GetConnectionStatus())) ||
+ DeathSignalReceived())
+ HandleCurrentUrlError();
+
+ } else if (!logonFailed)
+ HandleCurrentUrlError();
+
+ // if we are set up as a channel, we should notify our channel listener that
+ // we are stopping... so pass in ourself as the channel and not the underlying
+ // socket or file channel the protocol happens to be using
+ if (m_channelListener) {
+ NS_ASSERTION(m_mockChannel, "no request");
+ if (m_mockChannel) {
+ nsresult status;
+ m_mockChannel->GetStatus(&status);
+ if (!GetServerStateParser().LastCommandSuccessful() &&
+ NS_SUCCEEDED(status))
+ status = NS_MSG_ERROR_IMAP_COMMAND_FAILED;
+ rv = m_channelListener->OnStopRequest(m_mockChannel, status);
+ }
+ }
+ bool suspendUrl = false;
+ m_runningUrl->GetMoreHeadersToDownload(&suspendUrl);
+ if (mailnewsurl && m_imapMailFolderSink) {
+ rv = GetConnectionStatus();
+ // There are error conditions to check even if the connection is OK.
+ if (NS_SUCCEEDED(rv)) {
+ if (logonFailed) {
+ rv = NS_ERROR_FAILURE;
+ } else if (GetServerStateParser().CommandFailed()) {
+ rv = NS_MSG_ERROR_IMAP_COMMAND_FAILED;
+ }
+ }
+ if (NS_FAILED(rv)) {
+ MOZ_LOG(
+ IMAP, LogLevel::Debug,
+ ("URL failed with code 0x%" PRIx32 " (%s)", static_cast<uint32_t>(rv),
+ mailnewsurl->GetSpecOrDefault().get()));
+ // If discovery URL fails, clear the in-progress flag.
+ if (m_imapAction == nsIImapUrl::nsImapDiscoverAllBoxesUrl) {
+ m_hostSessionList->SetDiscoveryForHostInProgress(GetImapServerKey(),
+ false);
+ }
+ }
+ // Inform any nsIUrlListeners that the URL has finished. This will invoke
+ // nsIUrlListener.onStopRunningUrl().
+ m_imapMailFolderSink->SetUrlState(this, mailnewsurl, false, suspendUrl, rv);
+
+ // Doom the cache entry if shutting down or thread is terminated.
+ if (NS_FAILED(rv) && DeathSignalReceived() && m_mockChannel) {
+ MOZ_LOG(IMAPCache, LogLevel::Debug,
+ ("ProcessCurrentURL(): Call DoomCacheEntry()"));
+ DoomCacheEntry(mailnewsurl);
+ }
+ } else {
+ // That's seen at times in debug sessions.
+ NS_WARNING("missing url or sink");
+ }
+
+ // disable timeouts before caching connection.
+ if (m_transport)
+ m_transport->SetTimeout(nsISocketTransport::TIMEOUT_READ_WRITE,
+ PR_UINT32_MAX);
+
+ SetFlag(IMAP_CLEAN_UP_URL_STATE);
+
+ nsCOMPtr<nsISupports> copyState;
+ if (m_runningUrl) m_runningUrl->GetCopyState(getter_AddRefs(copyState));
+ // this is so hokey...we MUST clear any local references to the url
+ // BEFORE calling ReleaseUrlState
+ mailnewsurl = nullptr;
+
+ if (suspendUrl) m_imapServerSink->SuspendUrl(m_runningUrl);
+ // save the imap folder sink since we need it to do the CopyNextStreamMessage
+ RefPtr<ImapMailFolderSinkProxy> imapMailFolderSink = m_imapMailFolderSink;
+ // release the url as we are done with it...
+ ReleaseUrlState(false);
+ ResetProgressInfo();
+
+ ClearFlag(IMAP_CLEAN_UP_URL_STATE);
+
+ if (imapMailFolderSink) {
+ if (copyState) {
+ rv = imapMailFolderSink->CopyNextStreamMessage(
+ GetServerStateParser().LastCommandSuccessful() &&
+ NS_SUCCEEDED(GetConnectionStatus()),
+ copyState);
+ if (NS_FAILED(rv))
+ MOZ_LOG(IMAP, LogLevel::Info,
+ ("CopyNextStreamMessage failed: %" PRIx32,
+ static_cast<uint32_t>(rv)));
+
+ NS_ReleaseOnMainThread("nsImapProtocol, copyState", copyState.forget());
+ }
+ // we might need this to stick around for IDLE support
+ m_imapMailFolderSink = imapMailFolderSink;
+ imapMailFolderSink = nullptr;
+ } else
+ MOZ_LOG(IMAP, LogLevel::Info, ("null imapMailFolderSink"));
+
+ // now try queued urls, now that we've released this connection.
+ if (m_imapServerSink) {
+ if (NS_SUCCEEDED(GetConnectionStatus()))
+ rv = m_imapServerSink->LoadNextQueuedUrl(this, &anotherUrlRun);
+ else // if we don't do this, they'll just sit and spin until
+ // we run some other url on this server.
+ {
+ Log("ProcessCurrentURL", nullptr, "aborting queued urls");
+ rv = m_imapServerSink->AbortQueuedUrls();
+ }
+ }
+
+ // if we didn't run another url, release the server sink to
+ // cut circular refs.
+ if (!anotherUrlRun) m_imapServerSink = nullptr;
+
+ if (NS_FAILED(GetConnectionStatus()) || !GetServerStateParser().Connected() ||
+ GetServerStateParser().SyntaxError()) {
+ if (m_imapServerSink) m_imapServerSink->RemoveServerConnection(this);
+
+ if (!DeathSignalReceived()) {
+ TellThreadToDie();
+ }
+ } else {
+ if (m_imapServerSink) {
+ bool shuttingDown;
+ m_imapServerSink->GetServerShuttingDown(&shuttingDown);
+ if (shuttingDown) m_useIdle = false;
+ }
+ }
+ return anotherUrlRun;
+}
+
+bool nsImapProtocol::RetryUrl() {
+ nsCOMPtr<nsIImapUrl> kungFuGripImapUrl = m_runningUrl;
+ nsCOMPtr<nsIImapMockChannel> saveMockChannel;
+
+ // the mock channel might be null - that's OK.
+ if (m_imapServerSink)
+ (void)m_imapServerSink->PrepareToRetryUrl(kungFuGripImapUrl,
+ getter_AddRefs(saveMockChannel));
+
+ ReleaseUrlState(true);
+ if (m_imapServerSink) {
+ m_imapServerSink->RemoveServerConnection(this);
+ m_imapServerSink->RetryUrl(kungFuGripImapUrl, saveMockChannel);
+ }
+
+ // Hack for Bug 1586494.
+ // (this is a workaround to try and prevent a specific crash, and
+ // does nothing clarify the threading mess!)
+ // RetryUrl() is only ever called from the imap thread.
+ // Mockchannel dtor insists upon being run on the main thread.
+ // So make sure we don't accidentally cause the mockchannel to die right now.
+ if (saveMockChannel) {
+ NS_ReleaseOnMainThread("nsImapProtocol::RetryUrl",
+ saveMockChannel.forget());
+ }
+
+ return (m_imapServerSink != nullptr); // we're running a url (the same url)
+}
+
+// ignoreBadAndNOResponses --> don't throw a error dialog if this command
+// results in a NO or Bad response from the server..in other words the command
+// is "exploratory" and we don't really care if it succeeds or fails.
+void nsImapProtocol::ParseIMAPandCheckForNewMail(
+ const char* commandString, bool aIgnoreBadAndNOResponses) {
+ if (commandString)
+ GetServerStateParser().ParseIMAPServerResponse(commandString,
+ aIgnoreBadAndNOResponses);
+ else
+ GetServerStateParser().ParseIMAPServerResponse(m_currentCommand.get(),
+ aIgnoreBadAndNOResponses);
+ // **** fix me for new mail biff state *****
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////////
+// End of nsIStreamListenerSupport
+//////////////////////////////////////////////////////////////////////////////////////////////
+
+NS_IMETHODIMP
+nsImapProtocol::GetRunningUrl(nsIURI** result) {
+ if (result && m_runningUrl)
+ return m_runningUrl->QueryInterface(NS_GET_IID(nsIURI), (void**)result);
+ return NS_ERROR_NULL_POINTER;
+}
+
+NS_IMETHODIMP nsImapProtocol::GetRunningImapURL(nsIImapUrl** aImapUrl) {
+ if (aImapUrl && m_runningUrl)
+ return m_runningUrl->QueryInterface(NS_GET_IID(nsIImapUrl),
+ (void**)aImapUrl);
+ return NS_ERROR_NULL_POINTER;
+}
+
+/*
+ * 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)
+ */
+
+nsresult nsImapProtocol::SendData(const char* dataBuffer,
+ bool aSuppressLogging) {
+ nsresult rv = NS_ERROR_NULL_POINTER;
+
+ if (!m_transport) {
+ Log("SendData", nullptr, "clearing IMAP_CONNECTION_IS_OPEN");
+ // the connection died unexpectedly! so clear the open connection flag
+ ClearFlag(IMAP_CONNECTION_IS_OPEN);
+ TellThreadToDie();
+ SetConnectionStatus(NS_ERROR_FAILURE);
+ return NS_ERROR_FAILURE;
+ }
+
+ if (dataBuffer && m_outputStream) {
+ m_currentCommand = dataBuffer;
+ if (!aSuppressLogging)
+ Log("SendData", nullptr, dataBuffer);
+ else
+ Log("SendData", nullptr,
+ "Logging suppressed for this command (it probably contained "
+ "authentication information)");
+
+ {
+ // don't allow someone to close the stream/transport out from under us
+ // this can happen when the ui thread calls TellThreadToDie.
+ PR_CEnterMonitor(this);
+ uint32_t n;
+ if (m_outputStream)
+ rv = m_outputStream->Write(dataBuffer, PL_strlen(dataBuffer), &n);
+ PR_CExitMonitor(this);
+ }
+ if (NS_FAILED(rv)) {
+ Log("SendData", nullptr, "clearing IMAP_CONNECTION_IS_OPEN");
+ // the connection died unexpectedly! so clear the open connection flag
+ ClearFlag(IMAP_CONNECTION_IS_OPEN);
+ TellThreadToDie();
+ SetConnectionStatus(rv);
+ if (m_runningUrl && !m_retryUrlOnError) {
+ bool alreadyRerunningUrl;
+ m_runningUrl->GetRerunningUrl(&alreadyRerunningUrl);
+ if (!alreadyRerunningUrl) {
+ m_runningUrl->SetRerunningUrl(true);
+ m_retryUrlOnError = true;
+ }
+ }
+ }
+ }
+
+ return rv;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////////
+// Begin protocol state machine functions...
+//////////////////////////////////////////////////////////////////////////////////////////////
+
+// ProcessProtocolState - we override this only so we'll link - it should never
+// get called.
+
+nsresult nsImapProtocol::ProcessProtocolState(nsIURI* url,
+ nsIInputStream* inputStream,
+ uint64_t sourceOffset,
+ uint32_t length) {
+ return NS_OK;
+}
+
+class UrlListenerNotifierEvent : public mozilla::Runnable {
+ public:
+ UrlListenerNotifierEvent(nsIMsgMailNewsUrl* aUrl, nsIImapProtocol* aProtocol)
+ : mozilla::Runnable("UrlListenerNotifierEvent"),
+ mUrl(aUrl),
+ mProtocol(aProtocol) {}
+
+ NS_IMETHOD Run() {
+ if (mUrl) {
+ nsCOMPtr<nsIMsgFolder> folder;
+ mUrl->GetFolder(getter_AddRefs(folder));
+ NS_ENSURE_TRUE(folder, NS_OK);
+ nsCOMPtr<nsIImapMailFolderSink> folderSink(do_QueryInterface(folder));
+ // This causes the url listener to get OnStart and Stop notifications.
+ folderSink->SetUrlState(mProtocol, mUrl, true, false, NS_OK);
+ folderSink->SetUrlState(mProtocol, mUrl, false, false, NS_OK);
+ }
+ return NS_OK;
+ }
+
+ private:
+ nsCOMPtr<nsIMsgMailNewsUrl> mUrl;
+ nsCOMPtr<nsIImapProtocol> mProtocol;
+};
+
+bool nsImapProtocol::TryToRunUrlLocally(nsIURI* aURL, nsISupports* aConsumer) {
+ nsresult rv;
+ nsCOMPtr<nsIImapUrl> imapUrl(do_QueryInterface(aURL, &rv));
+ NS_ENSURE_SUCCESS(rv, false);
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(aURL);
+ nsCString messageIdString;
+ imapUrl->GetListOfMessageIds(messageIdString);
+ bool useLocalCache = false;
+ if (!messageIdString.IsEmpty() &&
+ !HandlingMultipleMessages(messageIdString)) {
+ nsImapAction action;
+ imapUrl->GetImapAction(&action);
+ nsCOMPtr<nsIMsgFolder> folder;
+ mailnewsUrl->GetFolder(getter_AddRefs(folder));
+ NS_ENSURE_TRUE(folder, false);
+
+ folder->HasMsgOffline(strtoul(messageIdString.get(), nullptr, 10),
+ &useLocalCache);
+ mailnewsUrl->SetMsgIsInLocalCache(useLocalCache);
+ // We're downloading a single message for offline use, and it's
+ // already offline. So we shouldn't do anything, but we do
+ // need to notify the url listener.
+ if (useLocalCache && action == nsIImapUrl::nsImapMsgDownloadForOffline) {
+ nsCOMPtr<nsIRunnable> event =
+ new UrlListenerNotifierEvent(mailnewsUrl, this);
+ // Post this as an event because it can lead to re-entrant calls to
+ // LoadNextQueuedUrl if the listener runs a new url.
+ if (event) NS_DispatchToCurrentThread(event);
+ return true;
+ }
+ }
+ if (!useLocalCache) return false;
+
+ nsCOMPtr<nsIImapMockChannel> mockChannel;
+ imapUrl->GetMockChannel(getter_AddRefs(mockChannel));
+ if (!mockChannel) return false;
+
+ nsImapMockChannel* imapChannel =
+ static_cast<nsImapMockChannel*>(mockChannel.get());
+ if (!imapChannel) return false;
+
+ nsCOMPtr<nsILoadGroup> loadGroup;
+ imapChannel->GetLoadGroup(getter_AddRefs(loadGroup));
+ if (!loadGroup) // if we don't have one, the url will snag one from the msg
+ // window...
+ mailnewsUrl->GetLoadGroup(getter_AddRefs(loadGroup));
+
+ if (loadGroup)
+ loadGroup->RemoveRequest((nsIRequest*)mockChannel,
+ nullptr /* context isupports */, NS_OK);
+
+ if (imapChannel->ReadFromLocalCache()) {
+ (void)imapChannel->NotifyStartEndReadFromCache(true);
+ return true;
+ }
+ return false;
+}
+
+// LoadImapUrl takes a url, initializes all of our url specific data by calling
+// SetupUrl. Finally, we signal the url to run monitor to let the imap main
+// thread loop process the current url (it is waiting on this monitor). There
+// is a contract that the imap thread has already been started before we
+// attempt to load a url...
+// LoadImapUrl() is called by nsImapIncomingServer to run a queued url on a free
+// connection.
+NS_IMETHODIMP nsImapProtocol::LoadImapUrl(nsIURI* aURL,
+ nsISupports* aConsumer) {
+ nsresult rv = NS_ERROR_FAILURE;
+ if (aURL) {
+#ifdef DEBUG_bienvenu
+ printf("loading url %s\n", aURL->GetSpecOrDefault().get());
+#endif
+ // We might be able to fulfill the request locally (e.g. fetching a message
+ // which is already stored offline).
+ if (TryToRunUrlLocally(aURL, aConsumer)) return NS_OK;
+ m_urlInProgress = true;
+ m_imapMailFolderSink = nullptr;
+ rv = SetupWithUrl(aURL, aConsumer);
+ m_lastActiveTime = PR_Now();
+ }
+ return rv;
+}
+
+nsresult nsImapProtocol::LoadImapUrlInternal() {
+ nsresult rv = NS_ERROR_FAILURE;
+
+ if (m_transport && m_mockChannel) {
+ m_transport->SetTimeout(nsISocketTransport::TIMEOUT_CONNECT,
+ gResponseTimeout + 60);
+ int32_t readWriteTimeout = gResponseTimeout;
+ if (m_runningUrl) {
+ m_runningUrl->GetImapAction(&m_imapAction);
+ // This is a silly hack, but the default of 100 seconds is typically way
+ // too long for things like APPEND, which should come back immediately.
+ // However, for large messages on some servers the final append response
+ // time can be longer. So now it is one-fifth of the configured
+ // `mailnews.tcptimeout' which defaults to 20 seconds.
+ if (m_imapAction == nsIImapUrl::nsImapAppendMsgFromFile ||
+ m_imapAction == nsIImapUrl::nsImapAppendDraftFromFile) {
+ readWriteTimeout = gAppendTimeout;
+ } else if (m_imapAction == nsIImapUrl::nsImapOnlineMove ||
+ m_imapAction == nsIImapUrl::nsImapOnlineCopy) {
+ nsCString messageIdString;
+ m_runningUrl->GetListOfMessageIds(messageIdString);
+ uint32_t copyCount = CountMessagesInIdString(messageIdString.get());
+ // If we're move/copying a large number of messages,
+ // which should be rare, increase the timeout based on number
+ // of messages. 40 messages per second should be sufficiently slow.
+ if (copyCount > 2400) // 40 * 60, 60 is default read write timeout
+ readWriteTimeout =
+ std::max(readWriteTimeout, (int32_t)copyCount / 40);
+ }
+ }
+ m_transport->SetTimeout(nsISocketTransport::TIMEOUT_READ_WRITE,
+ readWriteTimeout);
+ // Set the security info for the mock channel to be the security info for
+ // our underlying transport.
+ if (m_securityInfo) {
+ m_mockChannel->SetSecurityInfo(m_securityInfo);
+ }
+
+ SetSecurityCallbacksFromChannel(m_transport, m_mockChannel);
+
+ nsCOMPtr<nsITransportEventSink> sinkMC = do_QueryInterface(m_mockChannel);
+ if (sinkMC) {
+ nsCOMPtr<nsIThread> thread = do_GetMainThread();
+ RefPtr<nsImapTransportEventSink> sink = new nsImapTransportEventSink;
+ rv = net_NewTransportEventSinkProxy(getter_AddRefs(sink->m_proxy), sinkMC,
+ thread);
+ NS_ENSURE_SUCCESS(rv, rv);
+ m_transport->SetEventSink(sink, nullptr);
+ }
+
+ // And if we have a cache2 entry that we are saving the message to, set the
+ // security info on it too.
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_runningUrl);
+ if (mailnewsUrl && m_securityInfo) {
+ nsCOMPtr<nsICacheEntry> cacheEntry;
+ mailnewsUrl->GetMemCacheEntry(getter_AddRefs(cacheEntry));
+ if (cacheEntry) {
+ cacheEntry->SetSecurityInfo(m_securityInfo);
+ }
+ }
+ }
+
+ rv = SetupSinkProxy(); // generate proxies for all of the event sinks in the
+ // url
+ if (NS_FAILED(rv)) // URL can be invalid.
+ return rv;
+
+ if (m_transport && m_runningUrl) {
+ nsImapAction imapAction;
+ m_runningUrl->GetImapAction(&imapAction);
+ // if we're shutting down, and not running the kinds of urls we run at
+ // shutdown, then this should fail because running urls during
+ // shutdown will very likely fail and potentially hang.
+ nsCOMPtr<nsIMsgAccountManager> accountMgr =
+ do_GetService("@mozilla.org/messenger/account-manager;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ bool shuttingDown = false;
+ (void)accountMgr->GetShutdownInProgress(&shuttingDown);
+ if (shuttingDown && imapAction != nsIImapUrl::nsImapExpungeFolder &&
+ imapAction != nsIImapUrl::nsImapDeleteAllMsgs &&
+ imapAction != nsIImapUrl::nsImapDeleteFolder)
+ return NS_ERROR_FAILURE;
+
+ // if we're running a select or delete all, do a noop first.
+ // this should really be in the connection cache code when we know
+ // we're pulling out a selected state connection, but maybe we
+ // can get away with this.
+ m_needNoop = (imapAction == nsIImapUrl::nsImapSelectFolder ||
+ imapAction == nsIImapUrl::nsImapDeleteAllMsgs);
+
+ // We now have a url to run so signal the monitor for url ready to be
+ // processed...
+ ReentrantMonitorAutoEnter urlReadyMon(m_urlReadyToRunMonitor);
+ m_nextUrlReadyToRun = true;
+ urlReadyMon.Notify();
+
+ } // if we have an imap url and a transport
+ else {
+ NS_ASSERTION(false, "missing channel or running url");
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP nsImapProtocol::IsBusy(bool* aIsConnectionBusy,
+ bool* isInboxConnection) {
+ if (!aIsConnectionBusy || !isInboxConnection) return NS_ERROR_NULL_POINTER;
+ nsresult rv = NS_OK;
+ *aIsConnectionBusy = false;
+ *isInboxConnection = false;
+ if (!m_transport) {
+ // this connection might not be fully set up yet.
+ rv = NS_ERROR_FAILURE;
+ } else {
+ if (m_urlInProgress) // do we have a url? That means we're working on it...
+ *aIsConnectionBusy = true;
+
+ if (GetServerStateParser().GetIMAPstate() ==
+ nsImapServerResponseParser::kFolderSelected &&
+ GetServerStateParser().GetSelectedMailboxName() &&
+ PL_strcasecmp(GetServerStateParser().GetSelectedMailboxName(),
+ "Inbox") == 0)
+ *isInboxConnection = true;
+ }
+ return rv;
+}
+
+#define IS_SUBSCRIPTION_RELATED_ACTION(action) \
+ (action == nsIImapUrl::nsImapSubscribe || \
+ action == nsIImapUrl::nsImapUnsubscribe || \
+ action == nsIImapUrl::nsImapDiscoverAllBoxesUrl || \
+ action == nsIImapUrl::nsImapListFolder)
+
+// canRunUrl means the connection is not busy, and is in the selected state
+// for the desired folder (or authenticated).
+// has to wait means it's in the right selected state, but busy.
+NS_IMETHODIMP nsImapProtocol::CanHandleUrl(nsIImapUrl* aImapUrl,
+ bool* aCanRunUrl, bool* hasToWait) {
+ if (!aCanRunUrl || !hasToWait || !aImapUrl) return NS_ERROR_NULL_POINTER;
+ nsresult rv = NS_OK;
+ MutexAutoLock mon(mLock);
+
+ *aCanRunUrl = false; // assume guilty until proven otherwise...
+ *hasToWait = false;
+
+ if (DeathSignalReceived()) return NS_ERROR_FAILURE;
+
+ bool isBusy = false;
+ bool isInboxConnection = false;
+
+ if (!m_transport) {
+ // this connection might not be fully set up yet.
+ return NS_ERROR_FAILURE;
+ }
+ IsBusy(&isBusy, &isInboxConnection);
+ bool inSelectedState = GetServerStateParser().GetIMAPstate() ==
+ nsImapServerResponseParser::kFolderSelected;
+
+ nsAutoCString curSelectedUrlFolderName;
+ nsAutoCString pendingUrlFolderName;
+ if (inSelectedState)
+ curSelectedUrlFolderName = GetServerStateParser().GetSelectedMailboxName();
+
+ if (isBusy) {
+ nsImapState curUrlImapState;
+ NS_ASSERTION(m_runningUrl, "isBusy, but no running url.");
+ if (m_runningUrl) {
+ m_runningUrl->GetRequiredImapState(&curUrlImapState);
+ if (curUrlImapState == nsIImapUrl::nsImapSelectedState) {
+ char* folderName = GetFolderPathString();
+ if (!curSelectedUrlFolderName.Equals(folderName))
+ pendingUrlFolderName.Assign(folderName);
+ inSelectedState = true;
+ PR_Free(folderName);
+ }
+ }
+ }
+
+ nsImapState imapState;
+ nsImapAction actionForProposedUrl;
+ aImapUrl->GetImapAction(&actionForProposedUrl);
+ aImapUrl->GetRequiredImapState(&imapState);
+
+ // OK, this is a bit of a hack - we're going to pretend that
+ // these types of urls requires a selected state connection on
+ // the folder in question. This isn't technically true,
+ // but we would much rather use that connection for several reasons,
+ // one is that some UW servers require us to use that connection
+ // the other is that we don't want to leave a connection dangling in
+ // the selected state for the deleted folder.
+ // If we don't find a connection in that selected state,
+ // we'll fall back to the first free connection.
+ bool isSelectedStateUrl =
+ imapState == nsIImapUrl::nsImapSelectedState ||
+ actionForProposedUrl == nsIImapUrl::nsImapDeleteFolder ||
+ actionForProposedUrl == nsIImapUrl::nsImapRenameFolder ||
+ actionForProposedUrl == nsIImapUrl::nsImapMoveFolderHierarchy ||
+ actionForProposedUrl == nsIImapUrl::nsImapAppendDraftFromFile ||
+ actionForProposedUrl == nsIImapUrl::nsImapAppendMsgFromFile ||
+ actionForProposedUrl == nsIImapUrl::nsImapFolderStatus;
+
+ nsCOMPtr<nsIMsgMailNewsUrl> msgUrl = do_QueryInterface(aImapUrl);
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = msgUrl->GetServer(getter_AddRefs(server));
+ if (NS_SUCCEEDED(rv)) {
+ // compare host/user between url and connection.
+ nsCString urlHostName;
+ nsCString urlUserName;
+ rv = server->GetHostName(urlHostName);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = server->GetUsername(urlUserName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if ((GetImapHostName().IsEmpty() ||
+ urlHostName.Equals(GetImapHostName(),
+ nsCaseInsensitiveCStringComparator)) &&
+ (GetImapUserName().IsEmpty() ||
+ urlUserName.Equals(GetImapUserName(),
+ nsCaseInsensitiveCStringComparator))) {
+ if (isSelectedStateUrl) {
+ if (inSelectedState) {
+ // *** jt - in selected state can only run url with
+ // matching foldername
+ char* folderNameForProposedUrl = nullptr;
+ rv = aImapUrl->CreateServerSourceFolderPathString(
+ &folderNameForProposedUrl);
+ if (NS_SUCCEEDED(rv) && folderNameForProposedUrl) {
+ bool isInbox =
+ PL_strcasecmp("Inbox", folderNameForProposedUrl) == 0;
+ if (!curSelectedUrlFolderName.IsEmpty() ||
+ !pendingUrlFolderName.IsEmpty()) {
+ bool matched = isInbox
+ ? PL_strcasecmp(curSelectedUrlFolderName.get(),
+ folderNameForProposedUrl) == 0
+ : PL_strcmp(curSelectedUrlFolderName.get(),
+ folderNameForProposedUrl) == 0;
+ if (!matched && !pendingUrlFolderName.IsEmpty()) {
+ matched = isInbox ? PL_strcasecmp(pendingUrlFolderName.get(),
+ folderNameForProposedUrl) == 0
+ : PL_strcmp(pendingUrlFolderName.get(),
+ folderNameForProposedUrl) == 0;
+ }
+ if (matched) {
+ if (isBusy)
+ *hasToWait = true;
+ else
+ *aCanRunUrl = true;
+ }
+ }
+ }
+ MOZ_LOG(IMAP, LogLevel::Debug,
+ ("proposed url = %s folder for connection %s has To Wait = "
+ "%s can run = %s",
+ folderNameForProposedUrl, curSelectedUrlFolderName.get(),
+ (*hasToWait) ? "true" : "false",
+ (*aCanRunUrl) ? "true" : "false"));
+ PR_FREEIF(folderNameForProposedUrl);
+ }
+ } else // *** jt - an authenticated state url can be run in either
+ // authenticated or selected state
+ {
+ nsImapAction actionForRunningUrl;
+
+ // If proposed url is subscription related, and we are currently running
+ // a subscription url, then we want to queue the proposed url after the
+ // current url. Otherwise, we can run this url if we're not busy. If we
+ // never find a running subscription-related url, the caller will just
+ // use whatever free connection it can find, which is what we want.
+ if (IS_SUBSCRIPTION_RELATED_ACTION(actionForProposedUrl)) {
+ if (isBusy && m_runningUrl) {
+ m_runningUrl->GetImapAction(&actionForRunningUrl);
+ if (IS_SUBSCRIPTION_RELATED_ACTION(actionForRunningUrl)) {
+ *aCanRunUrl = false;
+ *hasToWait = true;
+ }
+ }
+ } else {
+ if (!isBusy) *aCanRunUrl = true;
+ }
+ }
+ }
+ }
+ return rv;
+}
+
+// Command tag handling stuff.
+// Zero tag number indicates never used so set it to an initial random number
+// between 1 and 100. Otherwise just increment the uint32_t value unless it
+// rolls to zero then set it to 1. Then convert the tag number to a string for
+// use in IMAP commands.
+void nsImapProtocol::IncrementCommandTagNumber() {
+ if (m_currentServerCommandTagNumber == 0) {
+ srand((unsigned)m_lastCheckTime);
+ m_currentServerCommandTagNumber = 1 + (rand() % 100);
+ } else if (++m_currentServerCommandTagNumber == 0) {
+ m_currentServerCommandTagNumber = 1;
+ }
+ sprintf(m_currentServerCommandTag, "%u", m_currentServerCommandTagNumber);
+}
+
+const char* nsImapProtocol::GetServerCommandTag() {
+ return m_currentServerCommandTag;
+}
+
+/**
+ * ProcessSelectedStateURL() is a helper for ProcessCurrentURL(). It handles
+ * running URLs which require the connection to be in the selected state.
+ * It will issue SELECT commands if needed to make sure the correct mailbox
+ * is selected.
+ */
+void nsImapProtocol::ProcessSelectedStateURL() {
+ nsCString mailboxName;
+ bool bMessageIdsAreUids = true;
+ bool moreHeadersToDownload;
+ imapMessageFlagsType msgFlags = 0;
+ nsCString urlHost;
+
+ // this can't fail, can it?
+ nsresult res;
+ res = m_runningUrl->GetImapAction(&m_imapAction);
+ m_runningUrl->MessageIdsAreUids(&bMessageIdsAreUids);
+ m_runningUrl->GetMsgFlags(&msgFlags);
+ m_runningUrl->GetMoreHeadersToDownload(&moreHeadersToDownload);
+
+ res = CreateServerSourceFolderPathString(getter_Copies(mailboxName));
+ if (NS_FAILED(res))
+ Log("ProcessSelectedStateURL", nullptr,
+ "error getting source folder path string");
+
+ if (NS_SUCCEEDED(res) && !DeathSignalReceived()) {
+ bool selectIssued = false;
+ if (GetServerStateParser().GetIMAPstate() ==
+ nsImapServerResponseParser::kFolderSelected) {
+ if (GetServerStateParser().GetSelectedMailboxName() &&
+ PL_strcmp(GetServerStateParser().GetSelectedMailboxName(),
+ mailboxName.get())) { // we are selected in another folder
+ if (m_closeNeededBeforeSelect) ImapClose();
+ if (GetServerStateParser().LastCommandSuccessful()) {
+ selectIssued = true;
+ SelectMailbox(mailboxName.get());
+ }
+ } else if (!GetServerStateParser()
+ .GetSelectedMailboxName()) { // why are we in the
+ // selected state with no
+ // box name?
+ SelectMailbox(mailboxName.get());
+ selectIssued = true;
+ } else if (moreHeadersToDownload &&
+ m_imapMailFolderSink) // we need to fetch older headers
+ {
+ nsTArray<nsMsgKey> msgIdList;
+ bool more;
+ m_imapMailFolderSink->GetMsgHdrsToDownload(
+ &more, &m_progressExpectedNumber, msgIdList);
+ if (msgIdList.Length() > 0) {
+ FolderHeaderDump(msgIdList.Elements(), msgIdList.Length());
+ m_runningUrl->SetMoreHeadersToDownload(more);
+ // We're going to be re-running this url.
+ if (more) m_runningUrl->SetRerunningUrl(true);
+ }
+ HeaderFetchCompleted();
+ } else {
+ // get new message counts, if any, from server
+ if (m_needNoop) {
+ // For some IMAP servers, to detect new email we must send imap
+ // SELECT even if already SELECTed on the same mailbox.
+ if (m_forceSelect) {
+ SelectMailbox(mailboxName.get());
+ selectIssued = true;
+ }
+
+ m_noopCount++;
+ if ((gPromoteNoopToCheckCount > 0 &&
+ (m_noopCount % gPromoteNoopToCheckCount) == 0) ||
+ CheckNeeded())
+ Check();
+ else
+ Noop(); // I think this is needed when we're using a cached
+ // connection
+ m_needNoop = false;
+ }
+ }
+ } else {
+ // go to selected state
+ SelectMailbox(mailboxName.get());
+ selectIssued = GetServerStateParser().LastCommandSuccessful();
+ }
+
+ if (selectIssued) RefreshACLForFolderIfNecessary(mailboxName.get());
+
+ bool uidValidityOk = true;
+ if (GetServerStateParser().LastCommandSuccessful() && selectIssued &&
+ (m_imapAction != nsIImapUrl::nsImapSelectFolder) &&
+ (m_imapAction != nsIImapUrl::nsImapLiteSelectFolder)) {
+ // error on the side of caution, if the fe event fails to set
+ // uidStruct->returnValidity, then assume that UIDVALIDITY did not roll.
+ // This is a common case event for attachments that are fetched within a
+ // browser context.
+ if (!DeathSignalReceived())
+ uidValidityOk = m_uidValidity == kUidUnknown ||
+ m_uidValidity == GetServerStateParser().FolderUID();
+ }
+
+ if (!uidValidityOk)
+ Log("ProcessSelectedStateURL", nullptr, "uid validity not ok");
+ if (GetServerStateParser().LastCommandSuccessful() &&
+ !DeathSignalReceived() &&
+ (uidValidityOk || m_imapAction == nsIImapUrl::nsImapDeleteAllMsgs)) {
+ if (GetServerStateParser().CurrentFolderReadOnly()) {
+ Log("ProcessSelectedStateURL", nullptr, "current folder read only");
+ if (m_imapAction == nsIImapUrl::nsImapAddMsgFlags ||
+ m_imapAction == nsIImapUrl::nsImapSubtractMsgFlags) {
+ bool canChangeFlag = false;
+ if (GetServerStateParser().ServerHasACLCapability() &&
+ m_imapMailFolderSink) {
+ uint32_t aclFlags = 0;
+
+ if (NS_SUCCEEDED(m_imapMailFolderSink->GetAclFlags(&aclFlags)) &&
+ aclFlags != 0) // make sure we have some acl flags
+ canChangeFlag = ((msgFlags & kImapMsgSeenFlag) &&
+ (aclFlags & IMAP_ACL_STORE_SEEN_FLAG));
+ } else
+ canChangeFlag = (GetServerStateParser().SettablePermanentFlags() &
+ msgFlags) == msgFlags;
+ if (!canChangeFlag) return;
+ }
+ if (m_imapAction == nsIImapUrl::nsImapExpungeFolder ||
+ m_imapAction == nsIImapUrl::nsImapDeleteMsg ||
+ m_imapAction == nsIImapUrl::nsImapDeleteAllMsgs)
+ return;
+ }
+ switch (m_imapAction) {
+ case nsIImapUrl::nsImapLiteSelectFolder:
+ if (GetServerStateParser().LastCommandSuccessful() &&
+ m_imapMailFolderSink && !moreHeadersToDownload) {
+ m_imapMailFolderSink->SetUidValidity(
+ GetServerStateParser().FolderUID());
+ ProcessMailboxUpdate(false); // handle uidvalidity change
+ }
+ break;
+ case nsIImapUrl::nsImapSaveMessageToDisk:
+ case nsIImapUrl::nsImapMsgFetch:
+ case nsIImapUrl::nsImapMsgFetchPeek:
+ case nsIImapUrl::nsImapMsgDownloadForOffline:
+ case nsIImapUrl::nsImapMsgPreview: {
+ nsCString messageIdString;
+ m_runningUrl->GetListOfMessageIds(messageIdString);
+ // we don't want to send the flags back in a group
+ if (HandlingMultipleMessages(messageIdString) ||
+ m_imapAction == nsIImapUrl::nsImapMsgDownloadForOffline ||
+ m_imapAction == nsIImapUrl::nsImapMsgPreview) {
+ // multiple messages, fetch them all
+ SetProgressString(IMAP_MESSAGES_STRING_INDEX);
+
+ m_progressCurrentNumber[m_stringIndex] = 0;
+ m_progressExpectedNumber =
+ CountMessagesInIdString(messageIdString.get());
+
+ FetchMessage(messageIdString,
+ (m_imapAction == nsIImapUrl::nsImapMsgPreview)
+ ? kBodyStart
+ : kEveryThingRFC822Peek);
+ if (m_imapAction == nsIImapUrl::nsImapMsgPreview)
+ HeaderFetchCompleted();
+ SetProgressString(IMAP_EMPTY_STRING_INDEX);
+ } else {
+ // A single message ID
+ nsIMAPeFetchFields whatToFetch = kEveryThingRFC822;
+ if (m_imapAction == nsIImapUrl::nsImapMsgFetchPeek)
+ whatToFetch = kEveryThingRFC822Peek;
+
+ // Note: Should no longer fetch a specific imap section (part).
+ // First, let's see if we're requesting a specific MIME part.
+ char* imappart = nullptr;
+ m_runningUrl->GetImapPartToFetch(&imappart);
+ MOZ_ASSERT(!imappart, "no longer fetching imap section/imappart");
+ // downloading a single message: try to do it by bodystructure,
+ // and/or do it by chunks
+ // Note: No longer doing bodystructure.
+ uint32_t messageSize = GetMessageSize(messageIdString);
+
+ // The "wontFit" and cache parameter calculations (customLimit,
+ // realLimit) are only for debug information logging below.
+ if (MOZ_LOG_TEST(IMAPCache, LogLevel::Debug)) {
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsurl =
+ do_QueryInterface(m_runningUrl);
+ if (mailnewsurl) {
+ bool wontFit = net::CacheObserver::EntryIsTooBig(
+ messageSize, gUseDiskCache2);
+ int64_t customLimit;
+ int64_t realLimit;
+ if (gUseDiskCache2) {
+ customLimit = net::CacheObserver::MaxDiskEntrySize();
+ realLimit = net::CacheObserver::DiskCacheCapacity();
+ } else {
+ customLimit = net::CacheObserver::MaxMemoryEntrySize();
+ realLimit = net::CacheObserver::MemoryCacheCapacity();
+ }
+ if (!(customLimit & (int64_t)0x80000000))
+ customLimit <<= 10; // multiply by 1024 to get num bytes
+ else
+ customLimit = (int32_t)customLimit; // make it negative
+ realLimit <<= (10 - 3); // 1/8th capacity, num bytes.
+ if (customLimit > -1 && customLimit < realLimit)
+ realLimit = customLimit;
+ MOZ_LOG(IMAPCache, LogLevel::Debug,
+ ("%s: customLimit=%" PRId64 ", realLimit=%" PRId64,
+ __func__, customLimit, realLimit));
+ MOZ_LOG(IMAPCache, LogLevel::Debug,
+ ("%s: URL=%s, messageSize=%d, cache too small=%d(bool)",
+ __func__, mailnewsurl->GetSpecOrDefault().get(),
+ messageSize, wontFit));
+ }
+ }
+ // Note again: No longer doing bodystructure.
+ // Fetch the whole thing, and try to do it in chunks.
+ MOZ_LOG(
+ IMAPCache, LogLevel::Debug,
+ ("%s: Fetch entire message with FetchTryChunking", __func__));
+ FetchTryChunking(messageIdString, whatToFetch, bMessageIdsAreUids,
+ NULL, messageSize, true);
+ // If fetch was not a peek, ensure that the message displays as
+ // read (not bold) in case the server fails to mark the message
+ // as SEEN.
+ if (GetServerStateParser().LastCommandSuccessful() &&
+ m_imapAction != nsIImapUrl::nsImapMsgFetchPeek) {
+ uint32_t uid = strtoul(messageIdString.get(), nullptr, 10);
+ int32_t index;
+ bool foundIt;
+ imapMessageFlagsType flags =
+ m_flagState->GetMessageFlagsFromUID(uid, &foundIt, &index);
+ if (foundIt) {
+ flags |= kImapMsgSeenFlag;
+ m_flagState->SetMessageFlags(index, flags);
+ }
+ }
+ }
+ } break;
+ case nsIImapUrl::nsImapExpungeFolder:
+ Expunge();
+ // note fall through to next cases.
+ [[fallthrough]];
+ case nsIImapUrl::nsImapSelectFolder:
+ case nsIImapUrl::nsImapSelectNoopFolder:
+ if (!moreHeadersToDownload) ProcessMailboxUpdate(true);
+ break;
+ case nsIImapUrl::nsImapMsgHeader: {
+ nsCString messageIds;
+ m_runningUrl->GetListOfMessageIds(messageIds);
+
+ FetchMessage(messageIds, kHeadersRFC822andUid);
+ // if we explicitly ask for headers, as opposed to getting them as a
+ // result of selecting the folder, or biff, send the
+ // headerFetchCompleted notification to flush out the header cache.
+ HeaderFetchCompleted();
+ } break;
+ case nsIImapUrl::nsImapSearch: {
+ nsAutoCString searchCriteriaString;
+ m_runningUrl->CreateSearchCriteriaString(
+ getter_Copies(searchCriteriaString));
+ Search(searchCriteriaString.get(), bMessageIdsAreUids);
+ // drop the results on the floor for now
+ } break;
+ case nsIImapUrl::nsImapUserDefinedMsgCommand: {
+ nsCString messageIdString;
+ nsCString command;
+
+ m_runningUrl->GetCommand(command);
+ m_runningUrl->GetListOfMessageIds(messageIdString);
+ IssueUserDefinedMsgCommand(command.get(), messageIdString.get());
+ } break;
+ case nsIImapUrl::nsImapUserDefinedFetchAttribute: {
+ nsCString messageIdString;
+ nsCString attribute;
+
+ m_runningUrl->GetCustomAttributeToFetch(attribute);
+ m_runningUrl->GetListOfMessageIds(messageIdString);
+ FetchMsgAttribute(messageIdString, attribute);
+ } break;
+ case nsIImapUrl::nsImapMsgStoreCustomKeywords: {
+ // If the server doesn't support user defined flags, don't try to
+ // define/set new ones. But if this is an attempt by TB to set or
+ // reset flags "Junk" or "NonJunk", change "Junk" or "NonJunk" to
+ // "$Junk" or "$NotJunk" respectively and store the modified flag
+ // name if the server doesn't support storing user defined flags
+ // and the server does allow storing the almost-standard flag names
+ // "$Junk" and "$NotJunk". Yahoo imap server is an example of this.
+ uint16_t userFlags = 0;
+ GetSupportedUserFlags(&userFlags);
+ bool userDefinedSettable = userFlags & kImapMsgSupportUserFlag;
+ bool stdJunkOk = GetServerStateParser().IsStdJunkNotJunkUseOk();
+
+ nsCString messageIdString;
+ nsCString addFlags;
+ nsCString subtractFlags;
+
+ m_runningUrl->GetListOfMessageIds(messageIdString);
+ m_runningUrl->GetCustomAddFlags(addFlags);
+ m_runningUrl->GetCustomSubtractFlags(subtractFlags);
+ if (!addFlags.IsEmpty()) {
+ if (!userDefinedSettable) {
+ if (stdJunkOk) {
+ if (addFlags.EqualsIgnoreCase("junk"))
+ addFlags = "$Junk";
+ else if (addFlags.EqualsIgnoreCase("nonjunk"))
+ addFlags = "$NotJunk";
+ else
+ break;
+ } else
+ break;
+ }
+ nsAutoCString storeString("+FLAGS (");
+ storeString.Append(addFlags);
+ storeString.Append(')');
+ Store(messageIdString, storeString.get(), true);
+ }
+ if (!subtractFlags.IsEmpty()) {
+ if (!userDefinedSettable) {
+ if (stdJunkOk) {
+ if (subtractFlags.EqualsIgnoreCase("junk"))
+ subtractFlags = "$Junk";
+ else if (subtractFlags.EqualsIgnoreCase("nonjunk"))
+ subtractFlags = "$NotJunk";
+ else
+ break;
+ } else
+ break;
+ }
+ nsAutoCString storeString("-FLAGS (");
+ storeString.Append(subtractFlags);
+ storeString.Append(')');
+ Store(messageIdString, storeString.get(), true);
+ }
+ } break;
+ case nsIImapUrl::nsImapDeleteMsg: {
+ nsCString messageIdString;
+ m_runningUrl->GetListOfMessageIds(messageIdString);
+
+ ProgressEventFunctionUsingName(
+ HandlingMultipleMessages(messageIdString)
+ ? "imapDeletingMessages"
+ : "imapDeletingMessage");
+
+ Store(messageIdString, "+FLAGS (\\Deleted)", bMessageIdsAreUids);
+
+ if (GetServerStateParser().LastCommandSuccessful()) {
+ nsCString canonicalName;
+ const char* selectedMailboxName =
+ GetServerStateParser().GetSelectedMailboxName();
+ if (selectedMailboxName) {
+ m_runningUrl->AllocateCanonicalPath(
+ selectedMailboxName, kOnlineHierarchySeparatorUnknown,
+ getter_Copies(canonicalName));
+ }
+
+ if (m_imapMessageSink)
+ m_imapMessageSink->NotifyMessageDeleted(
+ canonicalName.get(), false, messageIdString.get());
+ // notice we don't wait for this to finish...
+ } else
+ HandleMemoryFailure();
+ } break;
+ case nsIImapUrl::nsImapDeleteFolderAndMsgs:
+ DeleteFolderAndMsgs(mailboxName.get());
+ break;
+ case nsIImapUrl::nsImapDeleteAllMsgs: {
+ uint32_t numberOfMessages = GetServerStateParser().NumberOfMessages();
+ if (numberOfMessages) {
+ Store("1:*"_ns, "+FLAGS.SILENT (\\Deleted)",
+ false); // use sequence #'s
+
+ if (GetServerStateParser().LastCommandSuccessful())
+ Expunge(); // expunge messages with deleted flag
+ if (GetServerStateParser().LastCommandSuccessful()) {
+ nsCString canonicalName;
+ const char* selectedMailboxName =
+ GetServerStateParser().GetSelectedMailboxName();
+ if (selectedMailboxName) {
+ m_runningUrl->AllocateCanonicalPath(
+ selectedMailboxName, kOnlineHierarchySeparatorUnknown,
+ getter_Copies(canonicalName));
+ }
+
+ if (m_imapMessageSink)
+ m_imapMessageSink->NotifyMessageDeleted(canonicalName.get(),
+ true, nullptr);
+ }
+ }
+ bool deleteSelf = false;
+ DeleteSubFolders(mailboxName.get(), deleteSelf); // don't delete self
+ } break;
+ case nsIImapUrl::nsImapAppendDraftFromFile: {
+ OnAppendMsgFromFile();
+ } break;
+ case nsIImapUrl::nsImapAddMsgFlags: {
+ nsCString messageIdString;
+ m_runningUrl->GetListOfMessageIds(messageIdString);
+
+ ProcessStoreFlags(messageIdString, bMessageIdsAreUids, msgFlags,
+ true);
+ } break;
+ case nsIImapUrl::nsImapSubtractMsgFlags: {
+ nsCString messageIdString;
+ m_runningUrl->GetListOfMessageIds(messageIdString);
+
+ ProcessStoreFlags(messageIdString, bMessageIdsAreUids, msgFlags,
+ false);
+ } break;
+ case nsIImapUrl::nsImapSetMsgFlags: {
+ nsCString messageIdString;
+ m_runningUrl->GetListOfMessageIds(messageIdString);
+
+ ProcessStoreFlags(messageIdString, bMessageIdsAreUids, msgFlags,
+ true);
+ ProcessStoreFlags(messageIdString, bMessageIdsAreUids, ~msgFlags,
+ false);
+ } break;
+ case nsIImapUrl::nsImapBiff:
+ PeriodicBiff();
+ break;
+ case nsIImapUrl::nsImapOnlineCopy:
+ case nsIImapUrl::nsImapOnlineMove: {
+ nsCString messageIdString;
+ m_runningUrl->GetListOfMessageIds(messageIdString);
+ char* destinationMailbox =
+ OnCreateServerDestinationFolderPathString();
+
+ if (destinationMailbox) {
+ if (m_imapAction == nsIImapUrl::nsImapOnlineMove) {
+ if (HandlingMultipleMessages(messageIdString))
+ ProgressEventFunctionUsingNameWithString("imapMovingMessages",
+ destinationMailbox);
+ else
+ ProgressEventFunctionUsingNameWithString("imapMovingMessage",
+ destinationMailbox);
+ } else {
+ if (HandlingMultipleMessages(messageIdString))
+ ProgressEventFunctionUsingNameWithString("imapCopyingMessages",
+ destinationMailbox);
+ else
+ ProgressEventFunctionUsingNameWithString("imapCopyingMessage",
+ destinationMailbox);
+ }
+ Copy(messageIdString.get(), destinationMailbox, bMessageIdsAreUids);
+ PR_FREEIF(destinationMailbox);
+ ImapOnlineCopyState copyState;
+ if (DeathSignalReceived())
+ copyState = ImapOnlineCopyStateType::kInterruptedState;
+ else
+ copyState = GetServerStateParser().LastCommandSuccessful()
+ ? (ImapOnlineCopyState)
+ ImapOnlineCopyStateType::kSuccessfulCopy
+ : (ImapOnlineCopyState)
+ ImapOnlineCopyStateType::kFailedCopy;
+ if (m_imapMailFolderSink)
+ m_imapMailFolderSink->OnlineCopyCompleted(this, copyState);
+ // Don't mark message 'Deleted' for AOL servers or standard imap
+ // servers that support MOVE since we already issued an 'xaol-move'
+ // or 'move' command.
+ if (GetServerStateParser().LastCommandSuccessful() &&
+ (m_imapAction == nsIImapUrl::nsImapOnlineMove) &&
+ !(GetServerStateParser().ServerIsAOLServer() ||
+ GetServerStateParser().GetCapabilityFlag() &
+ kHasMoveCapability)) {
+ // Simulate MOVE for servers that don't support MOVE: do
+ // COPY-DELETE-EXPUNGE.
+ Store(messageIdString, "+FLAGS (\\Deleted \\Seen)",
+ bMessageIdsAreUids);
+ bool storeSuccessful =
+ GetServerStateParser().LastCommandSuccessful();
+ if (storeSuccessful) {
+ if (gExpungeAfterDelete) {
+ // This will expunge all emails marked as deleted in mailbox,
+ // not just the ones marked as deleted above.
+ Expunge();
+ } else {
+ // Check if UIDPLUS capable so we can just expunge emails we
+ // just copied and marked as deleted. This prevents expunging
+ // emails that other clients may have marked as deleted in the
+ // mailbox and don't want them to disappear. Only do
+ // UidExpunge() when user selected delete method is "Move it
+ // to this folder" or "Remove it immediately", not when the
+ // delete method is "Just mark it as deleted".
+ if (!GetShowDeletedMessages() &&
+ (GetServerStateParser().GetCapabilityFlag() &
+ kUidplusCapability)) {
+ UidExpunge(messageIdString);
+ }
+ }
+ }
+ if (m_imapMailFolderSink) {
+ copyState = storeSuccessful
+ ? (ImapOnlineCopyState)
+ ImapOnlineCopyStateType::kSuccessfulDelete
+ : (ImapOnlineCopyState)
+ ImapOnlineCopyStateType::kFailedDelete;
+ m_imapMailFolderSink->OnlineCopyCompleted(this, copyState);
+ }
+ }
+ } else
+ HandleMemoryFailure();
+ } break;
+ case nsIImapUrl::nsImapOnlineToOfflineCopy:
+ case nsIImapUrl::nsImapOnlineToOfflineMove: {
+ nsCString messageIdString;
+ nsresult rv = m_runningUrl->GetListOfMessageIds(messageIdString);
+ if (NS_SUCCEEDED(rv)) {
+ SetProgressString(IMAP_MESSAGES_STRING_INDEX);
+ m_progressCurrentNumber[m_stringIndex] = 0;
+ m_progressExpectedNumber =
+ CountMessagesInIdString(messageIdString.get());
+
+ FetchMessage(messageIdString, kEveryThingRFC822Peek);
+
+ SetProgressString(IMAP_EMPTY_STRING_INDEX);
+ if (m_imapMailFolderSink) {
+ ImapOnlineCopyState copyStatus;
+ copyStatus = GetServerStateParser().LastCommandSuccessful()
+ ? ImapOnlineCopyStateType::kSuccessfulCopy
+ : ImapOnlineCopyStateType::kFailedCopy;
+
+ m_imapMailFolderSink->OnlineCopyCompleted(this, copyStatus);
+ if (GetServerStateParser().LastCommandSuccessful() &&
+ (m_imapAction == nsIImapUrl::nsImapOnlineToOfflineMove)) {
+ Store(messageIdString, "+FLAGS (\\Deleted \\Seen)",
+ bMessageIdsAreUids);
+ if (GetServerStateParser().LastCommandSuccessful()) {
+ copyStatus = ImapOnlineCopyStateType::kSuccessfulDelete;
+ if (gExpungeAfterDelete) Expunge();
+ } else
+ copyStatus = ImapOnlineCopyStateType::kFailedDelete;
+
+ m_imapMailFolderSink->OnlineCopyCompleted(this, copyStatus);
+ }
+ }
+ } else
+ HandleMemoryFailure();
+ } break;
+ default:
+ if (GetServerStateParser().LastCommandSuccessful() && !uidValidityOk)
+ ProcessMailboxUpdate(false); // handle uidvalidity change
+ break;
+ }
+ }
+ } else if (!DeathSignalReceived())
+ HandleMemoryFailure();
+}
+
+nsresult nsImapProtocol::BeginMessageDownLoad(
+ uint32_t total_message_size, // for user, headers and body
+ const char* content_type) {
+ nsresult rv = NS_OK;
+ char* sizeString = PR_smprintf("OPEN Size: %ld", total_message_size);
+ Log("STREAM", sizeString, "Begin Message Download Stream");
+ PR_Free(sizeString);
+ // start counting how many bytes we see in this message after all
+ // transformations
+ m_bytesToChannel = 0;
+
+ if (content_type) {
+ m_fromHeaderSeen = false;
+ if (GetServerStateParser().GetDownloadingHeaders()) {
+ // if we get multiple calls to BeginMessageDownload w/o intervening
+ // calls to NormalEndMessageDownload or Abort, then we're just
+ // going to fake a NormalMessageEndDownload. This will most likely
+ // cause an empty header to get written to the db, and the user
+ // will have to delete the empty header themselves, which
+ // should remove the message from the server as well.
+ if (m_curHdrInfo) NormalMessageEndDownload();
+ if (!m_curHdrInfo) m_curHdrInfo = m_hdrDownloadCache->StartNewHdr();
+ if (m_curHdrInfo) m_curHdrInfo->SetMsgSize(total_message_size);
+ return NS_OK;
+ }
+ // if we have a mock channel, that means we have a channel listener who
+ // wants the message. So set up a pipe. We'll write the message into one end
+ // of the pipe and they will read it out of the other end.
+ if (m_channelListener) {
+ // create a pipe to pump the message into...the output will go to whoever
+ // is consuming the message display
+ // we create an "infinite" pipe in case we get extremely long lines from
+ // the imap server, and the consumer is waiting for a whole line
+ nsCOMPtr<nsIPipe> pipe = do_CreateInstance("@mozilla.org/pipe;1");
+ rv = pipe->Init(false, false, 4096, PR_UINT32_MAX);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // These always succeed because the pipe is initialized above.
+ MOZ_ALWAYS_SUCCEEDS(
+ pipe->GetInputStream(getter_AddRefs(m_channelInputStream)));
+ MOZ_ALWAYS_SUCCEEDS(
+ pipe->GetOutputStream(getter_AddRefs(m_channelOutputStream)));
+ }
+ // else, if we are saving the message to disk!
+ else if (m_imapMessageSink /* && m_imapAction == nsIImapUrl::nsImapSaveMessageToDisk */)
+ {
+ // we get here when download the inbox for offline use
+ nsCOMPtr<nsIFile> file;
+ bool addDummyEnvelope = true;
+ nsCOMPtr<nsIMsgMessageUrl> msgurl = do_QueryInterface(m_runningUrl);
+ msgurl->GetMessageFile(getter_AddRefs(file));
+ msgurl->GetAddDummyEnvelope(&addDummyEnvelope);
+ if (file)
+ rv = m_imapMessageSink->SetupMsgWriteStream(file, addDummyEnvelope);
+ }
+ if (m_imapMailFolderSink && m_runningUrl) {
+ nsCOMPtr<nsISupports> copyState;
+ if (m_runningUrl) {
+ m_runningUrl->GetCopyState(getter_AddRefs(copyState));
+ if (copyState) // only need this notification during copy
+ {
+ nsCOMPtr<nsIMsgMailNewsUrl> mailurl = do_QueryInterface(m_runningUrl);
+ m_imapMailFolderSink->StartMessage(mailurl);
+ }
+ }
+ }
+
+ } else
+ HandleMemoryFailure();
+ return rv;
+}
+
+void nsImapProtocol::GetShouldDownloadAllHeaders(bool* aResult) {
+ if (m_imapMailFolderSink)
+ m_imapMailFolderSink->GetShouldDownloadAllHeaders(aResult);
+}
+
+void nsImapProtocol::GetArbitraryHeadersToDownload(nsCString& aResult) {
+ if (m_imapServerSink) m_imapServerSink->GetArbitraryHeaders(aResult);
+}
+
+void nsImapProtocol::AdjustChunkSize() {
+ int32_t deltaInSeconds;
+
+ m_endTime = PR_Now();
+ PRTime2Seconds(m_endTime - m_startTime, &deltaInSeconds);
+ m_trackingTime = false;
+ if (deltaInSeconds < 0) return; // bogus for some reason
+
+ if (deltaInSeconds <= m_tooFastTime && m_curFetchSize >= m_chunkSize) {
+ m_chunkSize += m_chunkAddSize;
+ m_chunkThreshold = m_chunkSize + (m_chunkSize / 2);
+ // we used to have a max for the chunk size - I don't think that's needed.
+ } else if (deltaInSeconds <= m_idealTime)
+ return;
+ else {
+ if (m_chunkSize > m_chunkStartSize)
+ m_chunkSize = m_chunkStartSize;
+ else if (m_chunkSize > (m_chunkAddSize * 2))
+ m_chunkSize -= m_chunkAddSize;
+ m_chunkThreshold = m_chunkSize + (m_chunkSize / 2);
+ }
+ // remember these new values globally so new connections
+ // can take advantage of them.
+ if (gChunkSize != m_chunkSize) {
+ // will cause chunk size pref to be written in CloseStream.
+ gChunkSizeDirty = true;
+ gChunkSize = m_chunkSize;
+ gChunkThreshold = m_chunkThreshold;
+ }
+}
+
+// authenticated state commands
+
+// escape any backslashes or quotes. Backslashes are used a lot with our NT
+// server
+void nsImapProtocol::CreateEscapedMailboxName(const char* rawName,
+ nsCString& escapedName) {
+ escapedName.Assign(rawName);
+
+ for (int32_t strIndex = 0; *rawName; strIndex++) {
+ char currentChar = *rawName++;
+ if ((currentChar == '\\') || (currentChar == '\"'))
+ escapedName.Insert('\\', strIndex++);
+ }
+}
+void nsImapProtocol::SelectMailbox(const char* mailboxName) {
+ ProgressEventFunctionUsingNameWithString("imapStatusSelectingMailbox",
+ mailboxName);
+ IncrementCommandTagNumber();
+
+ m_closeNeededBeforeSelect = false; // initial value
+ GetServerStateParser().ResetFlagInfo();
+ nsCString escapedName;
+ CreateEscapedMailboxName(mailboxName, escapedName);
+ nsCString commandBuffer(GetServerCommandTag());
+ commandBuffer.AppendLiteral(" select \"");
+ commandBuffer.Append(escapedName.get());
+ commandBuffer.Append('"');
+ if (UseCondStore()) commandBuffer.AppendLiteral(" (CONDSTORE)");
+ commandBuffer.Append(CRLF);
+
+ nsresult res;
+ res = SendData(commandBuffer.get());
+ if (NS_FAILED(res)) return;
+ ParseIMAPandCheckForNewMail();
+
+ // Save the folder sink obtained in SetupSinkProxy() for whatever URL just
+ // caused this SELECT. Needed so idle and noop responses are using the correct
+ // folder when detecting changed flags or new messages.
+ m_imapMailFolderSinkSelected = m_imapMailFolderSink;
+ MOZ_ASSERT(m_imapMailFolderSinkSelected);
+ Log("SelectMailbox", nullptr, "got m_imapMailFolderSinkSelected");
+
+ int32_t numOfMessagesInFlagState = 0;
+ nsImapAction imapAction;
+ m_flagState->GetNumberOfMessages(&numOfMessagesInFlagState);
+ res = m_runningUrl->GetImapAction(&imapAction);
+ // if we've selected a mailbox, and we're not going to do an update because of
+ // the url type, but don't have the flags, go get them!
+ if (GetServerStateParser().LastCommandSuccessful() && NS_SUCCEEDED(res) &&
+ imapAction != nsIImapUrl::nsImapSelectFolder &&
+ imapAction != nsIImapUrl::nsImapExpungeFolder &&
+ imapAction != nsIImapUrl::nsImapLiteSelectFolder &&
+ imapAction != nsIImapUrl::nsImapDeleteAllMsgs &&
+ ((GetServerStateParser().NumberOfMessages() !=
+ numOfMessagesInFlagState) &&
+ (numOfMessagesInFlagState == 0))) {
+ ProcessMailboxUpdate(false);
+ }
+}
+
+void nsImapProtocol::FetchMsgAttribute(const nsCString& messageIds,
+ const nsCString& attribute) {
+ IncrementCommandTagNumber();
+
+ nsAutoCString commandString(GetServerCommandTag());
+ commandString.AppendLiteral(" UID fetch ");
+ commandString.Append(messageIds);
+ commandString.AppendLiteral(" (");
+ commandString.Append(attribute);
+ commandString.AppendLiteral(")" CRLF);
+ nsresult rv = SendData(commandString.get());
+
+ if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail(commandString.get());
+ GetServerStateParser().SetFetchingFlags(false);
+ // Always clear this flag after every fetch.
+ m_fetchingWholeMessage = false;
+}
+
+// this routine is used to fetch a message or messages, or headers for a
+// message...
+
+void nsImapProtocol::FallbackToFetchWholeMsg(const nsCString& messageId,
+ uint32_t messageSize) {
+ if (m_imapMessageSink && m_runningUrl) {
+ bool shouldStoreMsgOffline;
+ m_runningUrl->GetStoreOfflineOnFallback(&shouldStoreMsgOffline);
+ m_runningUrl->SetStoreResultsOffline(shouldStoreMsgOffline);
+ }
+ FetchTryChunking(messageId,
+ m_imapAction == nsIImapUrl::nsImapMsgFetchPeek
+ ? kEveryThingRFC822Peek
+ : kEveryThingRFC822,
+ true, nullptr, messageSize, true);
+}
+
+void nsImapProtocol::FetchMessage(const nsCString& messageIds,
+ nsIMAPeFetchFields whatToFetch,
+ const char* fetchModifier, uint32_t startByte,
+ uint32_t numBytes, char* part) {
+ IncrementCommandTagNumber();
+
+ nsCString commandString;
+ commandString = "%s UID fetch";
+
+ switch (whatToFetch) {
+ case kEveryThingRFC822:
+ m_flagChangeCount++;
+ m_fetchingWholeMessage = true;
+ if (m_trackingTime) AdjustChunkSize(); // we started another segment
+ m_startTime = PR_Now(); // save start of download time
+ m_trackingTime = true;
+ MOZ_LOG(IMAP, LogLevel::Debug,
+ ("FetchMessage everything: curFetchSize %u numBytes %u",
+ m_curFetchSize, numBytes));
+ if (numBytes > 0) m_curFetchSize = numBytes;
+
+ if (GetServerStateParser().ServerHasIMAP4Rev1Capability()) {
+ if (GetServerStateParser().GetCapabilityFlag() & kHasXSenderCapability)
+ commandString.AppendLiteral(" %s (XSENDER UID RFC822.SIZE BODY[]");
+ else
+ commandString.AppendLiteral(" %s (UID RFC822.SIZE BODY[]");
+ } else {
+ if (GetServerStateParser().GetCapabilityFlag() & kHasXSenderCapability)
+ commandString.AppendLiteral(" %s (XSENDER UID RFC822.SIZE RFC822");
+ else
+ commandString.AppendLiteral(" %s (UID RFC822.SIZE RFC822");
+ }
+ if (numBytes > 0) {
+ // if we are retrieving chunks
+ char* byterangeString = PR_smprintf("<%ld.%ld>", startByte, numBytes);
+ if (byterangeString) {
+ commandString.Append(byterangeString);
+ PR_Free(byterangeString);
+ }
+ }
+ commandString.Append(')');
+
+ break;
+
+ case kEveryThingRFC822Peek: {
+ MOZ_LOG(IMAP, LogLevel::Debug,
+ ("FetchMessage peek: curFetchSize %u numBytes %u", m_curFetchSize,
+ numBytes));
+ if (numBytes > 0) m_curFetchSize = numBytes;
+ const char* formatString = "";
+ eIMAPCapabilityFlags server_capabilityFlags =
+ GetServerStateParser().GetCapabilityFlag();
+
+ m_fetchingWholeMessage = true;
+ if (server_capabilityFlags & kIMAP4rev1Capability) {
+ // use body[].peek since rfc822.peek is not in IMAP4rev1
+ if (server_capabilityFlags & kHasXSenderCapability)
+ formatString = " %s (XSENDER UID RFC822.SIZE BODY.PEEK[]";
+ else
+ formatString = " %s (UID RFC822.SIZE BODY.PEEK[]";
+ } else {
+ if (server_capabilityFlags & kHasXSenderCapability)
+ formatString = " %s (XSENDER UID RFC822.SIZE RFC822.peek";
+ else
+ formatString = " %s (UID RFC822.SIZE RFC822.peek";
+ }
+
+ commandString.Append(formatString);
+ if (numBytes > 0) {
+ // if we are retrieving chunks
+ char* byterangeString = PR_smprintf("<%ld.%ld>", startByte, numBytes);
+ if (byterangeString) {
+ commandString.Append(byterangeString);
+ PR_Free(byterangeString);
+ }
+ }
+ commandString.Append(')');
+ } break;
+ case kHeadersRFC822andUid:
+ if (GetServerStateParser().ServerHasIMAP4Rev1Capability()) {
+ eIMAPCapabilityFlags server_capabilityFlags =
+ GetServerStateParser().GetCapabilityFlag();
+ bool aolImapServer =
+ ((server_capabilityFlags & kAOLImapCapability) != 0);
+ bool downloadAllHeaders = false;
+ // checks if we're filtering on "any header" or running a spam filter
+ // requiring all headers
+ GetShouldDownloadAllHeaders(&downloadAllHeaders);
+
+ if (!downloadAllHeaders) // if it's ok -- no filters on any header,
+ // etc.
+ {
+ char* headersToDL = nullptr;
+ char* what = nullptr;
+ const char* dbHeaders =
+ (gUseEnvelopeCmd) ? IMAP_DB_HEADERS : IMAP_ENV_AND_DB_HEADERS;
+ nsCString arbitraryHeaders;
+ GetArbitraryHeadersToDownload(arbitraryHeaders);
+ for (uint32_t i = 0; i < mCustomDBHeaders.Length(); i++) {
+ if (!FindInReadable(mCustomDBHeaders[i], arbitraryHeaders,
+ nsCaseInsensitiveCStringComparator)) {
+ if (!arbitraryHeaders.IsEmpty()) arbitraryHeaders.Append(' ');
+ arbitraryHeaders.Append(mCustomDBHeaders[i]);
+ }
+ }
+ for (uint32_t i = 0; i < mCustomHeaders.Length(); i++) {
+ if (!FindInReadable(mCustomHeaders[i], arbitraryHeaders,
+ nsCaseInsensitiveCStringComparator)) {
+ if (!arbitraryHeaders.IsEmpty()) arbitraryHeaders.Append(' ');
+ arbitraryHeaders.Append(mCustomHeaders[i]);
+ }
+ }
+ if (arbitraryHeaders.IsEmpty())
+ headersToDL = strdup(dbHeaders);
+ else
+ headersToDL =
+ PR_smprintf("%s %s", dbHeaders, arbitraryHeaders.get());
+
+ if (gUseEnvelopeCmd)
+ what = PR_smprintf(" ENVELOPE BODY.PEEK[HEADER.FIELDS (%s)])",
+ headersToDL);
+ else
+ what = PR_smprintf(" BODY.PEEK[HEADER.FIELDS (%s)])", headersToDL);
+ free(headersToDL);
+ if (what) {
+ commandString.AppendLiteral(" %s (UID ");
+ if (m_isGmailServer)
+ commandString.AppendLiteral("X-GM-MSGID X-GM-THRID X-GM-LABELS ");
+ if (aolImapServer)
+ commandString.AppendLiteral(" XAOL.SIZE");
+ else
+ commandString.AppendLiteral("RFC822.SIZE");
+ commandString.AppendLiteral(" FLAGS");
+ commandString.Append(what);
+ PR_Free(what);
+ } else {
+ commandString.AppendLiteral(
+ " %s (UID RFC822.SIZE BODY.PEEK[HEADER] FLAGS)");
+ }
+ } else
+ commandString.AppendLiteral(
+ " %s (UID RFC822.SIZE BODY.PEEK[HEADER] FLAGS)");
+ } else
+ commandString.AppendLiteral(
+ " %s (UID RFC822.SIZE RFC822.HEADER FLAGS)");
+ break;
+ case kUid:
+ commandString.AppendLiteral(" %s (UID)");
+ break;
+ case kFlags:
+ GetServerStateParser().SetFetchingFlags(true);
+ commandString.AppendLiteral(" %s (FLAGS)");
+ break;
+ case kRFC822Size:
+ commandString.AppendLiteral(" %s (RFC822.SIZE)");
+ break;
+ case kBodyStart: {
+ int32_t numBytesToFetch;
+ m_runningUrl->GetNumBytesToFetch(&numBytesToFetch);
+
+ commandString.AppendLiteral(
+ " %s (UID BODY.PEEK[HEADER.FIELDS (Content-Type "
+ "Content-Transfer-Encoding)] BODY.PEEK[TEXT]<0.");
+ commandString.AppendInt(numBytesToFetch);
+ commandString.AppendLiteral(">)");
+ } break;
+ case kRFC822HeadersOnly:
+ if (GetServerStateParser().ServerHasIMAP4Rev1Capability()) {
+ if (part) {
+ commandString.AppendLiteral(" %s (BODY[");
+ char* what = PR_smprintf("%s.HEADER])", part);
+ if (what) {
+ commandString.Append(what);
+ PR_Free(what);
+ } else
+ HandleMemoryFailure();
+ } else {
+ // headers for the top-level message
+ commandString.AppendLiteral(" %s (BODY[HEADER])");
+ }
+ } else
+ commandString.AppendLiteral(" %s (RFC822.HEADER)");
+ break;
+ case kMIMEPart:
+ commandString.AppendLiteral(" %s (BODY.PEEK[%s]");
+ if (numBytes > 0) {
+ // if we are retrieving chunks
+ char* byterangeString = PR_smprintf("<%ld.%ld>", startByte, numBytes);
+ if (byterangeString) {
+ commandString.Append(byterangeString);
+ PR_Free(byterangeString);
+ }
+ }
+ commandString.Append(')');
+ break;
+ case kMIMEHeader:
+ commandString.AppendLiteral(" %s (BODY[%s.MIME])");
+ break;
+ }
+
+ if (fetchModifier) commandString.Append(fetchModifier);
+
+ commandString.Append(CRLF);
+
+ // since messageIds can be infinitely long, use a dynamic buffer rather than
+ // the fixed one
+ const char* commandTag = GetServerCommandTag();
+ int protocolStringSize = commandString.Length() + messageIds.Length() +
+ PL_strlen(commandTag) + 1 +
+ (part ? PL_strlen(part) : 0);
+ char* protocolString = (char*)PR_CALLOC(protocolStringSize);
+
+ if (protocolString) {
+ char* cCommandStr = ToNewCString(commandString);
+ if ((whatToFetch == kMIMEPart) || (whatToFetch == kMIMEHeader)) {
+ PR_snprintf(protocolString, // string to create
+ protocolStringSize, // max size
+ cCommandStr, // format string
+ commandTag, // command tag
+ messageIds.get(), part);
+ } else {
+ PR_snprintf(protocolString, // string to create
+ protocolStringSize, // max size
+ cCommandStr, // format string
+ commandTag, // command tag
+ messageIds.get());
+ }
+
+ nsresult rv = SendData(protocolString);
+
+ free(cCommandStr);
+ if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail(protocolString);
+ PR_Free(protocolString);
+ GetServerStateParser().SetFetchingFlags(false);
+ // Always clear this flag after every fetch.
+ m_fetchingWholeMessage = false;
+ if (GetServerStateParser().LastCommandSuccessful() && CheckNeeded())
+ Check();
+ } else
+ HandleMemoryFailure();
+}
+
+void nsImapProtocol::FetchTryChunking(const nsCString& messageIds,
+ nsIMAPeFetchFields whatToFetch,
+ bool idIsUid, char* part,
+ uint32_t downloadSize, bool tryChunking) {
+ GetServerStateParser().SetTotalDownloadSize(downloadSize);
+ MOZ_LOG(IMAP, LogLevel::Debug,
+ ("FetchTryChunking: curFetchSize %u", downloadSize));
+ MOZ_ASSERT(!part, "fetching a part should no longer occur");
+ m_curFetchSize = downloadSize; // we'll change this if chunking.
+ if (m_fetchByChunks && tryChunking &&
+ GetServerStateParser().ServerHasIMAP4Rev1Capability() &&
+ (downloadSize > (uint32_t)m_chunkThreshold)) {
+ uint32_t startByte = 0;
+ m_curFetchSize = m_chunkSize;
+ GetServerStateParser().ClearLastFetchChunkReceived();
+ while (!DeathSignalReceived() && !GetPseudoInterrupted() &&
+ !GetServerStateParser().GetLastFetchChunkReceived() &&
+ GetServerStateParser().ContinueParse()) {
+ GetServerStateParser().ClearNumBytesFetched();
+ // This chunk is a fetch of m_chunkSize bytes. But m_chunkSize can be
+ // changed inside FetchMessage(). Save the original value of m_chunkSize
+ // to set the correct offset (startByte) for the next chunk.
+ int32_t bytesFetched = m_chunkSize;
+ FetchMessage(messageIds, whatToFetch, nullptr, startByte, bytesFetched,
+ part);
+ if (!GetServerStateParser().GetNumBytesFetched()) {
+ // Fetch returned zero bytes chunk from server. This occurs if the
+ // message was expunged during the fetch.
+ MOZ_LOG(IMAP, LogLevel::Info,
+ ("FetchTryChunking: Zero bytes chunk fetched; message probably "
+ "expunged"));
+ break;
+ }
+ startByte += bytesFetched;
+ }
+
+ // Only abort the stream if this is a normal message download
+ // Otherwise, let the body shell abort the stream.
+ if ((whatToFetch == kEveryThingRFC822) &&
+ ((startByte > 0 && (startByte < downloadSize) &&
+ (DeathSignalReceived() || GetPseudoInterrupted())) ||
+ !GetServerStateParser().ContinueParse())) {
+ AbortMessageDownLoad();
+ PseudoInterrupt(false);
+ }
+ } else {
+ // small message, or (we're not chunking and not doing bodystructure),
+ // or the server is not rev1.
+ // Just fetch the whole thing.
+ FetchMessage(messageIds, whatToFetch, nullptr, 0, 0, part);
+ }
+}
+
+void nsImapProtocol::PostLineDownLoadEvent(const char* line,
+ uint32_t uidOfMessage) {
+ if (!GetServerStateParser().GetDownloadingHeaders()) {
+ uint32_t byteCount = PL_strlen(line);
+ bool echoLineToMessageSink = false;
+ // if we have a channel listener, then just spool the message
+ // directly to the listener
+ if (m_channelListener) {
+ uint32_t count = 0;
+ if (m_channelOutputStream) {
+ nsresult rv = m_channelOutputStream->Write(line, byteCount, &count);
+ NS_ASSERTION(count == byteCount,
+ "IMAP channel pipe couldn't buffer entire write");
+ if (NS_SUCCEEDED(rv)) {
+ m_channelListener->OnDataAvailable(m_mockChannel,
+ m_channelInputStream, 0, count);
+ }
+ // else some sort of explosion?
+ }
+ }
+ if (m_runningUrl)
+ m_runningUrl->GetStoreResultsOffline(&echoLineToMessageSink);
+
+ m_bytesToChannel += byteCount;
+ if (m_imapMessageSink && line && echoLineToMessageSink &&
+ !GetPseudoInterrupted())
+ m_imapMessageSink->ParseAdoptedMsgLine(line, uidOfMessage, m_runningUrl);
+ }
+ // ***** We need to handle the pseudo interrupt here *****
+}
+
+// Handle a line seen by the parser.
+// * The argument |lineCopy| must be nullptr or should contain the same string
+// as |line|. |lineCopy| will be modified.
+// * A line may be passed by parts, e.g., "part1 part2\r\n" may be passed as
+// HandleMessageDownLoadLine("part 1 ", 1);
+// HandleMessageDownLoadLine("part 2\r\n", 0);
+// However, it is assumed that a CRLF or a CRCRLF is never split (i.e., this
+// is ensured *before* invoking this method).
+void nsImapProtocol::HandleMessageDownLoadLine(const char* line,
+ bool isPartialLine,
+ char* lineCopy) {
+ NS_ENSURE_TRUE_VOID(line);
+ NS_ASSERTION(lineCopy == nullptr || !PL_strcmp(line, lineCopy),
+ "line and lineCopy must contain the same string");
+ const char* messageLine = line;
+ uint32_t lineLength = strlen(messageLine);
+ const char* cEndOfLine = messageLine + lineLength;
+ char* localMessageLine = nullptr;
+
+ // If we obtain a partial line (due to fetching by chunks), we do not
+ // add/modify the end-of-line terminator.
+ if (!isPartialLine) {
+ // Change this line to native line termination, duplicate if necessary.
+ // Do not assume that the line really ends in CRLF
+ // to start with, even though it is supposed to be RFC822
+
+ // normalize line endings to CRLF unless we are saving the message to disk
+ bool canonicalLineEnding = true;
+ nsCOMPtr<nsIMsgMessageUrl> msgUrl = do_QueryInterface(m_runningUrl);
+
+ if (m_imapAction == nsIImapUrl::nsImapSaveMessageToDisk && msgUrl)
+ msgUrl->GetCanonicalLineEnding(&canonicalLineEnding);
+
+ NS_ASSERTION(MSG_LINEBREAK_LEN == 1 || (MSG_LINEBREAK_LEN == 2 &&
+ !PL_strcmp(CRLF, MSG_LINEBREAK)),
+ "violated assumptions on MSG_LINEBREAK");
+ if (MSG_LINEBREAK_LEN == 1 && !canonicalLineEnding) {
+ bool lineEndsWithCRorLF =
+ lineLength >= 1 && (cEndOfLine[-1] == '\r' || cEndOfLine[-1] == '\n');
+ char* endOfLine;
+ if (lineCopy && lineEndsWithCRorLF) // true for most lines
+ {
+ endOfLine = lineCopy + lineLength;
+ messageLine = lineCopy;
+ } else {
+ // leave enough room for one more char, MSG_LINEBREAK[0]
+ localMessageLine = (char*)PR_MALLOC(lineLength + 2);
+ if (!localMessageLine) // memory failure
+ return;
+ PL_strcpy(localMessageLine, line);
+ endOfLine = localMessageLine + lineLength;
+ messageLine = localMessageLine;
+ }
+
+ if (lineLength >= 2 && endOfLine[-2] == '\r' && endOfLine[-1] == '\n') {
+ if (lineLength >= 3 && endOfLine[-3] == '\r') // CRCRLF
+ {
+ endOfLine--;
+ lineLength--;
+ }
+ /* CRLF -> CR or LF */
+ endOfLine[-2] = MSG_LINEBREAK[0];
+ endOfLine[-1] = '\0';
+ lineLength--;
+ } else if (lineLength >= 1 &&
+ ((endOfLine[-1] == '\r') || (endOfLine[-1] == '\n'))) {
+ /* CR -> LF or LF -> CR */
+ endOfLine[-1] = MSG_LINEBREAK[0];
+ } else // no eol characters at all
+ {
+ endOfLine[0] = MSG_LINEBREAK[0]; // CR or LF
+ endOfLine[1] = '\0';
+ lineLength++;
+ }
+ } else // enforce canonical CRLF linebreaks
+ {
+ if (lineLength == 0 || (lineLength == 1 && cEndOfLine[-1] == '\n')) {
+ messageLine = CRLF;
+ lineLength = 2;
+ } else if (cEndOfLine[-1] != '\n' || cEndOfLine[-2] != '\r' ||
+ (lineLength >= 3 && cEndOfLine[-3] == '\r')) {
+ // The line does not end in CRLF (or it ends in CRCRLF).
+ // Copy line and leave enough room for two more chars (CR and LF).
+ localMessageLine = (char*)PR_MALLOC(lineLength + 3);
+ if (!localMessageLine) // memory failure
+ return;
+ PL_strcpy(localMessageLine, line);
+ char* endOfLine = localMessageLine + lineLength;
+ messageLine = localMessageLine;
+
+ if (lineLength >= 3 && endOfLine[-1] == '\n' && endOfLine[-2] == '\r') {
+ // CRCRLF -> CRLF
+ endOfLine[-2] = '\n';
+ endOfLine[-1] = '\0';
+ lineLength--;
+ } else if ((endOfLine[-1] == '\r') || (endOfLine[-1] == '\n')) {
+ // LF -> CRLF or CR -> CRLF
+ endOfLine[-1] = '\r';
+ endOfLine[0] = '\n';
+ endOfLine[1] = '\0';
+ lineLength++;
+ } else // no eol characters at all
+ {
+ endOfLine[0] = '\r';
+ endOfLine[1] = '\n';
+ endOfLine[2] = '\0';
+ lineLength += 2;
+ }
+ }
+ }
+ }
+ NS_ASSERTION(lineLength == PL_strlen(messageLine), "lineLength not accurate");
+
+ // check if sender obtained via XSENDER server extension matches "From:" field
+ const char* xSenderInfo = GetServerStateParser().GetXSenderInfo();
+ if (xSenderInfo && *xSenderInfo && !m_fromHeaderSeen) {
+ if (!PL_strncmp("From: ", messageLine, 6)) {
+ m_fromHeaderSeen = true;
+ if (PL_strstr(messageLine, xSenderInfo) != NULL)
+ // Adding a X-Mozilla-Status line here is not very elegant but it
+ // works. Another X-Mozilla-Status line is added to the message when
+ // downloading to a local folder; this new line will also contain the
+ // 'authed' flag we are adding here. (If the message is again
+ // uploaded to the server, this flag is lost.)
+ // 0x0200 == nsMsgMessageFlags::SenderAuthed
+ HandleMessageDownLoadLine("X-Mozilla-Status: 0200\r\n", false);
+ GetServerStateParser().FreeXSenderInfo();
+ }
+ }
+
+ if (GetServerStateParser().GetDownloadingHeaders()) {
+ if (!m_curHdrInfo)
+ BeginMessageDownLoad(GetServerStateParser().SizeOfMostRecentMessage(),
+ MESSAGE_RFC822);
+ if (m_curHdrInfo) {
+ if (NS_FAILED(m_curHdrInfo->CacheLine(
+ messageLine, GetServerStateParser().CurrentResponseUID())))
+ NS_ERROR("CacheLine for a header failed");
+ }
+ PR_Free(localMessageLine);
+ return;
+ }
+ // if this line is for a different message, or the incoming line is too big
+ if (((m_downloadLineCache->CurrentUID() !=
+ GetServerStateParser().CurrentResponseUID()) &&
+ !m_downloadLineCache->CacheEmpty()) ||
+ (m_downloadLineCache->SpaceAvailable() < lineLength + 1))
+ FlushDownloadCache();
+
+ // so now the cache is flushed, but this string might still be too big
+ if (m_downloadLineCache->SpaceAvailable() < lineLength + 1)
+ PostLineDownLoadEvent(messageLine,
+ GetServerStateParser().CurrentResponseUID());
+ else {
+ NS_ASSERTION(
+ (PL_strlen(messageLine) + 1) <= m_downloadLineCache->SpaceAvailable(),
+ "Oops... line length greater than space available");
+ if (NS_FAILED(m_downloadLineCache->CacheLine(
+ messageLine, GetServerStateParser().CurrentResponseUID())))
+ NS_ERROR("CacheLine for message body failed");
+ }
+ PR_Free(localMessageLine);
+}
+
+void nsImapProtocol::FlushDownloadCache() {
+ if (!m_downloadLineCache->CacheEmpty()) {
+ msg_line_info* downloadLine = m_downloadLineCache->GetCurrentLineInfo();
+ PostLineDownLoadEvent(downloadLine->adoptedMessageLine,
+ downloadLine->uidOfMessage);
+ m_downloadLineCache->ResetCache();
+ }
+}
+
+void nsImapProtocol::NormalMessageEndDownload() {
+ Log("STREAM", "CLOSE", "Normal Message End Download Stream");
+
+ if (m_trackingTime) AdjustChunkSize();
+ if (m_imapMailFolderSink && m_curHdrInfo &&
+ GetServerStateParser().GetDownloadingHeaders()) {
+ m_curHdrInfo->SetMsgSize(GetServerStateParser().SizeOfMostRecentMessage());
+ m_curHdrInfo->SetMsgUid(GetServerStateParser().CurrentResponseUID());
+ m_hdrDownloadCache->FinishCurrentHdr();
+ int32_t numHdrsCached;
+ m_hdrDownloadCache->GetNumHeaders(&numHdrsCached);
+ if (numHdrsCached == kNumHdrsToXfer) {
+ m_imapMailFolderSink->ParseMsgHdrs(this, m_hdrDownloadCache);
+ m_hdrDownloadCache->ResetAll();
+ }
+ }
+ FlushDownloadCache();
+
+ if (!GetServerStateParser().GetDownloadingHeaders()) {
+ int32_t updatedMessageSize = -1;
+ if (m_fetchingWholeMessage) {
+ updatedMessageSize = m_bytesToChannel;
+ if (m_bytesToChannel !=
+ GetServerStateParser().SizeOfMostRecentMessage()) {
+ MOZ_LOG(IMAP, LogLevel::Debug,
+ ("STREAM:CLOSE Server's RFC822.SIZE %u, actual size %u",
+ GetServerStateParser().SizeOfMostRecentMessage(),
+ m_bytesToChannel));
+ }
+ }
+ // need to know if we're downloading for display or not. We'll use action ==
+ // nsImapMsgFetch for now
+ nsImapAction imapAction =
+ nsIImapUrl::nsImapSelectFolder; // just set it to some legal value
+ if (m_runningUrl) m_runningUrl->GetImapAction(&imapAction);
+
+ if (m_imapMessageSink) {
+ if (m_mockChannel) {
+ // Have a mock channel, tell channel that write to cache is done.
+ m_mockChannel->SetWritingToCache(false);
+ MOZ_LOG(IMAP, LogLevel::Debug, ("%s: End cache write", __func__));
+ }
+ m_imapMessageSink->NormalEndMsgWriteStream(
+ m_downloadLineCache->CurrentUID(),
+ imapAction == nsIImapUrl::nsImapMsgFetch, m_runningUrl,
+ updatedMessageSize);
+ }
+
+ if (m_runningUrl && m_imapMailFolderSink) {
+ nsCOMPtr<nsISupports> copyState;
+ m_runningUrl->GetCopyState(getter_AddRefs(copyState));
+ if (copyState) // only need this notification during copy
+ {
+ nsCOMPtr<nsIMsgMailNewsUrl> mailUrl(do_QueryInterface(m_runningUrl));
+ m_imapMailFolderSink->EndMessage(mailUrl,
+ m_downloadLineCache->CurrentUID());
+ }
+ }
+ }
+ m_curHdrInfo = nullptr;
+}
+
+void nsImapProtocol::AbortMessageDownLoad() {
+ Log("STREAM", "CLOSE", "Abort Message Download Stream");
+
+ if (m_trackingTime) AdjustChunkSize();
+ FlushDownloadCache();
+ if (GetServerStateParser().GetDownloadingHeaders()) {
+ if (m_imapMailFolderSink)
+ m_imapMailFolderSink->AbortHeaderParseStream(this);
+ } else if (m_imapMessageSink)
+ m_imapMessageSink->AbortMsgWriteStream();
+
+ m_curHdrInfo = nullptr;
+}
+
+void nsImapProtocol::ProcessMailboxUpdate(bool handlePossibleUndo) {
+ if (DeathSignalReceived()) return;
+
+ // Update quota information
+ char* boxName;
+ GetSelectedMailboxName(&boxName);
+ GetQuotaDataIfSupported(boxName);
+ PR_Free(boxName);
+
+ // fetch the flags and uids of all existing messages or new ones
+ if (!DeathSignalReceived() && GetServerStateParser().NumberOfMessages()) {
+ if (handlePossibleUndo) {
+ // undo any delete flags we may have asked to
+ nsCString undoIdsStr;
+ nsAutoCString undoIds;
+
+ GetCurrentUrl()->GetListOfMessageIds(undoIdsStr);
+ undoIds.Assign(undoIdsStr);
+ if (!undoIds.IsEmpty()) {
+ char firstChar = (char)undoIds.CharAt(0);
+ undoIds.Cut(0, 1); // remove first character
+ // if this string started with a '-', then this is an undo of a delete
+ // if its a '+' its a redo
+ if (firstChar == '-')
+ Store(undoIds, "-FLAGS (\\Deleted)",
+ true); // most servers will fail silently on a failure, deal
+ // with it?
+ else if (firstChar == '+')
+ Store(undoIds, "+FLAGS (\\Deleted)",
+ true); // most servers will fail silently on a failure, deal
+ // with it?
+ else
+ NS_ASSERTION(false, "bogus undo Id's");
+ }
+ }
+
+ // make the parser record these flags
+ nsCString fetchStr;
+ int32_t added = 0, deleted = 0;
+
+ m_flagState->GetNumberOfMessages(&added);
+ deleted = m_flagState->NumberOfDeletedMessages();
+ bool flagStateEmpty = !added;
+ bool useCS = UseCondStore();
+
+ // Figure out if we need to do a full sync (UID Fetch Flags 1:*),
+ // a partial sync using CHANGEDSINCE, or a sync from the previous
+ // highwater mark.
+
+ // If the folder doesn't know about the highest uid, or the flag state
+ // is empty, and we're not using CondStore, we definitely need a full sync.
+ //
+ // Print to log items affecting needFullFolderSync:
+ MOZ_LOG(IMAP_CS, LogLevel::Debug,
+ ("Do full sync?: mFolderHighestUID=%" PRIu32 ", added=%" PRId32
+ ", useCS=%s",
+ mFolderHighestUID, added, useCS ? "true" : "false"));
+ bool needFullFolderSync = !mFolderHighestUID || (flagStateEmpty && !useCS);
+ bool needFolderSync = false;
+
+ if (!needFullFolderSync) {
+ // Figure out if we need to do a non-highwater mark sync.
+ // Set needFolderSync true when at least 1 of these 3 cases is true:
+ // 1. Have no uids in flag array or all flag elements are marked deleted
+ // AND not using CONDSTORE.
+ // 2. Have no uids in flag array or all flag elements are marked deleted
+ // AND using "just mark as deleted" and EXISTS response count differs from
+ // stored message count for folder.
+ // 3. Using CONDSTORE and highest MODSEQ response is not equal to stored
+ // mod seq for folder.
+
+ // Print to log items affecting needFolderSync:
+ // clang-format off
+ MOZ_LOG(IMAP_CS, LogLevel::Debug,
+ ("1. Do a sync?: added=%" PRId32 ", deleted=%" PRId32 ", useCS=%s",
+ added, deleted, useCS ? "true" : "false"));
+ MOZ_LOG(IMAP_CS, LogLevel::Debug,
+ ("2. Do a sync?: ShowDeletedMsgs=%s, exists=%" PRId32
+ ", mFolderTotalMsgCount=%" PRId32,
+ GetShowDeletedMessages() ? "true" : "false",
+ GetServerStateParser().NumberOfMessages(), mFolderTotalMsgCount));
+ // clang-format on
+ MOZ_LOG(IMAP_CS, LogLevel::Debug,
+ ("3. Do a sync?: fHighestModSeq=%" PRIu64
+ ", mFolderLastModSeq=%" PRIu64,
+ GetServerStateParser().fHighestModSeq, mFolderLastModSeq));
+
+ needFolderSync =
+ ((flagStateEmpty || added == deleted) &&
+ (!useCS || (GetShowDeletedMessages() &&
+ GetServerStateParser().NumberOfMessages() !=
+ mFolderTotalMsgCount))) ||
+ (useCS && GetServerStateParser().fHighestModSeq != mFolderLastModSeq);
+ }
+ MOZ_LOG(IMAP_CS, LogLevel::Debug,
+ ("needFullFolderSync=%s, needFolderSync=%s",
+ needFullFolderSync ? "true" : "false",
+ needFolderSync ? "true" : "false"));
+
+ if (needFullFolderSync || needFolderSync) {
+ nsCString idsToFetch("1:*");
+ char fetchModifier[40] = "";
+ if (!needFullFolderSync && !GetShowDeletedMessages() && useCS) {
+ m_flagState->StartCapture();
+ MOZ_LOG(IMAP_CS, LogLevel::Debug,
+ ("Doing UID fetch 1:* (CHANGEDSINCE %" PRIu64 ")",
+ mFolderLastModSeq));
+ PR_snprintf(fetchModifier, sizeof(fetchModifier),
+ " (CHANGEDSINCE %llu)", mFolderLastModSeq);
+ } else
+ m_flagState->SetPartialUIDFetch(false);
+
+ FetchMessage(idsToFetch, kFlags, fetchModifier);
+ // lets see if we should expunge during a full sync of flags.
+ if (GetServerStateParser().LastCommandSuccessful()) {
+ // if we did a CHANGEDSINCE fetch, do a sanity check on the msg counts
+ // to see if some other client may have done an expunge.
+ if (m_flagState->GetPartialUIDFetch()) {
+ uint32_t numExists = GetServerStateParser().NumberOfMessages();
+ uint32_t numPrevExists = mFolderTotalMsgCount;
+ MOZ_LOG(IMAP_CS, LogLevel::Debug,
+ ("Sanity, deleted=%" PRId32 ", numPrevExists=%" PRIu32
+ ", numExists=%" PRIu32,
+ m_flagState->NumberOfDeletedMessages(), numPrevExists,
+ numExists));
+ // Determine the number of new UIDs just fetched that are greater than
+ // the saved highest UID for the folder. numToCheck will contain the
+ // number of UIDs just fetched and, of course, not all are new.
+ uint32_t numNewUIDs = 0;
+ uint32_t numToCheck = m_flagState->GetNumAdded();
+ bool flagChangeDetected = false;
+ bool expungeHappened = false;
+ MOZ_LOG(IMAP_CS, LogLevel::Debug,
+ ("numToCheck=%" PRIu32, numToCheck));
+ if (numToCheck && mFolderHighestUID) {
+ uint32_t uid;
+ int32_t topIndex;
+ m_flagState->GetNumberOfMessages(&topIndex);
+ MOZ_LOG(
+ IMAP_CS, LogLevel::Debug,
+ ("Partial fetching. Number of UIDs stored=%" PRId32, topIndex));
+ do {
+ topIndex--;
+ // Check for potential infinite loop here. This has happened but
+ // don't know why. If topIndex is negative at this point, set
+ // expungeHappened true to recover by doing a full flag fetch.
+ if (topIndex < 0) {
+ expungeHappened = true;
+ MOZ_LOG(IMAP_CS, LogLevel::Error,
+ ("Zero or negative number of UIDs stored, do full flag "
+ "fetch"));
+ break;
+ }
+ m_flagState->GetUidOfMessage(topIndex, &uid);
+ if (uid && uid != nsMsgKey_None) {
+ if (uid > mFolderHighestUID) {
+ numNewUIDs++;
+ MOZ_LOG(IMAP_CS, LogLevel::Debug,
+ ("numNewUIDs=%" PRIu32 ", Added new UID=%" PRIu32,
+ numNewUIDs, uid));
+ numToCheck--;
+ } else {
+ // Just a flag change on an existing UID. No more new UIDs
+ // will be found. This does not detect an expunged message.
+ flagChangeDetected = true;
+ MOZ_LOG(IMAP_CS, LogLevel::Debug,
+ ("Not new uid=%" PRIu32, uid));
+ break;
+ }
+ } else {
+ MOZ_LOG(IMAP_CS, LogLevel::Debug,
+ ("UID is 0 or a gap, uid=0x%" PRIx32, uid));
+ break;
+ }
+ } while (numToCheck);
+ }
+
+ // Another client expunged at least one message if the number of new
+ // UIDs is not equal to the observed change in the number of messages
+ // existing in the folder.
+ expungeHappened =
+ expungeHappened || numNewUIDs != (numExists - numPrevExists);
+ if (expungeHappened) {
+ // Sanity check failed - need full fetch to remove expunged msgs.
+ MOZ_LOG(IMAP_CS, LogLevel::Debug,
+ ("Other client expunged msgs, do full fetch to remove "
+ "expunged msgs"));
+ m_flagState->Reset();
+ m_flagState->SetPartialUIDFetch(false);
+ FetchMessage("1:*"_ns, kFlags);
+ } else if (numNewUIDs == 0) {
+ // Nothing has been expunged and no new UIDs, so if just a flag
+ // change on existing message(s), avoid unneeded fetch of flags for
+ // messages with UIDs at and above uid (see var uid above) when
+ // "highwater mark" fetch occurs below.
+ if (mFolderHighestUID && flagChangeDetected) {
+ MOZ_LOG(IMAP_CS, LogLevel::Debug,
+ ("Avoid unneeded fetches after just flag changes"));
+ GetServerStateParser().ResetHighestRecordedUID();
+ }
+ }
+ }
+ int32_t numDeleted = m_flagState->NumberOfDeletedMessages();
+ // Don't do expunge when we are lite selecting folder (because we
+ // could be doing undo) or if gExpungeOption is kAutoExpungeNever.
+ // Expunge if we're always expunging, or the number of deleted messages
+ // is over the threshold, and we're either always respecting the
+ // threshold, or we're expunging based on the delete model, and the
+ // delete model is not "just mark it as deleted" (imap delete model).
+ if (m_imapAction != nsIImapUrl::nsImapLiteSelectFolder &&
+ gExpungeOption != kAutoExpungeNever &&
+ (gExpungeOption == kAutoExpungeAlways ||
+ (numDeleted >= gExpungeThreshold &&
+ (gExpungeOption == kAutoExpungeOnThreshold ||
+ (gExpungeOption == kAutoExpungeDeleteModel &&
+ !GetShowDeletedMessages())))))
+ Expunge();
+ }
+ } else {
+ // Obtain the highest (highwater mark) UID seen since the last UIDVALIDITY
+ // response occurred (associated with the most recent SELECT for the
+ // folder).
+ uint32_t highestRecordedUID = GetServerStateParser().HighestRecordedUID();
+ // if we're using CONDSTORE, and the parser hasn't seen any UIDs, use
+ // the highest UID previously seen and saved for the folder instead.
+ if (useCS && !highestRecordedUID) highestRecordedUID = mFolderHighestUID;
+ // clang-format off
+ MOZ_LOG(IMAP_CS, LogLevel::Debug,
+ ("Check for new messages above UID=%" PRIu32, highestRecordedUID));
+ // clang-format on
+ AppendUid(fetchStr, highestRecordedUID + 1);
+ fetchStr.AppendLiteral(":*");
+ FetchMessage(fetchStr, kFlags); // only new messages please
+ }
+ } else if (GetServerStateParser().LastCommandSuccessful()) {
+ GetServerStateParser().ResetFlagInfo();
+ // the flag state is empty, but not partial.
+ m_flagState->SetPartialUIDFetch(false);
+ }
+
+ if (GetServerStateParser().LastCommandSuccessful()) {
+ nsImapAction imapAction;
+ nsresult res = m_runningUrl->GetImapAction(&imapAction);
+ if (NS_SUCCEEDED(res) && imapAction == nsIImapUrl::nsImapLiteSelectFolder)
+ return;
+ }
+
+ nsTArray<nsMsgKey> msgIdList;
+
+ if (GetServerStateParser().LastCommandSuccessful()) {
+ ReentrantMonitorAutoEnter mon(m_waitForBodyIdsMonitor);
+ RefPtr<nsImapMailboxSpec> new_spec =
+ GetServerStateParser().CreateCurrentMailboxSpec();
+ nsImapAction imapAction;
+ nsresult res = m_runningUrl->GetImapAction(&imapAction);
+ if (NS_SUCCEEDED(res) && imapAction == nsIImapUrl::nsImapExpungeFolder)
+ new_spec->mBoxFlags |= kJustExpunged;
+
+ if (m_imapMailFolderSink) {
+ bool more;
+ m_imapMailFolderSink->UpdateImapMailboxInfo(this, new_spec);
+ m_imapMailFolderSink->GetMsgHdrsToDownload(
+ &more, &m_progressExpectedNumber, msgIdList);
+ // Assert that either it's empty string OR it must be header string.
+ MOZ_ASSERT((m_stringIndex == IMAP_EMPTY_STRING_INDEX) ||
+ (m_stringIndex == IMAP_HEADERS_STRING_INDEX));
+ m_progressCurrentNumber[m_stringIndex] = 0;
+ m_runningUrl->SetMoreHeadersToDownload(more);
+ // We're going to be re-running this url if there are more headers.
+ if (more) m_runningUrl->SetRerunningUrl(true);
+ }
+ }
+
+ if (GetServerStateParser().LastCommandSuccessful()) {
+ if (msgIdList.Length() > 0) {
+ FolderHeaderDump(msgIdList.Elements(), msgIdList.Length());
+ }
+ HeaderFetchCompleted();
+ // this might be bogus, how are we going to do pane notification and stuff
+ // when we fetch bodies without headers!
+ }
+
+ // wait for a list of bodies to fetch.
+ if (GetServerStateParser().LastCommandSuccessful()) {
+ nsTArray<nsMsgKey> msgIds;
+ WaitForPotentialListOfBodysToFetch(msgIds);
+ if (msgIds.Length() > 0 && GetServerStateParser().LastCommandSuccessful()) {
+ // Tell the url that it should store the msg fetch results offline,
+ // while we're dumping the messages, and then restore the setting.
+ bool wasStoringOffline;
+ m_runningUrl->GetStoreResultsOffline(&wasStoringOffline);
+ m_runningUrl->SetStoreResultsOffline(true);
+ // Assert that either it's empty string OR it must be message string.
+ MOZ_ASSERT((m_stringIndex == IMAP_EMPTY_STRING_INDEX) ||
+ (m_stringIndex == IMAP_MESSAGES_STRING_INDEX));
+ m_progressCurrentNumber[m_stringIndex] = 0;
+ m_progressExpectedNumber = msgIds.Length();
+ FolderMsgDump(msgIds.Elements(), msgIds.Length(), kEveryThingRFC822Peek);
+ m_runningUrl->SetStoreResultsOffline(wasStoringOffline);
+ }
+ }
+ if (!GetServerStateParser().LastCommandSuccessful())
+ GetServerStateParser().ResetFlagInfo();
+}
+
+void nsImapProtocol::FolderHeaderDump(uint32_t* msgUids, uint32_t msgCount) {
+ FolderMsgDump(msgUids, msgCount, kHeadersRFC822andUid);
+}
+
+void nsImapProtocol::FolderMsgDump(uint32_t* msgUids, uint32_t msgCount,
+ nsIMAPeFetchFields fields) {
+ // lets worry about this progress stuff later.
+ switch (fields) {
+ case kHeadersRFC822andUid:
+ SetProgressString(IMAP_HEADERS_STRING_INDEX);
+ break;
+ case kFlags:
+ SetProgressString(IMAP_FLAGS_STRING_INDEX);
+ break;
+ default:
+ SetProgressString(IMAP_MESSAGES_STRING_INDEX);
+ break;
+ }
+
+ FolderMsgDumpLoop(msgUids, msgCount, fields);
+
+ SetProgressString(IMAP_EMPTY_STRING_INDEX);
+}
+
+void nsImapProtocol::WaitForPotentialListOfBodysToFetch(
+ nsTArray<nsMsgKey>& msgIdList) {
+ PRIntervalTime sleepTime = kImapSleepTime;
+
+ ReentrantMonitorAutoEnter fetchListMon(m_fetchBodyListMonitor);
+ while (!m_fetchBodyListIsNew && !DeathSignalReceived())
+ fetchListMon.Wait(sleepTime);
+ m_fetchBodyListIsNew = false;
+
+ msgIdList = m_fetchBodyIdList.Clone();
+}
+
+// libmsg uses this to notify a running imap url about message bodies it should
+// download. why not just have libmsg explicitly download the message bodies?
+NS_IMETHODIMP nsImapProtocol::NotifyBodysToDownload(
+ const nsTArray<nsMsgKey>& keys) {
+ ReentrantMonitorAutoEnter fetchListMon(m_fetchBodyListMonitor);
+ m_fetchBodyIdList = keys.Clone();
+ m_fetchBodyListIsNew = true;
+ fetchListMon.Notify();
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapProtocol::GetFlagsForUID(uint32_t uid, bool* foundIt,
+ imapMessageFlagsType* resultFlags,
+ char** customFlags) {
+ int32_t i;
+
+ imapMessageFlagsType flags =
+ m_flagState->GetMessageFlagsFromUID(uid, foundIt, &i);
+ if (*foundIt) {
+ *resultFlags = flags;
+ if ((flags & kImapMsgCustomKeywordFlag) && customFlags)
+ m_flagState->GetCustomFlags(uid, customFlags);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapProtocol::GetFlagAndUidState(
+ nsIImapFlagAndUidState** aFlagState) {
+ NS_ENSURE_ARG_POINTER(aFlagState);
+ NS_IF_ADDREF(*aFlagState = m_flagState);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapProtocol::GetSupportedUserFlags(uint16_t* supportedFlags) {
+ if (!supportedFlags) return NS_ERROR_NULL_POINTER;
+
+ *supportedFlags = m_flagState->GetSupportedUserFlags();
+ return NS_OK;
+}
+void nsImapProtocol::FolderMsgDumpLoop(uint32_t* msgUids, uint32_t msgCount,
+ nsIMAPeFetchFields fields) {
+ int32_t msgCountLeft = msgCount;
+ uint32_t msgsDownloaded = 0;
+ do {
+ nsCString idString;
+ uint32_t msgsToDownload = msgCountLeft;
+ AllocateImapUidString(msgUids + msgsDownloaded, msgsToDownload, m_flagState,
+ idString); // 20 * 200
+ FetchMessage(idString, fields);
+ msgsDownloaded += msgsToDownload;
+ msgCountLeft -= msgsToDownload;
+ } while (msgCountLeft > 0 && !DeathSignalReceived());
+}
+
+void nsImapProtocol::HeaderFetchCompleted() {
+ if (m_imapMailFolderSink)
+ m_imapMailFolderSink->ParseMsgHdrs(this, m_hdrDownloadCache);
+ m_hdrDownloadCache->ReleaseAll();
+
+ if (m_imapMailFolderSink) m_imapMailFolderSink->HeaderFetchCompleted(this);
+}
+
+// Use the noop to tell the server we are still here, and therefore we are
+// willing to receive status updates. The recent or exists response from the
+// server could tell us that there is more mail waiting for us, but we need to
+// check the flags of the mail and the high water mark to make sure that we do
+// not tell the user that there is new mail when perhaps they have already read
+// it in another machine.
+
+void nsImapProtocol::PeriodicBiff() {
+ nsMsgBiffState startingState = m_currentBiffState;
+
+ if (GetServerStateParser().GetIMAPstate() ==
+ nsImapServerResponseParser::kFolderSelected) {
+ Noop(); // check the latest number of messages
+ int32_t numMessages = 0;
+ m_flagState->GetNumberOfMessages(&numMessages);
+ if (GetServerStateParser().NumberOfMessages() != numMessages) {
+ uint32_t id = GetServerStateParser().HighestRecordedUID() + 1;
+ nsCString fetchStr; // only update flags
+ uint32_t added = 0, deleted = 0;
+
+ deleted = m_flagState->NumberOfDeletedMessages();
+ added = numMessages;
+ if (!added || (added == deleted)) // empty keys, get them all
+ id = 1;
+
+ // sprintf(fetchStr, "%ld:%ld", id, id +
+ // GetServerStateParser().NumberOfMessages() -
+ // fFlagState->GetNumberOfMessages());
+ AppendUid(fetchStr, id);
+ fetchStr.AppendLiteral(":*");
+ FetchMessage(fetchStr, kFlags);
+ if (((uint32_t)m_flagState->GetHighestNonDeletedUID() >= id) &&
+ m_flagState->IsLastMessageUnseen())
+ m_currentBiffState = nsIMsgFolder::nsMsgBiffState_NewMail;
+ else
+ m_currentBiffState = nsIMsgFolder::nsMsgBiffState_NoMail;
+ } else
+ m_currentBiffState = nsIMsgFolder::nsMsgBiffState_NoMail;
+ } else
+ m_currentBiffState = nsIMsgFolder::nsMsgBiffState_Unknown;
+
+ if (startingState != m_currentBiffState)
+ SendSetBiffIndicatorEvent(m_currentBiffState);
+}
+
+void nsImapProtocol::SendSetBiffIndicatorEvent(nsMsgBiffState newState) {
+ if (m_imapMailFolderSink)
+ m_imapMailFolderSink->SetBiffStateAndUpdate(newState);
+}
+
+/* static */ void nsImapProtocol::LogImapUrl(const char* logMsg,
+ nsIImapUrl* imapUrl) {
+ if (MOZ_LOG_TEST(IMAP, LogLevel::Info)) {
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(imapUrl);
+ if (mailnewsUrl) {
+ nsAutoCString urlSpec, unescapedUrlSpec;
+ nsresult rv = mailnewsUrl->GetSpec(urlSpec);
+ if (NS_FAILED(rv)) return;
+ MsgUnescapeString(urlSpec, 0, unescapedUrlSpec);
+ MOZ_LOG(IMAP, LogLevel::Info, ("%s:%s", logMsg, unescapedUrlSpec.get()));
+ }
+ }
+}
+
+// log info including current state...
+void nsImapProtocol::Log(const char* logSubName, const char* extraInfo,
+ const char* logData) {
+ if (MOZ_LOG_TEST(IMAP, LogLevel::Info)) {
+ static const char nonAuthStateName[] = "NA";
+ static const char authStateName[] = "A";
+ static const char selectedStateName[] = "S";
+ const nsCString& hostName =
+ GetImapHostName(); // initialize to empty string
+
+ int32_t logDataLen = PL_strlen(logData); // PL_strlen checks for null
+ nsCString logDataLines;
+ const char* logDataToLog;
+ int32_t lastLineEnd;
+
+ // nspr line length is 512, and we allow some space for the log preamble.
+ const int kLogDataChunkSize = 400;
+
+ // break up buffers > 400 bytes on line boundaries.
+ if (logDataLen > kLogDataChunkSize) {
+ logDataLines.Assign(logData);
+ lastLineEnd = logDataLines.RFindChar('\n', kLogDataChunkSize);
+ // null terminate the last line
+ if (lastLineEnd == kNotFound) lastLineEnd = kLogDataChunkSize - 1;
+
+ logDataLines.Insert('\0', lastLineEnd + 1);
+ logDataToLog = logDataLines.get();
+ } else {
+ logDataToLog = logData;
+ lastLineEnd = logDataLen;
+ }
+ switch (GetServerStateParser().GetIMAPstate()) {
+ case nsImapServerResponseParser::kFolderSelected:
+ if (extraInfo)
+ MOZ_LOG(IMAP, LogLevel::Info,
+ ("%p:%s:%s-%s:%s:%s: %.400s", this, hostName.get(),
+ selectedStateName,
+ GetServerStateParser().GetSelectedMailboxName(), logSubName,
+ extraInfo, logDataToLog));
+ else
+ MOZ_LOG(IMAP, LogLevel::Info,
+ ("%p:%s:%s-%s:%s: %.400s", this, hostName.get(),
+ selectedStateName,
+ GetServerStateParser().GetSelectedMailboxName(), logSubName,
+ logDataToLog));
+ break;
+ case nsImapServerResponseParser::kNonAuthenticated:
+ case nsImapServerResponseParser::kAuthenticated: {
+ const char* stateName = (GetServerStateParser().GetIMAPstate() ==
+ nsImapServerResponseParser::kNonAuthenticated)
+ ? nonAuthStateName
+ : authStateName;
+ if (extraInfo)
+ MOZ_LOG(IMAP, LogLevel::Info,
+ ("%p:%s:%s:%s:%s: %.400s", this, hostName.get(), stateName,
+ logSubName, extraInfo, logDataToLog));
+ else
+ MOZ_LOG(IMAP, LogLevel::Info,
+ ("%p:%s:%s:%s: %.400s", this, hostName.get(), stateName,
+ logSubName, logDataToLog));
+ }
+ }
+
+ // dump the rest of the string in < 400 byte chunks
+ while (logDataLen > kLogDataChunkSize) {
+ logDataLines.Cut(
+ 0,
+ lastLineEnd + 2); // + 2 to account for the LF and the '\0' we added
+ logDataLen = logDataLines.Length();
+ lastLineEnd = (logDataLen > kLogDataChunkSize)
+ ? logDataLines.RFindChar('\n', kLogDataChunkSize)
+ : kNotFound;
+ // null terminate the last line
+ if (lastLineEnd == kNotFound) lastLineEnd = kLogDataChunkSize - 1;
+ logDataLines.Insert('\0', lastLineEnd + 1);
+ logDataToLog = logDataLines.get();
+ MOZ_LOG(IMAP, LogLevel::Info, ("%.400s", logDataToLog));
+ }
+ }
+}
+
+// In 4.5, this posted an event back to libmsg and blocked until it got a
+// response. We may still have to do this.It would be nice if we could preflight
+// this value, but we may not always know when we'll need it.
+uint32_t nsImapProtocol::GetMessageSize(const nsACString& messageId) {
+ uint32_t size = 0;
+ if (m_imapMessageSink)
+ m_imapMessageSink->GetMessageSizeFromDB(PromiseFlatCString(messageId).get(),
+ &size);
+ if (DeathSignalReceived()) size = 0;
+ return size;
+}
+
+// message id string utility functions
+/* static */ bool nsImapProtocol::HandlingMultipleMessages(
+ const nsCString& messageIdString) {
+ return (MsgFindCharInSet(messageIdString, ",:") != kNotFound);
+}
+
+uint32_t nsImapProtocol::CountMessagesInIdString(const char* idString) {
+ uint32_t numberOfMessages = 0;
+ char* uidString = PL_strdup(idString);
+
+ if (uidString) {
+ // This is in the form <id>,<id>, or <id1>:<id2>
+ char curChar = *uidString;
+ bool isRange = false;
+ int32_t curToken;
+ int32_t saveStartToken = 0;
+
+ for (char* curCharPtr = uidString; curChar && *curCharPtr;) {
+ char* currentKeyToken = curCharPtr;
+ curChar = *curCharPtr;
+ while (curChar != ':' && curChar != ',' && curChar != '\0')
+ curChar = *curCharPtr++;
+ *(curCharPtr - 1) = '\0';
+ curToken = atol(currentKeyToken);
+ if (isRange) {
+ while (saveStartToken < curToken) {
+ numberOfMessages++;
+ saveStartToken++;
+ }
+ }
+
+ numberOfMessages++;
+ isRange = (curChar == ':');
+ if (isRange) saveStartToken = curToken + 1;
+ }
+ PR_Free(uidString);
+ }
+ return numberOfMessages;
+}
+
+// It would be really nice not to have to use this method nearly as much as we
+// did in 4.5 - we need to think about this some. Some of it may just go away in
+// the new world order
+bool nsImapProtocol::DeathSignalReceived() {
+ // ignore mock channel status if we've been pseudo interrupted
+ // ### need to make sure we clear pseudo interrupted status appropriately.
+ if (!GetPseudoInterrupted() && m_mockChannel) {
+ nsresult returnValue;
+ m_mockChannel->GetStatus(&returnValue);
+ if (NS_FAILED(returnValue)) return false;
+ }
+
+ // Check the other way of cancelling.
+ ReentrantMonitorAutoEnter threadDeathMon(m_threadDeathMonitor);
+ return m_threadShouldDie;
+}
+
+NS_IMETHODIMP nsImapProtocol::ResetToAuthenticatedState() {
+ GetServerStateParser().PreauthSetAuthenticatedState();
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapProtocol::GetSelectedMailboxName(char** folderName) {
+ if (!folderName) return NS_ERROR_NULL_POINTER;
+ if (GetServerStateParser().GetSelectedMailboxName())
+ *folderName = PL_strdup((GetServerStateParser().GetSelectedMailboxName()));
+ return NS_OK;
+}
+
+bool nsImapProtocol::GetPseudoInterrupted() {
+ ReentrantMonitorAutoEnter pseudoInterruptMon(m_pseudoInterruptMonitor);
+ return m_pseudoInterrupted;
+}
+
+NS_IMETHODIMP
+nsImapProtocol::PseudoInterrupt(bool interrupt) {
+ ReentrantMonitorAutoEnter pseudoInterruptMon(m_pseudoInterruptMonitor);
+ m_pseudoInterrupted = interrupt;
+ if (interrupt) Log("CONTROL", NULL, "PSEUDO-Interrupted");
+ return NS_OK;
+}
+
+void nsImapProtocol::SetActive(bool active) {
+ ReentrantMonitorAutoEnter dataMemberMon(m_dataMemberMonitor);
+ m_active = active;
+}
+
+bool nsImapProtocol::GetActive() {
+ ReentrantMonitorAutoEnter dataMemberMon(m_dataMemberMonitor);
+ return m_active;
+}
+
+bool nsImapProtocol::GetShowAttachmentsInline() {
+ bool showAttachmentsInline = true;
+ if (m_imapServerSink)
+ m_imapServerSink->GetShowAttachmentsInline(&showAttachmentsInline);
+ return showAttachmentsInline;
+}
+
+// Adds a set of rights for a given user on a given mailbox on the current host.
+// if userName is NULL, it means "me," or MYRIGHTS.
+void nsImapProtocol::AddFolderRightsForUser(const char* mailboxName,
+ const char* userName,
+ const char* rights) {
+ if (!userName) userName = "";
+ if (m_imapServerSink)
+ m_imapServerSink->AddFolderRights(nsDependentCString(mailboxName),
+ nsDependentCString(userName),
+ nsDependentCString(rights));
+}
+
+void nsImapProtocol::SetCopyResponseUid(const char* msgIdString) {
+ if (m_imapMailFolderSink)
+ m_imapMailFolderSink->SetCopyResponseUid(msgIdString, m_runningUrl);
+}
+
+void nsImapProtocol::CommitNamespacesForHostEvent() {
+ if (m_imapServerSink) m_imapServerSink->CommitNamespaces();
+}
+
+// notifies libmsg that we have new capability data for the current host
+void nsImapProtocol::CommitCapability() {
+ if (m_imapServerSink) {
+ m_imapServerSink->SetCapability(GetServerStateParser().GetCapabilityFlag());
+ }
+}
+
+// rights is a single string of rights, as specified by RFC2086, the IMAP ACL
+// extension. Clears all rights for a given folder, for all users.
+void nsImapProtocol::ClearAllFolderRights() {
+ if (m_imapMailFolderSink) m_imapMailFolderSink->ClearFolderRights();
+}
+
+// Reads a line from the socket.
+// Upon failure, the thread will be flagged for shutdown, and
+// m_connectionStatus will be set to a failing code.
+// Remember that some socket errors are deferred until the first read
+// attempt, so this function could be the first place we hear about
+// connection issues (e.g. bad certificates for SSL).
+char* nsImapProtocol::CreateNewLineFromSocket() {
+ bool needMoreData = false;
+ char* newLine = nullptr;
+ uint32_t numBytesInLine = 0;
+ nsresult rv = NS_OK;
+ // we hold a ref to the input stream in case we get cancelled from the
+ // ui thread, which releases our ref to the input stream, and can
+ // cause the pipe to get deleted before the monitor the read is
+ // blocked on gets notified. When that happens, the imap thread
+ // will stay blocked.
+ nsCOMPtr<nsIInputStream> kungFuGrip = m_inputStream;
+
+ if (m_mockChannel) {
+ nsImapMockChannel* imapChannel =
+ static_cast<nsImapMockChannel*>(m_mockChannel.get());
+
+ mozilla::MonitorAutoLock lock(imapChannel->mSuspendedMonitor);
+
+ bool suspended = imapChannel->mSuspended;
+ if (suspended)
+ MOZ_LOG(IMAP, LogLevel::Debug,
+ ("Waiting until [imapChannel=%p] is resumed.", imapChannel));
+ while (imapChannel->mSuspended) {
+ lock.Wait();
+ }
+ if (suspended)
+ MOZ_LOG(
+ IMAP, LogLevel::Debug,
+ ("Done waiting, [imapChannel=%p] has been resumed.", imapChannel));
+ }
+
+ do {
+ newLine = m_inputStreamBuffer->ReadNextLine(m_inputStream, numBytesInLine,
+ needMoreData, &rv);
+ MOZ_LOG(IMAP, LogLevel::Verbose,
+ ("ReadNextLine [rv=0x%" PRIx32 " stream=%p nb=%u needmore=%u]",
+ static_cast<uint32_t>(rv), m_inputStream.get(), numBytesInLine,
+ needMoreData));
+
+ } while (!newLine && NS_SUCCEEDED(rv) &&
+ !DeathSignalReceived()); // until we get the next line and haven't
+ // been interrupted
+
+ kungFuGrip = nullptr;
+
+ if (NS_FAILED(rv)) {
+ switch (rv) {
+ case NS_ERROR_UNKNOWN_HOST:
+ case NS_ERROR_UNKNOWN_PROXY_HOST:
+ AlertUserEventUsingName("imapUnknownHostError");
+ break;
+ case NS_ERROR_CONNECTION_REFUSED:
+ case NS_ERROR_PROXY_CONNECTION_REFUSED:
+ AlertUserEventUsingName("imapConnectionRefusedError");
+ break;
+ case NS_ERROR_NET_TIMEOUT:
+ case NS_ERROR_NET_RESET:
+ case NS_BASE_STREAM_CLOSED:
+ case NS_ERROR_NET_INTERRUPT:
+ // we should retry on RESET, especially for SSL...
+ if ((TestFlag(IMAP_RECEIVED_GREETING) || rv == NS_ERROR_NET_RESET) &&
+ m_runningUrl && !m_retryUrlOnError) {
+ bool rerunningUrl;
+ nsImapAction imapAction;
+ m_runningUrl->GetRerunningUrl(&rerunningUrl);
+ m_runningUrl->GetImapAction(&imapAction);
+ // don't rerun if we already were rerunning. And don't rerun
+ // online move/copies that timeout.
+ if (!rerunningUrl && (rv != NS_ERROR_NET_TIMEOUT ||
+ (imapAction != nsIImapUrl::nsImapOnlineCopy &&
+ imapAction != nsIImapUrl::nsImapOnlineMove))) {
+ m_runningUrl->SetRerunningUrl(true);
+ m_retryUrlOnError = true;
+ break;
+ }
+ }
+ if (rv == NS_ERROR_NET_TIMEOUT)
+ AlertUserEventUsingName("imapNetTimeoutError");
+ else
+ AlertUserEventUsingName(TestFlag(IMAP_RECEIVED_GREETING)
+ ? "imapServerDisconnected"
+ : "imapServerDroppedConnection");
+ break;
+ default:
+ // This is probably a TLS error. Usually TLS errors won't show up until
+ // we do ReadNextLine() above. Since we're in the IMAP thread we can't
+ // call NSSErrorsService::GetErrorClass() to determine if the error
+ // should result in an non-fatal override dialog (usually certificate
+ // issues) or if it's a fatal protocol error that the user must be
+ // alerted to. Instead, we use some publicly-accessible macros and a
+ // function to determine this.
+ if (NS_ERROR_GET_MODULE(rv) == NS_ERROR_MODULE_SECURITY &&
+ NS_ERROR_GET_SEVERITY(rv) == NS_ERROR_SEVERITY_ERROR) {
+ // It's an error of class 21 (SSL/TLS/Security), e.g., overridable
+ // SSL_ERROR_BAD_CERT_DOMAIN from security/nss/lib/ssl/sslerr.h
+ // rv = 0x80000000 + 0x00450000 + 0x00150000 + 0x00002ff4 = 0x805A2ff4
+ int32_t sec_error = -1 * NS_ERROR_GET_CODE(rv); // = 0xFFFFD00C
+ if (!mozilla::psm::ErrorIsOverridable(sec_error)) {
+ AlertUserEventUsingName("imapTlsError");
+ }
+
+ // Stash the socket transport securityInfo on the URL so it will be
+ // available in nsIUrlListener OnStopRunningUrl() callbacks to trigger
+ // the override dialog or a security related error message.
+ // Currently this is only used to trigger the override dialog.
+ if (m_runningUrl) {
+ MOZ_ASSERT(!NS_IsMainThread());
+ nsCOMPtr<nsIMsgMailNewsUrl> mailNewsUrl =
+ do_QueryInterface(m_runningUrl);
+ if (mailNewsUrl) {
+ nsCOMPtr<nsITransportSecurityInfo> securityInfo;
+ GetTransportSecurityInfo(getter_AddRefs(securityInfo));
+ if (securityInfo) {
+ nsAutoCString logMsg("Security error - error code=");
+ nsAutoString errorCodeString;
+ securityInfo->GetErrorCodeString(errorCodeString);
+ logMsg.Append(NS_ConvertUTF16toUTF8(errorCodeString));
+ Log("CreateNewLineFromSocket", nullptr, logMsg.get());
+
+ mailNewsUrl->SetFailedSecInfo(securityInfo);
+ }
+ }
+ }
+ }
+ break;
+ }
+
+ nsAutoCString logMsg("clearing IMAP_CONNECTION_IS_OPEN - rv = ");
+ logMsg.AppendInt(static_cast<uint32_t>(rv), 16);
+ Log("CreateNewLineFromSocket", nullptr, logMsg.get());
+ ClearFlag(IMAP_CONNECTION_IS_OPEN);
+ TellThreadToDie();
+ }
+ Log("CreateNewLineFromSocket", nullptr, newLine);
+ SetConnectionStatus(newLine && numBytesInLine
+ ? NS_OK
+ : rv); // set > 0 if string is not null or empty
+ return newLine;
+}
+
+nsresult nsImapProtocol::GetConnectionStatus() { return m_connectionStatus; }
+
+void nsImapProtocol::SetConnectionStatus(nsresult status) {
+ // Log failure at Debug level, otherwise use Verbose to avoid huge logs
+ MOZ_LOG(
+ IMAP, NS_SUCCEEDED(status) ? LogLevel::Verbose : LogLevel::Debug,
+ ("SetConnectionStatus(0x%" PRIx32 ")", static_cast<uint32_t>(status)));
+ m_connectionStatus = status;
+}
+
+void nsImapProtocol::NotifyMessageFlags(imapMessageFlagsType flags,
+ const nsACString& keywords,
+ nsMsgKey key, uint64_t highestModSeq) {
+ if (m_imapMessageSink) {
+ // if we're selecting the folder, don't need to report the flags; we've
+ // already fetched them.
+ if (m_imapAction != nsIImapUrl::nsImapSelectFolder)
+ m_imapMessageSink->NotifyMessageFlags(flags, keywords, key,
+ highestModSeq);
+ }
+}
+
+void nsImapProtocol::NotifySearchHit(const char* hitLine) {
+ nsresult rv;
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl =
+ do_QueryInterface(m_runningUrl, &rv);
+ if (m_imapMailFolderSink)
+ m_imapMailFolderSink->NotifySearchHit(mailnewsUrl, hitLine);
+}
+
+void nsImapProtocol::SetMailboxDiscoveryStatus(EMailboxDiscoverStatus status) {
+ ReentrantMonitorAutoEnter mon(m_dataMemberMonitor);
+ m_discoveryStatus = status;
+}
+
+EMailboxDiscoverStatus nsImapProtocol::GetMailboxDiscoveryStatus() {
+ ReentrantMonitorAutoEnter mon(m_dataMemberMonitor);
+ return m_discoveryStatus;
+}
+
+bool nsImapProtocol::GetSubscribingNow() {
+ // ***** code me *****
+ return false; // ***** for now
+}
+
+void nsImapProtocol::DiscoverMailboxSpec(nsImapMailboxSpec* adoptedBoxSpec) {
+ nsImapNamespace* ns = nullptr;
+
+ NS_ASSERTION(m_hostSessionList, "fatal null host session list");
+ if (!m_hostSessionList) return;
+
+ m_hostSessionList->GetDefaultNamespaceOfTypeForHost(GetImapServerKey(),
+ kPersonalNamespace, ns);
+ const char* nsPrefix = ns ? ns->GetPrefix() : 0;
+
+ if (m_specialXListMailboxes.Count() > 0) {
+ nsCString strHashKey(adoptedBoxSpec->mAllocatedPathName);
+ int32_t hashValue = m_specialXListMailboxes.Get(strHashKey);
+ adoptedBoxSpec->mBoxFlags |= hashValue;
+ }
+
+ switch (m_hierarchyNameState) {
+ case kXListing:
+ if (adoptedBoxSpec->mBoxFlags &
+ (kImapXListTrash | kImapAllMail | kImapInbox | kImapSent | kImapSpam |
+ kImapDrafts)) {
+ nsCString mailboxName(adoptedBoxSpec->mAllocatedPathName);
+ m_specialXListMailboxes.InsertOrUpdate(mailboxName,
+ adoptedBoxSpec->mBoxFlags);
+ // Remember hierarchy delimiter in case this is the first time we've
+ // connected to the server and we need it to be correct for the
+ // two-level XLIST we send (INBOX is guaranteed to be in the first
+ // response).
+ if (adoptedBoxSpec->mBoxFlags & kImapInbox)
+ m_runningUrl->SetOnlineSubDirSeparator(
+ adoptedBoxSpec->mHierarchySeparator);
+ }
+ break;
+ case kListingForFolderFlags: {
+ // store mailbox flags from LIST for use by LSUB
+ nsCString mailboxName(adoptedBoxSpec->mAllocatedPathName);
+ m_standardListMailboxes.InsertOrUpdate(mailboxName,
+ adoptedBoxSpec->mBoxFlags);
+ } break;
+ case kListingForCreate:
+ case kNoOperationInProgress:
+ case kDiscoverTrashFolderInProgress:
+ case kListingForInfoAndDiscovery: {
+ // standard mailbox specs are stored in m_standardListMailboxes
+ // because LSUB does necessarily return all mailbox flags.
+ // count should be > 0 only when we are looking at response of LSUB
+ if (m_standardListMailboxes.Count() > 0) {
+ int32_t hashValue = 0;
+ nsCString strHashKey(adoptedBoxSpec->mAllocatedPathName);
+ if (m_standardListMailboxes.Get(strHashKey, &hashValue))
+ adoptedBoxSpec->mBoxFlags |= hashValue;
+ else
+ // if mailbox is not in hash list, then it is subscribed but does not
+ // exist, so we make sure it can't be selected
+ adoptedBoxSpec->mBoxFlags |= kNoselect;
+ }
+ if (ns &&
+ nsPrefix) // if no personal namespace, there can be no Trash folder
+ {
+ bool onlineTrashFolderExists = false;
+ if (m_hostSessionList) {
+ if (adoptedBoxSpec->mBoxFlags & (kImapTrash | kImapXListTrash)) {
+ m_hostSessionList->SetOnlineTrashFolderExistsForHost(
+ GetImapServerKey(), true);
+ onlineTrashFolderExists = true;
+ } else {
+ m_hostSessionList->GetOnlineTrashFolderExistsForHost(
+ GetImapServerKey(), onlineTrashFolderExists);
+ }
+ }
+
+ // Don't set the Trash flag if not using the Trash model
+ if (GetDeleteIsMoveToTrash() && !onlineTrashFolderExists &&
+ FindInReadable(m_trashFolderPath,
+ adoptedBoxSpec->mAllocatedPathName,
+ nsCaseInsensitiveCStringComparator)) {
+ bool trashExists = false;
+ if (StringBeginsWith(m_trashFolderPath, "INBOX/"_ns,
+ nsCaseInsensitiveCStringComparator)) {
+ nsAutoCString pathName(adoptedBoxSpec->mAllocatedPathName.get() +
+ 6);
+ trashExists =
+ StringBeginsWith(
+ adoptedBoxSpec->mAllocatedPathName, m_trashFolderPath,
+ nsCaseInsensitiveCStringComparator) && /* "INBOX/" */
+ pathName.Equals(Substring(m_trashFolderPath, 6),
+ nsCaseInsensitiveCStringComparator);
+ } else
+ trashExists = adoptedBoxSpec->mAllocatedPathName.Equals(
+ m_trashFolderPath, nsCaseInsensitiveCStringComparator);
+
+ if (m_hostSessionList)
+ m_hostSessionList->SetOnlineTrashFolderExistsForHost(
+ GetImapServerKey(), trashExists);
+
+ if (trashExists) adoptedBoxSpec->mBoxFlags |= kImapTrash;
+ }
+ }
+
+ // Discover the folder (shuttle over to libmsg, yay)
+ // Do this only if the folder name is not empty (i.e. the root)
+ if (!adoptedBoxSpec->mAllocatedPathName.IsEmpty()) {
+ if (m_hierarchyNameState == kListingForCreate)
+ adoptedBoxSpec->mBoxFlags |= kNewlyCreatedFolder;
+
+ if (m_imapServerSink) {
+ bool newFolder;
+
+ m_imapServerSink->PossibleImapMailbox(
+ adoptedBoxSpec->mAllocatedPathName,
+ adoptedBoxSpec->mHierarchySeparator, adoptedBoxSpec->mBoxFlags,
+ &newFolder);
+ // if it's a new folder to the server sink, setting discovery status
+ // to eContinueNew will cause us to get the ACL for the new folder.
+ if (newFolder) SetMailboxDiscoveryStatus(eContinueNew);
+
+ bool useSubscription = false;
+
+ if (m_hostSessionList)
+ m_hostSessionList->GetHostIsUsingSubscription(GetImapServerKey(),
+ useSubscription);
+
+ if ((GetMailboxDiscoveryStatus() != eContinue) &&
+ (GetMailboxDiscoveryStatus() != eContinueNew) &&
+ (GetMailboxDiscoveryStatus() != eListMyChildren)) {
+ SetConnectionStatus(NS_ERROR_FAILURE);
+ } else if (!adoptedBoxSpec->mAllocatedPathName.IsEmpty() &&
+ (GetMailboxDiscoveryStatus() == eListMyChildren) &&
+ (!useSubscription || GetSubscribingNow())) {
+ NS_ASSERTION(false, "we should never get here anymore");
+ SetMailboxDiscoveryStatus(eContinue);
+ } else if (GetMailboxDiscoveryStatus() == eContinueNew) {
+ if (m_hierarchyNameState == kListingForInfoAndDiscovery &&
+ !adoptedBoxSpec->mAllocatedPathName.IsEmpty() &&
+ !(adoptedBoxSpec->mBoxFlags & kNameSpace)) {
+ // remember the info here also
+ nsIMAPMailboxInfo* mb =
+ new nsIMAPMailboxInfo(adoptedBoxSpec->mAllocatedPathName,
+ adoptedBoxSpec->mHierarchySeparator);
+ m_listedMailboxList.AppendElement(mb);
+ }
+ SetMailboxDiscoveryStatus(eContinue);
+ }
+ }
+ }
+ } break;
+ case kDeleteSubFoldersInProgress: {
+ NS_ASSERTION(m_deletableChildren, "Oops .. null m_deletableChildren");
+ m_deletableChildren->AppendElement(adoptedBoxSpec->mAllocatedPathName);
+ } break;
+ case kListingForInfoOnly: {
+ // UpdateProgressWindowForUpgrade(adoptedBoxSpec->allocatedPathName);
+ ProgressEventFunctionUsingNameWithString(
+ "imapDiscoveringMailbox", adoptedBoxSpec->mAllocatedPathName.get());
+ nsIMAPMailboxInfo* mb =
+ new nsIMAPMailboxInfo(adoptedBoxSpec->mAllocatedPathName,
+ adoptedBoxSpec->mHierarchySeparator);
+ m_listedMailboxList.AppendElement(mb);
+ } break;
+ case kDiscoveringNamespacesOnly: {
+ } break;
+ default:
+ NS_ASSERTION(false, "we aren't supposed to be here");
+ break;
+ }
+}
+
+void nsImapProtocol::AlertUserEventUsingName(const char* aMessageName) {
+ if (m_imapServerSink) {
+ bool suppressErrorMsg = false;
+
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_runningUrl);
+ if (mailnewsUrl) mailnewsUrl->GetSuppressErrorMsgs(&suppressErrorMsg);
+
+ if (!suppressErrorMsg)
+ m_imapServerSink->FEAlertWithName(aMessageName, mailnewsUrl);
+ }
+}
+
+void nsImapProtocol::AlertUserEvent(const char* message) {
+ if (m_imapServerSink) {
+ bool suppressErrorMsg = false;
+
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_runningUrl);
+ if (mailnewsUrl) mailnewsUrl->GetSuppressErrorMsgs(&suppressErrorMsg);
+
+ if (!suppressErrorMsg)
+ m_imapServerSink->FEAlert(NS_ConvertASCIItoUTF16(message), mailnewsUrl);
+ }
+}
+
+void nsImapProtocol::AlertUserEventFromServer(const char* aServerEvent,
+ bool aForIdle) {
+ if (aServerEvent) {
+ // If called due to BAD/NO imap IDLE response, the server sink and running
+ // url are typically null when IDLE command is sent. So use the stored
+ // latest values for these so that the error alert notification occurs.
+ if (aForIdle && !m_imapServerSink && !m_runningUrl &&
+ m_imapServerSinkLatest) {
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl =
+ do_QueryInterface(m_runningUrlLatest);
+ m_imapServerSinkLatest->FEAlertFromServer(
+ nsDependentCString(aServerEvent), mailnewsUrl);
+ } else if (m_imapServerSink) {
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_runningUrl);
+ m_imapServerSink->FEAlertFromServer(nsDependentCString(aServerEvent),
+ mailnewsUrl);
+ }
+ }
+}
+
+void nsImapProtocol::ResetProgressInfo() {
+ m_lastProgressTime = 0;
+ m_lastPercent = -1;
+ m_lastProgressStringName.Truncate();
+}
+
+void nsImapProtocol::SetProgressString(uint32_t aStringIndex) {
+ m_stringIndex = aStringIndex;
+ MOZ_ASSERT(m_stringIndex <= IMAP_EMPTY_STRING_INDEX);
+ switch (m_stringIndex) {
+ case IMAP_HEADERS_STRING_INDEX:
+ m_progressStringName = "imapReceivingMessageHeaders3";
+ break;
+ case IMAP_MESSAGES_STRING_INDEX:
+ m_progressStringName = "imapFolderReceivingMessageOf3";
+ break;
+ case IMAP_FLAGS_STRING_INDEX:
+ m_progressStringName = "imapReceivingMessageFlags3";
+ break;
+ case IMAP_EMPTY_STRING_INDEX:
+ default:
+ break;
+ }
+}
+
+void nsImapProtocol::ShowProgress() {
+ if (m_imapServerSink && (m_stringIndex != IMAP_EMPTY_STRING_INDEX)) {
+ nsString progressString;
+ const char* mailboxName = GetServerStateParser().GetSelectedMailboxName();
+ nsString unicodeMailboxName;
+ nsresult rv = CopyFolderNameToUTF16(nsDependentCString(mailboxName),
+ unicodeMailboxName);
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ int32_t progressCurrentNumber = ++m_progressCurrentNumber[m_stringIndex];
+
+ PercentProgressUpdateEvent(m_progressStringName, unicodeMailboxName,
+ progressCurrentNumber, m_progressExpectedNumber);
+ }
+}
+
+void nsImapProtocol::ProgressEventFunctionUsingName(const char* aMsgName) {
+ if (m_imapMailFolderSink && !m_lastProgressStringName.Equals(aMsgName)) {
+ m_imapMailFolderSink->ProgressStatusString(this, aMsgName, nullptr);
+ m_lastProgressStringName.Assign(aMsgName);
+ // who's going to free this? Does ProgressStatusString complete
+ // synchronously?
+ }
+}
+
+void nsImapProtocol::ProgressEventFunctionUsingNameWithString(
+ const char* aMsgName, const char* aExtraInfo) {
+ if (m_imapMailFolderSink) {
+ nsString unicodeStr;
+ nsresult rv =
+ CopyFolderNameToUTF16(nsDependentCString(aExtraInfo), unicodeStr);
+ if (NS_SUCCEEDED(rv))
+ m_imapMailFolderSink->ProgressStatusString(this, aMsgName,
+ unicodeStr.get());
+ }
+}
+
+void nsImapProtocol::PercentProgressUpdateEvent(nsACString const& fmtStringName,
+ nsAString const& mailbox,
+ int64_t currentProgress,
+ int64_t maxProgress) {
+ int64_t nowMS = 0;
+ int32_t percent = (100 * currentProgress) / maxProgress;
+ if (percent == m_lastPercent)
+ return; // hasn't changed, right? So just return. Do we need to clear this
+ // anywhere?
+
+ if (percent < 100) // always need to do 100%
+ {
+ nowMS = PR_IntervalToMilliseconds(PR_IntervalNow());
+ if (nowMS - m_lastProgressTime < 750) return;
+ }
+
+ m_lastPercent = percent;
+ m_lastProgressTime = nowMS;
+
+ // set our max progress on the running URL
+ if (m_runningUrl) {
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl(do_QueryInterface(m_runningUrl));
+ mailnewsUrl->SetMaxProgress(maxProgress);
+ }
+
+ if (m_imapMailFolderSink) {
+ m_imapMailFolderSink->PercentProgress(this, fmtStringName, mailbox,
+ currentProgress, maxProgress);
+ }
+}
+
+// imap commands issued by the parser
+void nsImapProtocol::Store(const nsCString& messageList,
+ const char* messageData, bool idsAreUid) {
+ // turn messageList back into key array and then back into a message id list,
+ // but use the flag state to handle ranges correctly.
+ nsCString messageIdList;
+ nsTArray<nsMsgKey> msgKeys;
+ if (idsAreUid) ParseUidString(messageList.get(), msgKeys);
+
+ int32_t msgCountLeft = msgKeys.Length();
+ uint32_t msgsHandled = 0;
+ do {
+ nsCString idString;
+
+ uint32_t msgsToHandle = msgCountLeft;
+ if (idsAreUid)
+ AllocateImapUidString(msgKeys.Elements() + msgsHandled, msgsToHandle,
+ m_flagState, idString); // 20 * 200
+ else
+ idString.Assign(messageList);
+
+ msgsHandled += msgsToHandle;
+ msgCountLeft -= msgsToHandle;
+
+ IncrementCommandTagNumber();
+ const char* formatString;
+ if (idsAreUid)
+ formatString = "%s uid store %s %s\015\012";
+ else
+ formatString = "%s store %s %s\015\012";
+
+ // we might need to close this mailbox after this
+ m_closeNeededBeforeSelect =
+ GetDeleteIsMoveToTrash() && (PL_strcasestr(messageData, "\\Deleted"));
+
+ const char* commandTag = GetServerCommandTag();
+ int protocolStringSize = PL_strlen(formatString) + messageList.Length() +
+ PL_strlen(messageData) + PL_strlen(commandTag) + 1;
+ char* protocolString = (char*)PR_CALLOC(protocolStringSize);
+
+ if (protocolString) {
+ PR_snprintf(protocolString, // string to create
+ protocolStringSize, // max size
+ formatString, // format string
+ commandTag, // command tag
+ idString.get(), messageData);
+
+ nsresult rv = SendData(protocolString);
+ if (NS_SUCCEEDED(rv)) {
+ m_flagChangeCount++;
+ ParseIMAPandCheckForNewMail(protocolString);
+ if (GetServerStateParser().LastCommandSuccessful() && CheckNeeded())
+ Check();
+ }
+ PR_Free(protocolString);
+ } else
+ HandleMemoryFailure();
+ } while (msgCountLeft > 0 && !DeathSignalReceived());
+}
+
+void nsImapProtocol::IssueUserDefinedMsgCommand(const char* command,
+ const char* messageList) {
+ IncrementCommandTagNumber();
+
+ const char* formatString;
+ formatString = "%s uid %s %s\015\012";
+
+ const char* commandTag = GetServerCommandTag();
+ int protocolStringSize = PL_strlen(formatString) + PL_strlen(messageList) +
+ PL_strlen(command) + PL_strlen(commandTag) + 1;
+ char* protocolString = (char*)PR_CALLOC(protocolStringSize);
+
+ if (protocolString) {
+ PR_snprintf(protocolString, // string to create
+ protocolStringSize, // max size
+ formatString, // format string
+ commandTag, // command tag
+ command, messageList);
+
+ nsresult rv = SendData(protocolString);
+ if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail(protocolString);
+ PR_Free(protocolString);
+ } else
+ HandleMemoryFailure();
+}
+
+void nsImapProtocol::UidExpunge(const nsCString& messageSet) {
+ IncrementCommandTagNumber();
+ nsCString command(GetServerCommandTag());
+ command.AppendLiteral(" uid expunge ");
+ command.Append(messageSet);
+ command.Append(CRLF);
+ nsresult rv = SendData(command.get());
+ if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail();
+}
+
+void nsImapProtocol::Expunge() {
+ uint32_t aclFlags = 0;
+ if (GetServerStateParser().ServerHasACLCapability() && m_imapMailFolderSink)
+ m_imapMailFolderSink->GetAclFlags(&aclFlags);
+
+ if (aclFlags && !(aclFlags & IMAP_ACL_EXPUNGE_FLAG)) return;
+ ProgressEventFunctionUsingName("imapStatusExpungingMailbox");
+
+ if (gCheckDeletedBeforeExpunge) {
+ GetServerStateParser().ResetSearchResultSequence();
+ Search("SEARCH DELETED", false, false);
+ if (GetServerStateParser().LastCommandSuccessful()) {
+ nsImapSearchResultIterator* search =
+ GetServerStateParser().CreateSearchResultIterator();
+ nsMsgKey key = search->GetNextMessageNumber();
+ delete search;
+ if (key == 0) return; // no deleted messages to expunge (bug 235004)
+ }
+ }
+
+ IncrementCommandTagNumber();
+ nsAutoCString command(GetServerCommandTag());
+ command.AppendLiteral(" expunge" CRLF);
+
+ nsresult rv = SendData(command.get());
+ if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail();
+}
+
+void nsImapProtocol::HandleMemoryFailure() {
+ PR_CEnterMonitor(this);
+ // **** jefft fix me!!!!!! ******
+ // m_imapThreadIsRunning = false;
+ // SetConnectionStatus(-1);
+ PR_CExitMonitor(this);
+}
+
+void nsImapProtocol::HandleCurrentUrlError() {
+ // This is to handle a move/copy failing, especially because the user
+ // cancelled the password prompt.
+ (void)m_runningUrl->GetImapAction(&m_imapAction);
+ if (m_imapAction == nsIImapUrl::nsImapOfflineToOnlineMove ||
+ m_imapAction == nsIImapUrl::nsImapAppendMsgFromFile ||
+ m_imapAction == nsIImapUrl::nsImapAppendDraftFromFile) {
+ if (m_imapMailFolderSink)
+ m_imapMailFolderSink->OnlineCopyCompleted(
+ this, ImapOnlineCopyStateType::kFailedCopy);
+ }
+}
+
+void nsImapProtocol::StartTLS() {
+ IncrementCommandTagNumber();
+ nsCString tag(GetServerCommandTag());
+ nsCString command(tag);
+
+ command.AppendLiteral(" STARTTLS" CRLF);
+ nsresult rv = SendData(command.get());
+ bool ok = false;
+ if (NS_SUCCEEDED(rv)) {
+ nsCString expectOkResponse = tag + " OK "_ns;
+ char* serverResponse = nullptr;
+ do {
+ // This reads and discards lines not starting with "<tag> OK " or
+ // "<tag> BAD " and exits when when either are found. Otherwise, this
+ // exits on timeout when all lines in the buffer are read causing
+ // serverResponse to be set null. Usually just "<tag> OK " is present.
+ serverResponse = CreateNewLineFromSocket();
+ ok = serverResponse &&
+ !PL_strncasecmp(serverResponse, expectOkResponse.get(),
+ expectOkResponse.Length());
+ if (!ok && serverResponse) {
+ // Check for possible BAD response, e.g., server not STARTTLS capable.
+ nsCString expectBadResponse = tag + " BAD "_ns;
+ if (!PL_strncasecmp(serverResponse, expectBadResponse.get(),
+ expectBadResponse.Length())) {
+ PR_Free(serverResponse);
+ break;
+ }
+ }
+ PR_Free(serverResponse);
+ } while (serverResponse && !ok);
+ }
+ // ok == false implies a "<tag> BAD " response or time out on socket read.
+ // It could also be due to failure on SendData() above.
+ GetServerStateParser().SetCommandFailed(!ok);
+}
+
+void nsImapProtocol::Capability() {
+ ProgressEventFunctionUsingName("imapStatusCheckCompat");
+ IncrementCommandTagNumber();
+ nsCString command(GetServerCommandTag());
+
+ command.AppendLiteral(" capability" CRLF);
+
+ nsresult rv = SendData(command.get());
+ if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail();
+}
+
+void nsImapProtocol::ID() {
+ if (!gAppName[0]) return;
+ IncrementCommandTagNumber();
+ nsCString command(GetServerCommandTag());
+ command.AppendLiteral(" ID (\"name\" \"");
+ command.Append(gAppName);
+ command.AppendLiteral("\" \"version\" \"");
+ command.Append(gAppVersion);
+ command.AppendLiteral("\")" CRLF);
+
+ nsresult rv = SendData(command.get());
+ if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail();
+}
+
+void nsImapProtocol::EnableUTF8Accept() {
+ IncrementCommandTagNumber();
+ nsCString command(GetServerCommandTag());
+ command.AppendLiteral(" ENABLE UTF8=ACCEPT" CRLF);
+
+ nsresult rv = SendData(command.get());
+ if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail();
+}
+
+void nsImapProtocol::EnableCondStore() {
+ IncrementCommandTagNumber();
+ nsCString command(GetServerCommandTag());
+
+ command.AppendLiteral(" ENABLE CONDSTORE" CRLF);
+
+ nsresult rv = SendData(command.get());
+ if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail();
+}
+
+void nsImapProtocol::StartCompressDeflate() {
+ // only issue a compression request if we haven't already
+ if (!TestFlag(IMAP_ISSUED_COMPRESS_REQUEST)) {
+ SetFlag(IMAP_ISSUED_COMPRESS_REQUEST);
+ IncrementCommandTagNumber();
+ nsCString command(GetServerCommandTag());
+
+ command.AppendLiteral(" COMPRESS DEFLATE" CRLF);
+
+ nsresult rv = SendData(command.get());
+ if (NS_SUCCEEDED(rv)) {
+ ParseIMAPandCheckForNewMail();
+ if (GetServerStateParser().LastCommandSuccessful()) {
+ rv = BeginCompressing();
+ if (NS_FAILED(rv)) {
+ Log("CompressDeflate", nullptr, "failed to enable compression");
+ // we can't use this connection without compression any more, so die
+ ClearFlag(IMAP_CONNECTION_IS_OPEN);
+ TellThreadToDie();
+ SetConnectionStatus(rv);
+ return;
+ }
+ }
+ }
+ }
+}
+
+nsresult nsImapProtocol::BeginCompressing() {
+ // wrap the streams in compression layers that compress or decompress
+ // all traffic.
+ RefPtr<nsMsgCompressIStream> new_in = new nsMsgCompressIStream();
+ if (!new_in) return NS_ERROR_OUT_OF_MEMORY;
+
+ nsresult rv = new_in->InitInputStream(m_inputStream);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ m_inputStream = new_in;
+
+ RefPtr<nsMsgCompressOStream> new_out = new nsMsgCompressOStream();
+ if (!new_out) return NS_ERROR_OUT_OF_MEMORY;
+
+ rv = new_out->InitOutputStream(m_outputStream);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ m_outputStream = new_out;
+ return rv;
+}
+
+void nsImapProtocol::Language() {
+ // only issue the language request if we haven't done so already...
+ if (!TestFlag(IMAP_ISSUED_LANGUAGE_REQUEST)) {
+ SetFlag(IMAP_ISSUED_LANGUAGE_REQUEST);
+ ProgressEventFunctionUsingName("imapStatusCheckCompat");
+ IncrementCommandTagNumber();
+ nsCString command(GetServerCommandTag());
+
+ // extract the desired language attribute from prefs
+ nsresult rv = NS_OK;
+
+ // we need to parse out the first language out of this comma separated
+ // list.... i.e if we have en,ja we only want to send en to the server.
+ if (mAcceptLanguages.get()) {
+ nsAutoCString extractedLanguage;
+ LossyCopyUTF16toASCII(mAcceptLanguages, extractedLanguage);
+ int32_t pos = extractedLanguage.FindChar(',');
+ if (pos > 0) // we have a comma separated list of languages...
+ extractedLanguage.SetLength(pos); // truncate everything after the
+ // first comma (including the comma)
+
+ if (extractedLanguage.IsEmpty()) return;
+
+ command.AppendLiteral(" LANGUAGE ");
+ command.Append(extractedLanguage);
+ command.Append(CRLF);
+
+ rv = SendData(command.get());
+ if (NS_SUCCEEDED(rv))
+ ParseIMAPandCheckForNewMail(nullptr, true /* ignore bad or no result from the server for this command */);
+ }
+ }
+}
+
+void nsImapProtocol::EscapeUserNamePasswordString(const char* strToEscape,
+ nsCString* resultStr) {
+ if (strToEscape) {
+ uint32_t i = 0;
+ uint32_t escapeStrlen = strlen(strToEscape);
+ for (i = 0; i < escapeStrlen; i++) {
+ if (strToEscape[i] == '\\' || strToEscape[i] == '\"') {
+ resultStr->Append('\\');
+ }
+ resultStr->Append(strToEscape[i]);
+ }
+ }
+}
+
+void nsImapProtocol::InitPrefAuthMethods(int32_t authMethodPrefValue,
+ nsIMsgIncomingServer* aServer) {
+ // for m_prefAuthMethods, using the same flags as server capabilities.
+ switch (authMethodPrefValue) {
+ case nsMsgAuthMethod::none:
+ m_prefAuthMethods = kHasAuthNoneCapability;
+ break;
+ case nsMsgAuthMethod::old:
+ m_prefAuthMethods = kHasAuthOldLoginCapability;
+ break;
+ case nsMsgAuthMethod::passwordCleartext:
+ m_prefAuthMethods = kHasAuthOldLoginCapability | kHasAuthLoginCapability |
+ kHasAuthPlainCapability;
+ break;
+ case nsMsgAuthMethod::passwordEncrypted:
+ m_prefAuthMethods = kHasCRAMCapability;
+ break;
+ case nsMsgAuthMethod::NTLM:
+ m_prefAuthMethods = kHasAuthNTLMCapability | kHasAuthMSNCapability;
+ break;
+ case nsMsgAuthMethod::GSSAPI:
+ m_prefAuthMethods = kHasAuthGssApiCapability;
+ break;
+ case nsMsgAuthMethod::External:
+ m_prefAuthMethods = kHasAuthExternalCapability;
+ break;
+ case nsMsgAuthMethod::secure:
+ m_prefAuthMethods = kHasCRAMCapability | kHasAuthGssApiCapability |
+ kHasAuthNTLMCapability | kHasAuthMSNCapability;
+ break;
+ case nsMsgAuthMethod::OAuth2:
+ m_prefAuthMethods = kHasXOAuth2Capability;
+ break;
+ default:
+ NS_ASSERTION(false, "IMAP: authMethod pref invalid");
+ MOZ_LOG(IMAP, LogLevel::Error,
+ ("IMAP: bad pref authMethod = %d", authMethodPrefValue));
+ // fall to any
+ [[fallthrough]];
+ case nsMsgAuthMethod::anything:
+ m_prefAuthMethods = kHasAuthOldLoginCapability | kHasAuthLoginCapability |
+ kHasAuthPlainCapability | kHasCRAMCapability |
+ kHasAuthGssApiCapability | kHasAuthNTLMCapability |
+ kHasAuthMSNCapability | kHasAuthExternalCapability |
+ kHasXOAuth2Capability;
+ break;
+ }
+
+ if (m_prefAuthMethods & kHasXOAuth2Capability) {
+ mOAuth2Support = new mozilla::mailnews::OAuth2ThreadHelper(aServer);
+ if (!mOAuth2Support || !mOAuth2Support->SupportsOAuth2()) {
+ // Disable OAuth2 support if we don't have the prefs installed.
+ m_prefAuthMethods &= ~kHasXOAuth2Capability;
+ mOAuth2Support = nullptr;
+ MOZ_LOG(IMAP, LogLevel::Warning,
+ ("IMAP: no OAuth2 support for this server."));
+ }
+ }
+}
+
+/**
+ * Changes m_currentAuthMethod to pick the best remaining one
+ * which is allowed by server and prefs and not marked failed.
+ * The order of preference and trying of auth methods is encoded here.
+ */
+nsresult nsImapProtocol::ChooseAuthMethod() {
+ eIMAPCapabilityFlags serverCaps = GetServerStateParser().GetCapabilityFlag();
+ eIMAPCapabilityFlags availCaps =
+ serverCaps & m_prefAuthMethods & ~m_failedAuthMethods;
+
+ MOZ_LOG(IMAP, LogLevel::Debug,
+ ("IMAP auth: server caps 0x%" PRIx64 ", pref 0x%" PRIx64
+ ", failed 0x%" PRIx64 ", avail caps 0x%" PRIx64,
+ serverCaps, m_prefAuthMethods, m_failedAuthMethods, availCaps));
+ // clang-format off
+ MOZ_LOG(IMAP, LogLevel::Debug,
+ ("(GSSAPI = 0x%" PRIx64 ", CRAM = 0x%" PRIx64 ", NTLM = 0x%" PRIx64
+ ", MSN = 0x%" PRIx64 ", PLAIN = 0x%" PRIx64 ", LOGIN = 0x%" PRIx64
+ ", old-style IMAP login = 0x%" PRIx64
+ ", auth external IMAP login = 0x%" PRIx64 ", OAUTH2 = 0x%" PRIx64 ")",
+ kHasAuthGssApiCapability, kHasCRAMCapability, kHasAuthNTLMCapability,
+ kHasAuthMSNCapability, kHasAuthPlainCapability, kHasAuthLoginCapability,
+ kHasAuthOldLoginCapability, kHasAuthExternalCapability,
+ kHasXOAuth2Capability));
+ // clang-format on
+
+ if (kHasAuthExternalCapability & availCaps)
+ m_currentAuthMethod = kHasAuthExternalCapability;
+ else if (kHasAuthGssApiCapability & availCaps)
+ m_currentAuthMethod = kHasAuthGssApiCapability;
+ else if (kHasCRAMCapability & availCaps)
+ m_currentAuthMethod = kHasCRAMCapability;
+ else if (kHasAuthNTLMCapability & availCaps)
+ m_currentAuthMethod = kHasAuthNTLMCapability;
+ else if (kHasAuthMSNCapability & availCaps)
+ m_currentAuthMethod = kHasAuthMSNCapability;
+ else if (kHasXOAuth2Capability & availCaps)
+ m_currentAuthMethod = kHasXOAuth2Capability;
+ else if (kHasAuthPlainCapability & availCaps)
+ m_currentAuthMethod = kHasAuthPlainCapability;
+ else if (kHasAuthLoginCapability & availCaps)
+ m_currentAuthMethod = kHasAuthLoginCapability;
+ else if (kHasAuthOldLoginCapability & availCaps)
+ m_currentAuthMethod = kHasAuthOldLoginCapability;
+ else {
+ MOZ_LOG(IMAP, LogLevel::Debug, ("No remaining auth method"));
+ m_currentAuthMethod = kCapabilityUndefined;
+ return NS_ERROR_FAILURE;
+ }
+ MOZ_LOG(IMAP, LogLevel::Debug,
+ ("Trying auth method 0x%" PRIx64, m_currentAuthMethod));
+ return NS_OK;
+}
+
+void nsImapProtocol::MarkAuthMethodAsFailed(
+ eIMAPCapabilityFlags failedAuthMethod) {
+ MOZ_LOG(IMAP, LogLevel::Debug,
+ ("Marking auth method 0x%" PRIx64 " failed", failedAuthMethod));
+ m_failedAuthMethods |= failedAuthMethod;
+}
+
+/**
+ * Start over, trying all auth methods again
+ */
+void nsImapProtocol::ResetAuthMethods() {
+ MOZ_LOG(IMAP, LogLevel::Debug, ("resetting (failed) auth methods"));
+ m_currentAuthMethod = kCapabilityUndefined;
+ m_failedAuthMethods = 0;
+}
+
+nsresult nsImapProtocol::SendDataParseIMAPandCheckForNewMail(
+ const char* aData, const char* aCommand) {
+ nsresult rv;
+ bool isResend = false;
+ while (true) {
+ // Send authentication string (true: suppress logging the string).
+ rv = SendData(aData, true);
+ if (NS_FAILED(rv)) break;
+ ParseIMAPandCheckForNewMail(aCommand);
+ if (!GetServerStateParser().WaitingForMoreClientInput()) break;
+
+ // The server is asking for the authentication string again. So we send
+ // the same string again although we know that it might be rejected again.
+ // We do that to get a firm authentication failure instead of a resend
+ // request. That keeps things in order before failing authentication and
+ // trying another method if capable.
+ if (isResend) {
+ rv = NS_ERROR_FAILURE;
+ break;
+ }
+ isResend = true;
+ }
+
+ return rv;
+}
+
+nsresult nsImapProtocol::ClientID() {
+ IncrementCommandTagNumber();
+ nsCString command(GetServerCommandTag());
+ command += " CLIENTID UUID ";
+ command += m_clientId;
+ command += CRLF;
+ nsresult rv = SendDataParseIMAPandCheckForNewMail(command.get(), nullptr);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!GetServerStateParser().LastCommandSuccessful()) {
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+nsresult nsImapProtocol::AuthLogin(const char* userName,
+ const nsString& aPassword,
+ eIMAPCapabilityFlag flag) {
+ ProgressEventFunctionUsingName("imapStatusSendingAuthLogin");
+ IncrementCommandTagNumber();
+
+ char* currentCommand = nullptr;
+ nsresult rv;
+ NS_ConvertUTF16toUTF8 password(aPassword);
+ MOZ_LOG(IMAP, LogLevel::Debug,
+ ("IMAP: trying auth method 0x%" PRIx64, m_currentAuthMethod));
+
+ if (flag & kHasAuthExternalCapability) {
+ char* base64UserName = PL_Base64Encode(userName, strlen(userName), nullptr);
+ nsAutoCString command(GetServerCommandTag());
+ command.AppendLiteral(" authenticate EXTERNAL ");
+ command.Append(base64UserName);
+ command.Append(CRLF);
+ PR_Free(base64UserName);
+ rv = SendData(command.get());
+ ParseIMAPandCheckForNewMail();
+ nsImapServerResponseParser& parser = GetServerStateParser();
+ if (parser.LastCommandSuccessful()) return NS_OK;
+ parser.SetCapabilityFlag(parser.GetCapabilityFlag() &
+ ~kHasAuthExternalCapability);
+ } else if (flag & kHasCRAMCapability) {
+ NS_ENSURE_TRUE(m_imapServerSink, NS_ERROR_NULL_POINTER);
+ MOZ_LOG(IMAP, LogLevel::Debug, ("MD5 auth"));
+ // inform the server that we want to begin a CRAM authentication
+ // procedure...
+ nsAutoCString command(GetServerCommandTag());
+ command.AppendLiteral(" authenticate CRAM-MD5" CRLF);
+ rv = SendData(command.get());
+ NS_ENSURE_SUCCESS(rv, rv);
+ ParseIMAPandCheckForNewMail();
+ if (GetServerStateParser().LastCommandSuccessful()) {
+ char* digest = nullptr;
+ char* cramDigest = GetServerStateParser().fAuthChallenge;
+ char* decodedChallenge =
+ PL_Base64Decode(cramDigest, strlen(cramDigest), nullptr);
+ rv = m_imapServerSink->CramMD5Hash(decodedChallenge, password.get(),
+ &digest);
+ PR_Free(decodedChallenge);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_TRUE(digest, NS_ERROR_NULL_POINTER);
+ // The encoded digest is the hexadecimal representation of
+ // DIGEST_LENGTH characters, so it will be twice that length.
+ nsAutoCStringN<2 * DIGEST_LENGTH> encodedDigest;
+
+ for (uint32_t j = 0; j < DIGEST_LENGTH; j++) {
+ char hexVal[3];
+ PR_snprintf(hexVal, 3, "%.2x", 0x0ff & (unsigned short)(digest[j]));
+ encodedDigest.Append(hexVal);
+ }
+
+ PR_snprintf(m_dataOutputBuf, OUTPUT_BUFFER_SIZE, "%.255s %s", userName,
+ encodedDigest.get());
+ char* base64Str =
+ PL_Base64Encode(m_dataOutputBuf, strlen(m_dataOutputBuf), nullptr);
+ PR_snprintf(m_dataOutputBuf, OUTPUT_BUFFER_SIZE, "%s" CRLF, base64Str);
+ PR_Free(base64Str);
+ PR_Free(digest);
+ rv = SendData(m_dataOutputBuf);
+ NS_ENSURE_SUCCESS(rv, rv);
+ ParseIMAPandCheckForNewMail(command.get());
+ }
+ } // if CRAM response was received
+ else if (flag & kHasAuthGssApiCapability) {
+ MOZ_LOG(IMAP, LogLevel::Debug, ("MD5 auth"));
+
+ // Only try GSSAPI once - if it fails, its going to be because we don't
+ // have valid credentials
+ // MarkAuthMethodAsFailed(kHasAuthGssApiCapability);
+
+ // We do step1 first, so we don't try GSSAPI against a server which
+ // we can't get credentials for.
+ nsAutoCString response;
+
+ nsAutoCString service("imap@");
+ service.Append(m_hostName);
+ rv = DoGSSAPIStep1(service, userName, response);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString command(GetServerCommandTag());
+ command.AppendLiteral(" authenticate GSSAPI" CRLF);
+ rv = SendData(command.get());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ ParseIMAPandCheckForNewMail("AUTH GSSAPI");
+ if (GetServerStateParser().LastCommandSuccessful()) {
+ response += CRLF;
+ rv = SendData(response.get());
+ NS_ENSURE_SUCCESS(rv, rv);
+ ParseIMAPandCheckForNewMail(command.get());
+ nsresult gssrv = NS_OK;
+
+ while (GetServerStateParser().LastCommandSuccessful() &&
+ NS_SUCCEEDED(gssrv) && gssrv != NS_SUCCESS_AUTH_FINISHED) {
+ nsCString challengeStr(GetServerStateParser().fAuthChallenge);
+ gssrv = DoGSSAPIStep2(challengeStr, response);
+ if (NS_SUCCEEDED(gssrv)) {
+ response += CRLF;
+ rv = SendData(response.get());
+ } else
+ rv = SendData("*" CRLF);
+
+ NS_ENSURE_SUCCESS(rv, rv);
+ ParseIMAPandCheckForNewMail(command.get());
+ }
+ // TODO: whether it worked or not is shown by LastCommandSuccessful(), not
+ // gssrv, right?
+ }
+ } else if (flag & (kHasAuthNTLMCapability | kHasAuthMSNCapability)) {
+ MOZ_LOG(IMAP, LogLevel::Debug, ("NTLM auth"));
+ nsAutoCString command(GetServerCommandTag());
+ command.Append((flag & kHasAuthNTLMCapability) ? " authenticate NTLM" CRLF
+ : " authenticate MSN" CRLF);
+ rv = SendData(command.get());
+ ParseIMAPandCheckForNewMail(
+ "AUTH NTLM"); // this just waits for ntlm step 1
+ if (GetServerStateParser().LastCommandSuccessful()) {
+ nsAutoCString cmd;
+ rv = DoNtlmStep1(nsDependentCString(userName), aPassword, cmd);
+ NS_ENSURE_SUCCESS(rv, rv);
+ cmd += CRLF;
+ rv = SendData(cmd.get());
+ NS_ENSURE_SUCCESS(rv, rv);
+ ParseIMAPandCheckForNewMail(command.get());
+ if (GetServerStateParser().LastCommandSuccessful()) {
+ nsCString challengeStr(GetServerStateParser().fAuthChallenge);
+ nsCString response;
+ rv = DoNtlmStep2(challengeStr, response);
+ NS_ENSURE_SUCCESS(rv, rv);
+ response += CRLF;
+ rv = SendData(response.get());
+ ParseIMAPandCheckForNewMail(command.get());
+ }
+ }
+ } else if (flag & kHasAuthPlainCapability) {
+ MOZ_LOG(IMAP, LogLevel::Debug, ("PLAIN auth"));
+ PR_snprintf(m_dataOutputBuf, OUTPUT_BUFFER_SIZE,
+ "%s authenticate PLAIN" CRLF, GetServerCommandTag());
+ rv = SendData(m_dataOutputBuf);
+ NS_ENSURE_SUCCESS(rv, rv);
+ currentCommand = PL_strdup(
+ m_dataOutputBuf); /* StrAllocCopy(currentCommand, GetOutputBuffer()); */
+ ParseIMAPandCheckForNewMail();
+ if (GetServerStateParser().LastCommandSuccessful()) {
+ // RFC 4616
+ char plain_string[513];
+ memset(plain_string, 0, 513);
+ PR_snprintf(&plain_string[1], 256, "%.255s", userName);
+ uint32_t len = std::min<uint32_t>(PL_strlen(userName), 255u) +
+ 2; // We include two <NUL> characters.
+ PR_snprintf(&plain_string[len], 256, "%.255s", password.get());
+ len += std::min<uint32_t>(password.Length(), 255u);
+ char* base64Str = PL_Base64Encode(plain_string, len, nullptr);
+ PR_snprintf(m_dataOutputBuf, OUTPUT_BUFFER_SIZE, "%s" CRLF, base64Str);
+ PR_Free(base64Str);
+
+ rv = SendDataParseIMAPandCheckForNewMail(m_dataOutputBuf, currentCommand);
+ } // if the last command succeeded
+ } // if auth plain capability
+ else if (flag & kHasAuthLoginCapability) {
+ MOZ_LOG(IMAP, LogLevel::Debug, ("LOGIN auth"));
+ PR_snprintf(m_dataOutputBuf, OUTPUT_BUFFER_SIZE,
+ "%s authenticate LOGIN" CRLF, GetServerCommandTag());
+ rv = SendData(m_dataOutputBuf);
+ NS_ENSURE_SUCCESS(rv, rv);
+ currentCommand = PL_strdup(m_dataOutputBuf);
+ ParseIMAPandCheckForNewMail();
+
+ if (GetServerStateParser().LastCommandSuccessful()) {
+ char* base64Str = PL_Base64Encode(
+ userName, std::min<uint32_t>(PL_strlen(userName), 255u), nullptr);
+ PR_snprintf(m_dataOutputBuf, OUTPUT_BUFFER_SIZE, "%s" CRLF, base64Str);
+ PR_Free(base64Str);
+ rv = SendData(m_dataOutputBuf, true /* suppress logging */);
+ if (NS_SUCCEEDED(rv)) {
+ ParseIMAPandCheckForNewMail(currentCommand);
+ if (GetServerStateParser().LastCommandSuccessful()) {
+ base64Str = PL_Base64Encode(
+ password.get(), std::min<uint32_t>(password.Length(), 255u),
+ nullptr);
+ PR_snprintf(m_dataOutputBuf, OUTPUT_BUFFER_SIZE, "%s" CRLF,
+ base64Str);
+ PR_Free(base64Str);
+ rv = SendData(m_dataOutputBuf, true /* suppress logging */);
+ if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail(currentCommand);
+ } // if last command successful
+ } // if last command successful
+ } // if last command successful
+ } // if has auth login capability
+ else if (flag & kHasAuthOldLoginCapability) {
+ MOZ_LOG(IMAP, LogLevel::Debug, ("old-style auth"));
+ ProgressEventFunctionUsingName("imapStatusSendingLogin");
+ IncrementCommandTagNumber();
+ nsCString command(GetServerCommandTag());
+ nsAutoCString escapedUserName;
+ command.AppendLiteral(" login \"");
+ EscapeUserNamePasswordString(userName, &escapedUserName);
+ command.Append(escapedUserName);
+ command.AppendLiteral("\" \"");
+
+ // if the password contains a \, login will fail
+ // turn foo\bar into foo\\bar
+ nsAutoCString correctedPassword;
+ // We're assuming old style login doesn't want UTF-8
+ EscapeUserNamePasswordString(NS_LossyConvertUTF16toASCII(aPassword).get(),
+ &correctedPassword);
+ command.Append(correctedPassword);
+ command.AppendLiteral("\"" CRLF);
+ rv = SendData(command.get(), true /* suppress logging */);
+ NS_ENSURE_SUCCESS(rv, rv);
+ ParseIMAPandCheckForNewMail();
+ } else if (flag & kHasXOAuth2Capability) {
+ MOZ_LOG(IMAP, LogLevel::Debug, ("XOAUTH2 auth"));
+
+ // Get the XOAuth2 base64 string.
+ NS_ASSERTION(mOAuth2Support,
+ "What are we doing here without OAuth2 helper?");
+ if (!mOAuth2Support) return NS_ERROR_UNEXPECTED;
+ nsAutoCString base64Str;
+ mOAuth2Support->GetXOAuth2String(base64Str);
+ mOAuth2Support = nullptr; // Its purpose has been served.
+ if (base64Str.IsEmpty()) {
+ MOZ_LOG(IMAP, LogLevel::Debug, ("OAuth2 failed"));
+ return NS_ERROR_FAILURE;
+ }
+
+ // Send the data on the network.
+ nsAutoCString command(GetServerCommandTag());
+ command += " AUTHENTICATE XOAUTH2 ";
+ command += base64Str;
+ command += CRLF;
+
+ rv = SendDataParseIMAPandCheckForNewMail(command.get(), nullptr);
+ } else if (flag & kHasAuthNoneCapability) {
+ // TODO What to do? "login <username>" like POP?
+ return NS_ERROR_NOT_IMPLEMENTED;
+ } else {
+ MOZ_LOG(IMAP, LogLevel::Error, ("flags param has no auth scheme selected"));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ PR_Free(currentCommand);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return GetServerStateParser().LastCommandSuccessful() ? NS_OK
+ : NS_ERROR_FAILURE;
+}
+
+void nsImapProtocol::OnLSubFolders() {
+ // **** use to find out whether Drafts, Sent, & Templates folder
+ // exists or not even the user didn't subscribe to it
+ char* mailboxName = OnCreateServerSourceFolderPathString();
+ if (mailboxName) {
+ ProgressEventFunctionUsingName("imapStatusLookingForMailbox");
+ IncrementCommandTagNumber();
+ PR_snprintf(m_dataOutputBuf, OUTPUT_BUFFER_SIZE, "%s list \"\" \"%s\"" CRLF,
+ GetServerCommandTag(), mailboxName);
+ nsresult rv = SendData(m_dataOutputBuf);
+ if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail();
+ PR_Free(mailboxName);
+ } else {
+ HandleMemoryFailure();
+ }
+}
+
+void nsImapProtocol::OnAppendMsgFromFile() {
+ nsCOMPtr<nsIFile> file;
+ nsresult rv = NS_OK;
+ rv = m_runningUrl->GetMsgFile(getter_AddRefs(file));
+ if (NS_SUCCEEDED(rv) && file) {
+ char* mailboxName = OnCreateServerSourceFolderPathString();
+ if (mailboxName) {
+ imapMessageFlagsType flagsToSet = 0;
+ uint32_t msgFlags = 0;
+ PRTime date = 0;
+ nsCString keywords;
+ if (m_imapMessageSink)
+ m_imapMessageSink->GetCurMoveCopyMessageInfo(m_runningUrl, &date,
+ keywords, &msgFlags);
+
+ if (msgFlags & nsMsgMessageFlags::Read) flagsToSet |= kImapMsgSeenFlag;
+ if (msgFlags & nsMsgMessageFlags::MDNReportSent)
+ flagsToSet |= kImapMsgMDNSentFlag;
+ // convert msg flag label (0xE000000) to imap flag label (0x0E00)
+ if (msgFlags & nsMsgMessageFlags::Labels)
+ flagsToSet |= (msgFlags & nsMsgMessageFlags::Labels) >> 16;
+ if (msgFlags & nsMsgMessageFlags::Marked)
+ flagsToSet |= kImapMsgFlaggedFlag;
+ if (msgFlags & nsMsgMessageFlags::Replied)
+ flagsToSet |= kImapMsgAnsweredFlag;
+ if (msgFlags & nsMsgMessageFlags::Forwarded)
+ flagsToSet |= kImapMsgForwardedFlag;
+
+ // If the message copied was a draft, flag it as such
+ nsImapAction imapAction;
+ rv = m_runningUrl->GetImapAction(&imapAction);
+ if (NS_SUCCEEDED(rv) &&
+ (imapAction == nsIImapUrl::nsImapAppendDraftFromFile))
+ flagsToSet |= kImapMsgDraftFlag;
+ UploadMessageFromFile(file, mailboxName, date, flagsToSet, keywords);
+ PR_Free(mailboxName);
+ } else {
+ HandleMemoryFailure();
+ }
+ }
+}
+
+void nsImapProtocol::UploadMessageFromFile(nsIFile* file,
+ const char* mailboxName, PRTime date,
+ imapMessageFlagsType flags,
+ nsCString& keywords) {
+ if (!file || !mailboxName) return;
+ IncrementCommandTagNumber();
+
+ int64_t fileSize = 0;
+ int64_t totalSize;
+ uint32_t readCount;
+ char* dataBuffer = nullptr;
+ nsCString command(GetServerCommandTag());
+ nsCString escapedName;
+ CreateEscapedMailboxName(mailboxName, escapedName);
+ nsresult rv;
+ bool urlOk = false;
+ nsCString flagString;
+
+ nsCOMPtr<nsIInputStream> fileInputStream;
+
+ if (!escapedName.IsEmpty()) {
+ command.AppendLiteral(" append \"");
+ command.Append(escapedName);
+ command.Append('"');
+ if (flags || keywords.Length()) {
+ command.AppendLiteral(" (");
+
+ if (flags) {
+ SetupMessageFlagsString(flagString, flags,
+ GetServerStateParser().SupportsUserFlags());
+ command.Append(flagString);
+ }
+ if (keywords.Length()) {
+ if (flags) command.Append(' ');
+ command.Append(keywords);
+ }
+ command.Append(')');
+ }
+
+ // date should never be 0, but just in case...
+ if (date) {
+ /* Use PR_FormatTimeUSEnglish() to format the date in US English format,
+ then figure out what our local GMT offset is, and append it (since
+ PR_FormatTimeUSEnglish() can't do that.) Generate four digit years as
+ per RFC 1123 (superseding RFC 822.)
+ */
+ char szDateTime[64];
+ char dateStr[100];
+ PRExplodedTime exploded;
+ PR_ExplodeTime(date, PR_LocalTimeParameters, &exploded);
+ PR_FormatTimeUSEnglish(szDateTime, sizeof(szDateTime),
+ "%d-%b-%Y %H:%M:%S", &exploded);
+ PRExplodedTime now;
+ PR_ExplodeTime(PR_Now(), PR_LocalTimeParameters, &now);
+ int gmtoffset =
+ (now.tm_params.tp_gmt_offset + now.tm_params.tp_dst_offset) / 60;
+ PR_snprintf(dateStr, sizeof(dateStr), " \"%s %c%02d%02d\"", szDateTime,
+ (gmtoffset >= 0 ? '+' : '-'),
+ ((gmtoffset >= 0 ? gmtoffset : -gmtoffset) / 60),
+ ((gmtoffset >= 0 ? gmtoffset : -gmtoffset) % 60));
+
+ command.Append(dateStr);
+ }
+ if (m_allowUTF8Accept)
+ command.AppendLiteral(" UTF8 (~{");
+ else
+ command.AppendLiteral(" {");
+
+ dataBuffer = (char*)PR_CALLOC(COPY_BUFFER_SIZE + 1);
+ if (!dataBuffer) goto done;
+ rv = file->GetFileSize(&fileSize);
+ NS_ASSERTION(fileSize, "got empty file in UploadMessageFromFile");
+ if (NS_FAILED(rv) || !fileSize) goto done;
+ rv = NS_NewLocalFileInputStream(getter_AddRefs(fileInputStream), file);
+ if (NS_FAILED(rv) || !fileInputStream) goto done;
+ command.AppendInt((int32_t)fileSize);
+
+ // Set useLiteralPlus to true if server has capability LITERAL+ and
+ // LITERAL+ usage is enabled in the config editor,
+ // i.e., "mail.imap.use_literal_plus" = true.
+ bool useLiteralPlus =
+ (GetServerStateParser().GetCapabilityFlag() & kLiteralPlusCapability) &&
+ gUseLiteralPlus;
+ if (useLiteralPlus)
+ command.AppendLiteral("+}" CRLF);
+ else
+ command.AppendLiteral("}" CRLF);
+
+ rv = SendData(command.get());
+ if (NS_FAILED(rv)) goto done;
+
+ if (!useLiteralPlus) {
+ ParseIMAPandCheckForNewMail();
+ if (!GetServerStateParser().LastCommandSuccessful()) goto done;
+ }
+
+ totalSize = fileSize;
+ readCount = 0;
+ while (NS_SUCCEEDED(rv) && totalSize > 0) {
+ if (DeathSignalReceived()) goto done;
+ rv = fileInputStream->Read(dataBuffer, COPY_BUFFER_SIZE, &readCount);
+ if (NS_SUCCEEDED(rv) && !readCount) rv = NS_ERROR_FAILURE;
+
+ if (NS_SUCCEEDED(rv)) {
+ NS_ASSERTION(readCount <= (uint32_t)totalSize,
+ "got more bytes than there should be");
+ dataBuffer[readCount] = 0;
+ rv = SendData(dataBuffer);
+ totalSize -= readCount;
+ PercentProgressUpdateEvent(""_ns, u""_ns, fileSize - totalSize,
+ fileSize);
+ if (!totalSize) {
+ // The full message has been queued for sending, but the actual send
+ // is just now starting and can still potentially fail. From this
+ // point the progress cannot be determined, so just set the progress
+ // to "indeterminate" so that the user does not see an incorrect 100%
+ // complete on the progress bar while waiting for the retry dialog to
+ // appear if the send should fail.
+ m_lastProgressTime = 0; // Force progress bar update
+ m_lastPercent = -1; // Force progress bar update
+ PercentProgressUpdateEvent(""_ns, u""_ns, 0, -1); // Indeterminate
+ }
+ }
+ } // end while appending chunks
+
+ if (NS_SUCCEEDED(rv)) { // complete the append
+ if (m_allowUTF8Accept)
+ rv = SendData(")" CRLF);
+ else
+ rv = SendData(CRLF);
+ if (NS_FAILED(rv)) goto done;
+
+ ParseIMAPandCheckForNewMail(command.get());
+ if (!GetServerStateParser().LastCommandSuccessful()) goto done;
+
+ // If reached, the append completed without error. No more goto's!
+ // May still find problems in imap responses below so urlOk may still
+ // become false.
+ urlOk = true;
+
+ nsImapAction imapAction;
+ m_runningUrl->GetImapAction(&imapAction);
+
+ if (imapAction == nsIImapUrl::nsImapAppendDraftFromFile ||
+ imapAction == nsIImapUrl::nsImapAppendMsgFromFile) {
+ if (GetServerStateParser().GetCapabilityFlag() & kUidplusCapability) {
+ nsMsgKey newKey = GetServerStateParser().CurrentResponseUID();
+ if (m_imapMailFolderSink)
+ m_imapMailFolderSink->SetAppendMsgUid(newKey, m_runningUrl);
+
+ // Courier imap server seems to have problems with recently
+ // appended messages. Noop seems to clear its confusion.
+ if (FolderIsSelected(mailboxName)) Noop();
+
+ nsCString oldMsgId;
+ rv = m_runningUrl->GetListOfMessageIds(oldMsgId);
+ if (NS_SUCCEEDED(rv) && !oldMsgId.IsEmpty()) {
+ bool idsAreUids = true;
+ m_runningUrl->MessageIdsAreUids(&idsAreUids);
+ Store(oldMsgId, "+FLAGS (\\Deleted)", idsAreUids);
+ UidExpunge(oldMsgId);
+ }
+ // Only checks the last imap command in sequence above.
+ if (!GetServerStateParser().LastCommandSuccessful()) urlOk = false;
+ }
+ // for non UIDPLUS servers this code used to check for
+ // imapAction==nsIImapUrl::nsImapAppendMsgFromFile, which meant we'd get
+ // into this code whenever sending a message, as well as when copying
+ // messages to an imap folder from local folders or an other imap
+ // server. This made sending a message slow when there was a large sent
+ // folder. I don't believe this code worked anyway.
+ // *** code me to search for the newly appended message
+ else if (m_imapMailFolderSink &&
+ imapAction == nsIImapUrl::nsImapAppendDraftFromFile) {
+ // go to selected state
+ nsCString messageId;
+ rv = m_imapMailFolderSink->GetMessageId(m_runningUrl, messageId);
+ if (NS_SUCCEEDED(rv) && !messageId.IsEmpty()) {
+ // if the appended to folder isn't selected in the connection,
+ // select it.
+ if (!FolderIsSelected(mailboxName))
+ SelectMailbox(mailboxName);
+ else
+ Noop(); // See if this makes SEARCH work on the newly appended
+ // msg.
+
+ if (GetServerStateParser().LastCommandSuccessful()) {
+ command = "SEARCH UNDELETED HEADER Message-ID ";
+ command.Append(messageId);
+
+ // Clean up result sequence before issuing the cmd.
+ GetServerStateParser().ResetSearchResultSequence();
+
+ Search(command.get(), true, false);
+ if (GetServerStateParser().LastCommandSuccessful()) {
+ nsMsgKey newkey = nsMsgKey_None;
+ nsImapSearchResultIterator* searchResult =
+ GetServerStateParser().CreateSearchResultIterator();
+ newkey = searchResult->GetNextMessageNumber();
+ delete searchResult;
+ if (newkey != nsMsgKey_None)
+ m_imapMailFolderSink->SetAppendMsgUid(newkey, m_runningUrl);
+ } else
+ urlOk = false;
+ } else
+ urlOk = false;
+ }
+ }
+ }
+ }
+ }
+done:
+ // If imap command fails or network goes down, make sure URL sees the failure.
+ if (!urlOk) GetServerStateParser().SetCommandFailed(true);
+
+ PR_Free(dataBuffer);
+ if (fileInputStream) fileInputStream->Close();
+}
+
+// caller must free using PR_Free
+char* nsImapProtocol::OnCreateServerSourceFolderPathString() {
+ char* sourceMailbox = nullptr;
+ char hierarchyDelimiter = 0;
+ char onlineDelimiter = 0;
+ m_runningUrl->GetOnlineSubDirSeparator(&hierarchyDelimiter);
+ if (m_imapMailFolderSink)
+ m_imapMailFolderSink->GetOnlineDelimiter(&onlineDelimiter);
+
+ if (onlineDelimiter != kOnlineHierarchySeparatorUnknown &&
+ onlineDelimiter != hierarchyDelimiter)
+ m_runningUrl->SetOnlineSubDirSeparator(onlineDelimiter);
+
+ m_runningUrl->CreateServerSourceFolderPathString(&sourceMailbox);
+
+ return sourceMailbox;
+}
+
+// caller must free using PR_Free, safe to call from ui thread
+char* nsImapProtocol::GetFolderPathString() {
+ char* sourceMailbox = nullptr;
+ char onlineSubDirDelimiter = 0;
+ char hierarchyDelimiter = 0;
+ nsCOMPtr<nsIMsgFolder> msgFolder;
+
+ m_runningUrl->GetOnlineSubDirSeparator(&onlineSubDirDelimiter);
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_runningUrl);
+ mailnewsUrl->GetFolder(getter_AddRefs(msgFolder));
+ if (msgFolder) {
+ nsCOMPtr<nsIMsgImapMailFolder> imapFolder = do_QueryInterface(msgFolder);
+ if (imapFolder) {
+ imapFolder->GetHierarchyDelimiter(&hierarchyDelimiter);
+ if (hierarchyDelimiter != kOnlineHierarchySeparatorUnknown &&
+ onlineSubDirDelimiter != hierarchyDelimiter)
+ m_runningUrl->SetOnlineSubDirSeparator(hierarchyDelimiter);
+ }
+ }
+ m_runningUrl->CreateServerSourceFolderPathString(&sourceMailbox);
+
+ return sourceMailbox;
+}
+
+nsresult nsImapProtocol::CreateServerSourceFolderPathString(char** result) {
+ NS_ENSURE_ARG(result);
+ *result = OnCreateServerSourceFolderPathString();
+ return (*result) ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
+}
+
+// caller must free using PR_Free
+char* nsImapProtocol::OnCreateServerDestinationFolderPathString() {
+ char* destinationMailbox = nullptr;
+ char hierarchyDelimiter = 0;
+ char onlineDelimiter = 0;
+ m_runningUrl->GetOnlineSubDirSeparator(&hierarchyDelimiter);
+ if (m_imapMailFolderSink)
+ m_imapMailFolderSink->GetOnlineDelimiter(&onlineDelimiter);
+ if (onlineDelimiter != kOnlineHierarchySeparatorUnknown &&
+ onlineDelimiter != hierarchyDelimiter)
+ m_runningUrl->SetOnlineSubDirSeparator(onlineDelimiter);
+
+ m_runningUrl->CreateServerDestinationFolderPathString(&destinationMailbox);
+
+ return destinationMailbox;
+}
+
+void nsImapProtocol::OnCreateFolder(const char* aSourceMailbox) {
+ bool created = CreateMailboxRespectingSubscriptions(aSourceMailbox);
+ if (created) {
+ m_hierarchyNameState = kListingForCreate;
+ nsCString mailboxWODelim(aSourceMailbox);
+ RemoveHierarchyDelimiter(mailboxWODelim);
+ List(mailboxWODelim.get(), false);
+ m_hierarchyNameState = kNoOperationInProgress;
+ } else
+ FolderNotCreated(aSourceMailbox);
+}
+
+void nsImapProtocol::OnEnsureExistsFolder(const char* aSourceMailbox) {
+ // We need to handle the following edge case where the destination server
+ // wasn't authenticated when the folder name was encoded in
+ // `EnsureFolderExists()'. In this case we always get MUTF-7. Here we are
+ // authenticated and can rely on `m_allowUTF8Accept'. If the folder appears
+ // to be MUTF-7 and we need UTF-8, we re-encode it. If it's not ASCII, it
+ // must be already correct in UTF-8. And if it was ASCII to start with, it
+ // doesn't matter that we MUTF-7 decode and UTF-8 re-encode.
+
+ // `aSourceMailbox' is a path with hierarchy delimiters possibly. To determine
+ // if the edge case is in effect, we only want to check the leaf node for
+ // ASCII and this is only necessary when `m_allowUTF8Accept' is true.
+
+ // `fullPath' is modified below if leaf re-encoding is necessary and it must
+ // be defined here at top level so it stays in scope.
+ nsAutoCString fullPath(aSourceMailbox);
+
+ if (m_allowUTF8Accept) {
+ char onlineDirSeparator = kOnlineHierarchySeparatorUnknown;
+ m_runningUrl->GetOnlineSubDirSeparator(&onlineDirSeparator);
+
+ int32_t leafStart = fullPath.RFindChar(onlineDirSeparator);
+ nsAutoCString leafName;
+ if (leafStart == kNotFound) {
+ // This is a root level mailbox
+ leafName = fullPath;
+ fullPath.SetLength(0);
+ } else {
+ leafName = Substring(fullPath, leafStart + 1);
+ fullPath.SetLength(leafStart + 1);
+ }
+
+ if (NS_IsAscii(leafName.get())) {
+ MOZ_LOG(IMAP, LogLevel::Debug,
+ ("re-encode leaf of mailbox %s to UTF-8", aSourceMailbox));
+ nsAutoString utf16LeafName;
+ CopyMUTF7toUTF16(leafName, utf16LeafName);
+
+ // Convert UTF-16 to UTF-8 to create the folder.
+ nsAutoCString utf8LeafName;
+ CopyUTF16toUTF8(utf16LeafName, utf8LeafName);
+ fullPath.Append(utf8LeafName);
+ aSourceMailbox = fullPath.get();
+ MOZ_LOG(IMAP, LogLevel::Debug,
+ ("re-encoded leaf of mailbox %s to UTF-8", aSourceMailbox));
+ }
+ }
+ List(aSourceMailbox, false); // how to tell if that succeeded?
+ // If List() produces OK tagged response and an untagged "* LIST" response
+ // then the folder exists on the server. For simplicity, just look for
+ // a general untagged response.
+ bool folderExists = false;
+ if (GetServerStateParser().LastCommandSuccessful())
+ folderExists = GetServerStateParser().UntaggedResponse();
+
+ // try converting aSourceMailbox to canonical format
+ nsImapNamespace* nsForMailbox = nullptr;
+ m_hostSessionList->GetNamespaceForMailboxForHost(
+ GetImapServerKey(), aSourceMailbox, nsForMailbox);
+ // NS_ASSERTION (nsForMailbox, "Oops .. null nsForMailbox");
+
+ nsCString name;
+
+ if (nsForMailbox)
+ m_runningUrl->AllocateCanonicalPath(
+ aSourceMailbox, nsForMailbox->GetDelimiter(), getter_Copies(name));
+ else
+ m_runningUrl->AllocateCanonicalPath(
+ aSourceMailbox, kOnlineHierarchySeparatorUnknown, getter_Copies(name));
+
+ // Also check that the folder has been verified to exist in server sink.
+ bool verifiedExists = false;
+ if (folderExists && m_imapServerSink)
+ m_imapServerSink->FolderVerifiedOnline(name, &verifiedExists);
+
+ // If folder exists on server and is verified and known to exists in server
+ // sink, just subscribe the folder. Otherwise, create a new folder and
+ // then subscribe and do list again to make sure it's created.
+ if (folderExists && verifiedExists) {
+ Subscribe(aSourceMailbox);
+ } else {
+ bool created = CreateMailboxRespectingSubscriptions(aSourceMailbox);
+ if (created) {
+ List(aSourceMailbox, false);
+ // Check that we see an untagged response indicating folder now exists.
+ folderExists = GetServerStateParser().UntaggedResponse();
+ }
+ }
+ if (!GetServerStateParser().LastCommandSuccessful() || !folderExists)
+ FolderNotCreated(aSourceMailbox);
+}
+
+void nsImapProtocol::OnSubscribe(const char* sourceMailbox) {
+ Subscribe(sourceMailbox);
+}
+
+void nsImapProtocol::OnUnsubscribe(const char* sourceMailbox) {
+ // When we try to auto-unsubscribe from \Noselect folders,
+ // some servers report errors if we were already unsubscribed
+ // from them.
+ bool lastReportingErrors = GetServerStateParser().GetReportingErrors();
+ GetServerStateParser().SetReportingErrors(false);
+ Unsubscribe(sourceMailbox);
+ GetServerStateParser().SetReportingErrors(lastReportingErrors);
+}
+
+void nsImapProtocol::RefreshACLForFolderIfNecessary(const char* mailboxName) {
+ if (GetServerStateParser().ServerHasACLCapability()) {
+ if (!m_folderNeedsACLRefreshed && m_imapMailFolderSink)
+ m_imapMailFolderSink->GetFolderNeedsACLListed(&m_folderNeedsACLRefreshed);
+ if (m_folderNeedsACLRefreshed) {
+ RefreshACLForFolder(mailboxName);
+ m_folderNeedsACLRefreshed = false;
+ }
+ }
+}
+
+void nsImapProtocol::RefreshACLForFolder(const char* mailboxName) {
+ nsImapNamespace* ns = nullptr;
+ m_hostSessionList->GetNamespaceForMailboxForHost(GetImapServerKey(),
+ mailboxName, ns);
+ if (ns) {
+ switch (ns->GetType()) {
+ case kPersonalNamespace:
+ // It's a personal folder, most likely.
+ // I find it hard to imagine a server that supports ACL that doesn't
+ // support NAMESPACE, so most likely we KNOW that this is a personal,
+ // rather than the default, namespace.
+
+ // First, clear what we have.
+ ClearAllFolderRights();
+ // Now, get the new one.
+ GetMyRightsForFolder(mailboxName);
+ if (m_imapMailFolderSink) {
+ uint32_t aclFlags = 0;
+ if (NS_SUCCEEDED(m_imapMailFolderSink->GetAclFlags(&aclFlags)) &&
+ aclFlags & IMAP_ACL_ADMINISTER_FLAG)
+ GetACLForFolder(mailboxName);
+ }
+
+ // We're all done, refresh the icon/flags for this folder
+ RefreshFolderACLView(mailboxName, ns);
+ break;
+ default:
+ // We know it's a public folder or other user's folder.
+ // We only want our own rights
+
+ // First, clear what we have
+ ClearAllFolderRights();
+ // Now, get the new one.
+ GetMyRightsForFolder(mailboxName);
+ // We're all done, refresh the icon/flags for this folder
+ RefreshFolderACLView(mailboxName, ns);
+ break;
+ }
+ } else {
+ // no namespace, not even default... can this happen?
+ NS_ASSERTION(false, "couldn't get namespace");
+ }
+}
+
+void nsImapProtocol::RefreshFolderACLView(const char* mailboxName,
+ nsImapNamespace* nsForMailbox) {
+ nsCString canonicalMailboxName;
+
+ if (nsForMailbox)
+ m_runningUrl->AllocateCanonicalPath(mailboxName,
+ nsForMailbox->GetDelimiter(),
+ getter_Copies(canonicalMailboxName));
+ else
+ m_runningUrl->AllocateCanonicalPath(mailboxName,
+ kOnlineHierarchySeparatorUnknown,
+ getter_Copies(canonicalMailboxName));
+
+ if (m_imapServerSink)
+ m_imapServerSink->RefreshFolderRights(canonicalMailboxName);
+}
+
+void nsImapProtocol::GetACLForFolder(const char* mailboxName) {
+ IncrementCommandTagNumber();
+
+ nsCString command(GetServerCommandTag());
+ nsCString escapedName;
+ CreateEscapedMailboxName(mailboxName, escapedName);
+ command.AppendLiteral(" getacl \"");
+ command.Append(escapedName);
+ command.AppendLiteral("\"" CRLF);
+
+ nsresult rv = SendData(command.get());
+ if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail();
+}
+
+void nsImapProtocol::OnRefreshAllACLs() {
+ m_hierarchyNameState = kListingForInfoOnly;
+ nsIMAPMailboxInfo* mb = NULL;
+
+ // This will fill in the list
+ List("*", true);
+
+ int32_t total = m_listedMailboxList.Length(), count = 0;
+ GetServerStateParser().SetReportingErrors(false);
+ for (int32_t i = 0; i < total; i++) {
+ mb = m_listedMailboxList.ElementAt(i);
+ if (mb) // paranoia
+ {
+ char* onlineName = nullptr;
+ m_runningUrl->AllocateServerPath(
+ PromiseFlatCString(mb->GetMailboxName()).get(), mb->GetDelimiter(),
+ &onlineName);
+ if (onlineName) {
+ RefreshACLForFolder(onlineName);
+ free(onlineName);
+ }
+ PercentProgressUpdateEvent(""_ns, u""_ns, count, total);
+ delete mb;
+ count++;
+ }
+ }
+ m_listedMailboxList.Clear();
+
+ PercentProgressUpdateEvent(""_ns, u""_ns, 100, 100);
+ GetServerStateParser().SetReportingErrors(true);
+ m_hierarchyNameState = kNoOperationInProgress;
+}
+
+// any state commands
+void nsImapProtocol::Logout(bool shuttingDown /* = false */,
+ bool waitForResponse /* = true */) {
+ if (!shuttingDown) ProgressEventFunctionUsingName("imapStatusLoggingOut");
+
+ /******************************************************************
+ * due to the undo functionality we cannot issue ImapClose when logout; there
+ * is no way to do an undo if the message has been permanently expunge
+ * jt - 07/12/1999
+
+ bool closeNeeded = GetServerStateParser().GetIMAPstate() ==
+ nsImapServerResponseParser::kFolderSelected;
+
+ if (closeNeeded && GetDeleteIsMoveToTrash())
+ ImapClose();
+ ********************/
+
+ IncrementCommandTagNumber();
+
+ nsCString command(GetServerCommandTag());
+
+ command.AppendLiteral(" logout" CRLF);
+
+ nsresult rv = SendData(command.get());
+ if (m_transport && shuttingDown)
+ m_transport->SetTimeout(nsISocketTransport::TIMEOUT_READ_WRITE, 5);
+ // the socket may be dead before we read the response, so drop it.
+ if (NS_SUCCEEDED(rv) && waitForResponse) ParseIMAPandCheckForNewMail();
+}
+
+void nsImapProtocol::Noop() {
+ // ProgressUpdateEvent("noop...");
+ IncrementCommandTagNumber();
+ nsCString command(GetServerCommandTag());
+
+ command.AppendLiteral(" noop" CRLF);
+
+ nsresult rv = SendData(command.get());
+ if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail();
+}
+
+void nsImapProtocol::XServerInfo() {
+ ProgressEventFunctionUsingName("imapGettingServerInfo");
+ IncrementCommandTagNumber();
+ nsCString command(GetServerCommandTag());
+
+ command.AppendLiteral(
+ " XSERVERINFO MANAGEACCOUNTURL MANAGELISTSURL MANAGEFILTERSURL" CRLF);
+
+ nsresult rv = SendData(command.get());
+ if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail();
+}
+
+void nsImapProtocol::Netscape() {
+ ProgressEventFunctionUsingName("imapGettingServerInfo");
+ IncrementCommandTagNumber();
+
+ nsCString command(GetServerCommandTag());
+
+ command.AppendLiteral(" netscape" CRLF);
+
+ nsresult rv = SendData(command.get());
+ if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail();
+}
+
+void nsImapProtocol::XMailboxInfo(const char* mailboxName) {
+ ProgressEventFunctionUsingName("imapGettingMailboxInfo");
+ IncrementCommandTagNumber();
+ nsCString command(GetServerCommandTag());
+
+ command.AppendLiteral(" XMAILBOXINFO \"");
+ command.Append(mailboxName);
+ command.AppendLiteral("\" MANAGEURL POSTURL" CRLF);
+
+ nsresult rv = SendData(command.get());
+ if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail();
+}
+
+void nsImapProtocol::Namespace() {
+ IncrementCommandTagNumber();
+
+ nsCString command(GetServerCommandTag());
+ command.AppendLiteral(" namespace" CRLF);
+
+ nsresult rv = SendData(command.get());
+ if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail();
+}
+
+void nsImapProtocol::MailboxData() {
+ IncrementCommandTagNumber();
+
+ nsCString command(GetServerCommandTag());
+ command.AppendLiteral(" mailboxdata" CRLF);
+
+ nsresult rv = SendData(command.get());
+ if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail();
+}
+
+void nsImapProtocol::GetMyRightsForFolder(const char* mailboxName) {
+ IncrementCommandTagNumber();
+
+ nsCString command(GetServerCommandTag());
+ nsCString escapedName;
+ CreateEscapedMailboxName(mailboxName, escapedName);
+
+ if (MailboxIsNoSelectMailbox(escapedName.get()))
+ return; // Don't issue myrights on Noselect folder
+
+ command.AppendLiteral(" myrights \"");
+ command.Append(escapedName);
+ command.AppendLiteral("\"" CRLF);
+
+ nsresult rv = SendData(command.get());
+ if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail();
+}
+
+bool nsImapProtocol::FolderIsSelected(const char* mailboxName) {
+ return (GetServerStateParser().GetIMAPstate() ==
+ nsImapServerResponseParser::kFolderSelected &&
+ GetServerStateParser().GetSelectedMailboxName() &&
+ PL_strcmp(GetServerStateParser().GetSelectedMailboxName(),
+ mailboxName) == 0);
+}
+
+void nsImapProtocol::OnStatusForFolder(const char* mailboxName) {
+ bool untaggedResponse;
+ // RFC 3501 says:
+ // "the STATUS command SHOULD NOT be used on the currently selected mailbox",
+ // so use NOOP instead if mailboxName is the selected folder on this
+ // connection.
+ if (FolderIsSelected(mailboxName)) {
+ Noop();
+ // Did untagged responses occur during the NOOP response? If so, this
+ // indicates new mail or other changes in the mailbox. Handle this like an
+ // IDLE response which will cause a folder update.
+ if ((untaggedResponse = GetServerStateParser().UntaggedResponse()) &&
+ m_imapMailFolderSinkSelected) {
+ Log("OnStatusForFolder", nullptr,
+ "mailbox change on selected folder during noop");
+ m_imapMailFolderSinkSelected->OnNewIdleMessages();
+ }
+ mailboxName = nullptr; // for new_spec below. Obtain SELECTed mailbox data.
+ } else {
+ // Imap connection is not in selected state or imap connection is selected
+ // on a mailbox other than than the mailbox folderstatus URL is requesting
+ // status for.
+ untaggedResponse = true; // STATUS always produces an untagged response
+ IncrementCommandTagNumber();
+
+ nsAutoCString command(GetServerCommandTag());
+ nsCString escapedName;
+ CreateEscapedMailboxName(mailboxName, escapedName);
+
+ command.AppendLiteral(" STATUS \"");
+ command.Append(escapedName);
+ command.AppendLiteral("\" (UIDNEXT MESSAGES UNSEEN RECENT)" CRLF);
+
+ int32_t prevNumMessages = GetServerStateParser().NumberOfMessages();
+ nsresult rv = SendData(command.get());
+ if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail();
+
+ // Respond to possible untagged responses EXISTS and RECENT for the SELECTed
+ // folder. Handle as though this were an IDLE response. Can't check for any
+ // untagged as for Noop() above since STATUS always produces an untagged
+ // response for the target mailbox and possibly also for the SELECTed box.
+ // Of cource, this won't occur if imap connection is not in selected state.
+ if (GetServerStateParser().GetIMAPstate() ==
+ nsImapServerResponseParser::kFolderSelected &&
+ m_imapMailFolderSinkSelected &&
+ (GetServerStateParser().NumberOfRecentMessages() ||
+ prevNumMessages != GetServerStateParser().NumberOfMessages())) {
+ Log("OnStatusForFolder", nullptr,
+ "new mail on selected folder during status");
+ m_imapMailFolderSinkSelected->OnNewIdleMessages();
+ }
+ MOZ_ASSERT(m_imapMailFolderSink != m_imapMailFolderSinkSelected);
+ }
+
+ // Do this to ensure autosync detects changes in server counts and thus
+ // triggers a full body fetch for when NOOP or STATUS is sent above.
+ // But if NOOP didn't produce an untagged response, no need to do this.
+ // Note: For SELECTed noop() above, "folder sink" and "folder sink selected"
+ // both reference the same folder but are not always equal. So OK to use
+ // m_imapMailFolderSink below since it is correct for NOOP and STATUS cases.
+ if (untaggedResponse && GetServerStateParser().LastCommandSuccessful()) {
+ RefPtr<nsImapMailboxSpec> new_spec =
+ GetServerStateParser().CreateCurrentMailboxSpec(mailboxName);
+ if (new_spec && m_imapMailFolderSink)
+ m_imapMailFolderSink->UpdateImapMailboxStatus(this, new_spec);
+ }
+}
+
+void nsImapProtocol::OnListFolder(const char* aSourceMailbox, bool aBool) {
+ List(aSourceMailbox, aBool);
+}
+
+// Returns true if the mailbox is a NoSelect mailbox.
+// If we don't know about it, returns false.
+bool nsImapProtocol::MailboxIsNoSelectMailbox(const char* mailboxName) {
+ bool rv = false;
+
+ nsImapNamespace* nsForMailbox = nullptr;
+ m_hostSessionList->GetNamespaceForMailboxForHost(GetImapServerKey(),
+ mailboxName, nsForMailbox);
+ // NS_ASSERTION (nsForMailbox, "Oops .. null nsForMailbox");
+
+ nsCString name;
+
+ if (nsForMailbox)
+ m_runningUrl->AllocateCanonicalPath(
+ mailboxName, nsForMailbox->GetDelimiter(), getter_Copies(name));
+ else
+ m_runningUrl->AllocateCanonicalPath(
+ mailboxName, kOnlineHierarchySeparatorUnknown, getter_Copies(name));
+
+ if (name.IsEmpty()) return false;
+
+ NS_ASSERTION(m_imapServerSink,
+ "unexpected, no imap server sink, see bug #194335");
+ if (m_imapServerSink) m_imapServerSink->FolderIsNoSelect(name, &rv);
+ return rv;
+}
+
+nsresult nsImapProtocol::SetFolderAdminUrl(const char* mailboxName) {
+ nsresult rv =
+ NS_ERROR_NULL_POINTER; // if m_imapServerSink is null, rv will be this.
+
+ nsImapNamespace* nsForMailbox = nullptr;
+ m_hostSessionList->GetNamespaceForMailboxForHost(GetImapServerKey(),
+ mailboxName, nsForMailbox);
+
+ nsCString name;
+
+ if (nsForMailbox)
+ m_runningUrl->AllocateCanonicalPath(
+ mailboxName, nsForMailbox->GetDelimiter(), getter_Copies(name));
+ else
+ m_runningUrl->AllocateCanonicalPath(
+ mailboxName, kOnlineHierarchySeparatorUnknown, getter_Copies(name));
+
+ if (m_imapServerSink)
+ rv = m_imapServerSink->SetFolderAdminURL(
+ name, nsDependentCString(GetServerStateParser().GetManageFolderUrl()));
+ return rv;
+}
+// returns true is the delete succeeded (regardless of subscription changes)
+bool nsImapProtocol::DeleteMailboxRespectingSubscriptions(
+ const char* mailboxName) {
+ bool rv = true;
+ if (!MailboxIsNoSelectMailbox(mailboxName)) {
+ // Only try to delete it if it really exists
+ DeleteMailbox(mailboxName);
+ rv = GetServerStateParser().LastCommandSuccessful();
+ }
+
+ // We can unsubscribe even if the mailbox doesn't exist.
+ if (rv && m_autoUnsubscribe) // auto-unsubscribe is on
+ {
+ bool reportingErrors = GetServerStateParser().GetReportingErrors();
+ GetServerStateParser().SetReportingErrors(false);
+ Unsubscribe(mailboxName);
+ GetServerStateParser().SetReportingErrors(reportingErrors);
+ }
+ return (rv);
+}
+
+// returns true is the rename succeeded (regardless of subscription changes)
+// reallyRename tells us if we should really do the rename (true) or if we
+// should just move subscriptions (false)
+bool nsImapProtocol::RenameMailboxRespectingSubscriptions(
+ const char* existingName, const char* newName, bool reallyRename) {
+ bool rv = true;
+ if (reallyRename && !MailboxIsNoSelectMailbox(existingName)) {
+ RenameMailbox(existingName, newName);
+ rv = GetServerStateParser().LastCommandSuccessful();
+ }
+
+ if (rv) {
+ if (m_autoSubscribe) // if auto-subscribe is on
+ {
+ bool reportingErrors = GetServerStateParser().GetReportingErrors();
+ GetServerStateParser().SetReportingErrors(false);
+ Subscribe(newName);
+ GetServerStateParser().SetReportingErrors(reportingErrors);
+ }
+ if (m_autoUnsubscribe) // if auto-unsubscribe is on
+ {
+ bool reportingErrors = GetServerStateParser().GetReportingErrors();
+ GetServerStateParser().SetReportingErrors(false);
+ Unsubscribe(existingName);
+ GetServerStateParser().SetReportingErrors(reportingErrors);
+ }
+ }
+ return (rv);
+}
+
+bool nsImapProtocol::RenameHierarchyByHand(const char* oldParentMailboxName,
+ const char* newParentMailboxName) {
+ bool renameSucceeded = true;
+ char onlineDirSeparator = kOnlineHierarchySeparatorUnknown;
+ m_deletableChildren = new nsTArray<nsCString>();
+
+ bool nonHierarchicalRename =
+ ((GetServerStateParser().GetCapabilityFlag() & kNoHierarchyRename) ||
+ MailboxIsNoSelectMailbox(oldParentMailboxName));
+
+ m_hierarchyNameState = kDeleteSubFoldersInProgress;
+ nsImapNamespace* ns = nullptr;
+ m_hostSessionList->GetNamespaceForMailboxForHost(GetImapServerKey(),
+ oldParentMailboxName,
+ ns); // for delimiter
+ if (!ns) {
+ if (!PL_strcasecmp(oldParentMailboxName, "INBOX"))
+ m_hostSessionList->GetDefaultNamespaceOfTypeForHost(
+ GetImapServerKey(), kPersonalNamespace, ns);
+ }
+ if (ns) {
+ nsCString pattern(oldParentMailboxName);
+ pattern += ns->GetDelimiter();
+ pattern += "*";
+ bool isUsingSubscription = false;
+ m_hostSessionList->GetHostIsUsingSubscription(GetImapServerKey(),
+ isUsingSubscription);
+
+ if (isUsingSubscription)
+ Lsub(pattern.get(), false);
+ else
+ List(pattern.get(), false);
+ }
+ m_hierarchyNameState = kNoOperationInProgress;
+
+ if (GetServerStateParser().LastCommandSuccessful())
+ renameSucceeded = // rename this, and move subscriptions
+ RenameMailboxRespectingSubscriptions(oldParentMailboxName,
+ newParentMailboxName, true);
+
+ size_t numberToDelete = m_deletableChildren->Length();
+
+ for (size_t childIndex = 0; (childIndex < numberToDelete) && renameSucceeded;
+ childIndex++) {
+ nsCString name = m_deletableChildren->ElementAt(childIndex);
+ char* serverName = nullptr;
+ m_runningUrl->AllocateServerPath(name.get(), onlineDirSeparator,
+ &serverName);
+ if (!serverName) {
+ renameSucceeded = false;
+ break;
+ }
+ char* currentName = serverName;
+
+ // calculate the new name and do the rename
+ nsCString newChildName(newParentMailboxName);
+ newChildName += (currentName + PL_strlen(oldParentMailboxName));
+ // Pass in 'nonHierarchicalRename' to determine if we should really
+ // rename, or just move subscriptions.
+ renameSucceeded = RenameMailboxRespectingSubscriptions(
+ currentName, newChildName.get(), nonHierarchicalRename);
+ PR_FREEIF(currentName);
+ }
+
+ delete m_deletableChildren;
+ m_deletableChildren = nullptr;
+
+ return renameSucceeded;
+}
+
+bool nsImapProtocol::DeleteSubFolders(const char* selectedMailbox,
+ bool& aDeleteSelf) {
+ bool deleteSucceeded = true;
+ m_deletableChildren = new nsTArray<nsCString>;
+
+ bool folderDeleted = false;
+
+ m_hierarchyNameState = kDeleteSubFoldersInProgress;
+ nsCString pattern(selectedMailbox);
+ char onlineDirSeparator = kOnlineHierarchySeparatorUnknown;
+ m_runningUrl->GetOnlineSubDirSeparator(&onlineDirSeparator);
+ pattern.Append(onlineDirSeparator);
+ pattern.Append('*');
+
+ if (!pattern.IsEmpty()) {
+ List(pattern.get(), false);
+ }
+ m_hierarchyNameState = kNoOperationInProgress;
+
+ // this should be a short list so perform a sequential search for the
+ // longest name mailbox. Deleting the longest first will hopefully
+ // prevent the server from having problems about deleting parents
+ // ** jt - why? I don't understand this.
+ size_t numberToDelete = m_deletableChildren->Length();
+ size_t outerIndex, innerIndex;
+
+ // intelligently decide if myself(either plain format or following the
+ // dir-separator) is in the sub-folder list
+ bool folderInSubfolderList = false; // For Performance
+ char* selectedMailboxDir = nullptr;
+ {
+ int32_t length = strlen(selectedMailbox);
+ selectedMailboxDir = (char*)PR_MALLOC(length + 2);
+ if (selectedMailboxDir) // only do the intelligent test if there is
+ // enough memory
+ {
+ strcpy(selectedMailboxDir, selectedMailbox);
+ selectedMailboxDir[length] = onlineDirSeparator;
+ selectedMailboxDir[length + 1] = '\0';
+ size_t i;
+ for (i = 0; i < numberToDelete && !folderInSubfolderList; i++) {
+ const char* currentName = m_deletableChildren->ElementAt(i).get();
+ if (!strcmp(currentName, selectedMailbox) ||
+ !strcmp(currentName, selectedMailboxDir))
+ folderInSubfolderList = true;
+ }
+ }
+ }
+
+ deleteSucceeded = GetServerStateParser().LastCommandSuccessful();
+ for (outerIndex = 0; (outerIndex < numberToDelete) && deleteSucceeded;
+ outerIndex++) {
+ char* longestName = nullptr;
+ size_t longestIndex = 0; // fix bogus warning by initializing
+ for (innerIndex = 0; innerIndex < m_deletableChildren->Length();
+ innerIndex++) {
+ const char* currentName =
+ m_deletableChildren->ElementAt(innerIndex).get();
+ if (!longestName || strlen(longestName) < strlen(currentName)) {
+ longestName = (char*)currentName;
+ longestIndex = innerIndex;
+ }
+ }
+ if (longestName) {
+ char* serverName = nullptr;
+ m_runningUrl->AllocateServerPath(longestName, onlineDirSeparator,
+ &serverName);
+ m_deletableChildren->RemoveElementAt(longestIndex);
+ longestName = serverName;
+ }
+
+ // some imap servers include the selectedMailbox in the list of
+ // subfolders of the selectedMailbox. Check for this so we don't
+ // delete the selectedMailbox (usually the trash and doing an
+ // empty trash)
+ // The Cyrus imap server ignores the "INBOX.Trash" constraining
+ // string passed to the list command. Be defensive and make sure
+ // we only delete children of the trash
+ if (longestName && strcmp(selectedMailbox, longestName) &&
+ !strncmp(selectedMailbox, longestName, strlen(selectedMailbox))) {
+ if (selectedMailboxDir &&
+ !strcmp(selectedMailboxDir, longestName)) // just myself
+ {
+ if (aDeleteSelf) {
+ bool deleted = DeleteMailboxRespectingSubscriptions(longestName);
+ if (deleted) FolderDeleted(longestName);
+ folderDeleted = deleted;
+ deleteSucceeded = deleted;
+ }
+ } else {
+ if (m_imapServerSink)
+ m_imapServerSink->ResetServerConnection(
+ nsDependentCString(longestName));
+ bool deleted = false;
+ if (folderInSubfolderList) // for performance
+ {
+ nsTArray<nsCString>* pDeletableChildren = m_deletableChildren;
+ m_deletableChildren = nullptr;
+ bool folderDeleted = true;
+ deleted = DeleteSubFolders(longestName, folderDeleted);
+ // longestName may have subfolder list including itself
+ if (!folderDeleted) {
+ if (deleted)
+ deleted = DeleteMailboxRespectingSubscriptions(longestName);
+ if (deleted) FolderDeleted(longestName);
+ }
+ m_deletableChildren = pDeletableChildren;
+ } else {
+ deleted = DeleteMailboxRespectingSubscriptions(longestName);
+ if (deleted) FolderDeleted(longestName);
+ }
+ deleteSucceeded = deleted;
+ }
+ }
+ PR_FREEIF(longestName);
+ }
+
+ aDeleteSelf = folderDeleted; // feedback if myself is deleted
+ PR_Free(selectedMailboxDir);
+
+ delete m_deletableChildren;
+ m_deletableChildren = nullptr;
+
+ return deleteSucceeded;
+}
+
+void nsImapProtocol::FolderDeleted(const char* mailboxName) {
+ char onlineDelimiter = kOnlineHierarchySeparatorUnknown;
+ nsCString orphanedMailboxName;
+
+ if (mailboxName) {
+ m_runningUrl->AllocateCanonicalPath(mailboxName, onlineDelimiter,
+ getter_Copies(orphanedMailboxName));
+ if (m_imapServerSink)
+ m_imapServerSink->OnlineFolderDelete(orphanedMailboxName);
+ }
+}
+
+void nsImapProtocol::FolderNotCreated(const char* folderName) {
+ if (folderName && m_imapServerSink)
+ m_imapServerSink->OnlineFolderCreateFailed(nsDependentCString(folderName));
+}
+
+void nsImapProtocol::FolderRenamed(const char* oldName, const char* newName) {
+ char onlineDelimiter = kOnlineHierarchySeparatorUnknown;
+
+ if ((m_hierarchyNameState == kNoOperationInProgress) ||
+ (m_hierarchyNameState == kListingForInfoAndDiscovery))
+
+ {
+ nsCString canonicalOldName, canonicalNewName;
+ m_runningUrl->AllocateCanonicalPath(oldName, onlineDelimiter,
+ getter_Copies(canonicalOldName));
+ m_runningUrl->AllocateCanonicalPath(newName, onlineDelimiter,
+ getter_Copies(canonicalNewName));
+ AutoProxyReleaseMsgWindow msgWindow;
+ GetMsgWindow(getter_AddRefs(msgWindow));
+ m_imapServerSink->OnlineFolderRename(msgWindow, canonicalOldName,
+ canonicalNewName);
+ }
+}
+
+void nsImapProtocol::OnDeleteFolder(const char* sourceMailbox) {
+ // intelligently delete the folder
+ bool folderDeleted = true;
+ bool deleted = DeleteSubFolders(sourceMailbox, folderDeleted);
+ if (!folderDeleted) {
+ if (deleted) deleted = DeleteMailboxRespectingSubscriptions(sourceMailbox);
+ if (deleted) FolderDeleted(sourceMailbox);
+ }
+}
+
+void nsImapProtocol::RemoveMsgsAndExpunge() {
+ uint32_t numberOfMessages = GetServerStateParser().NumberOfMessages();
+ if (numberOfMessages) {
+ // Remove all msgs and expunge the folder (ie, compact it).
+ Store("1:*"_ns, "+FLAGS.SILENT (\\Deleted)",
+ false); // use sequence #'s
+ if (GetServerStateParser().LastCommandSuccessful()) Expunge();
+ }
+}
+
+void nsImapProtocol::DeleteFolderAndMsgs(const char* sourceMailbox) {
+ RemoveMsgsAndExpunge();
+ if (GetServerStateParser().LastCommandSuccessful()) {
+ // All msgs are deleted successfully - let's remove the folder itself.
+ bool reportingErrors = GetServerStateParser().GetReportingErrors();
+ GetServerStateParser().SetReportingErrors(false);
+ OnDeleteFolder(sourceMailbox);
+ GetServerStateParser().SetReportingErrors(reportingErrors);
+ }
+}
+
+void nsImapProtocol::OnRenameFolder(const char* sourceMailbox) {
+ char* destinationMailbox = OnCreateServerDestinationFolderPathString();
+
+ if (destinationMailbox) {
+ bool renamed = RenameHierarchyByHand(sourceMailbox, destinationMailbox);
+ if (renamed) FolderRenamed(sourceMailbox, destinationMailbox);
+
+ // Cause a LIST and re-discovery when slash and/or ^ are escaped. Also
+ // needed when folder renamed to non-ASCII UTF-8 when UTF8=ACCEPT in
+ // effect.
+ m_hierarchyNameState = kListingForCreate;
+ nsCString mailboxWODelim(destinationMailbox);
+ RemoveHierarchyDelimiter(mailboxWODelim);
+ List(mailboxWODelim.get(), false);
+ m_hierarchyNameState = kNoOperationInProgress;
+
+ PR_Free(destinationMailbox);
+ } else
+ HandleMemoryFailure();
+}
+
+void nsImapProtocol::OnMoveFolderHierarchy(const char* sourceMailbox) {
+ char* destinationMailbox = OnCreateServerDestinationFolderPathString();
+
+ if (destinationMailbox) {
+ nsCString newBoxName;
+ newBoxName.Adopt(destinationMailbox);
+
+ char onlineDirSeparator = kOnlineHierarchySeparatorUnknown;
+ m_runningUrl->GetOnlineSubDirSeparator(&onlineDirSeparator);
+
+ nsCString oldBoxName(sourceMailbox);
+ int32_t leafStart = oldBoxName.RFindChar(onlineDirSeparator);
+ nsCString leafName;
+
+ if (-1 == leafStart)
+ leafName = oldBoxName; // this is a root level box
+ else
+ leafName = Substring(oldBoxName, leafStart + 1);
+
+ if (!newBoxName.IsEmpty()) newBoxName.Append(onlineDirSeparator);
+ newBoxName.Append(leafName);
+ bool renamed = RenameHierarchyByHand(sourceMailbox, newBoxName.get());
+ if (renamed) FolderRenamed(sourceMailbox, newBoxName.get());
+ } else
+ HandleMemoryFailure();
+}
+
+// This is called to do mailbox discovery if discovery not already complete
+// for the "host" (i.e., server or account). Discovery still only occurs if
+// the imap action is appropriate and if discovery is not in progress due to
+// a running "discoverallboxes" URL.
+void nsImapProtocol::FindMailboxesIfNecessary() {
+ // biff should not discover mailboxes
+ nsImapAction imapAction;
+ (void)m_runningUrl->GetImapAction(&imapAction);
+ if ((imapAction != nsIImapUrl::nsImapBiff) &&
+ (imapAction != nsIImapUrl::nsImapVerifylogon) &&
+ (imapAction != nsIImapUrl::nsImapDiscoverAllBoxesUrl) &&
+ (imapAction != nsIImapUrl::nsImapUpgradeToSubscription) &&
+ !GetSubscribingNow()) {
+ // If discovery in progress, don't kick-off another discovery.
+ bool discoveryInProgress = false;
+ m_hostSessionList->GetDiscoveryForHostInProgress(GetImapServerKey(),
+ discoveryInProgress);
+ if (!discoveryInProgress) {
+ m_hostSessionList->SetDiscoveryForHostInProgress(GetImapServerKey(),
+ true);
+ DiscoverMailboxList();
+ }
+ }
+}
+
+void nsImapProtocol::DiscoverAllAndSubscribedBoxes() {
+ // used for subscribe pane
+ // iterate through all namespaces
+ uint32_t count = 0;
+ m_hostSessionList->GetNumberOfNamespacesForHost(GetImapServerKey(), count);
+
+ for (uint32_t i = 0; i < count; i++) {
+ nsImapNamespace* ns = nullptr;
+ m_hostSessionList->GetNamespaceNumberForHost(GetImapServerKey(), i, ns);
+ if (!ns) {
+ continue;
+ }
+ if ((gHideOtherUsersFromList && (ns->GetType() != kOtherUsersNamespace)) ||
+ !gHideOtherUsersFromList) {
+ const char* prefix = ns->GetPrefix();
+ if (prefix) {
+ nsAutoCString inboxNameWithDelim("INBOX");
+ inboxNameWithDelim.Append(ns->GetDelimiter());
+
+ // Only do it for non-empty namespace prefixes.
+ if (!gHideUnusedNamespaces && *prefix &&
+ PL_strcasecmp(prefix, inboxNameWithDelim.get())) {
+ // Explicitly discover each Namespace, just so they're
+ // there in the subscribe UI
+ RefPtr<nsImapMailboxSpec> boxSpec = new nsImapMailboxSpec;
+ boxSpec->mFolderSelected = false;
+ boxSpec->mHostName.Assign(GetImapHostName());
+ boxSpec->mConnection = this;
+ boxSpec->mFlagState = nullptr;
+ boxSpec->mDiscoveredFromLsub = true;
+ boxSpec->mOnlineVerified = true;
+ boxSpec->mBoxFlags = kNoselect;
+ boxSpec->mHierarchySeparator = ns->GetDelimiter();
+
+ m_runningUrl->AllocateCanonicalPath(
+ ns->GetPrefix(), ns->GetDelimiter(),
+ getter_Copies(boxSpec->mAllocatedPathName));
+ boxSpec->mNamespaceForFolder = ns;
+ boxSpec->mBoxFlags |= kNameSpace;
+
+ switch (ns->GetType()) {
+ case kPersonalNamespace:
+ boxSpec->mBoxFlags |= kPersonalMailbox;
+ break;
+ case kPublicNamespace:
+ boxSpec->mBoxFlags |= kPublicMailbox;
+ break;
+ case kOtherUsersNamespace:
+ boxSpec->mBoxFlags |= kOtherUsersMailbox;
+ break;
+ default: // (kUnknownNamespace)
+ break;
+ }
+
+ DiscoverMailboxSpec(boxSpec);
+ }
+
+ nsAutoCString allPattern(prefix);
+ allPattern += '*';
+
+ if (!m_imapServerSink) return;
+
+ m_imapServerSink->SetServerDoingLsub(true);
+ Lsub(allPattern.get(), true); // LSUB all the subscribed
+
+ m_imapServerSink->SetServerDoingLsub(false);
+ List(allPattern.get(), true); // LIST all folders
+ }
+ }
+ }
+}
+
+// DiscoverMailboxList() is used to actually do the discovery of folders
+// for a host. This is used both when we initially start up (and re-sync)
+// and also when the user manually requests a re-sync, by collapsing and
+// expanding a host in the folder pane. This is not used for the subscribe
+// pane.
+// DiscoverMailboxList() also gets the ACLs for each newly discovered folder
+void nsImapProtocol::DiscoverMailboxList() {
+ bool usingSubscription = false;
+
+ m_hostSessionList->GetHostIsUsingSubscription(GetImapServerKey(),
+ usingSubscription);
+ // Pretend that the Trash folder doesn't exist, so we will rediscover it if we
+ // need to.
+ m_hostSessionList->SetOnlineTrashFolderExistsForHost(GetImapServerKey(),
+ false);
+
+ // should we check a pref here, to be able to turn off XList?
+ bool hasXLIST =
+ GetServerStateParser().GetCapabilityFlag() & kHasXListCapability;
+ if (hasXLIST && usingSubscription) {
+ m_hierarchyNameState = kXListing;
+ nsAutoCString pattern("%");
+ List("%", true, true);
+ // We list the first and second levels since special folders are unlikely
+ // to be more than 2 levels deep.
+ char separator = 0;
+ m_runningUrl->GetOnlineSubDirSeparator(&separator);
+ pattern.Append(separator);
+ pattern += '%';
+ List(pattern.get(), true, true);
+ }
+
+ SetMailboxDiscoveryStatus(eContinue);
+ if (GetServerStateParser().ServerHasACLCapability())
+ m_hierarchyNameState = kListingForInfoAndDiscovery;
+ else
+ m_hierarchyNameState = kNoOperationInProgress;
+
+ // iterate through all namespaces and LSUB them.
+ uint32_t count = 0;
+ m_hostSessionList->GetNumberOfNamespacesForHost(GetImapServerKey(), count);
+ for (uint32_t i = 0; i < count; i++) {
+ nsImapNamespace* ns = nullptr;
+ m_hostSessionList->GetNamespaceNumberForHost(GetImapServerKey(), i, ns);
+ if (ns) {
+ const char* prefix = ns->GetPrefix();
+ if (prefix) {
+ nsAutoCString inboxNameWithDelim("INBOX");
+ inboxNameWithDelim.Append(ns->GetDelimiter());
+
+ // static bool gHideUnusedNamespaces = true;
+ // mscott -> WARNING!!! i where are we going to get this
+ // global variable for unused name spaces from???
+ // dmb - we should get this from a per-host preference,
+ // I'd say. But for now, just make it true.
+ // Only do it for non-empty namespace prefixes, and for non-INBOX prefix
+ if (!gHideUnusedNamespaces && *prefix &&
+ PL_strcasecmp(prefix, inboxNameWithDelim.get())) {
+ // Explicitly discover each Namespace, so that we can
+ // create subfolders of them,
+ RefPtr<nsImapMailboxSpec> boxSpec = new nsImapMailboxSpec;
+ boxSpec->mFolderSelected = false;
+ boxSpec->mHostName = GetImapHostName();
+ boxSpec->mConnection = this;
+ boxSpec->mFlagState = nullptr;
+ boxSpec->mDiscoveredFromLsub = true;
+ boxSpec->mOnlineVerified = true;
+ boxSpec->mBoxFlags = kNoselect;
+ boxSpec->mHierarchySeparator = ns->GetDelimiter();
+ // Until |AllocateCanonicalPath()| gets updated:
+ m_runningUrl->AllocateCanonicalPath(
+ ns->GetPrefix(), ns->GetDelimiter(),
+ getter_Copies(boxSpec->mAllocatedPathName));
+ boxSpec->mNamespaceForFolder = ns;
+ boxSpec->mBoxFlags |= kNameSpace;
+
+ switch (ns->GetType()) {
+ case kPersonalNamespace:
+ boxSpec->mBoxFlags |= kPersonalMailbox;
+ break;
+ case kPublicNamespace:
+ boxSpec->mBoxFlags |= kPublicMailbox;
+ break;
+ case kOtherUsersNamespace:
+ boxSpec->mBoxFlags |= kOtherUsersMailbox;
+ break;
+ default: // (kUnknownNamespace)
+ break;
+ }
+
+ DiscoverMailboxSpec(boxSpec);
+ }
+
+ // now do the folders within this namespace
+ nsCString pattern;
+ nsCString pattern2;
+ if (usingSubscription) {
+ pattern.Append(prefix);
+ pattern.Append('*');
+ } else {
+ pattern.Append(prefix);
+ pattern.Append('%'); // mscott just need one percent right?
+ // pattern = PR_smprintf("%s%%", prefix);
+ char delimiter = ns->GetDelimiter();
+ if (delimiter) {
+ // delimiter might be NIL, in which case there's no hierarchy anyway
+ pattern2 = prefix;
+ pattern2 += "%";
+ pattern2 += delimiter;
+ pattern2 += "%";
+ // pattern2 = PR_smprintf("%s%%%c%%", prefix, delimiter);
+ }
+ }
+ // Note: It is important to make sure we are respecting the
+ // server_sub_directory preference when calling List and Lsub (2nd arg =
+ // true), otherwise we end up with performance issues or even crashes
+ // when connecting to servers that expose the users entire home
+ // directory (like UW-IMAP).
+ if (usingSubscription) { // && !GetSubscribingNow()) should never get
+ // here from subscribe pane
+ if (GetServerStateParser().GetCapabilityFlag() &
+ kHasListExtendedCapability)
+ Lsub(pattern.get(), true); // do LIST (SUBSCRIBED)
+ else {
+ // store mailbox flags from LIST
+ EMailboxHierarchyNameState currentState = m_hierarchyNameState;
+ m_hierarchyNameState = kListingForFolderFlags;
+ List(pattern.get(), true);
+ m_hierarchyNameState = currentState;
+ // then do LSUB using stored flags
+ Lsub(pattern.get(), true);
+ m_standardListMailboxes.Clear();
+ }
+ } else {
+ List(pattern.get(), true, hasXLIST);
+ List(pattern2.get(), true, hasXLIST);
+ }
+ }
+ }
+ }
+
+ // explicitly LIST the INBOX if (a) we're not using subscription, or (b) we
+ // are using subscription and the user wants us to always show the INBOX.
+ bool listInboxForHost = false;
+ m_hostSessionList->GetShouldAlwaysListInboxForHost(GetImapServerKey(),
+ listInboxForHost);
+ if (!usingSubscription || listInboxForHost) List("INBOX", true);
+
+ m_hierarchyNameState = kNoOperationInProgress;
+
+ MailboxDiscoveryFinished();
+
+ // Get the ACLs for newly discovered folders
+ if (GetServerStateParser().ServerHasACLCapability()) {
+ int32_t total = m_listedMailboxList.Length(), cnt = 0;
+ // Let's not turn this off here, since we don't turn it on after
+ // GetServerStateParser().SetReportingErrors(false);
+ if (total) {
+ ProgressEventFunctionUsingName("imapGettingACLForFolder");
+ nsIMAPMailboxInfo* mb = nullptr;
+ do {
+ if (m_listedMailboxList.Length() == 0) break;
+
+ mb = m_listedMailboxList[0]; // get top element
+ m_listedMailboxList.RemoveElementAt(
+ 0); // XP_ListRemoveTopObject(fListedMailboxList);
+ if (mb) {
+ if (FolderNeedsACLInitialized(
+ PromiseFlatCString(mb->GetMailboxName()).get())) {
+ char* onlineName = nullptr;
+ m_runningUrl->AllocateServerPath(
+ PromiseFlatCString(mb->GetMailboxName()).get(),
+ mb->GetDelimiter(), &onlineName);
+ if (onlineName) {
+ RefreshACLForFolder(onlineName);
+ PR_Free(onlineName);
+ }
+ }
+ PercentProgressUpdateEvent(""_ns, u""_ns, cnt, total);
+ delete mb; // this is the last time we're using the list, so delete
+ // the entries here
+ cnt++;
+ }
+ } while (mb && !DeathSignalReceived());
+ }
+ }
+}
+
+bool nsImapProtocol::FolderNeedsACLInitialized(const char* folderName) {
+ bool rv = false;
+ m_imapServerSink->FolderNeedsACLInitialized(nsDependentCString(folderName),
+ &rv);
+ return rv;
+}
+
+void nsImapProtocol::MailboxDiscoveryFinished() {
+ if (!DeathSignalReceived() && !GetSubscribingNow() &&
+ ((m_hierarchyNameState == kNoOperationInProgress) ||
+ (m_hierarchyNameState == kListingForInfoAndDiscovery))) {
+ nsImapNamespace* ns = nullptr;
+ m_hostSessionList->GetDefaultNamespaceOfTypeForHost(GetImapServerKey(),
+ kPersonalNamespace, ns);
+ const char* personalDir = ns ? ns->GetPrefix() : 0;
+
+ bool trashFolderExists = false;
+ bool usingSubscription = false;
+ m_hostSessionList->GetOnlineTrashFolderExistsForHost(GetImapServerKey(),
+ trashFolderExists);
+ m_hostSessionList->GetHostIsUsingSubscription(GetImapServerKey(),
+ usingSubscription);
+ if (!trashFolderExists && GetDeleteIsMoveToTrash() && usingSubscription) {
+ // maybe we're not subscribed to the Trash folder
+ if (personalDir) {
+ m_hierarchyNameState = kDiscoverTrashFolderInProgress;
+ List(m_trashFolderPath.get(), true);
+ m_hierarchyNameState = kNoOperationInProgress;
+ }
+ }
+
+ // There is no Trash folder (either LIST'd or LSUB'd), and we're using the
+ // Delete-is-move-to-Trash model, and there is a personal namespace
+ if (!trashFolderExists && GetDeleteIsMoveToTrash() && ns) {
+ nsCString onlineTrashName;
+ m_runningUrl->AllocateServerPath(m_trashFolderPath.get(),
+ ns->GetDelimiter(),
+ getter_Copies(onlineTrashName));
+
+ GetServerStateParser().SetReportingErrors(false);
+ bool created =
+ CreateMailboxRespectingSubscriptions(onlineTrashName.get());
+ GetServerStateParser().SetReportingErrors(true);
+
+ // force discovery of new trash folder.
+ if (created) {
+ m_hierarchyNameState = kDiscoverTrashFolderInProgress;
+ List(onlineTrashName.get(), false);
+ m_hierarchyNameState = kNoOperationInProgress;
+ } else
+ m_hostSessionList->SetOnlineTrashFolderExistsForHost(GetImapServerKey(),
+ true);
+ } // if trash folder doesn't exist
+ m_hostSessionList->SetHaveWeEverDiscoveredFoldersForHost(GetImapServerKey(),
+ true);
+ // notify front end that folder discovery is complete....
+ if (m_imapServerSink) m_imapServerSink->DiscoveryDone();
+
+ // Clear the discovery in progress flag.
+ m_hostSessionList->SetDiscoveryForHostInProgress(GetImapServerKey(), false);
+ }
+}
+
+// returns the mailboxName with the IMAP delimiter removed from the tail end
+void nsImapProtocol::RemoveHierarchyDelimiter(nsCString& mailboxName) {
+ char onlineDelimiter[2] = {0, 0};
+ if (m_imapMailFolderSink)
+ m_imapMailFolderSink->GetOnlineDelimiter(&onlineDelimiter[0]);
+ // take the hierarchy delimiter off the end, if any.
+ if (onlineDelimiter[0]) mailboxName.Trim(onlineDelimiter, false, true);
+}
+
+// returns true is the create succeeded (regardless of subscription changes)
+bool nsImapProtocol::CreateMailboxRespectingSubscriptions(
+ const char* mailboxName) {
+ CreateMailbox(mailboxName);
+ bool rv = GetServerStateParser().LastCommandSuccessful();
+ if (rv && m_autoSubscribe) // auto-subscribe is on
+ {
+ // create succeeded - let's subscribe to it
+ bool reportingErrors = GetServerStateParser().GetReportingErrors();
+ GetServerStateParser().SetReportingErrors(false);
+ nsCString mailboxWODelim(mailboxName);
+ RemoveHierarchyDelimiter(mailboxWODelim);
+ OnSubscribe(mailboxWODelim.get());
+ GetServerStateParser().SetReportingErrors(reportingErrors);
+ }
+ return rv;
+}
+
+void nsImapProtocol::CreateMailbox(const char* mailboxName) {
+ ProgressEventFunctionUsingName("imapStatusCreatingMailbox");
+
+ IncrementCommandTagNumber();
+
+ nsCString escapedName;
+ CreateEscapedMailboxName(mailboxName, escapedName);
+ nsCString command(GetServerCommandTag());
+ command += " create \"";
+ command += escapedName;
+ command += "\"" CRLF;
+
+ nsresult rv = SendData(command.get());
+ if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail();
+ // If that failed, let's list the parent folder to see if
+ // it allows inferiors, so we won't try to create sub-folders
+ // of the parent folder again in the current session.
+ if (GetServerStateParser().CommandFailed()) {
+ // Figure out parent folder name.
+ nsCString parentName(mailboxName);
+ char hierarchyDelimiter;
+ m_runningUrl->GetOnlineSubDirSeparator(&hierarchyDelimiter);
+ int32_t leafPos = parentName.RFindChar(hierarchyDelimiter);
+ if (leafPos > 0) {
+ parentName.SetLength(leafPos);
+ List(parentName.get(), false);
+ // We still want the caller to know the create failed, so restore that.
+ GetServerStateParser().SetCommandFailed(true);
+ }
+ }
+}
+
+void nsImapProtocol::DeleteMailbox(const char* mailboxName) {
+ // check if this connection currently has the folder to be deleted selected.
+ // If so, we should close it because at least some UW servers don't like you
+ // deleting a folder you have open.
+ if (FolderIsSelected(mailboxName)) ImapClose();
+
+ ProgressEventFunctionUsingNameWithString("imapStatusDeletingMailbox",
+ mailboxName);
+
+ IncrementCommandTagNumber();
+
+ nsCString escapedName;
+ CreateEscapedMailboxName(mailboxName, escapedName);
+ nsCString command(GetServerCommandTag());
+ command += " delete \"";
+ command += escapedName;
+ command += "\"" CRLF;
+
+ nsresult rv = SendData(command.get());
+ if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail();
+}
+
+void nsImapProtocol::RenameMailbox(const char* existingName,
+ const char* newName) {
+ // just like DeleteMailbox; Some UW servers don't like it.
+ if (FolderIsSelected(existingName)) ImapClose();
+
+ ProgressEventFunctionUsingNameWithString("imapStatusRenamingMailbox",
+ existingName);
+
+ IncrementCommandTagNumber();
+
+ nsCString escapedExistingName;
+ nsCString escapedNewName;
+ CreateEscapedMailboxName(existingName, escapedExistingName);
+ CreateEscapedMailboxName(newName, escapedNewName);
+ nsCString command(GetServerCommandTag());
+ command += " rename \"";
+ command += escapedExistingName;
+ command += "\" \"";
+ command += escapedNewName;
+ command += "\"" CRLF;
+
+ nsresult rv = SendData(command.get());
+ if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail();
+}
+
+bool nsImapProtocol::GetListSubscribedIsBrokenOnServer() {
+ // This is a workaround for an issue with LIST(SUBSCRIBED) crashing older
+ // versions of Zimbra
+ if (FindInReadable("\"NAME\" \"Zimbra\""_ns,
+ GetServerStateParser().GetServerID(),
+ nsCaseInsensitiveCStringComparator)) {
+ nsCString serverID(GetServerStateParser().GetServerID());
+ int start = serverID.LowerCaseFindASCII("\"version\" \"") + 11;
+ int length = serverID.LowerCaseFindASCII("\" ", start);
+ const nsDependentCSubstring serverVersionSubstring =
+ Substring(serverID, start, length);
+ nsCString serverVersionStr(serverVersionSubstring);
+ Version serverVersion(serverVersionStr.get());
+ Version sevenTwoThree("7.2.3_");
+ Version eightZeroZero("8.0.0_");
+ Version eightZeroThree("8.0.3_");
+ if ((serverVersion < sevenTwoThree) ||
+ ((serverVersion >= eightZeroZero) && (serverVersion < eightZeroThree)))
+ return true;
+ }
+ return false;
+}
+
+void nsImapProtocol::Lsub(const char* mailboxPattern,
+ bool addDirectoryIfNecessary) {
+ ProgressEventFunctionUsingName("imapStatusLookingForMailbox");
+
+ IncrementCommandTagNumber();
+
+ char* boxnameWithOnlineDirectory = nullptr;
+ if (addDirectoryIfNecessary)
+ m_runningUrl->AddOnlineDirectoryIfNecessary(mailboxPattern,
+ &boxnameWithOnlineDirectory);
+
+ nsCString escapedPattern;
+ CreateEscapedMailboxName(
+ boxnameWithOnlineDirectory ? boxnameWithOnlineDirectory : mailboxPattern,
+ escapedPattern);
+
+ nsCString command(GetServerCommandTag());
+ eIMAPCapabilityFlags flag = GetServerStateParser().GetCapabilityFlag();
+ bool useListSubscribed = (flag & kHasListExtendedCapability) &&
+ !GetListSubscribedIsBrokenOnServer();
+ if (useListSubscribed)
+ command += " list (subscribed)";
+ else
+ command += " lsub";
+ command += " \"\" \"";
+ command += escapedPattern;
+ if (useListSubscribed && (flag & kHasSpecialUseCapability))
+ command += "\" return (special-use)" CRLF;
+ else
+ command += "\"" CRLF;
+
+ PR_Free(boxnameWithOnlineDirectory);
+
+ nsresult rv = SendData(command.get());
+ if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail(command.get(), true);
+}
+
+void nsImapProtocol::List(const char* mailboxPattern,
+ bool addDirectoryIfNecessary, bool useXLIST) {
+ ProgressEventFunctionUsingName("imapStatusLookingForMailbox");
+
+ IncrementCommandTagNumber();
+
+ char* boxnameWithOnlineDirectory = nullptr;
+ if (addDirectoryIfNecessary)
+ m_runningUrl->AddOnlineDirectoryIfNecessary(mailboxPattern,
+ &boxnameWithOnlineDirectory);
+
+ nsCString escapedPattern;
+ CreateEscapedMailboxName(
+ boxnameWithOnlineDirectory ? boxnameWithOnlineDirectory : mailboxPattern,
+ escapedPattern);
+
+ nsCString command(GetServerCommandTag());
+ command += useXLIST ? " xlist \"\" \"" : " list \"\" \"";
+ command += escapedPattern;
+ command += "\"" CRLF;
+
+ PR_Free(boxnameWithOnlineDirectory);
+
+ nsresult rv = SendData(command.get());
+ if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail(command.get(), true);
+}
+
+void nsImapProtocol::Subscribe(const char* mailboxName) {
+ ProgressEventFunctionUsingNameWithString("imapStatusSubscribeToMailbox",
+ mailboxName);
+
+ IncrementCommandTagNumber();
+
+ nsCString escapedName;
+ CreateEscapedMailboxName(mailboxName, escapedName);
+
+ nsCString command(GetServerCommandTag());
+ command += " subscribe \"";
+ command += escapedName;
+ command += "\"" CRLF;
+
+ nsresult rv = SendData(command.get());
+ if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail();
+}
+
+void nsImapProtocol::Unsubscribe(const char* mailboxName) {
+ ProgressEventFunctionUsingNameWithString("imapStatusUnsubscribeMailbox",
+ mailboxName);
+ IncrementCommandTagNumber();
+
+ nsCString escapedName;
+ CreateEscapedMailboxName(mailboxName, escapedName);
+
+ nsCString command(GetServerCommandTag());
+ command += " unsubscribe \"";
+ command += escapedName;
+ command += "\"" CRLF;
+
+ nsresult rv = SendData(command.get());
+ if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail();
+}
+
+void nsImapProtocol::Idle() {
+ IncrementCommandTagNumber();
+
+ if (m_urlInProgress) return;
+ nsAutoCString command(GetServerCommandTag());
+ command += " IDLE" CRLF;
+ nsresult rv = SendData(command.get());
+ if (NS_SUCCEEDED(rv)) {
+ // Typically, we'll just get back only a continuation char on IDLE response,
+ // "+ idling". However, it is possible untagged responses will occur before
+ // and/or after the '+' which we treat the same as later untagged responses
+ // signaled by the socket thread. If untagged responses occur on IDLE,
+ // HandleIdleResponses() will trigger a select URL which will exit idle mode
+ // and update the selected folder. Finally, if IDLE responds with tagged BAD
+ // or NO, HandleIdleResponses() will return false.
+ m_idle = HandleIdleResponses();
+ }
+}
+
+// until we can fix the hang on shutdown waiting for server
+// responses, we need to not wait for the server response
+// on shutdown.
+void nsImapProtocol::EndIdle(bool waitForResponse /* = true */) {
+ // clear the async wait - otherwise, we have trouble doing a blocking read
+ // below.
+ nsCOMPtr<nsIAsyncInputStream> asyncInputStream =
+ do_QueryInterface(m_inputStream);
+ if (asyncInputStream) asyncInputStream->AsyncWait(nullptr, 0, 0, nullptr);
+ nsresult rv = SendData("DONE" CRLF);
+ // set a short timeout if we don't want to wait for a response
+ if (m_transport && !waitForResponse)
+ m_transport->SetTimeout(nsISocketTransport::TIMEOUT_READ_WRITE, 5);
+ if (NS_SUCCEEDED(rv)) {
+ m_idle = false;
+ ParseIMAPandCheckForNewMail();
+ // If waiting for response (i.e., not shutting down), check for IDLE
+ // untagged response(s) occurring after DONE is sent, which can occur and is
+ // mentioned in the IDLE rfc as a possibility. This is similar to the checks
+ // done in OnStatusForFolder().
+ if (waitForResponse && m_imapMailFolderSinkSelected &&
+ GetServerStateParser().UntaggedResponse()) {
+ Log("EndIdle", nullptr, "idle response after idle DONE");
+ m_imapMailFolderSinkSelected->OnNewIdleMessages();
+ }
+ }
+ // Set m_imapMailFolderSink null only if shutting down or if DONE succeeds.
+ // We need to keep m_imapMailFolderSink if DONE fails or times out when not
+ // shutting down so the URL that is attempting to run on this connection can
+ // retry or signal a failed status when SetUrlState is called in
+ // ProcessCurrentUrl to invoke nsIUrlListener.onStopRunningUrl.
+ if (!waitForResponse || GetServerStateParser().LastCommandSuccessful())
+ m_imapMailFolderSink = nullptr;
+}
+
+void nsImapProtocol::Search(const char* searchCriteria, bool useUID,
+ bool notifyHit /* true */) {
+ m_notifySearchHit = notifyHit;
+ ProgressEventFunctionUsingName("imapStatusSearchMailbox");
+ IncrementCommandTagNumber();
+
+ nsCString protocolString(GetServerCommandTag());
+ // the searchCriteria string contains the 'search ....' string
+ if (useUID) protocolString.AppendLiteral(" uid");
+ protocolString.Append(' ');
+ protocolString.Append(searchCriteria);
+ // the search criteria can contain string literals, which means we
+ // need to break up the protocol string by CRLF's, and after sending CRLF,
+ // wait for the server to respond OK before sending more data
+ nsresult rv;
+ int32_t crlfIndex;
+ while ((crlfIndex = protocolString.Find(CRLF)) != kNotFound &&
+ !DeathSignalReceived()) {
+ nsAutoCString tempProtocolString;
+ tempProtocolString = StringHead(protocolString, crlfIndex + 2);
+ rv = SendData(tempProtocolString.get());
+ if (NS_FAILED(rv)) return;
+ ParseIMAPandCheckForNewMail();
+ protocolString.Cut(0, crlfIndex + 2);
+ }
+ protocolString.Append(CRLF);
+
+ rv = SendData(protocolString.get());
+ if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail();
+}
+
+void nsImapProtocol::Copy(const char* messageList,
+ const char* destinationMailbox, bool idsAreUid) {
+ IncrementCommandTagNumber();
+
+ nsCString escapedDestination;
+ CreateEscapedMailboxName(destinationMailbox, escapedDestination);
+
+ // turn messageList back into key array and then back into a message id list,
+ // but use the flag state to handle ranges correctly.
+ nsCString messageIdList;
+ nsTArray<nsMsgKey> msgKeys;
+ if (idsAreUid) ParseUidString(messageList, msgKeys);
+
+ int32_t msgCountLeft = msgKeys.Length();
+ uint32_t msgsHandled = 0;
+
+ do {
+ nsCString idString;
+
+ uint32_t msgsToHandle = msgCountLeft;
+ if (idsAreUid)
+ AllocateImapUidString(msgKeys.Elements() + msgsHandled, msgsToHandle,
+ m_flagState, idString);
+ else
+ idString.Assign(messageList);
+
+ msgsHandled += msgsToHandle;
+ msgCountLeft -= msgsToHandle;
+
+ IncrementCommandTagNumber();
+ nsAutoCString protocolString(GetServerCommandTag());
+ if (idsAreUid) protocolString.AppendLiteral(" uid");
+ // If it's a MOVE operation on aol servers then use 'xaol-move' cmd.
+ if ((m_imapAction == nsIImapUrl::nsImapOnlineMove) &&
+ GetServerStateParser().ServerIsAOLServer())
+ protocolString.AppendLiteral(" xaol-move ");
+ else if ((m_imapAction == nsIImapUrl::nsImapOnlineMove) &&
+ GetServerStateParser().GetCapabilityFlag() & kHasMoveCapability)
+ protocolString.AppendLiteral(" move ");
+ else
+ protocolString.AppendLiteral(" copy ");
+
+ protocolString.Append(idString);
+ protocolString.AppendLiteral(" \"");
+ protocolString.Append(escapedDestination);
+ protocolString.AppendLiteral("\"" CRLF);
+
+ nsresult rv = SendData(protocolString.get());
+ if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail(protocolString.get());
+ } while (msgCountLeft > 0 && !DeathSignalReceived());
+}
+
+void nsImapProtocol::NthLevelChildList(const char* onlineMailboxPrefix,
+ int32_t depth) {
+ NS_ASSERTION(depth >= 0, "Oops ... depth must be equal or greater than 0");
+ if (depth < 0) return;
+
+ nsCString truncatedPrefix(onlineMailboxPrefix);
+ char16_t slash = '/';
+ if (truncatedPrefix.Last() == slash)
+ truncatedPrefix.SetLength(truncatedPrefix.Length() - 1);
+
+ nsAutoCString pattern(truncatedPrefix);
+ nsAutoCString suffix;
+ int count = 0;
+ char separator = 0;
+ m_runningUrl->GetOnlineSubDirSeparator(&separator);
+ suffix.Assign(separator);
+ suffix += '%';
+
+ while (count < depth) {
+ pattern += suffix;
+ count++;
+ List(pattern.get(), false);
+ }
+}
+
+/**
+ * ProcessAuthenticatedStateURL() is a helper for ProcessCurrentURL() which
+ * handles running URLs which require the connection to be in the
+ * Authenticated state.
+ */
+void nsImapProtocol::ProcessAuthenticatedStateURL() {
+ nsImapAction imapAction;
+ char* sourceMailbox = nullptr;
+ m_runningUrl->GetImapAction(&imapAction);
+
+ // switch off of the imap url action and take an appropriate action
+ switch (imapAction) {
+ case nsIImapUrl::nsImapLsubFolders:
+ OnLSubFolders();
+ break;
+ case nsIImapUrl::nsImapAppendMsgFromFile:
+ OnAppendMsgFromFile();
+ break;
+ case nsIImapUrl::nsImapDiscoverAllBoxesUrl:
+ NS_ASSERTION(!GetSubscribingNow(),
+ "Oops ... should not get here from subscribe UI");
+ DiscoverMailboxList();
+ break;
+ case nsIImapUrl::nsImapDiscoverAllAndSubscribedBoxesUrl:
+ DiscoverAllAndSubscribedBoxes();
+ break;
+ case nsIImapUrl::nsImapCreateFolder:
+ sourceMailbox = OnCreateServerSourceFolderPathString();
+ OnCreateFolder(sourceMailbox);
+ break;
+ case nsIImapUrl::nsImapEnsureExistsFolder:
+ sourceMailbox = OnCreateServerSourceFolderPathString();
+ OnEnsureExistsFolder(sourceMailbox);
+ break;
+ case nsIImapUrl::nsImapDiscoverChildrenUrl: {
+ char* canonicalParent = nullptr;
+ m_runningUrl->CreateServerSourceFolderPathString(&canonicalParent);
+ if (canonicalParent) {
+ NthLevelChildList(canonicalParent, 2);
+ PR_Free(canonicalParent);
+ }
+ break;
+ }
+ case nsIImapUrl::nsImapSubscribe:
+ sourceMailbox = OnCreateServerSourceFolderPathString();
+ OnSubscribe(sourceMailbox); // used to be called subscribe
+
+ if (GetServerStateParser().LastCommandSuccessful()) {
+ bool shouldList;
+ // if url is an external click url, then we should list the folder
+ // after subscribing to it, so we can select it.
+ m_runningUrl->GetExternalLinkUrl(&shouldList);
+ if (shouldList) OnListFolder(sourceMailbox, true);
+ }
+ break;
+ case nsIImapUrl::nsImapUnsubscribe:
+ sourceMailbox = OnCreateServerSourceFolderPathString();
+ OnUnsubscribe(sourceMailbox);
+ break;
+ case nsIImapUrl::nsImapRefreshACL:
+ sourceMailbox = OnCreateServerSourceFolderPathString();
+ RefreshACLForFolder(sourceMailbox);
+ break;
+ case nsIImapUrl::nsImapRefreshAllACLs:
+ OnRefreshAllACLs();
+ break;
+ case nsIImapUrl::nsImapListFolder:
+ sourceMailbox = OnCreateServerSourceFolderPathString();
+ OnListFolder(sourceMailbox, false);
+ break;
+ case nsIImapUrl::nsImapFolderStatus:
+ sourceMailbox = OnCreateServerSourceFolderPathString();
+ OnStatusForFolder(sourceMailbox);
+ break;
+ case nsIImapUrl::nsImapRefreshFolderUrls:
+ sourceMailbox = OnCreateServerSourceFolderPathString();
+ XMailboxInfo(sourceMailbox);
+ if (GetServerStateParser().LastCommandSuccessful())
+ SetFolderAdminUrl(sourceMailbox);
+ break;
+ case nsIImapUrl::nsImapDeleteFolder:
+ sourceMailbox = OnCreateServerSourceFolderPathString();
+ OnDeleteFolder(sourceMailbox);
+ break;
+ case nsIImapUrl::nsImapRenameFolder:
+ sourceMailbox = OnCreateServerSourceFolderPathString();
+ OnRenameFolder(sourceMailbox);
+ break;
+ case nsIImapUrl::nsImapMoveFolderHierarchy:
+ sourceMailbox = OnCreateServerSourceFolderPathString();
+ OnMoveFolderHierarchy(sourceMailbox);
+ break;
+ case nsIImapUrl::nsImapVerifylogon:
+ break;
+ default:
+ break;
+ }
+ PR_Free(sourceMailbox);
+}
+
+void nsImapProtocol::ProcessAfterAuthenticated() {
+ // if we're a netscape server, and we haven't got the admin url, get it
+ bool hasAdminUrl = true;
+
+ // If a capability response didn't occur during authentication, request
+ // the capabilities again to ensure the full capability set is known.
+ if (!m_capabilityResponseOccurred) Capability();
+
+ if (NS_SUCCEEDED(m_hostSessionList->GetHostHasAdminURL(GetImapServerKey(),
+ hasAdminUrl)) &&
+ !hasAdminUrl) {
+ if (GetServerStateParser().ServerHasServerInfo()) {
+ XServerInfo();
+ if (GetServerStateParser().LastCommandSuccessful() && m_imapServerSink) {
+ m_imapServerSink->SetMailServerUrls(
+ GetServerStateParser().GetMailAccountUrl(),
+ GetServerStateParser().GetManageListsUrl(),
+ GetServerStateParser().GetManageFiltersUrl());
+ // we've tried to ask for it, so don't try again this session.
+ m_hostSessionList->SetHostHasAdminURL(GetImapServerKey(), true);
+ }
+ } else if (GetServerStateParser().ServerIsNetscape3xServer()) {
+ Netscape();
+ if (GetServerStateParser().LastCommandSuccessful() && m_imapServerSink)
+ m_imapServerSink->SetMailServerUrls(
+ GetServerStateParser().GetMailAccountUrl(), EmptyCString(),
+ EmptyCString());
+ }
+ }
+
+ if (GetServerStateParser().ServerHasNamespaceCapability()) {
+ bool nameSpacesOverridable = false;
+ bool haveNameSpacesForHost = false;
+ m_hostSessionList->GetNamespacesOverridableForHost(GetImapServerKey(),
+ nameSpacesOverridable);
+ m_hostSessionList->GetGotNamespacesForHost(GetImapServerKey(),
+ haveNameSpacesForHost);
+
+ // mscott: VERIFY THIS CLAUSE!!!!!!!
+ if (nameSpacesOverridable && !haveNameSpacesForHost) Namespace();
+ }
+
+ // If the server supports compression, turn it on now.
+ // Choosing this spot (after login has finished) because
+ // many proxies (e.g. perdition, nginx) talk IMAP to the
+ // client until login is finished, then hand off to the
+ // backend. If we enable compression early the proxy
+ // will be confused.
+ if (UseCompressDeflate()) StartCompressDeflate();
+
+ if ((GetServerStateParser().GetCapabilityFlag() & kHasEnableCapability) &&
+ UseCondStore())
+ EnableCondStore();
+
+ if ((GetServerStateParser().GetCapabilityFlag() & kHasIDCapability) &&
+ m_sendID) {
+ ID();
+ if (m_imapServerSink && !GetServerStateParser().GetServerID().IsEmpty())
+ m_imapServerSink->SetServerID(GetServerStateParser().GetServerID());
+ }
+
+ bool utf8AcceptAllowed = m_allowUTF8Accept;
+ m_allowUTF8Accept = false;
+ if (utf8AcceptAllowed &&
+ ((GetServerStateParser().GetCapabilityFlag() &
+ (kHasEnableCapability | kHasUTF8AcceptCapability)) ==
+ (kHasEnableCapability | kHasUTF8AcceptCapability))) {
+ if (m_imapServerSink) {
+ EnableUTF8Accept();
+ m_allowUTF8Accept = GetServerStateParser().fUtf8AcceptEnabled;
+ // m_allowUTF8Accept affects imap append handling. See
+ // UploadMessageFromFile().
+ m_imapServerSink->SetServerUtf8AcceptEnabled(m_allowUTF8Accept);
+ GetServerStateParser().fUtf8AcceptEnabled = false;
+ } else {
+ NS_WARNING("UTF8=ACCEPT not enabled due to null m_imapServerSink");
+ }
+ }
+}
+
+void nsImapProtocol::SetupMessageFlagsString(nsCString& flagString,
+ imapMessageFlagsType flags,
+ uint16_t userFlags) {
+ if (flags & kImapMsgSeenFlag) flagString.AppendLiteral("\\Seen ");
+ if (flags & kImapMsgAnsweredFlag) flagString.AppendLiteral("\\Answered ");
+ if (flags & kImapMsgFlaggedFlag) flagString.AppendLiteral("\\Flagged ");
+ if (flags & kImapMsgDeletedFlag) flagString.AppendLiteral("\\Deleted ");
+ if (flags & kImapMsgDraftFlag) flagString.AppendLiteral("\\Draft ");
+ if (flags & kImapMsgRecentFlag) flagString.AppendLiteral("\\Recent ");
+ if ((flags & kImapMsgForwardedFlag) &&
+ (userFlags & kImapMsgSupportForwardedFlag))
+ flagString.AppendLiteral("$Forwarded "); // Not always available
+ if ((flags & kImapMsgMDNSentFlag) && (userFlags & kImapMsgSupportMDNSentFlag))
+ flagString.AppendLiteral("$MDNSent "); // Not always available
+
+ // eat the last space
+ if (!flagString.IsEmpty()) flagString.SetLength(flagString.Length() - 1);
+}
+
+void nsImapProtocol::ProcessStoreFlags(const nsCString& messageIdsString,
+ bool idsAreUids,
+ imapMessageFlagsType flags,
+ bool addFlags) {
+ nsCString flagString;
+
+ uint16_t userFlags = GetServerStateParser().SupportsUserFlags();
+ uint16_t settableFlags = GetServerStateParser().SettablePermanentFlags();
+
+ if (!addFlags && (flags & userFlags) && !(flags & settableFlags)) {
+ if (m_runningUrl)
+ m_runningUrl->SetExtraStatus(nsIImapUrl::ImapStatusFlagsNotSettable);
+ return; // if cannot set any of the flags bail out
+ }
+
+ if (addFlags)
+ flagString = "+Flags (";
+ else
+ flagString = "-Flags (";
+
+ if (flags & kImapMsgSeenFlag && kImapMsgSeenFlag & settableFlags)
+ flagString.AppendLiteral("\\Seen ");
+ if (flags & kImapMsgAnsweredFlag && kImapMsgAnsweredFlag & settableFlags)
+ flagString.AppendLiteral("\\Answered ");
+ if (flags & kImapMsgFlaggedFlag && kImapMsgFlaggedFlag & settableFlags)
+ flagString.AppendLiteral("\\Flagged ");
+ if (flags & kImapMsgDeletedFlag && kImapMsgDeletedFlag & settableFlags)
+ flagString.AppendLiteral("\\Deleted ");
+ if (flags & kImapMsgDraftFlag && kImapMsgDraftFlag & settableFlags)
+ flagString.AppendLiteral("\\Draft ");
+ if (flags & kImapMsgForwardedFlag && kImapMsgSupportForwardedFlag & userFlags)
+ flagString.AppendLiteral("$Forwarded "); // if supported
+ if (flags & kImapMsgMDNSentFlag && kImapMsgSupportMDNSentFlag & userFlags)
+ flagString.AppendLiteral("$MDNSent "); // if supported
+
+ if (flagString.Length() > 8) // if more than "+Flags ("
+ {
+ // replace the final space with ')'
+ flagString.SetCharAt(')', flagString.Length() - 1);
+
+ Store(messageIdsString, flagString.get(), idsAreUids);
+ if (m_runningUrl && idsAreUids) {
+ nsCString messageIdString;
+ m_runningUrl->GetListOfMessageIds(messageIdString);
+ nsTArray<nsMsgKey> msgKeys;
+ ParseUidString(messageIdString.get(), msgKeys);
+
+ int32_t msgCount = msgKeys.Length();
+ for (int32_t i = 0; i < msgCount; i++) {
+ bool found;
+ imapMessageFlagsType resultFlags;
+ // check if the flags were added/removed, and if the uid really exists.
+ nsresult rv = GetFlagsForUID(msgKeys[i], &found, &resultFlags, nullptr);
+ if (NS_FAILED(rv) || !found ||
+ (addFlags && ((flags & resultFlags) != flags)) ||
+ (!addFlags && ((flags & resultFlags) != 0))) {
+ m_runningUrl->SetExtraStatus(nsIImapUrl::ImapStatusFlagChangeFailed);
+ break;
+ }
+ }
+ }
+ }
+}
+
+/**
+ * This will cause all messages marked deleted to be expunged with no untagged
+ * response so it can cause unexpected data loss if used improperly.
+ */
+void nsImapProtocol::ImapClose(bool shuttingDown /* = false */,
+ bool waitForResponse /* = true */) {
+ IncrementCommandTagNumber();
+
+ nsCString command(GetServerCommandTag());
+ command.AppendLiteral(" close" CRLF);
+
+ if (!shuttingDown) ProgressEventFunctionUsingName("imapStatusCloseMailbox");
+
+ GetServerStateParser().ResetFlagInfo();
+
+ nsresult rv = SendData(command.get());
+ if (m_transport && shuttingDown)
+ m_transport->SetTimeout(nsISocketTransport::TIMEOUT_READ_WRITE, 5);
+
+ if (NS_SUCCEEDED(rv) && waitForResponse) ParseIMAPandCheckForNewMail();
+}
+
+void nsImapProtocol::XAOL_Option(const char* option) {
+ IncrementCommandTagNumber();
+
+ nsCString command(GetServerCommandTag());
+ command.AppendLiteral(" XAOL-OPTION ");
+ command.Append(option);
+ command.Append(CRLF);
+
+ nsresult rv = SendData(command.get());
+ if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail();
+}
+
+void nsImapProtocol::Check() {
+ // ProgressUpdateEvent("Checking mailbox...");
+
+ IncrementCommandTagNumber();
+
+ nsCString command(GetServerCommandTag());
+ command.AppendLiteral(" check" CRLF);
+
+ nsresult rv = SendData(command.get());
+ if (NS_SUCCEEDED(rv)) {
+ m_flagChangeCount = 0;
+ m_lastCheckTime = PR_Now();
+ ParseIMAPandCheckForNewMail();
+ }
+}
+
+nsresult nsImapProtocol::GetMsgWindow(nsIMsgWindow** aMsgWindow) {
+ nsresult rv;
+ *aMsgWindow = nullptr;
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl =
+ do_QueryInterface(m_runningUrl, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!m_imapProtocolSink) return NS_ERROR_FAILURE;
+ return m_imapProtocolSink->GetUrlWindow(mailnewsUrl, aMsgWindow);
+}
+
+/**
+ * Get password from RAM, disk (password manager) or user (dialog)
+ * @return NS_MSG_PASSWORD_PROMPT_CANCELLED
+ * (which is NS_SUCCEEDED!) when user cancelled
+ * NS_FAILED(rv) for other errors
+ */
+nsresult nsImapProtocol::GetPassword(nsString& password,
+ bool newPasswordRequested) {
+ // we are in the imap thread so *NEVER* try to extract the password with UI
+ NS_ENSURE_TRUE(m_imapServerSink, NS_ERROR_NULL_POINTER);
+ NS_ENSURE_TRUE(m_server, NS_ERROR_NULL_POINTER);
+ nsresult rv;
+
+ password = nsString();
+ // Get the password already stored in mem
+ rv = m_imapServerSink->GetServerPassword(password);
+ if (NS_FAILED(rv) || password.IsEmpty()) {
+ // First see if there's an associated window. We don't want to produce a
+ // password prompt if there is no window, e.g., during biff.
+ AutoProxyReleaseMsgWindow msgWindow;
+ GetMsgWindow(getter_AddRefs(msgWindow));
+ if (msgWindow) {
+ m_passwordStatus = NS_OK;
+ m_passwordObtained = false;
+
+ // Get the password from pw manager (harddisk) or user (dialog)
+ rv = m_imapServerSink->AsyncGetPassword(this, newPasswordRequested,
+ password);
+
+ if (NS_SUCCEEDED(rv)) {
+ while (password.IsEmpty()) {
+ bool shuttingDown = false;
+ (void)m_imapServerSink->GetServerShuttingDown(&shuttingDown);
+ if (shuttingDown) {
+ // Note: If we fix bug 1783573 this check could be ditched.
+ rv = NS_ERROR_FAILURE;
+ break;
+ }
+
+ ReentrantMonitorAutoEnter mon(m_passwordReadyMonitor);
+ if (!m_passwordObtained && !NS_FAILED(m_passwordStatus) &&
+ m_passwordStatus != NS_MSG_PASSWORD_PROMPT_CANCELLED &&
+ !DeathSignalReceived()) {
+ mon.Wait(PR_MillisecondsToInterval(1000));
+ }
+
+ if (NS_FAILED(m_passwordStatus) ||
+ m_passwordStatus == NS_MSG_PASSWORD_PROMPT_CANCELLED) {
+ rv = m_passwordStatus;
+ break;
+ }
+
+ if (DeathSignalReceived()) {
+ rv = NS_ERROR_FAILURE;
+ break;
+ }
+
+ if (m_passwordObtained) {
+ rv = m_passwordStatus;
+ password = m_password;
+ break;
+ }
+ } // end while
+ }
+ } else {
+ // If no msgWindow (i.e., unattended operation like biff, filtering or
+ // autosync) try to get the password directly from login mgr. If it's not
+ // there, will return NS_ERROR_NOT_AVAILABLE and the connection will fail
+ // with only the IMAP log message: `password prompt failed or user
+ // canceled it'. No password prompt occurs.
+ rv = m_imapServerSink->SyncGetPassword(password);
+ }
+ }
+ if (!password.IsEmpty()) m_lastPasswordSent = password;
+ return rv;
+}
+
+NS_IMETHODIMP nsImapProtocol::OnPromptStartAsync(
+ nsIMsgAsyncPromptCallback* aCallback) {
+ bool result = false;
+ OnPromptStart(&result);
+ return aCallback->OnAuthResult(result);
+}
+
+// This is called from the UI thread.
+NS_IMETHODIMP
+nsImapProtocol::OnPromptStart(bool* aResult) {
+ nsresult rv;
+ nsCOMPtr<nsIImapIncomingServer> imapServer = do_QueryReferent(m_server, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIMsgWindow> msgWindow;
+
+ *aResult = false;
+ GetMsgWindow(getter_AddRefs(msgWindow));
+ nsString password = m_lastPasswordSent;
+ rv = imapServer->PromptPassword(msgWindow, password);
+
+ ReentrantMonitorAutoEnter passwordMon(m_passwordReadyMonitor);
+
+ m_password = password;
+ m_passwordStatus = rv;
+ if (!m_password.IsEmpty()) *aResult = true;
+
+ // Notify the imap thread that we have a password.
+ m_passwordObtained = true;
+ passwordMon.Notify();
+ return rv;
+}
+
+NS_IMETHODIMP
+nsImapProtocol::OnPromptAuthAvailable() {
+ nsresult rv;
+ nsCOMPtr<nsIMsgIncomingServer> imapServer = do_QueryReferent(m_server, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsresult status = imapServer->GetPassword(m_password);
+
+ ReentrantMonitorAutoEnter passwordMon(m_passwordReadyMonitor);
+
+ m_passwordStatus = status;
+ // Notify the imap thread that we have a password.
+ m_passwordObtained = true;
+ passwordMon.Notify();
+ return m_passwordStatus;
+}
+
+NS_IMETHODIMP
+nsImapProtocol::OnPromptCanceled() {
+ // A prompt was cancelled, so notify the imap thread.
+ ReentrantMonitorAutoEnter passwordMon(m_passwordReadyMonitor);
+ m_passwordStatus = NS_MSG_PASSWORD_PROMPT_CANCELLED;
+ passwordMon.Notify();
+ return NS_OK;
+}
+
+// Called when capability response is parsed.
+void nsImapProtocol::SetCapabilityResponseOccurred() {
+ m_capabilityResponseOccurred = true;
+}
+
+bool nsImapProtocol::TryToLogon() {
+ MOZ_LOG(IMAP, LogLevel::Debug, ("Try to log in"));
+ NS_ENSURE_TRUE(m_imapServerSink, false);
+ bool loginSucceeded = false;
+ bool skipLoop = false;
+ nsAutoString password;
+ nsAutoCString userName;
+
+ // If remains false when authentication is complete it means that a
+ // capability response didn't occur within the authentication response so
+ // capabilities will be requested explicitly.
+ m_capabilityResponseOccurred = false;
+
+ nsresult rv = ChooseAuthMethod();
+ if (NS_FAILED(rv)) // all methods failed
+ {
+ // are there any matching login schemes at all?
+ if (!(GetServerStateParser().GetCapabilityFlag() & m_prefAuthMethods)) {
+ // Pref doesn't match server. Now, find an appropriate error msg.
+
+ // pref has plaintext pw & server claims to support encrypted pw
+ if (m_prefAuthMethods ==
+ (kHasAuthOldLoginCapability | kHasAuthLoginCapability |
+ kHasAuthPlainCapability) &&
+ GetServerStateParser().GetCapabilityFlag() & kHasCRAMCapability)
+ // tell user to change to encrypted pw
+ AlertUserEventUsingName("imapAuthChangePlainToEncrypt");
+ // pref has encrypted pw & server claims to support plaintext pw
+ else if (m_prefAuthMethods == kHasCRAMCapability &&
+ GetServerStateParser().GetCapabilityFlag() &
+ (kHasAuthOldLoginCapability | kHasAuthLoginCapability |
+ kHasAuthPlainCapability)) {
+ // have SSL
+ if (m_socketType == nsMsgSocketType::SSL ||
+ m_socketType == nsMsgSocketType::alwaysSTARTTLS)
+ // tell user to change to plaintext pw
+ AlertUserEventUsingName("imapAuthChangeEncryptToPlainSSL");
+ else
+ // tell user to change to plaintext pw, with big warning
+ AlertUserEventUsingName("imapAuthChangeEncryptToPlainNoSSL");
+ } else
+ // just "change auth method"
+ AlertUserEventUsingName("imapAuthMechNotSupported");
+
+ skipLoop = true;
+ } else {
+ // try to reset failed methods and try them again
+ ResetAuthMethods();
+ rv = ChooseAuthMethod();
+ if (NS_FAILED(rv)) // all methods failed
+ {
+ MOZ_LOG(IMAP, LogLevel::Error,
+ ("huch? there are auth methods, and we reset failed ones, but "
+ "ChooseAuthMethod still fails."));
+ return false;
+ }
+ }
+ }
+
+ // Check the uri host for localhost indicators to see if we
+ // should bypass the SSL check for clientid.
+ // Unfortunately we cannot call IsOriginPotentiallyTrustworthy
+ // here because it can only be called from the main thread.
+ bool isLocalhostConnection = false;
+ if (m_mockChannel) {
+ nsCOMPtr<nsIURI> uri;
+ m_mockChannel->GetURI(getter_AddRefs(uri));
+ if (uri) {
+ nsCString uriHost;
+ uri->GetHost(uriHost);
+ if (uriHost.Equals("127.0.0.1") || uriHost.Equals("::1") ||
+ uriHost.Equals("localhost")) {
+ isLocalhostConnection = true;
+ }
+ }
+ }
+
+ // Whether our connection can be considered 'secure' and whether
+ // we should allow the CLIENTID to be sent over this channel.
+ bool isSecureConnection =
+ (m_connectionType.EqualsLiteral("starttls") ||
+ m_connectionType.EqualsLiteral("ssl") || isLocalhostConnection);
+
+ // Before running the ClientID command we check for clientid
+ // support by checking the server capability flags for the
+ // flag kHasClientIDCapability.
+ // We check that the m_clientId string is not empty, and
+ // we ensure the connection can be considered secure.
+ if ((GetServerStateParser().GetCapabilityFlag() & kHasClientIDCapability) &&
+ !m_clientId.IsEmpty() && isSecureConnection) {
+ rv = ClientID();
+ if (NS_FAILED(rv)) {
+ MOZ_LOG(IMAP, LogLevel::Error,
+ ("TryToLogon: Could not issue CLIENTID command"));
+ skipLoop = true;
+ }
+ }
+
+ // Get username, either the stored one or from user
+ rv = m_imapServerSink->GetLoginUsername(userName);
+ if (NS_FAILED(rv) || userName.IsEmpty()) {
+ // The user hit "Cancel" on the dialog box
+ skipLoop = true;
+ }
+
+ // clang-format off
+ /*
+ * Login can fail for various reasons:
+ * 1. Server claims to support GSSAPI, but it really doesn't.
+ * Or the client doesn't support GSSAPI, or is not logged in yet.
+ * (GSSAPI is a mechanism without password in apps).
+ * 2. Server claims to support CRAM-MD5, but it's broken and will fail despite correct password.
+ * 2.1. Some servers say they support CRAM but are so badly broken that trying it causes
+ * all subsequent login attempts to fail during this connection (bug 231303).
+ * So we use CRAM/NTLM/MSN only if enabled in prefs.
+ * Update: if it affects only some ISPs, we can maybe use the ISP DB
+ * and disable CRAM specifically for these.
+ * 3. Prefs are set to require auth methods which the server doesn't support
+ * (per CAPS or we tried and they failed).
+ * 4. User provided wrong password.
+ * 5. We tried too often and the server shut us down, so even a correct attempt
+ * will now (currently) fail.
+ * The above problems may overlap, e.g. 3. with 1. and 2., and we can't differentiate
+ * between 2. and 4., which is really unfortunate.
+ */
+ // clang-format on
+
+ bool newPasswordRequested = false;
+ // remember the msgWindow before we start trying to logon, because if the
+ // server drops the connection on errors, TellThreadToDie will null out the
+ // protocolsink and we won't be able to get the msgWindow.
+ AutoProxyReleaseMsgWindow msgWindow;
+ GetMsgWindow(getter_AddRefs(msgWindow));
+
+ // This loops over 1) auth methods (only one per loop) and 2) password tries
+ // (with UI)
+ while (!loginSucceeded && !skipLoop && !DeathSignalReceived()) {
+ // Get password
+ if (m_currentAuthMethod !=
+ kHasAuthGssApiCapability && // GSSAPI uses no pw in apps
+ m_currentAuthMethod != kHasAuthExternalCapability &&
+ m_currentAuthMethod != kHasXOAuth2Capability &&
+ m_currentAuthMethod != kHasAuthNoneCapability) {
+ rv = GetPassword(password, newPasswordRequested);
+ newPasswordRequested = false;
+ if (rv == NS_MSG_PASSWORD_PROMPT_CANCELLED || NS_FAILED(rv)) {
+ MOZ_LOG(IMAP, LogLevel::Error,
+ ("IMAP: password prompt failed or user canceled it"));
+ break;
+ }
+ MOZ_LOG(IMAP, LogLevel::Debug, ("got new password"));
+ }
+
+ bool lastReportingErrors = GetServerStateParser().GetReportingErrors();
+ GetServerStateParser().SetReportingErrors(
+ false); // turn off errors - we'll put up our own.
+
+ rv = AuthLogin(userName.get(), password, m_currentAuthMethod);
+
+ GetServerStateParser().SetReportingErrors(
+ lastReportingErrors); // restore error reports
+ loginSucceeded = NS_SUCCEEDED(rv);
+
+ if (!loginSucceeded) {
+ MOZ_LOG(IMAP, LogLevel::Debug, ("authlogin failed"));
+ MarkAuthMethodAsFailed(m_currentAuthMethod);
+ rv = ChooseAuthMethod(); // change m_currentAuthMethod to try other one
+ // next round
+
+ if (NS_FAILED(rv)) // all methods failed
+ {
+ if (m_prefAuthMethods == kHasAuthGssApiCapability) {
+ // GSSAPI failed, and it's the only available method,
+ // and it's password-less, so nothing left to do.
+ AlertUserEventUsingName("imapAuthGssapiFailed");
+ break;
+ }
+
+ if (m_prefAuthMethods & kHasXOAuth2Capability) {
+ // OAuth2 failed. Entering password does not help.
+ AlertUserEventUsingName("imapOAuth2Error");
+ break;
+ }
+
+ // The reason that we failed might be a wrong password, so
+ // ask user what to do
+ MOZ_LOG(IMAP, LogLevel::Warning,
+ ("IMAP: ask user what to do (after login failed): new "
+ "passwort, retry, cancel"));
+ if (!m_imapServerSink) break;
+ // if there's no msg window, don't forget the password
+ if (!msgWindow) break;
+ int32_t buttonPressed = 1;
+ rv = m_imapServerSink->PromptLoginFailed(msgWindow, &buttonPressed);
+ if (NS_FAILED(rv)) break;
+ if (buttonPressed == 2) // 'New password' button
+ {
+ MOZ_LOG(IMAP, LogLevel::Warning, ("new password button pressed."));
+ // Forget the current password
+ password.Truncate();
+ m_hostSessionList->SetPasswordForHost(GetImapServerKey(),
+ EmptyString());
+ m_imapServerSink->ForgetPassword();
+ m_password.Truncate();
+ MOZ_LOG(IMAP, LogLevel::Warning, ("password reset (nulled)"));
+ newPasswordRequested = true;
+ // Will call GetPassword() in beginning of next loop
+
+ // Try all possible auth methods again with the new password.
+ ResetAuthMethods();
+ } else if (buttonPressed == 0) // Retry button
+ {
+ MOZ_LOG(IMAP, LogLevel::Warning, ("retry button pressed"));
+ // Try all possible auth methods again
+ ResetAuthMethods();
+ } else if (buttonPressed == 1) // Cancel button
+ {
+ MOZ_LOG(IMAP, LogLevel::Warning, ("cancel button pressed"));
+ break; // Abort quickly
+ }
+
+ // TODO what is this for? When does it get set to != unknown again?
+ m_currentBiffState = nsIMsgFolder::nsMsgBiffState_Unknown;
+ SendSetBiffIndicatorEvent(m_currentBiffState);
+ } // all methods failed
+ } // login failed
+ } // while
+
+ if (loginSucceeded) {
+ MOZ_LOG(IMAP, LogLevel::Debug, ("login succeeded"));
+ bool passwordAlreadyVerified;
+ m_hostSessionList->SetPasswordForHost(GetImapServerKey(), password);
+ rv = m_hostSessionList->GetPasswordVerifiedOnline(GetImapServerKey(),
+ passwordAlreadyVerified);
+ if (NS_SUCCEEDED(rv) && !passwordAlreadyVerified) {
+ // First successful login for this server/host during this session.
+ m_hostSessionList->SetPasswordVerifiedOnline(GetImapServerKey());
+ }
+
+ bool imapPasswordIsNew = !passwordAlreadyVerified;
+ if (imapPasswordIsNew) {
+ if (m_currentBiffState == nsIMsgFolder::nsMsgBiffState_Unknown) {
+ m_currentBiffState = nsIMsgFolder::nsMsgBiffState_NoMail;
+ SendSetBiffIndicatorEvent(m_currentBiffState);
+ }
+ m_imapServerSink->SetUserAuthenticated(true);
+ }
+
+ nsImapAction imapAction;
+ m_runningUrl->GetImapAction(&imapAction);
+ // We don't want to do any more processing if we're just
+ // verifying the ability to logon because it can leave us in
+ // a half-constructed state.
+ if (imapAction != nsIImapUrl::nsImapVerifylogon)
+ ProcessAfterAuthenticated();
+ } else // login failed
+ {
+ MOZ_LOG(IMAP, LogLevel::Error, ("login failed entirely"));
+ m_currentBiffState = nsIMsgFolder::nsMsgBiffState_Unknown;
+ SendSetBiffIndicatorEvent(m_currentBiffState);
+ HandleCurrentUrlError();
+ SetConnectionStatus(NS_ERROR_FAILURE); // stop netlib
+ }
+
+ return loginSucceeded;
+}
+
+void nsImapProtocol::UpdateFolderQuotaData(nsImapQuotaAction aAction,
+ nsCString& aQuotaRoot,
+ uint64_t aUsed, uint64_t aMax) {
+ NS_ASSERTION(m_imapMailFolderSink, "m_imapMailFolderSink is null!");
+
+ m_imapMailFolderSink->SetFolderQuotaData(aAction, aQuotaRoot, aUsed, aMax);
+}
+
+void nsImapProtocol::GetQuotaDataIfSupported(const char* aBoxName) {
+ // If server doesn't have quota support, don't do anything
+ if (!(GetServerStateParser().GetCapabilityFlag() & kQuotaCapability)) return;
+
+ nsCString escapedName;
+ CreateEscapedMailboxName(aBoxName, escapedName);
+
+ IncrementCommandTagNumber();
+
+ nsAutoCString quotacommand(GetServerCommandTag());
+ quotacommand.AppendLiteral(" getquotaroot \"");
+ quotacommand.Append(escapedName);
+ quotacommand.AppendLiteral("\"" CRLF);
+
+ NS_ASSERTION(m_imapMailFolderSink, "m_imapMailFolderSink is null!");
+ if (m_imapMailFolderSink)
+ m_imapMailFolderSink->SetFolderQuotaCommandIssued(true);
+
+ nsresult quotarv = SendData(quotacommand.get());
+ if (NS_SUCCEEDED(quotarv))
+ ParseIMAPandCheckForNewMail(nullptr, true); // don't display errors.
+}
+
+bool nsImapProtocol::GetDeleteIsMoveToTrash() {
+ bool rv = false;
+ NS_ASSERTION(m_hostSessionList, "fatal... null host session list");
+ if (m_hostSessionList)
+ m_hostSessionList->GetDeleteIsMoveToTrashForHost(GetImapServerKey(), rv);
+ return rv;
+}
+
+bool nsImapProtocol::GetShowDeletedMessages() {
+ bool rv = false;
+ if (m_hostSessionList)
+ m_hostSessionList->GetShowDeletedMessagesForHost(GetImapServerKey(), rv);
+ return rv;
+}
+
+bool nsImapProtocol::CheckNeeded() {
+ if (m_flagChangeCount >= kFlagChangesBeforeCheck) return true;
+
+ int32_t deltaInSeconds;
+
+ PRTime2Seconds(PR_Now() - m_lastCheckTime, &deltaInSeconds);
+
+ return (deltaInSeconds >= kMaxSecondsBeforeCheck);
+}
+
+bool nsImapProtocol::UseCondStore() {
+ // Check that the server is capable of cond store, and the user
+ // hasn't disabled the use of constore for this server.
+ return m_useCondStore &&
+ GetServerStateParser().GetCapabilityFlag() & kHasCondStoreCapability &&
+ GetServerStateParser().fUseModSeq;
+}
+
+bool nsImapProtocol::UseCompressDeflate() {
+ // Check that the server is capable of compression, and the user
+ // hasn't disabled the use of compression for this server.
+ return m_useCompressDeflate && GetServerStateParser().GetCapabilityFlag() &
+ kHasCompressDeflateCapability;
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////
+// The following is the implementation of nsImapMockChannel and an intermediary
+// imap steam listener. The stream listener is used to make a clean binding
+// between the imap mock channel and the memory cache channel (if we are reading
+// from the cache)
+// Used by both offline storage "cache" and by the system cache called "cache2".
+//////////////////////////////////////////////////////////////////////////////////////////////
+
+// WARNING: the cache stream listener is intended to be accessed from the UI
+// thread! it will NOT create another proxy for the stream listener that gets
+// passed in...
+class nsImapCacheStreamListener : public nsIStreamListener {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+
+ nsImapCacheStreamListener();
+
+ nsresult Init(nsIStreamListener* aStreamListener,
+ nsIImapMockChannel* aMockChannelToUse, bool cache2 = false);
+
+ protected:
+ virtual ~nsImapCacheStreamListener();
+ nsCOMPtr<nsIImapMockChannel> mChannelToUse;
+ nsCOMPtr<nsIStreamListener> mListener;
+ bool mCache2; // Initialized for cache2 usage
+ bool mStarting; // Used with cache2. Indicates 1st data segment is read.
+
+ private:
+ static bool mGoodCache2;
+ static const uint32_t kPeekBufSize;
+ static nsresult Peeker(nsIInputStream* aInStr, void* aClosure,
+ const char* aBuffer, uint32_t aOffset, uint32_t aCount,
+ uint32_t* aCountWritten);
+};
+
+NS_IMPL_ADDREF(nsImapCacheStreamListener)
+NS_IMPL_RELEASE(nsImapCacheStreamListener)
+
+NS_INTERFACE_MAP_BEGIN(nsImapCacheStreamListener)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIStreamListener)
+ NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
+ NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
+NS_INTERFACE_MAP_END
+
+nsImapCacheStreamListener::nsImapCacheStreamListener() {}
+bool nsImapCacheStreamListener::mGoodCache2 = false;
+const uint32_t nsImapCacheStreamListener::kPeekBufSize = 101;
+
+nsImapCacheStreamListener::~nsImapCacheStreamListener() { mStarting = true; }
+
+nsresult nsImapCacheStreamListener::Init(nsIStreamListener* aStreamListener,
+ nsIImapMockChannel* aMockChannelToUse,
+ bool aCache2 /*false*/) {
+ NS_ENSURE_ARG(aStreamListener);
+ NS_ENSURE_ARG(aMockChannelToUse);
+
+ mChannelToUse = aMockChannelToUse;
+ mListener = aStreamListener;
+ mCache2 = aCache2;
+ mStarting = true;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapCacheStreamListener::OnStartRequest(nsIRequest* request) {
+ if (!mChannelToUse) {
+ NS_ERROR("OnStartRequest called after OnStopRequest");
+ return NS_ERROR_NULL_POINTER;
+ }
+ if (!mCache2 || !mStarting) {
+ return mListener->OnStartRequest(mChannelToUse);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapCacheStreamListener::OnStopRequest(nsIRequest* request,
+ nsresult aStatus) {
+ if (!mListener) {
+ NS_ERROR("OnStopRequest called twice");
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ nsresult rv = NS_OK;
+ if (!mCache2 || !mStarting) {
+ rv = mListener->OnStopRequest(mChannelToUse, aStatus);
+
+ mListener = nullptr;
+ mChannelToUse->Close();
+ mChannelToUse = nullptr;
+ }
+ return rv;
+}
+
+/*
+ * Called when cache2 is in effect on first available data segment returned
+ * to check that cache entry looks like it it a valid email header. With
+ * cache2 memory cache this could be done synchronously. But with disk cache
+ * it can only be done asynchronously like this.
+ * Note: If NS_OK returned, the peeked at bytes are consumed here and not passed
+ * on to the listener so a special return value is used.
+ */
+nsresult nsImapCacheStreamListener::Peeker(nsIInputStream* aInStr,
+ void* aClosure, const char* aBuffer,
+ uint32_t aOffset, uint32_t aCount,
+ uint32_t* aCountWritten) {
+ char peekBuf[kPeekBufSize];
+ aCount = aCount > sizeof peekBuf ? sizeof peekBuf : aCount;
+ memcpy(peekBuf, aBuffer, aCount);
+ peekBuf[aCount] = 0; // Null terminate the starting header data.
+ int32_t findPos = MsgFindCharInSet(nsDependentCString(peekBuf), ":\n\r", 0);
+ // Check that the first line is a header line, i.e., with a ':' in it
+ // Or that it begins with "From " because some IMAP servers allow that,
+ // even though it's technically invalid.
+ mGoodCache2 = ((findPos != -1 && peekBuf[findPos] == ':') ||
+ !(strncmp(peekBuf, "From ", 5)));
+ return NS_BASE_STREAM_WOULD_BLOCK; // So stream buffer not "consumed"
+}
+
+NS_IMETHODIMP
+nsImapCacheStreamListener::OnDataAvailable(nsIRequest* request,
+ nsIInputStream* aInStream,
+ uint64_t aSourceOffset,
+ uint32_t aCount) {
+ if (mCache2 && mStarting) {
+ // Peeker() does check of leading bytes and sets mGoodCache2.
+ uint32_t numRead;
+ aInStream->ReadSegments(Peeker, nullptr, kPeekBufSize - 1, &numRead);
+ MOZ_LOG(IMAPCache, LogLevel::Debug,
+ ("%s: mGoodCache2=%d(bool)", __func__, mGoodCache2));
+
+ if (mGoodCache2) {
+ // Do deferred setup of loadGroup and OnStartRequest and then forward
+ // the verified first segment to the actual listener.
+ mStarting = false;
+ mListener->OnStartRequest(mChannelToUse);
+ } else {
+ MOZ_LOG(IMAPCache, LogLevel::Error,
+ ("%s: cache entry bad so just read imap here", __func__));
+ mChannelToUse->ReadFromImapConnection();
+ return NS_ERROR_FAILURE; // no more starts, one more stop occurs
+ }
+ }
+ // Forward the segment to the actual listener.
+ return mListener->OnDataAvailable(mChannelToUse, aInStream, aSourceOffset,
+ aCount);
+}
+
+NS_IMPL_ISUPPORTS_INHERITED(nsImapMockChannel, nsHashPropertyBag,
+ nsIImapMockChannel, nsIMailChannel, nsIChannel,
+ nsIRequest, nsICacheEntryOpenCallback,
+ nsITransportEventSink, nsISupportsWeakReference)
+
+nsImapMockChannel::nsImapMockChannel()
+ : mSuspendedMonitor("nsImapMockChannel"), mSuspended(false) {
+ m_cancelStatus = NS_OK;
+ mLoadFlags = 0;
+ mChannelClosed = false;
+ mReadingFromCache = false;
+ mContentLength = mozilla::dom::InternalResponse::UNKNOWN_BODY_SIZE;
+ mContentDisposition = nsIChannel::DISPOSITION_INLINE;
+ mWritingToCache = false;
+}
+
+nsImapMockChannel::~nsImapMockChannel() {
+ // if we're offline, we may not get to close the channel correctly.
+ // we need to do this to send the url state change notification in
+ // the case of mem and disk cache reads.
+ MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread(),
+ "should only access mock channel on ui thread");
+ if (!mChannelClosed) Close();
+}
+
+nsresult nsImapMockChannel::NotifyStartEndReadFromCache(bool start) {
+ nsresult rv = NS_OK;
+ mReadingFromCache = start;
+ nsCOMPtr<nsIImapUrl> imapUrl = do_QueryInterface(m_url, &rv);
+ nsCOMPtr<nsIImapProtocol> imapProtocol = do_QueryReferent(mProtocol);
+ if (imapUrl) {
+ nsCOMPtr<nsIImapMailFolderSink> folderSink;
+ rv = imapUrl->GetImapMailFolderSink(getter_AddRefs(folderSink));
+ if (folderSink) {
+ nsCOMPtr<nsIMsgMailNewsUrl> mailUrl = do_QueryInterface(m_url);
+ rv = folderSink->SetUrlState(nullptr /* we don't know the protocol */,
+ mailUrl, start, false, m_cancelStatus);
+
+ // Required for killing ImapProtocol thread
+ if (NS_FAILED(m_cancelStatus) && imapProtocol)
+ imapProtocol->TellThreadToDie(false);
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapMockChannel::Close() {
+ if (mReadingFromCache)
+ NotifyStartEndReadFromCache(false);
+ else {
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_url);
+ if (mailnewsUrl) {
+ nsCOMPtr<nsICacheEntry> cacheEntry;
+ mailnewsUrl->GetMemCacheEntry(getter_AddRefs(cacheEntry));
+ if (cacheEntry) cacheEntry->MarkValid();
+ // remove the channel from the load group
+ nsCOMPtr<nsILoadGroup> loadGroup;
+ GetLoadGroup(getter_AddRefs(loadGroup));
+ // if the mock channel wasn't initialized with a load group then
+ // use our load group (they may differ)
+ if (!loadGroup) mailnewsUrl->GetLoadGroup(getter_AddRefs(loadGroup));
+ if (loadGroup)
+ loadGroup->RemoveRequest((nsIRequest*)this, nullptr, NS_OK);
+ }
+ }
+
+ m_channelListener = nullptr;
+ mChannelClosed = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMockChannel::GetProgressEventSink(
+ nsIProgressEventSink** aProgressEventSink) {
+ NS_IF_ADDREF(*aProgressEventSink = mProgressEventSink);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMockChannel::SetProgressEventSink(
+ nsIProgressEventSink* aProgressEventSink) {
+ mProgressEventSink = aProgressEventSink;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMockChannel::GetChannelListener(
+ nsIStreamListener** aChannelListener) {
+ NS_IF_ADDREF(*aChannelListener = m_channelListener);
+ return NS_OK;
+}
+
+// now implement our mock implementation of the channel interface...we forward
+// all calls to the real channel if we have one...otherwise we return something
+// bogus...
+
+NS_IMETHODIMP nsImapMockChannel::SetLoadGroup(nsILoadGroup* aLoadGroup) {
+ m_loadGroup = aLoadGroup;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMockChannel::GetLoadGroup(nsILoadGroup** aLoadGroup) {
+ NS_IF_ADDREF(*aLoadGroup = m_loadGroup);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMockChannel::GetTRRMode(nsIRequest::TRRMode* aTRRMode) {
+ return GetTRRModeImpl(aTRRMode);
+}
+
+NS_IMETHODIMP nsImapMockChannel::SetTRRMode(nsIRequest::TRRMode aTRRMode) {
+ return SetTRRModeImpl(aTRRMode);
+}
+
+NS_IMETHODIMP nsImapMockChannel::GetLoadInfo(nsILoadInfo** aLoadInfo) {
+ NS_IF_ADDREF(*aLoadInfo = m_loadInfo);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMockChannel::SetLoadInfo(nsILoadInfo* aLoadInfo) {
+ m_loadInfo = aLoadInfo;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMockChannel::GetOriginalURI(nsIURI** aURI) {
+ // IMap does not seem to have the notion of an original URI :-(
+ // *aURI = m_originalUrl ? m_originalUrl : m_url;
+ NS_IF_ADDREF(*aURI = m_url);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMockChannel::SetOriginalURI(nsIURI* aURI) {
+ // IMap does not seem to have the notion of an original URI :-(
+ // MOZ_ASSERT_UNREACHABLE("nsImapMockChannel::SetOriginalURI");
+ // return NS_ERROR_NOT_IMPLEMENTED;
+ return NS_OK; // ignore
+}
+
+NS_IMETHODIMP nsImapMockChannel::GetURI(nsIURI** aURI) {
+ NS_IF_ADDREF(*aURI = m_url);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMockChannel::SetURI(nsIURI* aURI) {
+ m_url = aURI;
+#ifdef DEBUG_bienvenu
+ if (!aURI) printf("Clearing URI\n");
+#endif
+ if (m_url) {
+ // if we don't have a progress event sink yet, get it from the url for
+ // now...
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_url);
+ if (mailnewsUrl && !mProgressEventSink) {
+ nsCOMPtr<nsIMsgStatusFeedback> statusFeedback;
+ mailnewsUrl->GetStatusFeedback(getter_AddRefs(statusFeedback));
+ mProgressEventSink = do_QueryInterface(statusFeedback);
+ }
+ // If this is a fetch URL and we can, get the message size from the message
+ // header and set it to be the content length.
+ // Note that for an attachment URL, this will set the content length to be
+ // equal to the size of the entire message.
+ nsCOMPtr<nsIImapUrl> imapUrl(do_QueryInterface(m_url));
+ nsImapAction imapAction;
+ imapUrl->GetImapAction(&imapAction);
+ if (imapAction == nsIImapUrl::nsImapMsgFetch) {
+ nsCOMPtr<nsIMsgMessageUrl> msgUrl(do_QueryInterface(m_url));
+ if (msgUrl) {
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ // A failure to get a message header isn't an error
+ msgUrl->GetMessageHeader(getter_AddRefs(msgHdr));
+ if (msgHdr) {
+ uint32_t messageSize;
+ if (NS_SUCCEEDED(msgHdr->GetMessageSize(&messageSize)))
+ SetContentLength(messageSize);
+ }
+ }
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMockChannel::Open(nsIInputStream** _retval) {
+ nsCOMPtr<nsIStreamListener> listener;
+ nsresult rv =
+ nsContentSecurityManager::doContentSecurityCheck(this, listener);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (m_url) {
+ nsCOMPtr<nsIImapUrl> imapUrl(do_QueryInterface(m_url, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsImapAction imapAction;
+ imapUrl->GetImapAction(&imapAction);
+ // If we're shutting down, and not running the kinds of urls we run at
+ // shutdown, then this should fail because running urls during
+ // shutdown will very likely fail and potentially hang.
+ nsCOMPtr<nsIMsgAccountManager> accountMgr =
+ do_GetService("@mozilla.org/messenger/account-manager;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ bool shuttingDown = false;
+ (void)accountMgr->GetShutdownInProgress(&shuttingDown);
+ if (shuttingDown && imapAction != nsIImapUrl::nsImapExpungeFolder &&
+ imapAction != nsIImapUrl::nsImapDeleteAllMsgs &&
+ imapAction != nsIImapUrl::nsImapDeleteFolder)
+ return NS_ERROR_FAILURE;
+ }
+ return NS_ImplementChannelOpen(this, _retval);
+}
+
+NS_IMETHODIMP
+nsImapMockChannel::OnCacheEntryAvailable(nsICacheEntry* entry, bool aNew,
+ nsresult status) {
+ if (MOZ_LOG_TEST(IMAPCache, LogLevel::Debug)) {
+ MOZ_LOG(IMAPCache, LogLevel::Debug,
+ ("%s: Create/write new cache entry=%s", __func__,
+ aNew ? "true" : "false"));
+ if (NS_SUCCEEDED(status)) {
+ nsAutoCString key;
+ entry->GetKey(key);
+ MOZ_LOG(IMAPCache, LogLevel::Debug,
+ ("%s: Cache entry key = |%s|", __func__, key.get()));
+ }
+ }
+
+ // make sure we didn't close the channel before the async call back came in...
+ // hmmm....if we had write access and we canceled this mock channel then I
+ // wonder if we should be invalidating the cache entry before kicking out...
+ if (mChannelClosed) {
+ if (NS_SUCCEEDED(status)) {
+ entry->AsyncDoom(nullptr);
+ }
+ return NS_OK;
+ }
+
+ if (!m_url) {
+ // Something has gone terribly wrong.
+ NS_WARNING("m_url is null in OnCacheEntryAvailable");
+ return Cancel(NS_ERROR_UNEXPECTED);
+ }
+
+ do {
+ // For "normal" read/write access we always see status == NS_OK here. aNew
+ // indicates whether the cache entry is new and needs to be written, or not
+ // new and can be read. If AsyncOpenURI() was called with access read-only,
+ // status == NS_ERROR_CACHE_KEY_NOT_FOUND can be received here and we just
+ // read the data directly from imap.
+ if (NS_FAILED(status)) {
+ MOZ_LOG(IMAPCache, LogLevel::Debug,
+ ("%s: status parameter bad, preference "
+ "browser.cache.memory.enable not true?",
+ __func__));
+ break;
+ }
+
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_url, &rv);
+ mailnewsUrl->SetMemCacheEntry(entry);
+
+ if (aNew) {
+ // Writing cache so insert a "stream listener Tee" into the stream from
+ // the imap fetch to direct the message into the cache and to our current
+ // channel listener. But first get the size of the message to be fetched.
+ // If message too big to fit in cache, the message just goes to the
+ // stream listener. If unable to get the size, messageSize remains 0 so
+ // assume it fits in cache, right or wrong.
+ uint32_t messageSize = 0;
+ nsCOMPtr<nsIMsgMessageUrl> msgUrl(do_QueryInterface(m_url));
+ if (msgUrl) {
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ msgUrl->GetMessageHeader(getter_AddRefs(msgHdr));
+ if (msgHdr) {
+ msgHdr->GetMessageSize(&messageSize);
+ MOZ_LOG(IMAPCache, LogLevel::Debug,
+ ("%s: messageSize=%d", __func__, messageSize));
+ } else
+ MOZ_LOG(IMAPCache, LogLevel::Debug,
+ ("%s: Can't get msgHdr", __func__));
+ }
+ // Check if message fits in a cache entry. If too big, or if unable to
+ // create or initialize the tee or open the stream to the entry, will
+ // fall thought and only do ReadFromImapConnection() called below and the
+ // message will not be cached.
+ bool tooBig =
+ net::CacheObserver::EntryIsTooBig(messageSize, gUseDiskCache2);
+ if (!tooBig) {
+ // Message fits in cache. Create the tee.
+ nsCOMPtr<nsIStreamListenerTee> tee =
+ do_CreateInstance(NS_STREAMLISTENERTEE_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIOutputStream> out;
+ rv = entry->OpenOutputStream(0, -1, getter_AddRefs(out));
+ if (NS_SUCCEEDED(rv)) {
+ rv = tee->Init(m_channelListener, out, nullptr);
+ m_channelListener = tee;
+ } else
+ NS_WARNING(
+ "IMAP Protocol failed to open output stream to Necko cache");
+ }
+ }
+ if (tooBig || NS_FAILED(rv)) {
+ // Need this so next OpenCacheEntry() triggers OnCacheEntryAvailable()
+ // since nothing was actually written to cache. Without this there is no
+ // response to next OpenCacheEntry call.
+ entry->AsyncDoom(nullptr);
+ MOZ_LOG(IMAPCache, LogLevel::Debug,
+ ("%s: Not writing to cache, msg too big or other errors",
+ __func__));
+ } else {
+ mWritingToCache = true;
+ MOZ_LOG(IMAPCache, LogLevel::Debug,
+ ("%s: Begin cache WRITE", __func__));
+ }
+ } else {
+ // We are reading cache (!aNew)
+ mWritingToCache = false;
+ if (MOZ_LOG_TEST(IMAPCache, LogLevel::Debug)) {
+ int64_t size = 0;
+ rv = entry->GetDataSize(&size);
+ if (rv == NS_ERROR_IN_PROGRESS)
+ MOZ_LOG(IMAPCache, LogLevel::Debug,
+ ("%s: Concurrent cache READ, no size available", __func__));
+ MOZ_LOG(IMAPCache, LogLevel::Debug,
+ ("%s: Begin cache READ, size=%" PRIi64, __func__, size));
+ }
+ rv = ReadFromCache2(entry);
+ if (NS_SUCCEEDED(rv)) {
+ NotifyStartEndReadFromCache(true);
+ entry->MarkValid();
+ return NS_OK; // Return here since reading from the cache succeeded.
+ }
+ entry->AsyncDoom(nullptr); // Doom entry if we failed to read from cache.
+ mailnewsUrl->SetMemCacheEntry(
+ nullptr); // We aren't going to be reading from the cache.
+ }
+ } while (false);
+
+ // If reading from the cache failed or if we are writing into the cache, or if
+ // or message is too big for cache or other errors occur, do
+ // ReadFromImapConnection to fetch message from imap server.
+ if (!aNew)
+ MOZ_LOG(IMAPCache, LogLevel::Debug,
+ ("%s: Cache READ failed so read from imap", __func__));
+ return ReadFromImapConnection();
+}
+
+NS_IMETHODIMP
+nsImapMockChannel::OnCacheEntryCheck(nsICacheEntry* entry, uint32_t* aResult) {
+ *aResult = nsICacheEntryOpenCallback::ENTRY_WANTED;
+
+ // Check concurrent read: We can't read concurrently since we don't know
+ // that the entry will ever be written successfully. It may be aborted
+ // due to a size limitation. If reading concurrently, the following function
+ // will return NS_ERROR_IN_PROGRESS. Then we tell the cache to wait until
+ // the write is finished.
+ int64_t size = 0;
+ nsresult rv = entry->GetDataSize(&size);
+ if (rv == NS_ERROR_IN_PROGRESS) {
+ *aResult = nsICacheEntryOpenCallback::RECHECK_AFTER_WRITE_FINISHED;
+ MOZ_LOG(IMAPCache, LogLevel::Debug,
+ ("OnCacheEntryCheck(): Attempted cache write while reading, will "
+ "try again"));
+ }
+ return NS_OK;
+}
+
+nsresult nsImapMockChannel::OpenCacheEntry() {
+ nsresult rv;
+ if (!gCache2Storage) {
+ // Only need to do this once since cache2 is used by all accounts and
+ // folders. Get the cache storage object from the imap service.
+ nsCOMPtr<nsIImapService> imapService =
+ do_GetService("@mozilla.org/messenger/imapservice;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Obtain the cache storage object used by all channels in this session.
+ // This will return disk cache (default) or memory cache as determined by
+ // the boolean pref "mail.imap.use_disk_cache2"
+ rv = imapService->GetCacheStorage(getter_AddRefs(gCache2Storage));
+ NS_ENSURE_SUCCESS(rv, rv);
+ MOZ_LOG(IMAPCache, LogLevel::Debug,
+ ("%s: Obtained storage obj for |%s| cache2", __func__,
+ gUseDiskCache2 ? "disk" : "mem"));
+ }
+
+ int32_t uidValidity = -1;
+ uint32_t cacheAccess = nsICacheStorage::OPEN_NORMALLY;
+
+ nsCOMPtr<nsIImapUrl> imapUrl = do_QueryInterface(m_url, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIImapMailFolderSink> folderSink;
+ rv = imapUrl->GetImapMailFolderSink(getter_AddRefs(folderSink));
+ if (folderSink) folderSink->GetUidValidity(&uidValidity);
+
+ // If we're storing the message in the offline store, don't
+ // write/save to cache2 cache. (Not sure if this even happens!)
+ bool storeResultsOffline;
+ imapUrl->GetStoreResultsOffline(&storeResultsOffline);
+ if (storeResultsOffline) cacheAccess = nsICacheStorage::OPEN_READONLY;
+
+ // clang-format off
+ MOZ_LOG(IMAPCache, LogLevel::Debug,
+ ("%s: For URL = |%s|", __func__, m_url->GetSpecOrDefault().get()));
+ // clang-format on
+
+ // Use the uid validity as part of the cache key, so that if the uid validity
+ // changes, we won't re-use the wrong cache entries.
+ nsAutoCString extension;
+ extension.AppendInt(uidValidity, 16);
+
+ // Open a cache entry where the key is the potentially modified URL.
+ nsAutoCString path;
+ m_url->GetPathQueryRef(path);
+
+ // First we need to "normalise" the URL by extracting ?part= and &filename.
+ // The path should only contain: ?part=x.y&filename=file.ext
+ // These are seen in the wild:
+ // /;section=2?part=1.2&filename=A01.JPG
+ // ?section=2?part=1.2&filename=A01.JPG&type=image/jpeg&filename=A01.JPG
+ // ?part=1.2&type=image/jpeg&filename=IMG_C0030.jpg
+ // ?header=quotebody&part=1.2&filename=lijbmghmkilicioj.png
+ nsCString partQuery = MsgExtractQueryPart(path, "?part=");
+ if (partQuery.IsEmpty()) {
+ partQuery = MsgExtractQueryPart(path, "&part=");
+ if (!partQuery.IsEmpty()) {
+ // ? indicates a part query, so set the first character to that.
+ partQuery.SetCharAt('?', 0);
+ }
+ }
+ nsCString filenameQuery = MsgExtractQueryPart(path, "&filename=");
+ MOZ_LOG(IMAPCache, LogLevel::Debug,
+ ("%s: part = |%s|, filename = |%s|", __func__, partQuery.get(),
+ filenameQuery.get()));
+
+ // Truncate path at either /; or ?
+ MsgRemoveQueryPart(path);
+
+ nsCOMPtr<nsIURI> newUri;
+ rv = NS_MutateURI(m_url).SetPathQueryRef(path).Finalize(newUri);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (partQuery.IsEmpty()) {
+ // Not accessing a part but the whole message.
+ MOZ_LOG(IMAPCache, LogLevel::Debug,
+ ("%s: Call AsyncOpenURI on entire message", __func__));
+ } else {
+ // Access just a part. Set up part extraction and read in the part from the
+ // whole cached message. Note: Parts are now never individually written to
+ // or read from cache.
+ SetupPartExtractorListener(imapUrl, m_channelListener);
+ MOZ_LOG(IMAPCache, LogLevel::Debug,
+ ("%s: Call AsyncOpenURI to read part from entire message cache",
+ __func__));
+ }
+ return gCache2Storage->AsyncOpenURI(newUri, extension, cacheAccess, this);
+}
+
+// Pumps content of cache2 entry to channel listener. If a part was
+// requested in the original URL seen in OpenCacheEntry(), it will be extracted
+// from the whole message by the channel listener. So to obtain a single part
+// always requires reading the complete message from cache.
+nsresult nsImapMockChannel::ReadFromCache2(nsICacheEntry* entry) {
+ NS_ENSURE_ARG(entry);
+
+ bool useCacheEntry = true;
+ nsresult rv;
+ nsAutoCString entryKey;
+
+ entry->GetKey(entryKey);
+
+ // Compare cache entry size with message size. Init to an invalid value.
+ int64_t entrySize = -1;
+
+ // We don't expect concurrent read here, so this call should always work.
+ rv = entry->GetDataSize(&entrySize);
+
+ nsCOMPtr<nsIMsgMessageUrl> msgUrl(do_QueryInterface(m_url));
+ if (msgUrl && NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ // A failure to get a message header isn't an automatic error
+ msgUrl->GetMessageHeader(getter_AddRefs(msgHdr));
+ if (msgHdr) {
+ uint32_t messageSize;
+ if (NS_SUCCEEDED(rv = msgHdr->GetMessageSize(&messageSize)) &&
+ messageSize != entrySize) {
+ // clang-format off
+ MOZ_LOG(IMAP, LogLevel::Warning,
+ ("%s: Size mismatch for %s: message %" PRIu32
+ ", cache %" PRIi64,
+ __func__, entryKey.get(), messageSize, entrySize));
+ MOZ_LOG(IMAPCache, LogLevel::Debug,
+ ("%s: Size mismatch for %s: message %" PRIu32
+ ", cache %" PRIi64,
+ __func__, entryKey.get(), messageSize, entrySize));
+ // clang-format on
+ useCacheEntry = false;
+ }
+ }
+ }
+ // Cache entry is invalid if GetDataSize() or GetMessageSize failed or if
+ // otherwise unable to obtain the cache entry size. (Not sure if it's possible
+ // to have a 0 length cache entry but negative is definitely invalid.)
+ if (NS_FAILED(rv) || entrySize < 1) useCacheEntry = false;
+
+ nsCOMPtr<nsIInputStream> ins;
+ if (useCacheEntry) {
+ if (NS_SUCCEEDED(rv = entry->OpenInputStream(0, getter_AddRefs(ins)))) {
+ uint64_t bytesAvailable = 0;
+ rv = ins->Available(&bytesAvailable);
+ // Note: bytesAvailable will usually be zero (at least for disk cache
+ // since only async access occurs) so don't check it.
+ if (NS_FAILED(rv)) {
+ MOZ_LOG(IMAPCache, LogLevel::Debug,
+ ("%s: Input stream for disk cache not useable", __func__));
+ useCacheEntry = false;
+ }
+ }
+ }
+
+ if (NS_SUCCEEDED(rv) && useCacheEntry) {
+ nsCOMPtr<nsIInputStreamPump> pump;
+ if (NS_SUCCEEDED(
+ rv = NS_NewInputStreamPump(getter_AddRefs(pump), ins.forget()))) {
+ // Create and use a cache listener object.
+ RefPtr<nsImapCacheStreamListener> cacheListener =
+ new nsImapCacheStreamListener();
+
+ cacheListener->Init(m_channelListener, this, true);
+ rv = pump->AsyncRead(cacheListener);
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIImapUrl> imapUrl = do_QueryInterface(m_url);
+ imapUrl->SetMsgLoadingFromCache(true);
+ // Set the cache entry's security info status as our security
+ // info status...
+ nsCOMPtr<nsITransportSecurityInfo> securityInfo;
+ entry->GetSecurityInfo(getter_AddRefs(securityInfo));
+ SetSecurityInfo(securityInfo);
+ MOZ_LOG(IMAPCache, LogLevel::Debug,
+ ("%s: Cache entry accepted and being read", __func__));
+ } // if AsyncRead succeeded.
+ } // new pump
+ } // if useCacheEntry
+
+ if (!useCacheEntry || NS_FAILED(rv)) {
+ // Cache entry appears to be unusable. Return an error so will still attempt
+ // to read the data via just an imap fetch (the "old fashioned" way).
+ if (NS_SUCCEEDED(rv)) rv = NS_ERROR_FAILURE;
+ MOZ_LOG(IMAPCache, LogLevel::Debug,
+ ("%s: Cache entry rejected, returning error %" PRIx32, __func__,
+ static_cast<uint32_t>(rv)));
+ }
+ return rv;
+}
+
+class nsReadFromImapConnectionFailure : public mozilla::Runnable {
+ public:
+ explicit nsReadFromImapConnectionFailure(nsImapMockChannel* aChannel)
+ : mozilla::Runnable("nsReadFromImapConnectionFailure"),
+ mImapMockChannel(aChannel) {}
+
+ NS_IMETHOD Run() {
+ if (mImapMockChannel) {
+ mImapMockChannel->RunOnStopRequestFailure();
+ }
+ return NS_OK;
+ }
+
+ private:
+ RefPtr<nsImapMockChannel> mImapMockChannel;
+};
+
+nsresult nsImapMockChannel::RunOnStopRequestFailure() {
+ if (m_channelListener) {
+ m_channelListener->OnStopRequest(this, NS_MSG_ERROR_MSG_NOT_OFFLINE);
+ }
+ return NS_OK;
+}
+
+// This is called when the message requested by the url isn't yet in offline
+// store or not yet in cache. It is also called if the storage is corrupt. This
+// creates an imap connection to process the url. This is usually called from
+// the mock channel or possibly from nsImapCacheStreamListener::OnDataAvailable.
+NS_IMETHODIMP nsImapMockChannel::ReadFromImapConnection() {
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIImapUrl> imapUrl = do_QueryInterface(m_url);
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_url);
+
+ bool localOnly = false;
+ imapUrl->GetLocalFetchOnly(&localOnly);
+ if (localOnly) {
+ // This will cause an OnStartRunningUrl, and the subsequent close
+ // will then cause an OnStopRunningUrl with the cancel status.
+ NotifyStartEndReadFromCache(true);
+ Cancel(NS_MSG_ERROR_MSG_NOT_OFFLINE);
+
+ // Dispatch error notification, so ReadFromImapConnection() returns *before*
+ // the error is sent to the listener's OnStopRequest(). This avoids
+ // endless recursion where the caller relies on async execution.
+ nsCOMPtr<nsIRunnable> event = new nsReadFromImapConnectionFailure(this);
+ NS_DispatchToCurrentThread(event);
+ return NS_MSG_ERROR_MSG_NOT_OFFLINE;
+ }
+
+ nsCOMPtr<nsILoadGroup> loadGroup;
+ GetLoadGroup(getter_AddRefs(loadGroup));
+ if (!loadGroup) // if we don't have one, the url will snag one from the msg
+ // window...
+ mailnewsUrl->GetLoadGroup(getter_AddRefs(loadGroup));
+
+ // okay, add the mock channel to the load group..
+ if (loadGroup)
+ loadGroup->AddRequest((nsIRequest*)this, nullptr /* context isupports */);
+
+ // loading the url consists of asking the server to add the url to it's imap
+ // event queue....
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = mailnewsUrl->GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIImapIncomingServer> imapServer(do_QueryInterface(server, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Assume AsyncRead is always called from the UI thread.....
+ return imapServer->GetImapConnectionAndLoadUrl(imapUrl, m_channelListener);
+}
+
+// for messages stored in our offline cache, we have special code to handle
+// that... If it's in the local cache, we return true and we can abort the
+// download because this method does the rest of the work.
+bool nsImapMockChannel::ReadFromLocalCache() {
+ nsresult rv = NS_OK;
+
+ nsCOMPtr<nsIImapUrl> imapUrl = do_QueryInterface(m_url);
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_url, &rv);
+
+ bool useLocalCache = false;
+ mailnewsUrl->GetMsgIsInLocalCache(&useLocalCache);
+ if (!useLocalCache) {
+ return false;
+ }
+
+ nsAutoCString messageIdString;
+
+ SetupPartExtractorListener(imapUrl, m_channelListener);
+
+ imapUrl->GetListOfMessageIds(messageIdString);
+ nsCOMPtr<nsIMsgFolder> folder;
+ rv = mailnewsUrl->GetFolder(getter_AddRefs(folder));
+ NS_ENSURE_SUCCESS(rv, false);
+ if (!folder) {
+ return false;
+ }
+ // we want to create a file channel and read the msg from there.
+ nsMsgKey msgKey = strtoul(messageIdString.get(), nullptr, 10);
+ nsCOMPtr<nsIMsgDBHdr> hdr;
+ rv = folder->GetMessageHeader(msgKey, getter_AddRefs(hdr));
+ NS_ENSURE_SUCCESS(rv, false);
+ nsCOMPtr<nsIInputStream> msgStream;
+ rv = folder->GetLocalMsgStream(hdr, getter_AddRefs(msgStream));
+ NS_ENSURE_SUCCESS(rv, false);
+ // dougt - This may break the ablity to "cancel" a read from offline
+ // mail reading. fileChannel->SetLoadGroup(m_loadGroup);
+ RefPtr<nsImapCacheStreamListener> cacheListener =
+ new nsImapCacheStreamListener();
+ cacheListener->Init(m_channelListener, this);
+
+ // create a stream pump that will async read the message.
+ nsCOMPtr<nsIInputStreamPump> pump;
+ rv = NS_NewInputStreamPump(getter_AddRefs(pump), msgStream.forget());
+ NS_ENSURE_SUCCESS(rv, false);
+ rv = pump->AsyncRead(cacheListener);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ // if the msg is unread, we should mark it read on the server. This lets
+ // the code running this url know we're loading from the cache, if it cares.
+ imapUrl->SetMsgLoadingFromCache(true);
+ return true;
+}
+
+NS_IMETHODIMP nsImapMockChannel::AsyncOpen(nsIStreamListener* aListener) {
+ nsCOMPtr<nsIStreamListener> listener = aListener;
+ nsresult rv =
+ nsContentSecurityManager::doContentSecurityCheck(this, listener);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t port;
+ if (!m_url) return NS_ERROR_NULL_POINTER;
+ rv = m_url->GetPort(&port);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = NS_CheckPortSafety(port, "imap");
+ if (NS_FAILED(rv)) return rv;
+
+ // set the stream listener and then load the url
+ NS_ASSERTION(!m_channelListener, "shouldn't already have a listener");
+ m_channelListener = listener;
+ nsCOMPtr<nsIImapUrl> imapUrl(do_QueryInterface(m_url));
+
+ nsImapAction imapAction;
+ imapUrl->GetImapAction(&imapAction);
+
+ bool externalLink = true;
+ imapUrl->GetExternalLinkUrl(&externalLink);
+
+ if (externalLink) {
+ // for security purposes, only allow imap urls originating from external
+ // sources perform a limited set of actions. Currently the allowed set
+ // includes: 1) folder selection 2) message fetch 3) message part fetch
+
+ if (!(imapAction == nsIImapUrl::nsImapSelectFolder ||
+ imapAction == nsIImapUrl::nsImapMsgFetch ||
+ imapAction == nsIImapUrl::nsImapOpenMimePart ||
+ imapAction == nsIImapUrl::nsImapMsgFetchPeek))
+ return NS_ERROR_FAILURE; // abort the running of this url....it failed a
+ // security check
+ }
+
+ if (ReadFromLocalCache()) {
+ (void)NotifyStartEndReadFromCache(true);
+ return NS_OK;
+ }
+
+ // okay, it's not in the local cache, now check the memory cache...
+ // but we can't download for offline use from the memory cache
+ if (imapAction != nsIImapUrl::nsImapMsgDownloadForOffline) {
+ rv = OpenCacheEntry();
+ if (NS_SUCCEEDED(rv)) return rv;
+ }
+
+ SetupPartExtractorListener(imapUrl, m_channelListener);
+ // if for some reason open cache entry failed then just default to opening an
+ // imap connection for the url
+ return ReadFromImapConnection();
+}
+
+nsresult nsImapMockChannel::SetupPartExtractorListener(
+ nsIImapUrl* aUrl, nsIStreamListener* aConsumer) {
+ // if the url we are loading refers to a specific part then we need
+ // libmime to extract that part from the message for us.
+ bool refersToPart = false;
+ aUrl->GetMimePartSelectorDetected(&refersToPart);
+ if (refersToPart) {
+ nsCOMPtr<nsIStreamConverterService> converter =
+ do_GetService("@mozilla.org/streamConverters;1");
+ if (converter && aConsumer) {
+ nsCOMPtr<nsIStreamListener> newConsumer;
+ converter->AsyncConvertData("message/rfc822", "*/*", aConsumer,
+ static_cast<nsIChannel*>(this),
+ getter_AddRefs(newConsumer));
+ if (newConsumer) m_channelListener = newConsumer;
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMockChannel::GetLoadFlags(nsLoadFlags* aLoadFlags) {
+ //*aLoadFlags = nsIRequest::LOAD_NORMAL;
+ *aLoadFlags = mLoadFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMockChannel::SetLoadFlags(nsLoadFlags aLoadFlags) {
+ mLoadFlags = aLoadFlags;
+ return NS_OK; // don't fail when trying to set this
+}
+
+NS_IMETHODIMP nsImapMockChannel::GetContentType(nsACString& aContentType) {
+ if (mContentType.IsEmpty()) {
+ nsImapAction imapAction = 0;
+ if (m_url) {
+ nsCOMPtr<nsIImapUrl> imapUrl = do_QueryInterface(m_url);
+ if (imapUrl) {
+ imapUrl->GetImapAction(&imapAction);
+ }
+ }
+ if (imapAction == nsIImapUrl::nsImapSelectFolder)
+ aContentType.AssignLiteral("x-application-imapfolder");
+ else
+ aContentType.AssignLiteral("message/rfc822");
+ } else
+ aContentType = mContentType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMockChannel::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 nsImapMockChannel::GetContentCharset(
+ nsACString& aContentCharset) {
+ aContentCharset.Assign(mCharset);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMockChannel::SetContentCharset(
+ const nsACString& aContentCharset) {
+ mCharset.Assign(aContentCharset);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapMockChannel::GetContentDisposition(uint32_t* aContentDisposition) {
+ *aContentDisposition = mContentDisposition;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapMockChannel::SetContentDisposition(uint32_t aContentDisposition) {
+ mContentDisposition = aContentDisposition;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapMockChannel::GetContentDispositionFilename(
+ nsAString& aContentDispositionFilename) {
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsImapMockChannel::SetContentDispositionFilename(
+ const nsAString& aContentDispositionFilename) {
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsImapMockChannel::GetContentDispositionHeader(
+ nsACString& aContentDispositionHeader) {
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP nsImapMockChannel::GetContentLength(int64_t* aContentLength) {
+ *aContentLength = mContentLength;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapMockChannel::SetContentLength(int64_t aContentLength) {
+ mContentLength = aContentLength;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMockChannel::GetOwner(nsISupports** aPrincipal) {
+ NS_IF_ADDREF(*aPrincipal = mOwner);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMockChannel::SetOwner(nsISupports* aPrincipal) {
+ mOwner = aPrincipal;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMockChannel::GetSecurityInfo(
+ nsITransportSecurityInfo** aSecurityInfo) {
+ NS_IF_ADDREF(*aSecurityInfo = mSecurityInfo);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMockChannel::SetSecurityInfo(
+ nsITransportSecurityInfo* aSecurityInfo) {
+ mSecurityInfo = aSecurityInfo;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapMockChannel::GetIsDocument(bool* aIsDocument) {
+ return NS_GetIsDocumentChannel(this, aIsDocument);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// From nsIRequest
+////////////////////////////////////////////////////////////////////////////////
+
+NS_IMETHODIMP nsImapMockChannel::GetName(nsACString& result) {
+ if (m_url) return m_url->GetSpec(result);
+ result.Truncate();
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMockChannel::IsPending(bool* result) {
+ *result = m_channelListener != nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMockChannel::GetStatus(nsresult* status) {
+ *status = m_cancelStatus;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMockChannel::SetWritingToCache(bool aWriting) {
+ mWritingToCache = aWriting;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMockChannel::GetWritingToCache(bool* result) {
+ *result = mWritingToCache;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMockChannel::SetImapProtocol(nsIImapProtocol* aProtocol) {
+ mProtocol = do_GetWeakReference(aProtocol);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMockChannel::SetCanceledReason(const nsACString& aReason) {
+ return SetCanceledReasonImpl(aReason);
+}
+
+NS_IMETHODIMP nsImapMockChannel::GetCanceledReason(nsACString& aReason) {
+ return GetCanceledReasonImpl(aReason);
+}
+
+NS_IMETHODIMP nsImapMockChannel::CancelWithReason(nsresult aStatus,
+ const nsACString& aReason) {
+ return CancelWithReasonImpl(aStatus, aReason);
+}
+
+NS_IMETHODIMP nsImapMockChannel::Cancel(nsresult status) {
+ MOZ_DIAGNOSTIC_ASSERT(
+ NS_IsMainThread(),
+ "nsImapMockChannel::Cancel should only be called from UI thread");
+ MOZ_LOG(IMAPCache, LogLevel::Debug,
+ ("nsImapMockChannel::%s: entering", __func__));
+ m_cancelStatus = status;
+ nsCOMPtr<nsIImapProtocol> imapProtocol = do_QueryReferent(mProtocol);
+
+ // if we aren't reading from the cache and we get canceled...doom our cache
+ // entry if write is still in progress...
+ if (m_url) {
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_url);
+ MOZ_LOG(IMAPCache, LogLevel::Debug,
+ ("%s: Doom cache entry only if writing=%d(bool), url=%s", __func__,
+ mWritingToCache, m_url->GetSpecOrDefault().get()));
+ if (mWritingToCache) DoomCacheEntry(mailnewsUrl);
+ }
+
+ // The associated ImapProtocol thread must be unblocked before being killed.
+ // Otherwise, it will be deadlocked.
+ ResumeAndNotifyOne();
+
+ // Required for killing ImapProtocol thread
+ if (imapProtocol) imapProtocol->TellThreadToDie(false);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMockChannel::GetCanceled(bool* aCanceled) {
+ nsresult status = NS_ERROR_FAILURE;
+ GetStatus(&status);
+ *aCanceled = NS_FAILED(status);
+ return NS_OK;
+}
+
+/**
+ * Suspends the current request. This may have the effect of closing
+ * any underlying transport (in order to free up resources), although
+ * any open streams remain logically opened and will continue delivering
+ * data when the transport is resumed.
+ *
+ * Calling cancel() on a suspended request must not send any
+ * notifications (such as onstopRequest) until the request is resumed.
+ *
+ * NOTE: some implementations are unable to immediately suspend, and
+ * may continue to deliver events already posted to an event queue. In
+ * general, callers should be capable of handling events even after
+ * suspending a request.
+ */
+NS_IMETHODIMP nsImapMockChannel::Suspend() {
+ MOZ_LOG(IMAP, LogLevel::Debug, ("Suspending [this=%p].", this));
+
+ mozilla::MonitorAutoLock lock(mSuspendedMonitor);
+ NS_ENSURE_TRUE(!mSuspended, NS_ERROR_NOT_AVAILABLE);
+ mSuspended = true;
+
+ MOZ_LOG(IMAP, LogLevel::Debug, ("Suspended [this=%p].", this));
+
+ return NS_OK;
+}
+
+/**
+ * Resumes the current request. This may have the effect of re-opening
+ * any underlying transport and will resume the delivery of data to
+ * any open streams.
+ */
+NS_IMETHODIMP nsImapMockChannel::Resume() {
+ MOZ_LOG(IMAP, LogLevel::Debug, ("Resuming [this=%p].", this));
+
+ nsresult rv = ResumeAndNotifyOne();
+
+ MOZ_LOG(IMAP, LogLevel::Debug, ("Resumed [this=%p].", this));
+
+ return rv;
+}
+
+nsresult nsImapMockChannel::ResumeAndNotifyOne() {
+ mozilla::MonitorAutoLock lock(mSuspendedMonitor);
+ NS_ENSURE_TRUE(mSuspended, NS_ERROR_NOT_AVAILABLE);
+ mSuspended = false;
+ lock.Notify();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapMockChannel::GetNotificationCallbacks(
+ nsIInterfaceRequestor** aNotificationCallbacks) {
+ NS_IF_ADDREF(*aNotificationCallbacks = mCallbacks.get());
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapMockChannel::SetNotificationCallbacks(
+ nsIInterfaceRequestor* aNotificationCallbacks) {
+ mCallbacks = aNotificationCallbacks;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapMockChannel::OnTransportStatus(nsITransport* transport, nsresult status,
+ int64_t progress, int64_t progressMax) {
+ if (NS_FAILED(m_cancelStatus) || (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;
+}
+
+nsIMAPMailboxInfo::nsIMAPMailboxInfo(const nsACString& aName, char aDelimiter) {
+ mMailboxName.Assign(aName);
+ mDelimiter = aDelimiter;
+ mChildrenListed = false;
+}
+
+nsIMAPMailboxInfo::~nsIMAPMailboxInfo() {}
+
+void nsIMAPMailboxInfo::SetChildrenListed(bool childrenListed) {
+ mChildrenListed = childrenListed;
+}
+
+bool nsIMAPMailboxInfo::GetChildrenListed() { return mChildrenListed; }
+
+const nsACString& nsIMAPMailboxInfo::GetMailboxName() { return mMailboxName; }
+
+char nsIMAPMailboxInfo::GetDelimiter() { return mDelimiter; }
diff --git a/comm/mailnews/imap/src/nsImapProtocol.h b/comm/mailnews/imap/src/nsImapProtocol.h
new file mode 100644
index 0000000000..815ac103dc
--- /dev/null
+++ b/comm/mailnews/imap/src/nsImapProtocol.h
@@ -0,0 +1,848 @@
+/* -*- 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 nsImapProtocol_h___
+#define nsImapProtocol_h___
+
+#include "mozilla/Attributes.h"
+#include "nsIImapProtocol.h"
+#include "nsIImapUrl.h"
+
+#include "nsMsgProtocol.h"
+#include "nsIStreamListener.h"
+#include "nsIAsyncOutputStream.h"
+#include "nsIAsyncInputStream.h"
+#include "nsImapCore.h"
+#include "nsString.h"
+#include "nsIProgressEventSink.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsISocketTransport.h"
+
+// UI Thread proxy helper
+#include "nsIImapProtocolSink.h"
+
+#include "../public/nsIImapHostSessionList.h"
+#include "nsImapServerResponseParser.h"
+#include "nsImapFlagAndUidState.h"
+#include "nsImapNamespace.h"
+#include "nsTArray.h"
+#include "nsIWeakReferenceUtils.h"
+#include "nsMsgLineBuffer.h" // we need this to use the nsMsgLineStreamBuffer helper class...
+#include "nsIInputStream.h"
+#include "nsIMsgIncomingServer.h"
+#include "nsCOMArray.h"
+#include "nsIThread.h"
+#include "nsIRunnable.h"
+#include "nsIImapMockChannel.h"
+#include "nsILoadGroup.h"
+#include "nsCOMPtr.h"
+#include "nsIMsgWindow.h"
+#include "nsIImapHeaderXferInfo.h"
+#include "nsMsgLineBuffer.h"
+#include "nsIAsyncInputStream.h"
+#include "nsIMsgFolder.h"
+#include "nsIMsgAsyncPrompter.h"
+#include "mozilla/ReentrantMonitor.h"
+#include "nsSyncRunnableHelpers.h"
+#include "nsICacheEntryOpenCallback.h"
+#include "nsIProtocolProxyCallback.h"
+#include "nsIStringBundle.h"
+#include "nsHashPropertyBag.h"
+#include "nsMailChannel.h"
+
+#include "mozilla/Monitor.h"
+
+class nsIMAPMessagePartID;
+class nsIPrefBranch;
+
+#define kDownLoadCacheSize 16000u // was 1536 - try making it bigger
+
+typedef struct _msg_line_info {
+ const char* adoptedMessageLine;
+ uint32_t uidOfMessage;
+} msg_line_info;
+
+class nsMsgImapLineDownloadCache : public nsIImapHeaderInfo,
+ public nsByteArray {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIIMAPHEADERINFO
+ nsMsgImapLineDownloadCache();
+ uint32_t CurrentUID();
+ uint32_t SpaceAvailable();
+ bool CacheEmpty();
+
+ msg_line_info* GetCurrentLineInfo();
+
+ private:
+ virtual ~nsMsgImapLineDownloadCache();
+
+ msg_line_info* fLineInfo;
+ int32_t m_msgSize;
+};
+
+#define kNumHdrsToXfer 10
+
+class nsMsgImapHdrXferInfo : public nsIImapHeaderXferInfo {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIIMAPHEADERXFERINFO
+ nsMsgImapHdrXferInfo();
+ void ResetAll(); // reset HeaderInfos for re-use
+ void ReleaseAll(); // release HeaderInfos (frees up memory)
+ // this will return null if we're full, in which case the client code
+ // should transfer the headers and retry.
+ nsIImapHeaderInfo* StartNewHdr();
+ // call when we've finished adding lines to current hdr
+ void FinishCurrentHdr();
+
+ private:
+ virtual ~nsMsgImapHdrXferInfo();
+ nsCOMArray<nsIImapHeaderInfo> m_hdrInfos;
+ int32_t m_nextFreeHdrInfo;
+};
+
+// This class contains the name of a mailbox and whether or not
+// its children have been listed.
+class nsIMAPMailboxInfo {
+ public:
+ nsIMAPMailboxInfo(const nsACString& aName, char aDelimiter);
+ virtual ~nsIMAPMailboxInfo();
+
+ void SetChildrenListed(bool childrenListed);
+ bool GetChildrenListed();
+ const nsACString& GetMailboxName();
+ char GetDelimiter();
+
+ protected:
+ nsCString mMailboxName;
+ bool mChildrenListed;
+ char mDelimiter;
+};
+
+// State Flags (Note, I use the word state in terms of storing
+// state information about the connection (authentication, have we sent
+// commands, etc. I do not intend it to refer to protocol state)
+// Use these flags in conjunction with SetFlag/TestFlag/ClearFlag instead
+// of creating PRBools for everything....
+// clang-format off
+#define IMAP_RECEIVED_GREETING 0x00000001 // should we pause for the next read
+#define IMAP_CONNECTION_IS_OPEN 0x00000004 // is the connection currently open?
+#define IMAP_WAITING_FOR_DATA 0x00000008
+#define IMAP_CLEAN_UP_URL_STATE 0x00000010 // processing clean up url state
+#define IMAP_ISSUED_LANGUAGE_REQUEST 0x00000020 // make sure we only issue the language
+ // request once per connection...
+#define IMAP_ISSUED_COMPRESS_REQUEST 0x00000040 // make sure we only request compression once
+
+// There are 3 types of progress strings for items downloaded from IMAP servers.
+// An index is needed to keep track of the current count of the number of each
+// item type downloaded. The IMAP_EMPTY_STRING_INDEX means no string displayed.
+#define IMAP_NUMBER_OF_PROGRESS_STRINGS 4
+#define IMAP_HEADERS_STRING_INDEX 0
+#define IMAP_FLAGS_STRING_INDEX 1
+#define IMAP_MESSAGES_STRING_INDEX 2
+#define IMAP_EMPTY_STRING_INDEX 3
+// clang-format on
+
+/**
+ * nsImapProtocol is, among other things, the underlying nsIChannel
+ * implementation for the IMAP protocol. However, it's usually hidden away
+ * behind nsImapMockChannel objects. It also represents the 'real' connection
+ * to the IMAP server - it maintains the nsISocketTransport.
+ * Because there can be multiple IMAP requests queued up, NS_NewChannel()
+ * will return nsImapMockChannel objects instead, to keep the request in a
+ * holding pattern until the connection is free. At which time the mock
+ * channel will just forward calls onward to the nsImapProtocol.
+ *
+ * The url scheme we implement here encodes various IMAP commands as URLs.
+ * Some URLs are just traditional I/O based nsIChannel transactions, but
+ * many others have side effects. For example, an IMAP folder discovery
+ * command might cause the creation of the nsImapMailFolder hierarchy under
+ * the the nsImapIncomingServer.
+ * Such side effects are communicated via the various "Sink" interfaces. This
+ * helps decouple the IMAP code from the rest of the system:
+ *
+ * - nsIImapServerSink (implemented by nsImapIncomingServer)
+ * - nsIImapMailFolderSink (implemented by nsImapMailFolder)
+ * - nsIImapMessageSink (implemented by nsImapMailFolder)
+ *
+ * Internal to nsImapProtocol, these sink classes all have corresponding proxy
+ * implementations (ImapServerSinkProxy, ImapMailFolderSinkProxy and
+ * ImapMessageSinkProxy). These allow us to safely call the sink objects, in
+ * a synchronous fashion, from I/O threads (threads other than the main one).
+ * When an IMAP routine calls a member function of one of these sink proxies,
+ * it dispatches a call to the real sink object on the main thread, then
+ * blocks until the call is completed.
+ */
+class nsImapProtocol : public nsIImapProtocol,
+ public nsIInputStreamCallback,
+ public nsSupportsWeakReference,
+ public nsMsgProtocol,
+ public nsIImapProtocolSink,
+ public nsIMsgAsyncPromptListener,
+ public nsIProtocolProxyCallback {
+ public:
+ struct TCPKeepalive {
+ // For enabling and setting TCP keepalive (not related to IMAP IDLE).
+ std::atomic<bool> enabled;
+ std::atomic<int32_t> idleTimeS;
+ std::atomic<int32_t> retryIntervalS;
+ };
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIINPUTSTREAMCALLBACK
+ NS_DECL_NSIPROTOCOLPROXYCALLBACK
+ nsImapProtocol();
+
+ virtual nsresult ProcessProtocolState(nsIURI* url,
+ nsIInputStream* inputStream,
+ uint64_t sourceOffset,
+ uint32_t length) override;
+
+ //////////////////////////////////////////////////////////////////////////////////
+ // we support the nsIImapProtocol interface
+ //////////////////////////////////////////////////////////////////////////////////
+ NS_DECL_NSIIMAPPROTOCOL
+
+ //////////////////////////////////////////////////////////////////////////////////
+ // we support the nsIImapProtocolSink interface
+ //////////////////////////////////////////////////////////////////////////////////
+ NS_DECL_NSIIMAPPROTOCOLSINK
+
+ NS_DECL_NSIMSGASYNCPROMPTLISTENER
+
+ // message id string utilities.
+ uint32_t CountMessagesInIdString(const char* idString);
+ static bool HandlingMultipleMessages(const nsCString& messageIdString);
+ // escape slashes and double quotes in username/passwords for insecure login.
+ static void EscapeUserNamePasswordString(const char* strToEscape,
+ nsCString* resultStr);
+
+ // used to start fetching a message.
+ void GetShouldDownloadAllHeaders(bool* aResult);
+ void GetArbitraryHeadersToDownload(nsCString& aResult);
+ virtual void AdjustChunkSize();
+ virtual void FetchMessage(const nsCString& messageIds,
+ nsIMAPeFetchFields whatToFetch,
+ const char* fetchModifier = nullptr,
+ uint32_t startByte = 0, uint32_t numBytes = 0,
+ char* part = 0);
+ void FetchTryChunking(const nsCString& messageIds,
+ nsIMAPeFetchFields whatToFetch, bool idIsUid,
+ char* part, uint32_t downloadSize, bool tryChunking);
+ void FallbackToFetchWholeMsg(const nsCString& messageId,
+ uint32_t messageSize);
+ // used when streaming a message fetch
+ virtual nsresult BeginMessageDownLoad(
+ uint32_t totalSize, // for user, headers and body
+ const char* contentType); // some downloads are header only
+ virtual void HandleMessageDownLoadLine(const char* line, bool isPartialLine,
+ char* lineCopy = nullptr);
+ virtual void NormalMessageEndDownload();
+ virtual void AbortMessageDownLoad();
+ virtual void PostLineDownLoadEvent(const char* line, uint32_t uid);
+ void FlushDownloadCache();
+
+ virtual void SetMailboxDiscoveryStatus(EMailboxDiscoverStatus status);
+ virtual EMailboxDiscoverStatus GetMailboxDiscoveryStatus();
+
+ virtual void ProcessMailboxUpdate(bool handlePossibleUndo);
+ // Send log output...
+ void Log(const char* logSubName, const char* extraInfo, const char* logData);
+ static void LogImapUrl(const char* logMsg, nsIImapUrl* imapUrl);
+ // Comment from 4.5: We really need to break out the thread synchronizer from
+ // the connection class...Not sure what this means
+ bool GetPseudoInterrupted();
+
+ uint32_t GetMessageSize(const nsACString& messageId);
+ bool GetSubscribingNow();
+
+ bool DeathSignalReceived();
+ void ResetProgressInfo();
+ void SetActive(bool active);
+ bool GetActive();
+
+ bool GetShowAttachmentsInline();
+
+ // Sets whether or not the content referenced by the current ActiveEntry has
+ // been modified. Used for MIME parts on demand.
+ void SetContentModified(IMAP_ContentModifiedType modified);
+ bool GetShouldFetchAllParts();
+ bool GetIgnoreExpunges() { return m_ignoreExpunges; }
+ // Generic accessors required by the imap parser
+ char* CreateNewLineFromSocket();
+ nsresult GetConnectionStatus();
+ void SetConnectionStatus(nsresult status);
+
+ // Cleanup the connection and shutdown the thread.
+ void TellThreadToDie();
+
+ const nsCString&
+ GetImapHostName(); // return the host name from the url for the
+ // current connection
+ const nsCString& GetImapUserName(); // return the user name from the identity
+ const char*
+ GetImapServerKey(); // return the user name from the incoming server;
+
+ // state set by the imap parser...
+ void NotifyMessageFlags(imapMessageFlagsType flags,
+ const nsACString& keywords, nsMsgKey key,
+ uint64_t highestModSeq);
+ void NotifySearchHit(const char* hitLine);
+
+ // Event handlers for the imap parser.
+ void DiscoverMailboxSpec(nsImapMailboxSpec* adoptedBoxSpec);
+ void AlertUserEventUsingName(const char* aMessageId);
+ void AlertUserEvent(const char* message);
+ void AlertUserEventFromServer(const char* aServerEvent,
+ bool aForIdle = false);
+
+ void ProgressEventFunctionUsingName(const char* aMsgId);
+ void ProgressEventFunctionUsingNameWithString(const char* aMsgName,
+ const char* aExtraInfo);
+ void PercentProgressUpdateEvent(nsACString const& fmtStringName,
+ nsAString const& mailbox,
+ int64_t currentProgress, int64_t maxProgress);
+ void ShowProgress();
+
+ // utility function calls made by the server
+
+ void Copy(const char* messageList, const char* destinationMailbox,
+ bool idsAreUid);
+ void Search(const char* searchCriteria, bool useUID, bool notifyHit = true);
+ // imap commands issued by the parser
+ void Store(const nsCString& aMessageList, const char* aMessageData,
+ bool aIdsAreUid);
+ void ProcessStoreFlags(const nsCString& messageIds, bool idsAreUids,
+ imapMessageFlagsType flags, bool addFlags);
+ void IssueUserDefinedMsgCommand(const char* command, const char* messageList);
+ void FetchMsgAttribute(const nsCString& messageIds,
+ const nsCString& attribute);
+ void Expunge();
+ void UidExpunge(const nsCString& messageSet);
+ void ImapClose(bool shuttingDown = false, bool waitForResponse = true);
+ void Check();
+ void SelectMailbox(const char* mailboxName);
+ // more imap commands
+ void Logout(bool shuttingDown = false, bool waitForResponse = true);
+ void Noop();
+ void XServerInfo();
+ void Netscape();
+ void XMailboxInfo(const char* mailboxName);
+ void XAOL_Option(const char* option);
+ void MailboxData();
+ void GetMyRightsForFolder(const char* mailboxName);
+ void Bodystructure(const nsCString& messageId, bool idIsUid);
+
+ // this function does not ref count!!! be careful!!!
+ nsIImapUrl* GetCurrentUrl() { return m_runningUrl; }
+
+ // acl and namespace stuff
+ // notifies libmsg that we have a new personal/default namespace that we're
+ // using
+ void CommitNamespacesForHostEvent();
+ // notifies libmsg that we have new capability data for the current host
+ void CommitCapability();
+
+ // Adds a set of rights for a given user on a given mailbox on the current
+ // host. if userName is NULL, it means "me," or MYRIGHTS. rights is a single
+ // string of rights, as specified by RFC2086, the IMAP ACL extension.
+ void AddFolderRightsForUser(const char* mailboxName, const char* userName,
+ const char* rights);
+ // Clears all rights for the current folder, for all users.
+ void ClearAllFolderRights();
+ void RefreshFolderACLView(const char* mailboxName,
+ nsImapNamespace* nsForMailbox);
+
+ nsresult SetFolderAdminUrl(const char* mailboxName);
+ void HandleMemoryFailure();
+ void HandleCurrentUrlError();
+
+ // UIDPLUS extension
+ void SetCopyResponseUid(const char* msgIdString);
+
+ // Quota support
+ void UpdateFolderQuotaData(nsImapQuotaAction aAction, nsCString& aQuotaRoot,
+ uint64_t aUsed, uint64_t aMax);
+
+ bool GetPreferPlainText() { return m_preferPlainText; }
+
+ int32_t GetCurFetchSize() { return m_curFetchSize; }
+
+ const nsString& GetEmptyMimePartString() { return m_emptyMimePartString; }
+ void SetCapabilityResponseOccurred();
+
+ // Start event loop. This is called on IMAP thread
+ bool RunImapThreadMainLoop();
+
+ private:
+ virtual ~nsImapProtocol();
+ // the following flag is used to determine when a url is currently being run.
+ // It is cleared when we finish processng a url and it is set whenever we call
+ // Load on a url
+ bool m_urlInProgress;
+
+ /** The nsIImapURL that is currently running. */
+ nsCOMPtr<nsIImapUrl> m_runningUrl;
+ nsCOMPtr<nsIImapUrl> m_runningUrlLatest;
+ /** Current imap action associated with this connection. */
+ nsImapAction m_imapAction;
+
+ nsCString m_hostName;
+ nsCString m_userName;
+ nsCString m_serverKey;
+ char* m_dataOutputBuf;
+ RefPtr<nsMsgLineStreamBuffer> m_inputStreamBuffer;
+ nsCString m_trashFolderPath;
+
+ /** The socket connection to the IMAP server. */
+ nsCOMPtr<nsISocketTransport> m_transport;
+ nsCOMPtr<nsITransportSecurityInfo> m_securityInfo;
+
+ /** Stream to handle data coming in from the IMAP server. */
+ nsCOMPtr<nsIInputStream> m_inputStream;
+
+ nsCOMPtr<nsIAsyncInputStream> m_channelInputStream;
+ nsCOMPtr<nsIAsyncOutputStream> m_channelOutputStream;
+
+ /** The currently running request. */
+ nsCOMPtr<nsIImapMockChannel> m_mockChannel;
+
+ uint32_t m_bytesToChannel;
+ bool m_fetchingWholeMessage;
+ // nsCOMPtr<nsIRequest> mAsyncReadRequest; // we're going to cancel this when
+ // we're done with the conn.
+
+ // ******* Thread support *******
+ PRThread* m_thread;
+ mozilla::ReentrantMonitor
+ m_dataAvailableMonitor; // used to notify the arrival of data from the
+ // server
+ mozilla::ReentrantMonitor
+ m_urlReadyToRunMonitor; // used to notify the arrival of a new url to be
+ // processed
+ mozilla::ReentrantMonitor m_pseudoInterruptMonitor;
+ mozilla::ReentrantMonitor m_dataMemberMonitor;
+ mozilla::ReentrantMonitor m_threadDeathMonitor;
+ mozilla::ReentrantMonitor m_waitForBodyIdsMonitor;
+ mozilla::ReentrantMonitor m_fetchBodyListMonitor;
+ mozilla::ReentrantMonitor m_passwordReadyMonitor;
+ mozilla::Mutex mLock;
+ // If we get an async password prompt, this is where the UI thread
+ // stores the password, before notifying the imap thread of the password
+ // via the m_passwordReadyMonitor.
+ nsString m_password;
+ // Set to the result of nsImapServer::PromptPassword
+ nsresult m_passwordStatus;
+ bool m_passwordObtained;
+
+ bool m_imapThreadIsRunning;
+ void ImapThreadMainLoop(void);
+ nsresult m_connectionStatus;
+ nsCString m_connectionType;
+
+ bool m_nextUrlReadyToRun;
+ bool m_idleResponseReadyToHandle;
+ nsWeakPtr m_server;
+
+ RefPtr<ImapMailFolderSinkProxy> m_imapMailFolderSink;
+ RefPtr<ImapMailFolderSinkProxy> m_imapMailFolderSinkSelected;
+ RefPtr<ImapMessageSinkProxy> m_imapMessageSink;
+ RefPtr<ImapServerSinkProxy> m_imapServerSink;
+ RefPtr<ImapServerSinkProxy> m_imapServerSinkLatest;
+ RefPtr<ImapProtocolSinkProxy> m_imapProtocolSink;
+
+ // helper function to setup imap sink interface proxies
+ nsresult SetupSinkProxy();
+ // End thread support stuff
+ nsresult LoadImapUrlInternal();
+
+ bool GetDeleteIsMoveToTrash();
+ bool GetShowDeletedMessages();
+ nsCString m_currentCommand;
+ nsImapServerResponseParser m_parser;
+ nsImapServerResponseParser& GetServerStateParser() { return m_parser; }
+
+ bool HandleIdleResponses();
+ virtual bool ProcessCurrentURL();
+ void EstablishServerConnection();
+ virtual void ParseIMAPandCheckForNewMail(const char* commandString = nullptr,
+ bool ignoreBadNOResponses = false);
+ // biff
+ void PeriodicBiff();
+ void SendSetBiffIndicatorEvent(nsMsgBiffState newState);
+
+ // folder opening and listing header functions
+ void FolderHeaderDump(uint32_t* msgUids, uint32_t msgCount);
+ void FolderMsgDump(uint32_t* msgUids, uint32_t msgCount,
+ nsIMAPeFetchFields fields);
+ void FolderMsgDumpLoop(uint32_t* msgUids, uint32_t msgCount,
+ nsIMAPeFetchFields fields);
+ void WaitForPotentialListOfBodysToFetch(nsTArray<nsMsgKey>& msgIdList);
+ void HeaderFetchCompleted();
+ void UploadMessageFromFile(nsIFile* file, const char* mailboxName,
+ PRTime date, imapMessageFlagsType flags,
+ nsCString& keywords);
+
+ // mailbox name utilities.
+ void CreateEscapedMailboxName(const char* rawName, nsCString& escapedName);
+ void SetupMessageFlagsString(nsCString& flagString,
+ imapMessageFlagsType flags, uint16_t userFlags);
+
+ // body fetching listing data
+ bool m_fetchBodyListIsNew;
+ nsTArray<nsMsgKey> m_fetchBodyIdList;
+
+ // initialization function given a new url and transport layer
+ nsresult SetupWithUrl(nsIURI* aURL, nsISupports* aConsumer);
+ nsresult SetupWithUrlCallback(nsIProxyInfo* proxyInfo);
+ void ReleaseUrlState(bool rerunningUrl); // release any state that is stored
+ // on a per action basis.
+ /**
+ * Last ditch effort to run the url without using an imap connection.
+ * If it turns out that we don't need to run the url at all (e.g., we're
+ * trying to download a single message for offline use and it has already
+ * been downloaded, this function will send the appropriate notifications.
+ *
+ * @returns true if the url has been run locally, or doesn't need to be run.
+ */
+ bool TryToRunUrlLocally(nsIURI* aURL, nsISupports* aConsumer);
+
+ ////////////////////////////////////////////////////////////////////////////////////////
+ // Communication methods --> Reading and writing protocol
+ ////////////////////////////////////////////////////////////////////////////////////////
+
+ // SendData not only writes the NULL terminated data in dataBuffer to our
+ // output stream but it also informs the consumer that the data has been
+ // written to the stream. aSuppressLogging --> set to true if you wish to
+ // suppress logging for this particular command. this is useful for making
+ // sure we don't log authentication information like the user's password
+ // (which was encoded anyway), but still we shouldn't add that information to
+ // the log.
+ nsresult SendData(const char* dataBuffer,
+ bool aSuppressLogging = false) override;
+
+ // state ported over from 4.5
+ bool m_pseudoInterrupted;
+ bool m_active;
+ bool m_folderNeedsSubscribing;
+ bool m_folderNeedsACLRefreshed;
+
+ bool m_threadShouldDie;
+
+ // use to prevent re-entering TellThreadToDie.
+ bool m_inThreadShouldDie;
+ // If the UI thread has signalled the IMAP thread to die, and the
+ // connection has timed out, this will be set to FALSE.
+ bool m_safeToCloseConnection;
+
+ RefPtr<nsImapFlagAndUidState> m_flagState;
+ nsMsgBiffState m_currentBiffState;
+ // manage the IMAP server command tags
+ // 11 = enough memory for the decimal representation of MAX_UINT + trailing
+ // nul
+ char m_currentServerCommandTag[11];
+ uint32_t m_currentServerCommandTagNumber;
+ void IncrementCommandTagNumber();
+ const char* GetServerCommandTag();
+
+ void StartTLS();
+
+ // login related methods.
+ nsresult GetPassword(nsString& password, bool aNewPasswordRequested);
+ void InitPrefAuthMethods(int32_t authMethodPrefValue,
+ nsIMsgIncomingServer* aServer);
+ nsresult ChooseAuthMethod();
+ void MarkAuthMethodAsFailed(eIMAPCapabilityFlags failedAuthMethod);
+ void ResetAuthMethods();
+
+ // All of these methods actually issue protocol
+ void Capability(); // query host for capabilities.
+ void ID(); // send RFC 2971 app info to server
+ void EnableUTF8Accept();
+ void EnableCondStore();
+ void StartCompressDeflate();
+ nsresult BeginCompressing();
+ void Language(); // set the language on the server if it supports it
+ void Namespace();
+ void InsecureLogin(const char* userName, const nsCString& password);
+ nsresult ClientID();
+ nsresult AuthLogin(const char* userName, const nsString& password,
+ eIMAPCapabilityFlag flag);
+ nsresult SendDataParseIMAPandCheckForNewMail(const char* data,
+ const char* command);
+ void ProcessAuthenticatedStateURL();
+ void ProcessAfterAuthenticated();
+ void ProcessSelectedStateURL();
+ bool TryToLogon();
+
+ // ProcessAuthenticatedStateURL() used to be one giant if statement. I've
+ // broken out a set of actions based on the imap action passed into the url.
+ // The following functions are imap protocol handlers for each action. They
+ // are called by ProcessAuthenticatedStateUrl.
+ void OnLSubFolders();
+ void OnAppendMsgFromFile();
+
+ char* GetFolderPathString(); // OK to call from UI thread
+
+ char* OnCreateServerSourceFolderPathString();
+ char* OnCreateServerDestinationFolderPathString();
+ nsresult CreateServerSourceFolderPathString(char** result);
+ void OnCreateFolder(const char* aSourceMailbox);
+ void OnEnsureExistsFolder(const char* aSourceMailbox);
+ void OnSubscribe(const char* aSourceMailbox);
+ void OnUnsubscribe(const char* aSourceMailbox);
+ void RefreshACLForFolderIfNecessary(const char* mailboxName);
+ void RefreshACLForFolder(const char* aSourceMailbox);
+ void GetACLForFolder(const char* aMailboxName);
+ void OnRefreshAllACLs();
+ void OnListFolder(const char* aSourceMailbox, bool aBool);
+ void OnStatusForFolder(const char* sourceMailbox);
+ void OnDeleteFolder(const char* aSourceMailbox);
+ void OnRenameFolder(const char* aSourceMailbox);
+ void OnMoveFolderHierarchy(const char* aSourceMailbox);
+ void DeleteFolderAndMsgs(const char* aSourceMailbox);
+ void RemoveMsgsAndExpunge();
+ void FindMailboxesIfNecessary();
+ void CreateMailbox(const char* mailboxName);
+ void DeleteMailbox(const char* mailboxName);
+ void RenameMailbox(const char* existingName, const char* newName);
+ void RemoveHierarchyDelimiter(nsCString& mailboxName);
+ bool CreateMailboxRespectingSubscriptions(const char* mailboxName);
+ bool DeleteMailboxRespectingSubscriptions(const char* mailboxName);
+ bool RenameMailboxRespectingSubscriptions(const char* existingName,
+ const char* newName,
+ bool reallyRename);
+ // notify the fe that a folder was deleted
+ void FolderDeleted(const char* mailboxName);
+ // notify the fe that a folder creation failed
+ void FolderNotCreated(const char* mailboxName);
+ // notify the fe that a folder was deleted
+ void FolderRenamed(const char* oldName, const char* newName);
+
+ bool FolderIsSelected(const char* mailboxName);
+
+ bool MailboxIsNoSelectMailbox(const char* mailboxName);
+ bool FolderNeedsACLInitialized(const char* folderName);
+ void DiscoverMailboxList();
+ void DiscoverAllAndSubscribedBoxes();
+ void MailboxDiscoveryFinished();
+ void NthLevelChildList(const char* onlineMailboxPrefix, int32_t depth);
+ // LIST SUBSCRIBED command (from RFC 5258) crashes some servers. so we need to
+ // identify those servers
+ bool GetListSubscribedIsBrokenOnServer();
+ void Lsub(const char* mailboxPattern, bool addDirectoryIfNecessary);
+ void List(const char* mailboxPattern, bool addDirectoryIfNecessary,
+ bool useXLIST = false);
+ void Subscribe(const char* mailboxName);
+ void Unsubscribe(const char* mailboxName);
+ void Idle();
+ void EndIdle(bool waitForResponse = true);
+ // Some imap servers include the mailboxName following the dir-separator in
+ // the list of subfolders of the mailboxName. In fact, they are the same. So
+ // we should decide if we should delete such subfolder and provide feedback if
+ // the delete operation succeed.
+ bool DeleteSubFolders(const char* aMailboxName, bool& aDeleteSelf);
+ bool RenameHierarchyByHand(const char* oldParentMailboxName,
+ const char* newParentMailboxName);
+ bool RetryUrl();
+
+ nsresult GlobalInitialization(nsIPrefBranch* aPrefBranch);
+ nsresult Configure(int32_t TooFastTime, int32_t IdealTime,
+ int32_t ChunkAddSize, int32_t ChunkSize,
+ int32_t ChunkThreshold, bool FetchByChunks);
+ nsresult GetMsgWindow(nsIMsgWindow** aMsgWindow);
+ // End Process AuthenticatedState Url helper methods
+
+ virtual char const* GetType() override { return "imap"; }
+
+ // Quota support
+ void GetQuotaDataIfSupported(const char* aBoxName);
+
+ // CondStore support - true if server supports it, and the user hasn't
+ // disabled it.
+ bool UseCondStore();
+ // false if pref "mail.server.serverxxx.use_condstore" is false;
+ bool m_useCondStore;
+ // COMPRESS=DEFLATE support - true if server supports it, and the user hasn't
+ // disabled it.
+ bool UseCompressDeflate();
+ // false if pref "mail.server.serverxxx.use_compress_deflate" is false;
+ bool m_useCompressDeflate;
+ // these come from the nsIDBFolderInfo in the msgDatabase and
+ // are initialized in nsImapProtocol::SetupWithUrl.
+ uint64_t mFolderLastModSeq;
+ int32_t mFolderTotalMsgCount;
+ uint32_t mFolderHighestUID;
+ bool m_allowUTF8Accept;
+
+ bool m_isGmailServer;
+ nsTArray<nsCString> mCustomDBHeaders;
+ nsTArray<nsCString> mCustomHeaders;
+ bool m_trackingTime;
+ PRTime m_startTime;
+ PRTime m_endTime;
+ PRTime m_lastActiveTime;
+ int32_t m_tooFastTime;
+ int32_t m_idealTime;
+ int32_t m_chunkAddSize;
+ int32_t m_chunkStartSize;
+ bool m_fetchByChunks;
+ bool m_sendID;
+ int32_t m_curFetchSize;
+ bool m_ignoreExpunges;
+ eIMAPCapabilityFlags m_prefAuthMethods; // set of capability flags (in
+ // nsImapCore.h) for auth methods
+ eIMAPCapabilityFlags m_failedAuthMethods; // ditto
+ eIMAPCapabilityFlag m_currentAuthMethod; // exactly one capability flag, or 0
+ int32_t m_socketType;
+ int32_t m_chunkSize;
+ int32_t m_chunkThreshold;
+ RefPtr<nsMsgImapLineDownloadCache> m_downloadLineCache;
+ RefPtr<nsMsgImapHdrXferInfo> m_hdrDownloadCache;
+ nsCOMPtr<nsIImapHeaderInfo> m_curHdrInfo;
+ // mapping between mailboxes and the corresponding folder flags
+ nsTHashMap<nsCStringHashKey, int32_t> m_standardListMailboxes;
+ // mapping between special xlist mailboxes and the corresponding folder flags
+ nsTHashMap<nsCStringHashKey, int32_t> m_specialXListMailboxes;
+
+ nsCOMPtr<nsIImapHostSessionList> m_hostSessionList;
+
+ bool m_fromHeaderSeen;
+
+ nsString mAcceptLanguages;
+
+ nsCString m_clientId;
+
+ // progress stuff
+ void SetProgressString(uint32_t aStringIndex);
+
+ nsCString m_progressStringName;
+ uint32_t m_stringIndex;
+ int32_t m_progressCurrentNumber[IMAP_NUMBER_OF_PROGRESS_STRINGS];
+ int32_t m_progressExpectedNumber;
+ nsCString m_lastProgressStringName;
+ int32_t m_lastPercent;
+ int64_t m_lastProgressTime;
+
+ bool m_notifySearchHit;
+ bool m_needNoop;
+ bool m_idle;
+ bool m_useIdle;
+ int32_t m_noopCount;
+ bool m_autoSubscribe, m_autoUnsubscribe, m_autoSubscribeOnOpen;
+ bool m_closeNeededBeforeSelect;
+ bool m_retryUrlOnError;
+ bool m_preferPlainText;
+ bool m_forceSelect;
+
+ int32_t m_uidValidity; // stored uid validity for the selected folder.
+
+ enum EMailboxHierarchyNameState {
+ kNoOperationInProgress,
+ // kDiscoverBaseFolderInProgress, - Unused. Keeping for historical reasons.
+ kDiscoverTrashFolderInProgress,
+ kDeleteSubFoldersInProgress,
+ kListingForInfoOnly,
+ kListingForInfoAndDiscovery,
+ kDiscoveringNamespacesOnly,
+ kXListing,
+ kListingForFolderFlags,
+ kListingForCreate
+ };
+ EMailboxHierarchyNameState m_hierarchyNameState;
+ EMailboxDiscoverStatus m_discoveryStatus;
+ nsTArray<nsIMAPMailboxInfo*> m_listedMailboxList;
+ nsTArray<nsCString>* m_deletableChildren;
+ uint32_t m_flagChangeCount;
+ PRTime m_lastCheckTime;
+
+ bool CheckNeeded();
+
+ nsString m_emptyMimePartString;
+
+ RefPtr<mozilla::mailnews::OAuth2ThreadHelper> mOAuth2Support;
+ bool m_capabilityResponseOccurred;
+
+ nsresult IsTransportAlive(bool* alive);
+ nsresult TransportStartTLS();
+ void GetTransportSecurityInfo(nsITransportSecurityInfo** aSecurityInfo);
+};
+
+// This small class is a "mock" channel because it is a mockery of the imap
+// channel's implementation... it's a light weight channel that we can return to
+// necko when they ask for a channel on a url before we actually have an imap
+// protocol instance around which can run the url. Please see my comments in
+// nsIImapMockChannel.idl for more details..
+//
+// Threading concern: This class lives entirely in the UI thread.
+
+class nsICacheEntry;
+
+class nsImapMockChannel : public nsIImapMockChannel,
+ public nsICacheEntryOpenCallback,
+ public nsITransportEventSink,
+ public nsSupportsWeakReference,
+ public nsMailChannel,
+ public nsHashPropertyBag {
+ public:
+ friend class nsImapProtocol;
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIIMAPMOCKCHANNEL
+ NS_DECL_NSICHANNEL
+ NS_DECL_NSIREQUEST
+ NS_DECL_NSICACHEENTRYOPENCALLBACK
+ NS_DECL_NSITRANSPORTEVENTSINK
+
+ nsImapMockChannel();
+ static nsresult Create(const nsIID& iid, void** result);
+ nsresult RunOnStopRequestFailure();
+
+ protected:
+ virtual ~nsImapMockChannel();
+ nsCOMPtr<nsIURI> m_url;
+
+ nsCOMPtr<nsIURI> m_originalUrl;
+ nsCOMPtr<nsILoadGroup> m_loadGroup;
+ nsCOMPtr<nsILoadInfo> m_loadInfo;
+ nsCOMPtr<nsIStreamListener> m_channelListener;
+ nsresult m_cancelStatus;
+ nsLoadFlags mLoadFlags;
+ nsCOMPtr<nsIProgressEventSink> mProgressEventSink;
+ nsCOMPtr<nsIInterfaceRequestor> mCallbacks;
+ nsCOMPtr<nsISupports> mOwner;
+ nsCOMPtr<nsITransportSecurityInfo> mSecurityInfo;
+ nsCString mContentType;
+ nsCString mCharset;
+ nsWeakPtr mProtocol;
+
+ bool mChannelClosed;
+ bool mReadingFromCache;
+ int64_t mContentLength;
+ bool mWritingToCache;
+
+ mozilla::Monitor mSuspendedMonitor;
+ bool mSuspended;
+
+ nsresult ResumeAndNotifyOne();
+
+ // cache related helper methods
+ nsresult OpenCacheEntry(); // makes a request to the cache service for a
+ // cache entry for a url
+ bool ReadFromLocalCache(); // attempts to read the url out of our local
+ // (offline) cache....
+ nsresult ReadFromCache2(nsICacheEntry* entry); // pipes message from cache2
+ // entry to channel listener
+ nsresult NotifyStartEndReadFromCache(bool start);
+
+ // we end up daisy chaining multiple nsIStreamListeners into the load process.
+ nsresult SetupPartExtractorListener(nsIImapUrl* aUrl,
+ nsIStreamListener* aConsumer);
+
+ uint32_t mContentDisposition;
+};
+
+#endif // nsImapProtocol_h___
diff --git a/comm/mailnews/imap/src/nsImapSearchResults.cpp b/comm/mailnews/imap/src/nsImapSearchResults.cpp
new file mode 100644
index 0000000000..0932126a46
--- /dev/null
+++ b/comm/mailnews/imap/src/nsImapSearchResults.cpp
@@ -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/. */
+
+#include "msgCore.h" // for pre-compiled headers
+
+#include "nsImapSearchResults.h"
+#include "prmem.h"
+#include "nsCRT.h"
+
+nsImapSearchResultSequence::nsImapSearchResultSequence() {}
+
+nsImapSearchResultSequence*
+nsImapSearchResultSequence::CreateSearchResultSequence() {
+ return new nsImapSearchResultSequence;
+}
+
+void nsImapSearchResultSequence::Clear(void) {
+ int32_t i = Length();
+ while (0 <= --i) {
+ char* string = ElementAt(i);
+ PR_Free(string);
+ }
+ nsTArray<char*>::Clear();
+}
+
+nsImapSearchResultSequence::~nsImapSearchResultSequence() { Clear(); }
+
+void nsImapSearchResultSequence::ResetSequence() { Clear(); }
+
+void nsImapSearchResultSequence::AddSearchResultLine(const char* searchLine) {
+ // The first add becomes node 2. Fix this.
+ char* copiedSequence = PL_strdup(searchLine + 9); // 9 == "* SEARCH "
+
+ if (copiedSequence) // if we can't allocate this then the search won't hit
+ AppendElement(copiedSequence);
+}
+
+nsImapSearchResultIterator::nsImapSearchResultIterator(
+ nsImapSearchResultSequence& sequence)
+ : fSequence(sequence) {
+ ResetIterator();
+}
+
+nsImapSearchResultIterator::~nsImapSearchResultIterator() {}
+
+void nsImapSearchResultIterator::ResetIterator() {
+ fSequenceIndex = 0;
+ fCurrentLine = (char*)fSequence.SafeElementAt(fSequenceIndex);
+ fPositionInCurrentLine = fCurrentLine;
+}
+
+int32_t nsImapSearchResultIterator::GetNextMessageNumber() {
+ int32_t returnValue = 0;
+ if (fPositionInCurrentLine) {
+ returnValue = atoi(fPositionInCurrentLine);
+
+ // eat the current number
+ while (isdigit(*++fPositionInCurrentLine))
+ ;
+
+ if (*fPositionInCurrentLine == 0xD) // found CR, no more digits on line
+ {
+ fCurrentLine = (char*)fSequence.SafeElementAt(++fSequenceIndex);
+ fPositionInCurrentLine = fCurrentLine;
+ } else // eat the space
+ fPositionInCurrentLine++;
+ }
+
+ return returnValue;
+}
diff --git a/comm/mailnews/imap/src/nsImapSearchResults.h b/comm/mailnews/imap/src/nsImapSearchResults.h
new file mode 100644
index 0000000000..4eca197e29
--- /dev/null
+++ b/comm/mailnews/imap/src/nsImapSearchResults.h
@@ -0,0 +1,40 @@
+/* -*- 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 nsImapSearchResults_h___
+#define nsImapSearchResults_h___
+
+#include "nsTArray.h"
+
+class nsImapSearchResultSequence : public nsTArray<char*> {
+ public:
+ virtual ~nsImapSearchResultSequence();
+ static nsImapSearchResultSequence* CreateSearchResultSequence();
+
+ virtual void AddSearchResultLine(const char* searchLine);
+ virtual void ResetSequence();
+ void Clear();
+
+ friend class nsImapSearchResultIterator;
+
+ private:
+ nsImapSearchResultSequence();
+};
+
+class nsImapSearchResultIterator {
+ public:
+ explicit nsImapSearchResultIterator(nsImapSearchResultSequence& sequence);
+ virtual ~nsImapSearchResultIterator();
+
+ void ResetIterator();
+ int32_t GetNextMessageNumber(); // returns 0 at end of list
+ private:
+ nsImapSearchResultSequence& fSequence;
+ int32_t fSequenceIndex;
+ char* fCurrentLine;
+ char* fPositionInCurrentLine;
+};
+
+#endif
diff --git a/comm/mailnews/imap/src/nsImapServerResponseParser.cpp b/comm/mailnews/imap/src/nsImapServerResponseParser.cpp
new file mode 100644
index 0000000000..a52279b6c9
--- /dev/null
+++ b/comm/mailnews/imap/src/nsImapServerResponseParser.cpp
@@ -0,0 +1,2640 @@
+/* -*- 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 "nsMimeTypes.h"
+#include "nsImapCore.h"
+#include "nsImapProtocol.h"
+#include "nsImapServerResponseParser.h"
+#include "nsIImapFlagAndUidState.h"
+#include "nsImapNamespace.h"
+#include "nsImapUtils.h"
+#include "nsCRT.h"
+#include "nsMsgUtils.h"
+#include "mozilla/Logging.h"
+
+////////////////// nsImapServerResponseParser /////////////////////////
+
+extern mozilla::LazyLogModule IMAP; // defined in nsImapProtocol.cpp
+
+nsImapServerResponseParser::nsImapServerResponseParser(
+ nsImapProtocol& imapProtocolConnection)
+ : nsImapGenericParser(),
+ fReportingErrors(true),
+ fCurrentFolderReadOnly(false),
+ fCurrentLineContainedFlagInfo(false),
+ fServerIsNetscape3xServer(false),
+ fSeqNumOfFirstUnseenMsg(0),
+ fNumberOfExistingMessages(0),
+ fNumberOfRecentMessages(0),
+ fSizeOfMostRecentMessage(0),
+ fTotalDownloadSize(0),
+ fCurrentCommandTag(nullptr),
+ fSelectedMailboxName(nullptr),
+ fIMAPstate(kNonAuthenticated),
+ fLastChunk(false),
+ fNextChunkStartsWithNewline(false),
+ fServerConnection(imapProtocolConnection),
+ fHostSessionList(nullptr) {
+ fSearchResults = nsImapSearchResultSequence::CreateSearchResultSequence();
+ fFolderAdminUrl = nullptr;
+ fNetscapeServerVersionString = nullptr;
+ fXSenderInfo = nullptr;
+ fSupportsUserDefinedFlags = 0;
+ fSettablePermanentFlags = 0;
+ fCapabilityFlag = kCapabilityUndefined;
+ fLastAlert = nullptr;
+ fDownloadingHeaders = false;
+ fGotPermanentFlags = false;
+ fFolderUIDValidity = 0;
+ fHighestModSeq = 0;
+ fAuthChallenge = nullptr;
+ fStatusUnseenMessages = 0;
+ fStatusRecentMessages = 0;
+ fStatusNextUID = nsMsgKey_None;
+ fNextUID = nsMsgKey_None;
+ fStatusExistingMessages = 0;
+ fReceivedHeaderOrSizeForUID = nsMsgKey_None;
+ fUtf8AcceptEnabled = false;
+ fStdJunkNotJunkUseOk = false;
+}
+
+nsImapServerResponseParser::~nsImapServerResponseParser() {
+ PR_Free(fCurrentCommandTag);
+ delete fSearchResults;
+ PR_Free(fFolderAdminUrl);
+ PR_Free(fNetscapeServerVersionString);
+ PR_Free(fXSenderInfo);
+ PR_Free(fLastAlert);
+ PR_Free(fSelectedMailboxName);
+ PR_Free(fAuthChallenge);
+}
+
+bool nsImapServerResponseParser::LastCommandSuccessful() {
+ return (!CommandFailed() && !fServerConnection.DeathSignalReceived() &&
+ nsImapGenericParser::LastCommandSuccessful());
+}
+
+// returns true if things look ok to continue
+bool nsImapServerResponseParser::GetNextLineForParser(char** nextLine) {
+ bool rv = true;
+ *nextLine = fServerConnection.CreateNewLineFromSocket();
+ if (fServerConnection.DeathSignalReceived() ||
+ NS_FAILED(fServerConnection.GetConnectionStatus()))
+ rv = false;
+ // we'd really like to try to silently reconnect, but we shouldn't put this
+ // message up just in the interrupt case
+ if (NS_FAILED(fServerConnection.GetConnectionStatus()) &&
+ !fServerConnection.DeathSignalReceived())
+ fServerConnection.AlertUserEventUsingName("imapServerDisconnected");
+ return rv;
+}
+
+bool nsImapServerResponseParser::CommandFailed() {
+ return fCurrentCommandFailed;
+}
+
+void nsImapServerResponseParser::SetCommandFailed(bool failed) {
+ fCurrentCommandFailed = failed;
+}
+
+bool nsImapServerResponseParser::UntaggedResponse() {
+ return fUntaggedResponse;
+}
+
+void nsImapServerResponseParser::SetFlagState(nsIImapFlagAndUidState* state) {
+ fFlagState = state;
+}
+
+uint32_t nsImapServerResponseParser::SizeOfMostRecentMessage() {
+ return fSizeOfMostRecentMessage;
+}
+
+// Call this when adding a pipelined command to the session
+void nsImapServerResponseParser::IncrementNumberOfTaggedResponsesExpected(
+ const char* newExpectedTag) {
+ fNumberOfTaggedResponsesExpected++;
+ PR_Free(fCurrentCommandTag);
+ fCurrentCommandTag = PL_strdup(newExpectedTag);
+ if (!fCurrentCommandTag) HandleMemoryFailure();
+}
+
+void nsImapServerResponseParser::InitializeState() {
+ fCurrentCommandFailed = false;
+ fNumberOfRecentMessages = 0;
+ fReceivedHeaderOrSizeForUID = nsMsgKey_None;
+ fUntaggedResponse = false;
+}
+
+// RFC3501: response = *(continue-req / response-data) response-done
+// response-data = "*" SP (resp-cond-state / resp-cond-bye /
+// mailbox-data / message-data / capability-data) CRLF
+// continue-req = "+" SP (resp-text / base64) CRLF
+void nsImapServerResponseParser::ParseIMAPServerResponse(
+ const char* aCurrentCommand, bool aIgnoreBadAndNOResponses,
+ char* aGreetingWithCapability) {
+ NS_ASSERTION(aCurrentCommand && *aCurrentCommand != '\r' &&
+ *aCurrentCommand != '\n' && *aCurrentCommand != ' ',
+ "Invalid command string");
+ bool sendingIdleDone = !strcmp(aCurrentCommand, "DONE" CRLF);
+ if (sendingIdleDone) fWaitingForMoreClientInput = false;
+
+ // Reinitialize the parser
+ SetConnected(true);
+ SetSyntaxError(false);
+
+ // Reinitialize our state
+ InitializeState();
+
+ // the default is to not pipeline
+ fNumberOfTaggedResponsesExpected = 1;
+ int numberOfTaggedResponsesReceived = 0;
+
+ nsCString copyCurrentCommand(aCurrentCommand);
+ if (!fServerConnection.DeathSignalReceived()) {
+ char* placeInTokenString = nullptr;
+ char* tagToken = nullptr;
+ const char* commandToken = nullptr;
+ bool inIdle = false;
+ if (!sendingIdleDone) {
+ placeInTokenString = copyCurrentCommand.BeginWriting();
+ tagToken = NS_strtok(WHITESPACE, &placeInTokenString);
+ commandToken = NS_strtok(WHITESPACE, &placeInTokenString);
+ } else
+ commandToken = "DONE";
+ if (tagToken) {
+ PR_Free(fCurrentCommandTag);
+ fCurrentCommandTag = PL_strdup(tagToken);
+ if (!fCurrentCommandTag) HandleMemoryFailure();
+ inIdle = commandToken && !strcmp(commandToken, "IDLE");
+ }
+
+ if (commandToken && ContinueParse())
+ PreProcessCommandToken(commandToken, aCurrentCommand);
+
+ if (ContinueParse()) {
+ ResetLexAnalyzer();
+
+ if (aGreetingWithCapability) {
+ PR_FREEIF(fCurrentLine);
+ fCurrentLine = aGreetingWithCapability;
+ }
+
+ // When inIdle, only one pass through "do" and "while" below occurs.
+ do {
+ AdvanceToNextToken();
+
+ // untagged responses [RFC3501, Sec. 2.2.2]
+ while (ContinueParse() && fNextToken && *fNextToken == '*') {
+ response_data();
+ if (ContinueParse()) {
+ if (!fAtEndOfLine)
+ SetSyntaxError(true);
+ else if (!inIdle && !fCurrentCommandFailed &&
+ !aGreetingWithCapability)
+ AdvanceToNextToken();
+ }
+ // For checking expected response to IDLE command below and
+ // for checking if an untagged response occurred by the caller.
+ fUntaggedResponse = true;
+ }
+
+ // command continuation request [RFC3501, Sec. 7.5]
+ if (ContinueParse() && fNextToken &&
+ *fNextToken == '+') // never pipeline APPEND or AUTHENTICATE
+ {
+ NS_ASSERTION(
+ (fNumberOfTaggedResponsesExpected -
+ numberOfTaggedResponsesReceived) == 1,
+ " didn't get the number of tagged responses we expected");
+ numberOfTaggedResponsesReceived = fNumberOfTaggedResponsesExpected;
+ if (commandToken && !PL_strcasecmp(commandToken, "authenticate") &&
+ placeInTokenString &&
+ (!PL_strncasecmp(placeInTokenString, "CRAM-MD5",
+ strlen("CRAM-MD5")) ||
+ !PL_strncasecmp(placeInTokenString, "NTLM", strlen("NTLM")) ||
+ !PL_strncasecmp(placeInTokenString, "GSSAPI",
+ strlen("GSSAPI")) ||
+ !PL_strncasecmp(placeInTokenString, "MSN", strlen("MSN")))) {
+ // we need to store the challenge from the server if we are using
+ // CRAM-MD5 or NTLM.
+ authChallengeResponse_data();
+ }
+ } else
+ numberOfTaggedResponsesReceived++;
+
+ if (numberOfTaggedResponsesReceived < fNumberOfTaggedResponsesExpected)
+ response_tagged();
+
+ } while (
+ ContinueParse() && !inIdle &&
+ (numberOfTaggedResponsesReceived < fNumberOfTaggedResponsesExpected));
+
+ // check and see if the server is waiting for more input
+ // it's possible that we ate this + while parsing certain responses (like
+ // cram data), in these cases, the parsing routine for that specific
+ // command will manually set fWaitingForMoreClientInput so we don't lose
+ // that information....
+ if ((fNextToken && *fNextToken == '+') || inIdle) {
+ if (inIdle &&
+ !((fNextToken && *fNextToken == '+') || fUntaggedResponse)) {
+ // IDLE "response" + will not be "eaten" as described above since it
+ // is not an authentication response. So if IDLE response does not
+ // begin with '+' (continuation) or '*' (untagged and probably useful
+ // response) then something is wrong and it is probably a tagged
+ // NO or BAD due to transient error or bad configuration of the
+ // server.
+ if (!PL_strcmp(fCurrentCommandTag, fNextToken)) {
+ response_tagged();
+ } else {
+ // Expected tag doesn't match the received tag. Not good, start
+ // over.
+ response_fatal();
+ }
+ // Show an alert notification containing the server response to bad
+ // IDLE.
+ fServerConnection.AlertUserEventFromServer(fCurrentLine, true);
+ } else {
+ fWaitingForMoreClientInput = true;
+ }
+ }
+ // if we aren't still waiting for more input....
+ else if (!fWaitingForMoreClientInput && !aGreetingWithCapability) {
+ if (ContinueParse()) response_done();
+
+ if (ContinueParse() && !CommandFailed()) {
+ // a successful command may change the eIMAPstate
+ ProcessOkCommand(commandToken);
+ } else if (CommandFailed()) {
+ // a failed command may change the eIMAPstate
+ ProcessBadCommand(commandToken);
+ if (fReportingErrors && !aIgnoreBadAndNOResponses)
+ fServerConnection.AlertUserEventFromServer(fCurrentLine, false);
+ }
+ }
+ }
+ } else
+ SetConnected(false);
+}
+
+void nsImapServerResponseParser::HandleMemoryFailure() {
+ fServerConnection.AlertUserEventUsingName("imapOutOfMemory");
+ nsImapGenericParser::HandleMemoryFailure();
+}
+
+// SEARCH is the only command that requires pre-processing for now.
+// others will be added here.
+void nsImapServerResponseParser::PreProcessCommandToken(
+ const char* commandToken, const char* currentCommand) {
+ fCurrentCommandIsSingleMessageFetch = false;
+ fWaitingForMoreClientInput = false;
+
+ if (!PL_strcasecmp(commandToken, "SEARCH"))
+ fSearchResults->ResetSequence();
+ else if (!PL_strcasecmp(commandToken, "SELECT") && currentCommand) {
+ // the mailbox name must be quoted, so strip the quotes
+ const char* openQuote = PL_strchr(currentCommand, '"');
+ NS_ASSERTION(openQuote, "expected open quote in imap server response");
+ if (!openQuote) { // ill formed select command
+ openQuote = PL_strchr(currentCommand, ' ');
+ }
+ PR_Free(fSelectedMailboxName);
+ fSelectedMailboxName = PL_strdup(openQuote + 1);
+ if (fSelectedMailboxName) {
+ // strip the escape chars and the ending quote
+ char* currentChar = fSelectedMailboxName;
+ while (*currentChar) {
+ if (*currentChar == '\\') {
+ PL_strcpy(currentChar, currentChar + 1);
+ currentChar++; // skip what we are escaping
+ } else if (*currentChar == '\"')
+ *currentChar = 0; // end quote
+ else
+ currentChar++;
+ }
+ } else
+ HandleMemoryFailure();
+
+ // we don't want bogus info for this new box
+ // delete fFlagState; // not our object
+ // fFlagState = nullptr;
+ } else if (!PL_strcasecmp(commandToken, "CLOSE")) {
+ return; // just for debugging
+ // we don't want bogus info outside the selected state
+ // delete fFlagState; // not our object
+ // fFlagState = nullptr;
+ } else if (!PL_strcasecmp(commandToken, "UID")) {
+ nsCString copyCurrentCommand(currentCommand);
+ if (!fServerConnection.DeathSignalReceived()) {
+ char* placeInTokenString = copyCurrentCommand.BeginWriting();
+ (void)NS_strtok(WHITESPACE, &placeInTokenString); // skip tag token
+ (void)NS_strtok(WHITESPACE, &placeInTokenString); // skip uid token
+ char* fetchToken = NS_strtok(WHITESPACE, &placeInTokenString);
+ if (!PL_strcasecmp(fetchToken, "FETCH")) {
+ char* uidStringToken = NS_strtok(WHITESPACE, &placeInTokenString);
+ // , and : are uid delimiters
+ if (!PL_strchr(uidStringToken, ',') && !PL_strchr(uidStringToken, ':'))
+ fCurrentCommandIsSingleMessageFetch = true;
+ }
+ }
+ }
+}
+
+const char* nsImapServerResponseParser::GetSelectedMailboxName() {
+ return fSelectedMailboxName;
+}
+
+nsImapSearchResultIterator*
+nsImapServerResponseParser::CreateSearchResultIterator() {
+ return new nsImapSearchResultIterator(*fSearchResults);
+}
+
+nsImapServerResponseParser::eIMAPstate
+nsImapServerResponseParser::GetIMAPstate() {
+ return fIMAPstate;
+}
+
+void nsImapServerResponseParser::PreauthSetAuthenticatedState() {
+ fIMAPstate = kAuthenticated;
+}
+
+void nsImapServerResponseParser::ProcessOkCommand(const char* commandToken) {
+ if (!PL_strcasecmp(commandToken, "LOGIN") ||
+ !PL_strcasecmp(commandToken, "AUTHENTICATE"))
+ fIMAPstate = kAuthenticated;
+ else if (!PL_strcasecmp(commandToken, "LOGOUT"))
+ fIMAPstate = kNonAuthenticated;
+ else if (!PL_strcasecmp(commandToken, "SELECT") ||
+ !PL_strcasecmp(commandToken, "EXAMINE"))
+ fIMAPstate = kFolderSelected;
+ else if (!PL_strcasecmp(commandToken, "CLOSE")) {
+ fIMAPstate = kAuthenticated;
+ // we no longer have a selected mailbox.
+ PR_FREEIF(fSelectedMailboxName);
+ } else if ((!PL_strcasecmp(commandToken, "LIST")) ||
+ (!PL_strcasecmp(commandToken, "LSUB")) ||
+ (!PL_strcasecmp(commandToken, "XLIST"))) {
+ // fServerConnection.MailboxDiscoveryFinished();
+ // This used to be reporting that we were finished
+ // discovering folders for each time we issued a
+ // LIST or LSUB. So if we explicitly listed the
+ // INBOX, or Trash, or namespaces, we would get multiple
+ // "done" states, even though we hadn't finished.
+ // Move this to be called from the connection object
+ // itself.
+ } else if (!PL_strcasecmp(commandToken, "FETCH")) {
+ if (!fZeroLengthMessageUidString.IsEmpty()) {
+ // "Deleting zero length message");
+ fServerConnection.Store(fZeroLengthMessageUidString, "+Flags (\\Deleted)",
+ true);
+ if (LastCommandSuccessful()) fServerConnection.Expunge();
+
+ fZeroLengthMessageUidString.Truncate();
+ }
+ } else if (!PL_strcasecmp(commandToken, "GETQUOTAROOT")) {
+ if (LastCommandSuccessful()) {
+ nsCString str;
+ fServerConnection.UpdateFolderQuotaData(kValidateQuota, str, 0, 0);
+ }
+ }
+}
+
+void nsImapServerResponseParser::ProcessBadCommand(const char* commandToken) {
+ if (!PL_strcasecmp(commandToken, "LOGIN") ||
+ !PL_strcasecmp(commandToken, "AUTHENTICATE"))
+ fIMAPstate = kNonAuthenticated;
+ else if (!PL_strcasecmp(commandToken, "LOGOUT"))
+ fIMAPstate = kNonAuthenticated; // ??
+ else if (!PL_strcasecmp(commandToken, "SELECT") ||
+ !PL_strcasecmp(commandToken, "EXAMINE"))
+ fIMAPstate = kAuthenticated; // nothing selected
+ else if (!PL_strcasecmp(commandToken, "CLOSE"))
+ fIMAPstate = kAuthenticated; // nothing selected
+}
+
+// RFC3501: response-data = "*" SP (resp-cond-state / resp-cond-bye /
+// mailbox-data / message-data / capability-data) CRLF
+// These are ``untagged'' responses [RFC3501, Sec. 2.2.2]
+/*
+ The RFC1730 grammar spec did not allow one symbol look ahead to determine
+ between mailbox_data / message_data so I combined the numeric possibilities
+ of mailbox_data and all of message_data into numeric_mailbox_data.
+
+ It is assumed that the initial "*" is already consumed before calling this
+ method. The production implemented here is
+ response_data ::= (resp_cond_state / resp_cond_bye /
+ mailbox_data / numeric_mailbox_data /
+ capability_data)
+ CRLF
+*/
+void nsImapServerResponseParser::response_data() {
+ AdvanceToNextToken();
+
+ if (ContinueParse()) {
+ // Instead of comparing lots of strings and make function calls, try to
+ // pre-flight the possibilities based on the first letter of the token.
+ switch (NS_ToUpper(fNextToken[0])) {
+ case 'O': // OK
+ if (NS_ToUpper(fNextToken[1]) == 'K')
+ resp_cond_state(false);
+ else
+ SetSyntaxError(true);
+ break;
+ case 'N': // NO
+ if (NS_ToUpper(fNextToken[1]) == 'O')
+ resp_cond_state(false);
+ else if (!PL_strcasecmp(fNextToken, "NAMESPACE"))
+ namespace_data();
+ else
+ SetSyntaxError(true);
+ break;
+ case 'B': // BAD
+ if (!PL_strcasecmp(fNextToken, "BAD"))
+ resp_cond_state(false);
+ else if (!PL_strcasecmp(fNextToken, "BYE"))
+ resp_cond_bye();
+ else
+ SetSyntaxError(true);
+ break;
+ case 'F':
+ if (!PL_strcasecmp(fNextToken, "FLAGS"))
+ mailbox_data();
+ else
+ SetSyntaxError(true);
+ break;
+ case 'P':
+ if (PL_strcasecmp(fNextToken, "PERMANENTFLAGS"))
+ mailbox_data();
+ else
+ SetSyntaxError(true);
+ break;
+ case 'L':
+ if (!PL_strcasecmp(fNextToken, "LIST") ||
+ !PL_strcasecmp(fNextToken, "LSUB"))
+ mailbox_data();
+ else if (!PL_strcasecmp(fNextToken, "LANGUAGE"))
+ language_data();
+ else
+ SetSyntaxError(true);
+ break;
+ case 'M':
+ if (!PL_strcasecmp(fNextToken, "MAILBOX"))
+ mailbox_data();
+ else if (!PL_strcasecmp(fNextToken, "MYRIGHTS"))
+ myrights_data(false);
+ else
+ SetSyntaxError(true);
+ break;
+ case 'S':
+ if (!PL_strcasecmp(fNextToken, "SEARCH"))
+ mailbox_data();
+ else if (!PL_strcasecmp(fNextToken, "STATUS")) {
+ AdvanceToNextToken();
+ if (fNextToken) {
+ char* mailboxName = CreateAstring();
+ PL_strfree(mailboxName);
+ }
+ while (ContinueParse() && !fAtEndOfLine) {
+ AdvanceToNextToken();
+ if (!fNextToken) break;
+
+ if (*fNextToken == '(') fNextToken++;
+ if (!PL_strcasecmp(fNextToken, "UIDNEXT")) {
+ AdvanceToNextToken();
+ if (fNextToken) {
+ fStatusNextUID = strtoul(fNextToken, nullptr, 10);
+ // if this token ends in ')', then it is the last token
+ // else we advance
+ if (*(fNextToken + strlen(fNextToken) - 1) == ')')
+ fNextToken += strlen(fNextToken) - 1;
+ }
+ } else if (!PL_strcasecmp(fNextToken, "MESSAGES")) {
+ AdvanceToNextToken();
+ if (fNextToken) {
+ fStatusExistingMessages = strtoul(fNextToken, nullptr, 10);
+ // if this token ends in ')', then it is the last token
+ // else we advance
+ if (*(fNextToken + strlen(fNextToken) - 1) == ')')
+ fNextToken += strlen(fNextToken) - 1;
+ }
+ } else if (!PL_strcasecmp(fNextToken, "UNSEEN")) {
+ AdvanceToNextToken();
+ if (fNextToken) {
+ fStatusUnseenMessages = strtoul(fNextToken, nullptr, 10);
+ // if this token ends in ')', then it is the last token
+ // else we advance
+ if (*(fNextToken + strlen(fNextToken) - 1) == ')')
+ fNextToken += strlen(fNextToken) - 1;
+ }
+ } else if (!PL_strcasecmp(fNextToken, "RECENT")) {
+ AdvanceToNextToken();
+ if (fNextToken) {
+ fStatusRecentMessages = strtoul(fNextToken, nullptr, 10);
+ // if this token ends in ')', then it is the last token
+ // else we advance
+ if (*(fNextToken + strlen(fNextToken) - 1) == ')')
+ fNextToken += strlen(fNextToken) - 1;
+ }
+ } else if (*fNextToken == ')')
+ break;
+ else if (!fAtEndOfLine)
+ SetSyntaxError(true);
+ }
+ } else
+ SetSyntaxError(true);
+ break;
+ case 'C':
+ if (!PL_strcasecmp(fNextToken, "CAPABILITY"))
+ capability_data();
+ else
+ SetSyntaxError(true);
+ break;
+ case 'V':
+ if (!PL_strcasecmp(fNextToken, "VERSION")) {
+ // figure out the version of the Netscape server here
+ PR_FREEIF(fNetscapeServerVersionString);
+ AdvanceToNextToken();
+ if (!fNextToken)
+ SetSyntaxError(true);
+ else {
+ fNetscapeServerVersionString = CreateAstring();
+ AdvanceToNextToken();
+ if (fNetscapeServerVersionString) {
+ fServerIsNetscape3xServer =
+ (*fNetscapeServerVersionString == '3');
+ }
+ }
+ skip_to_CRLF();
+ } else
+ SetSyntaxError(true);
+ break;
+ case 'A':
+ if (!PL_strcasecmp(fNextToken, "ACL")) {
+ acl_data();
+ } else if (!PL_strcasecmp(fNextToken, "ACCOUNT-URL")) {
+ fMailAccountUrl.Truncate();
+ AdvanceToNextToken();
+ if (!fNextToken)
+ SetSyntaxError(true);
+ else {
+ fMailAccountUrl.Adopt(CreateAstring());
+ AdvanceToNextToken();
+ }
+ } else
+ SetSyntaxError(true);
+ break;
+ case 'E':
+ if (!PL_strcasecmp(fNextToken, "ENABLED")) enable_data();
+ break;
+ case 'X':
+ if (!PL_strcasecmp(fNextToken, "XSERVERINFO"))
+ xserverinfo_data();
+ else if (!PL_strcasecmp(fNextToken, "XMAILBOXINFO"))
+ xmailboxinfo_data();
+ else if (!PL_strcasecmp(fNextToken, "XAOL-OPTION"))
+ skip_to_CRLF();
+ else if (!PL_strcasecmp(fNextToken, "XLIST"))
+ mailbox_data();
+ else {
+ // check if custom command
+ nsAutoCString customCommand;
+ fServerConnection.GetCurrentUrl()->GetCommand(customCommand);
+ if (customCommand.Equals(fNextToken)) {
+ nsAutoCString customCommandResponse;
+ while (Connected() && !fAtEndOfLine) {
+ AdvanceToNextToken();
+ customCommandResponse.Append(fNextToken);
+ customCommandResponse.Append(' ');
+ }
+ fServerConnection.GetCurrentUrl()->SetCustomCommandResult(
+ customCommandResponse);
+ } else
+ SetSyntaxError(true);
+ }
+ break;
+ case 'Q':
+ if (!PL_strcasecmp(fNextToken, "QUOTAROOT") ||
+ !PL_strcasecmp(fNextToken, "QUOTA"))
+ quota_data();
+ else
+ SetSyntaxError(true);
+ break;
+ case 'I':
+ id_data();
+ break;
+ default:
+ if (IsNumericString(fNextToken))
+ numeric_mailbox_data();
+ else
+ SetSyntaxError(true);
+ break;
+ }
+
+ if (ContinueParse()) PostProcessEndOfLine();
+ }
+}
+
+void nsImapServerResponseParser::PostProcessEndOfLine() {
+ // for now we only have to do one thing here
+ // a fetch response to a 'uid store' command might return the flags
+ // before it returns the uid of the message. So we need both before
+ // we report the new flag info to the front end
+
+ // also check and be sure that there was a UID in the current response
+ if (fCurrentLineContainedFlagInfo && CurrentResponseUID()) {
+ fCurrentLineContainedFlagInfo = false;
+ nsCString customFlags;
+ fFlagState->GetCustomFlags(CurrentResponseUID(),
+ getter_Copies(customFlags));
+ fServerConnection.NotifyMessageFlags(fSavedFlagInfo, customFlags,
+ CurrentResponseUID(), fHighestModSeq);
+ }
+}
+
+/*
+ mailbox_data ::= "FLAGS" SPACE flag_list /
+ "LIST" SPACE mailbox_list /
+ "LSUB" SPACE mailbox_list /
+ "XLIST" SPACE mailbox_list /
+ "MAILBOX" SPACE text /
+ "SEARCH" [SPACE 1#nz_number] /
+ number SPACE "EXISTS" / number SPACE "RECENT"
+
+This production was changed to accommodate predictive parsing
+
+ mailbox_data ::= "FLAGS" SPACE flag_list /
+ "LIST" SPACE mailbox_list /
+ "LSUB" SPACE mailbox_list /
+ "XLIST" SPACE mailbox_list /
+ "MAILBOX" SPACE text /
+ "SEARCH" [SPACE 1#nz_number]
+*/
+void nsImapServerResponseParser::mailbox_data() {
+ if (!PL_strcasecmp(fNextToken, "FLAGS")) {
+ // this handles the case where we got the permanent flags response
+ // before the flags response, in which case, we want to ignore these flags.
+ if (fGotPermanentFlags)
+ skip_to_CRLF();
+ else
+ parse_folder_flags(true);
+ } else if (!PL_strcasecmp(fNextToken, "LIST") ||
+ !PL_strcasecmp(fNextToken, "XLIST")) {
+ AdvanceToNextToken();
+ if (ContinueParse()) mailbox_list(false);
+ } else if (!PL_strcasecmp(fNextToken, "LSUB")) {
+ AdvanceToNextToken();
+ if (ContinueParse()) mailbox_list(true);
+ } else if (!PL_strcasecmp(fNextToken, "MAILBOX"))
+ skip_to_CRLF();
+ else if (!PL_strcasecmp(fNextToken, "SEARCH")) {
+ fSearchResults->AddSearchResultLine(fCurrentLine);
+ fServerConnection.NotifySearchHit(fCurrentLine);
+ skip_to_CRLF();
+ }
+}
+
+/*
+ mailbox_list ::= "(" #("\Marked" / "\Noinferiors" /
+ "\Noselect" / "\Unmarked" / flag_extension) ")"
+ SPACE (<"> QUOTED_CHAR <"> / nil) SPACE mailbox
+*/
+
+void nsImapServerResponseParser::mailbox_list(bool discoveredFromLsub) {
+ RefPtr<nsImapMailboxSpec> boxSpec = new nsImapMailboxSpec;
+ boxSpec->mFolderSelected = false;
+ boxSpec->mBoxFlags = kNoFlags;
+ boxSpec->mAllocatedPathName.Truncate();
+ boxSpec->mHostName.Truncate();
+ boxSpec->mConnection = &fServerConnection;
+ boxSpec->mFlagState = nullptr;
+ boxSpec->mDiscoveredFromLsub = discoveredFromLsub;
+ boxSpec->mOnlineVerified = true;
+ boxSpec->mBoxFlags &= ~kNameSpace;
+
+ bool endOfFlags = false;
+ fNextToken++; // eat the first "("
+ do {
+ if (!PL_strncasecmp(fNextToken, "\\Marked", 7))
+ boxSpec->mBoxFlags |= kMarked;
+ else if (!PL_strncasecmp(fNextToken, "\\Unmarked", 9))
+ boxSpec->mBoxFlags |= kUnmarked;
+ else if (!PL_strncasecmp(fNextToken, "\\Noinferiors", 12)) {
+ boxSpec->mBoxFlags |= kNoinferiors;
+ // RFC 5258 \Noinferiors implies \HasNoChildren
+ if (fCapabilityFlag & kHasListExtendedCapability)
+ boxSpec->mBoxFlags |= kHasNoChildren;
+ } else if (!PL_strncasecmp(fNextToken, "\\Noselect", 9))
+ boxSpec->mBoxFlags |= kNoselect;
+ else if (!PL_strncasecmp(fNextToken, "\\Drafts", 7))
+ boxSpec->mBoxFlags |= kImapDrafts;
+ else if (!PL_strncasecmp(fNextToken, "\\Trash", 6))
+ boxSpec->mBoxFlags |= kImapXListTrash;
+ else if (!PL_strncasecmp(fNextToken, "\\Sent", 5))
+ boxSpec->mBoxFlags |= kImapSent;
+ else if (!PL_strncasecmp(fNextToken, "\\Spam", 5) ||
+ !PL_strncasecmp(fNextToken, "\\Junk", 5))
+ boxSpec->mBoxFlags |= kImapSpam;
+ else if (!PL_strncasecmp(fNextToken, "\\Archive", 8))
+ boxSpec->mBoxFlags |= kImapArchive;
+ else if (!PL_strncasecmp(fNextToken, "\\All", 4) ||
+ !PL_strncasecmp(fNextToken, "\\AllMail", 8))
+ boxSpec->mBoxFlags |= kImapAllMail;
+ else if (!PL_strncasecmp(fNextToken, "\\Inbox", 6))
+ boxSpec->mBoxFlags |= kImapInbox;
+ else if (!PL_strncasecmp(fNextToken, "\\NonExistent", 11)) {
+ boxSpec->mBoxFlags |= kNonExistent;
+ // RFC 5258 \NonExistent implies \Noselect
+ boxSpec->mBoxFlags |= kNoselect;
+ } else if (!PL_strncasecmp(fNextToken, "\\Subscribed", 10))
+ boxSpec->mBoxFlags |= kSubscribed;
+ else if (!PL_strncasecmp(fNextToken, "\\Remote", 6))
+ boxSpec->mBoxFlags |= kRemote;
+ else if (!PL_strncasecmp(fNextToken, "\\HasChildren", 11))
+ boxSpec->mBoxFlags |= kHasChildren;
+ else if (!PL_strncasecmp(fNextToken, "\\HasNoChildren", 13))
+ boxSpec->mBoxFlags |= kHasNoChildren;
+ // we ignore flag other extensions
+
+ endOfFlags = *(fNextToken + strlen(fNextToken) - 1) == ')';
+ AdvanceToNextToken();
+ } while (!endOfFlags && ContinueParse());
+
+ if (ContinueParse()) {
+ if (*fNextToken == '"') {
+ fNextToken++;
+ if (*fNextToken == '\\') // handle escaped char
+ boxSpec->mHierarchySeparator = *(fNextToken + 1);
+ else
+ boxSpec->mHierarchySeparator = *fNextToken;
+ } else // likely NIL. Discovered late in 4.02 that we do not handle
+ // literals here (e.g. {10} <10 chars>), although this is almost
+ // impossibly unlikely
+ boxSpec->mHierarchySeparator = kOnlineHierarchySeparatorNil;
+ AdvanceToNextToken();
+ if (ContinueParse()) mailbox(boxSpec);
+ }
+}
+
+/* mailbox ::= "INBOX" / astring
+ */
+void nsImapServerResponseParser::mailbox(nsImapMailboxSpec* boxSpec) {
+ char* boxname = nullptr;
+ const char* serverKey = fServerConnection.GetImapServerKey();
+ bool xlistInbox = boxSpec->mBoxFlags & kImapInbox;
+
+ if (!PL_strcasecmp(fNextToken, "INBOX") || xlistInbox) {
+ boxname = PL_strdup("INBOX");
+ if (xlistInbox) PR_Free(CreateAstring());
+ AdvanceToNextToken();
+ } else {
+ boxname = CreateAstring();
+ AdvanceToNextToken();
+ }
+
+ if (boxname && fHostSessionList) {
+ fHostSessionList->SetNamespaceHierarchyDelimiterFromMailboxForHost(
+ serverKey, boxname, boxSpec->mHierarchySeparator);
+
+ nsImapNamespace* ns = nullptr;
+ fHostSessionList->GetNamespaceForMailboxForHost(serverKey, boxname, ns);
+ if (ns) {
+ switch (ns->GetType()) {
+ case kPersonalNamespace:
+ boxSpec->mBoxFlags |= kPersonalMailbox;
+ break;
+ case kPublicNamespace:
+ boxSpec->mBoxFlags |= kPublicMailbox;
+ break;
+ case kOtherUsersNamespace:
+ boxSpec->mBoxFlags |= kOtherUsersMailbox;
+ break;
+ default: // (kUnknownNamespace)
+ break;
+ }
+ boxSpec->mNamespaceForFolder = ns;
+ }
+ }
+
+ if (!boxname) {
+ if (!fServerConnection.DeathSignalReceived()) HandleMemoryFailure();
+ } else if (boxSpec->mConnection && boxSpec->mConnection->GetCurrentUrl()) {
+ boxSpec->mConnection->GetCurrentUrl()->AllocateCanonicalPath(
+ boxname, boxSpec->mHierarchySeparator,
+ getter_Copies(boxSpec->mAllocatedPathName));
+ nsIURI* aURL = nullptr;
+ boxSpec->mConnection->GetCurrentUrl()->QueryInterface(NS_GET_IID(nsIURI),
+ (void**)&aURL);
+ if (aURL) aURL->GetHost(boxSpec->mHostName);
+
+ NS_IF_RELEASE(aURL);
+ // storage for the boxSpec is now owned by server connection
+ fServerConnection.DiscoverMailboxSpec(boxSpec);
+
+ // if this was cancelled by the user,then we sure don't want to
+ // send more mailboxes their way
+ if (NS_FAILED(fServerConnection.GetConnectionStatus())) SetConnected(false);
+ }
+
+ if (boxname) PL_strfree(boxname);
+}
+
+/*
+ message_data ::= nz_number SPACE ("EXPUNGE" /
+ ("FETCH" SPACE msg_fetch) / msg_obsolete)
+
+was changed to
+
+numeric_mailbox_data ::= number SPACE "EXISTS" / number SPACE "RECENT"
+ / nz_number SPACE ("EXPUNGE" /
+ ("FETCH" SPACE msg_fetch) / msg_obsolete)
+
+*/
+void nsImapServerResponseParser::numeric_mailbox_data() {
+ int32_t tokenNumber = atoi(fNextToken);
+ AdvanceToNextToken();
+
+ if (ContinueParse()) {
+ if (!PL_strcasecmp(fNextToken, "FETCH")) {
+ fFetchResponseIndex = tokenNumber;
+ AdvanceToNextToken();
+ if (ContinueParse()) msg_fetch();
+ } else if (!PL_strcasecmp(fNextToken, "EXISTS")) {
+ fNumberOfExistingMessages = tokenNumber;
+ AdvanceToNextToken();
+ } else if (!PL_strcasecmp(fNextToken, "RECENT")) {
+ fNumberOfRecentMessages = tokenNumber;
+ AdvanceToNextToken();
+ } else if (!PL_strcasecmp(fNextToken, "EXPUNGE")) {
+ if (!fServerConnection.GetIgnoreExpunges())
+ fFlagState->ExpungeByIndex((uint32_t)tokenNumber);
+ skip_to_CRLF();
+ } else
+ msg_obsolete();
+ }
+}
+
+/*
+msg_fetch ::= "(" 1#("BODY" SPACE body /
+"BODYSTRUCTURE" SPACE body /
+"BODY[" section "]" SPACE nstring /
+"ENVELOPE" SPACE envelope /
+"FLAGS" SPACE "(" #(flag / "\Recent") ")" /
+"INTERNALDATE" SPACE date_time /
+"MODSEQ" SPACE "(" nz_number ")" /
+"RFC822" [".HEADER" / ".TEXT"] SPACE nstring /
+"RFC822.SIZE" SPACE number /
+"UID" SPACE uniqueid) ")"
+
+*/
+
+void nsImapServerResponseParser::msg_fetch() {
+ bool bNeedEndMessageDownload = false;
+
+ // we have not seen a uid response or flags for this fetch, yet
+ fCurrentResponseUID = 0;
+ fCurrentLineContainedFlagInfo = false;
+ fSizeOfMostRecentMessage = 0;
+ // show any incremental progress, for instance, for header downloading
+ fServerConnection.ShowProgress();
+
+ fNextToken++; // eat the '(' character
+
+ // some of these productions are ignored for now
+ while (ContinueParse() && (*fNextToken != ')')) {
+ if (!PL_strcasecmp(fNextToken, "FLAGS")) {
+ if (fCurrentResponseUID == 0)
+ fFlagState->GetUidOfMessage(fFetchResponseIndex - 1,
+ &fCurrentResponseUID);
+
+ AdvanceToNextToken();
+ if (ContinueParse()) flags();
+
+ if (ContinueParse()) { // eat the closing ')'
+ fNextToken++;
+ // there may be another ')' to close out
+ // msg_fetch. If there is then don't advance
+ if (*fNextToken != ')') AdvanceToNextToken();
+ }
+ } else if (!PL_strcasecmp(fNextToken, "UID")) {
+ AdvanceToNextToken();
+ if (ContinueParse()) {
+ fCurrentResponseUID = strtoul(fNextToken, nullptr, 10);
+ if (fCurrentResponseUID > fHighestRecordedUID)
+ fHighestRecordedUID = fCurrentResponseUID;
+ // size came before UID
+ if (fSizeOfMostRecentMessage)
+ fReceivedHeaderOrSizeForUID = CurrentResponseUID();
+ // if this token ends in ')', then it is the last token
+ // else we advance
+ char lastTokenChar = *(fNextToken + strlen(fNextToken) - 1);
+ if (lastTokenChar == ')')
+ fNextToken += strlen(fNextToken) - 1;
+ else if (lastTokenChar < '0' || lastTokenChar > '9') {
+ // GIANT HACK
+ // this is a corrupt uid - see if it's pre 5.08 Zimbra omitting
+ // a space between the UID and MODSEQ
+ if (strlen(fNextToken) > 6 &&
+ !strcmp("MODSEQ", fNextToken + strlen(fNextToken) - 6))
+ fNextToken += strlen(fNextToken) - 6;
+ } else
+ AdvanceToNextToken();
+ }
+ } else if (!PL_strcasecmp(fNextToken, "MODSEQ")) {
+ AdvanceToNextToken();
+ if (ContinueParse()) {
+ fNextToken++; // eat '('
+ uint64_t modSeq = ParseUint64Str(fNextToken);
+ if (modSeq > fHighestModSeq) fHighestModSeq = modSeq;
+
+ if (PL_strcasestr(fNextToken, ")")) {
+ // eat token chars until we get the ')'
+ fNextToken = strchr(fNextToken, ')');
+ if (fNextToken) {
+ fNextToken++;
+ if (*fNextToken != ')') AdvanceToNextToken();
+ } else
+ SetSyntaxError(true);
+ } else {
+ SetSyntaxError(true);
+ }
+ }
+ } else if (!PL_strcasecmp(fNextToken, "RFC822") ||
+ !PL_strcasecmp(fNextToken, "RFC822.HEADER") ||
+ !PL_strncasecmp(fNextToken, "BODY[HEADER", 11) ||
+ !PL_strncasecmp(fNextToken, "BODY[]", 6) ||
+ !PL_strcasecmp(fNextToken, "RFC822.TEXT") ||
+ (!PL_strncasecmp(fNextToken, "BODY[", 5) &&
+ PL_strstr(fNextToken, "HEADER"))) {
+ if (fCurrentResponseUID == 0)
+ fFlagState->GetUidOfMessage(fFetchResponseIndex - 1,
+ &fCurrentResponseUID);
+
+ if (!PL_strcasecmp(fNextToken, "RFC822.HEADER") ||
+ !PL_strcasecmp(fNextToken, "BODY[HEADER]")) {
+ // all of this message's headers
+ AdvanceToNextToken();
+ fDownloadingHeaders = true;
+ BeginMessageDownload(MESSAGE_RFC822); // initialize header parser
+ bNeedEndMessageDownload = false;
+ if (ContinueParse()) msg_fetch_headers(nullptr);
+ } else if (!PL_strncasecmp(fNextToken, "BODY[HEADER.FIELDS", 19)) {
+ fDownloadingHeaders = true;
+ BeginMessageDownload(MESSAGE_RFC822); // initialize header parser
+ // specific message header fields
+ while (ContinueParse() && fNextToken[strlen(fNextToken) - 1] != ']')
+ AdvanceToNextToken();
+ if (ContinueParse()) {
+ bNeedEndMessageDownload = false;
+ AdvanceToNextToken();
+ if (ContinueParse()) msg_fetch_headers(nullptr);
+ }
+ } else {
+ char* whereHeader = PL_strstr(fNextToken, "HEADER");
+ if (whereHeader) {
+ const char* startPartNum = fNextToken + 5;
+ if (whereHeader > startPartNum) {
+ int32_t partLength =
+ whereHeader - startPartNum - 1; //-1 for the dot!
+ char* partNum = (char*)PR_CALLOC((partLength + 1) * sizeof(char));
+ if (partNum) {
+ PL_strncpy(partNum, startPartNum, partLength);
+ if (ContinueParse()) {
+ if (PL_strstr(fNextToken, "FIELDS")) {
+ while (ContinueParse() &&
+ fNextToken[strlen(fNextToken) - 1] != ']')
+ AdvanceToNextToken();
+ }
+ if (ContinueParse()) {
+ AdvanceToNextToken();
+ if (ContinueParse()) msg_fetch_headers(partNum);
+ }
+ }
+ PR_Free(partNum);
+ }
+ } else
+ SetSyntaxError(true);
+ } else {
+ fDownloadingHeaders = false;
+
+ bool chunk = false;
+ int32_t origin = 0;
+ if (!PL_strncasecmp(fNextToken, "BODY[]<", 7)) {
+ char* tokenCopy = 0;
+ tokenCopy = PL_strdup(fNextToken);
+ if (tokenCopy) {
+ char* originString =
+ tokenCopy + 7; // where the byte number starts
+ char* closeBracket = PL_strchr(tokenCopy, '>');
+ if (closeBracket && originString && *originString) {
+ *closeBracket = 0;
+ origin = atoi(originString);
+ chunk = true;
+ }
+ PR_Free(tokenCopy);
+ }
+ }
+
+ AdvanceToNextToken();
+ if (ContinueParse()) {
+ msg_fetch_content(chunk, origin, MESSAGE_RFC822);
+ }
+ }
+ }
+ } else if (!PL_strcasecmp(fNextToken, "RFC822.SIZE") ||
+ !PL_strcasecmp(fNextToken, "XAOL.SIZE")) {
+ AdvanceToNextToken();
+ if (ContinueParse()) {
+ bool sendEndMsgDownload =
+ (GetDownloadingHeaders() &&
+ fReceivedHeaderOrSizeForUID == CurrentResponseUID());
+ fSizeOfMostRecentMessage = strtoul(fNextToken, nullptr, 10);
+ fReceivedHeaderOrSizeForUID = CurrentResponseUID();
+ if (sendEndMsgDownload) {
+ fServerConnection.NormalMessageEndDownload();
+ fReceivedHeaderOrSizeForUID = nsMsgKey_None;
+ }
+
+ if (fSizeOfMostRecentMessage == 0 && CurrentResponseUID()) {
+ // on no, bogus Netscape 2.0 mail server bug
+ char uidString[100];
+ sprintf(uidString, "%ld", (long)CurrentResponseUID());
+
+ if (!fZeroLengthMessageUidString.IsEmpty())
+ fZeroLengthMessageUidString += ",";
+
+ fZeroLengthMessageUidString += uidString;
+ }
+
+ // if this token ends in ')', then it is the last token
+ // else we advance
+ if (*(fNextToken + strlen(fNextToken) - 1) == ')')
+ fNextToken += strlen(fNextToken) - 1;
+ else
+ AdvanceToNextToken();
+ }
+ } else if (!PL_strcasecmp(fNextToken, "XSENDER")) {
+ PR_FREEIF(fXSenderInfo);
+ AdvanceToNextToken();
+ if (!fNextToken)
+ SetSyntaxError(true);
+ else {
+ fXSenderInfo = CreateAstring();
+ AdvanceToNextToken();
+ }
+ } else if (!PL_strcasecmp(fNextToken, "X-GM-MSGID")) {
+ AdvanceToNextToken();
+ if (!fNextToken)
+ SetSyntaxError(true);
+ else {
+ fMsgID = CreateAtom();
+ AdvanceToNextToken();
+ nsCString msgIDValue;
+ msgIDValue.Assign(fMsgID);
+ if (fCurrentResponseUID == 0)
+ fFlagState->GetUidOfMessage(fFetchResponseIndex - 1,
+ &fCurrentResponseUID);
+ fFlagState->SetCustomAttribute(fCurrentResponseUID, "X-GM-MSGID"_ns,
+ msgIDValue);
+ PR_FREEIF(fMsgID);
+ }
+ } else if (!PL_strcasecmp(fNextToken, "X-GM-THRID")) {
+ AdvanceToNextToken();
+ if (!fNextToken)
+ SetSyntaxError(true);
+ else {
+ fThreadID = CreateAtom();
+ AdvanceToNextToken();
+ nsCString threadIDValue;
+ threadIDValue.Assign(fThreadID);
+ if (fCurrentResponseUID == 0)
+ fFlagState->GetUidOfMessage(fFetchResponseIndex - 1,
+ &fCurrentResponseUID);
+ fFlagState->SetCustomAttribute(fCurrentResponseUID, "X-GM-THRID"_ns,
+ threadIDValue);
+ PR_FREEIF(fThreadID);
+ }
+ } else if (!PL_strcasecmp(fNextToken, "X-GM-LABELS")) {
+ AdvanceToNextToken();
+ if (!fNextToken)
+ SetSyntaxError(true);
+ else {
+ fLabels = CreateParenGroup();
+ nsCString labelsValue;
+ labelsValue.Assign(fLabels);
+ labelsValue.Cut(0, 1);
+ labelsValue.Cut(labelsValue.Length() - 1, 1);
+ if (fCurrentResponseUID == 0)
+ fFlagState->GetUidOfMessage(fFetchResponseIndex - 1,
+ &fCurrentResponseUID);
+ fFlagState->SetCustomAttribute(fCurrentResponseUID, "X-GM-LABELS"_ns,
+ labelsValue);
+ PR_FREEIF(fLabels);
+ }
+ }
+
+ // I only fetch RFC822 so I should never see these BODY responses
+ else if (!PL_strcasecmp(fNextToken, "BODY"))
+ skip_to_CRLF(); // I never ask for this
+ else if (!PL_strncasecmp(fNextToken, "BODY[TEXT", 9)) {
+ // This parses the "preview" response (first 2048 bytes of body text).
+ mime_part_data(); // Note: TEXT is not an actual mime part.
+ } else if (!PL_strcasecmp(fNextToken, "ENVELOPE")) {
+ fDownloadingHeaders = true;
+ bNeedEndMessageDownload = true;
+ BeginMessageDownload(MESSAGE_RFC822);
+ envelope_data();
+ } else if (!PL_strcasecmp(fNextToken, "INTERNALDATE")) {
+ fDownloadingHeaders =
+ true; // we only request internal date while downloading headers
+ if (!bNeedEndMessageDownload) BeginMessageDownload(MESSAGE_RFC822);
+ bNeedEndMessageDownload = true;
+ internal_date();
+ } else if (!PL_strcasecmp(fNextToken, "XAOL-ENVELOPE")) {
+ fDownloadingHeaders = true;
+ if (!bNeedEndMessageDownload) BeginMessageDownload(MESSAGE_RFC822);
+ bNeedEndMessageDownload = true;
+ xaolenvelope_data();
+ } else {
+ nsImapAction imapAction;
+ if (!fServerConnection.GetCurrentUrl()) return;
+ fServerConnection.GetCurrentUrl()->GetImapAction(&imapAction);
+ nsAutoCString userDefinedFetchAttribute;
+ fServerConnection.GetCurrentUrl()->GetCustomAttributeToFetch(
+ userDefinedFetchAttribute);
+ if ((imapAction == nsIImapUrl::nsImapUserDefinedFetchAttribute &&
+ !strcmp(userDefinedFetchAttribute.get(), fNextToken)) ||
+ imapAction == nsIImapUrl::nsImapUserDefinedMsgCommand) {
+ AdvanceToNextToken();
+ char* fetchResult;
+ if (fNextToken[0] == '(')
+ // look through the tokens until we find the closing ')'
+ // we can have a result like the following:
+ // ((A B) (C D) (E F))
+ fetchResult = CreateParenGroup();
+ else {
+ fetchResult = CreateAstring();
+ AdvanceToNextToken();
+ }
+ if (imapAction == nsIImapUrl::nsImapUserDefinedFetchAttribute)
+ fServerConnection.GetCurrentUrl()->SetCustomAttributeResult(
+ nsDependentCString(fetchResult));
+ if (imapAction == nsIImapUrl::nsImapUserDefinedMsgCommand)
+ fServerConnection.GetCurrentUrl()->SetCustomCommandResult(
+ nsDependentCString(fetchResult));
+ PR_Free(fetchResult);
+ } else
+ SetSyntaxError(true);
+ }
+ }
+
+ if (ContinueParse()) {
+ if (CurrentResponseUID() && CurrentResponseUID() != nsMsgKey_None &&
+ fCurrentLineContainedFlagInfo && fFlagState) {
+ fFlagState->AddUidFlagPair(CurrentResponseUID(), fSavedFlagInfo,
+ fFetchResponseIndex - 1);
+ for (uint32_t i = 0; i < fCustomFlags.Length(); i++)
+ fFlagState->AddUidCustomFlagPair(CurrentResponseUID(),
+ fCustomFlags[i].get());
+ fCustomFlags.Clear();
+ }
+
+ if (fFetchingAllFlags)
+ fCurrentLineContainedFlagInfo =
+ false; // do not fire if in PostProcessEndOfLine
+
+ AdvanceToNextToken(); // eat the ')' ending token
+ // should be at end of line
+ if (bNeedEndMessageDownload) {
+ if (ContinueParse()) {
+ // complete the message download
+ fServerConnection.NormalMessageEndDownload();
+ } else
+ fServerConnection.AbortMessageDownLoad();
+ }
+ }
+}
+
+typedef enum _envelopeItemType {
+ envelopeString,
+ envelopeAddress
+} envelopeItemType;
+
+typedef struct {
+ const char* name;
+ envelopeItemType type;
+} envelopeItem;
+
+// RFC3501: envelope = "(" env-date SP env-subject SP env-from SP
+// env-sender SP env-reply-to SP env-to SP env-cc SP
+// env-bcc SP env-in-reply-to SP env-message-id ")"
+// env-date = nstring
+// env-subject = nstring
+// env-from = "(" 1*address ")" / nil
+// env-sender = "(" 1*address ")" / nil
+// env-reply-to= "(" 1*address ")" / nil
+// env-to = "(" 1*address ")" / nil
+// env-cc = "(" 1*address ")" / nil
+// env-bcc = "(" 1*address ")" / nil
+// env-in-reply-to = nstring
+// env-message-id = nstring
+
+static const envelopeItem EnvelopeTable[] = {
+ {"Date", envelopeString}, {"Subject", envelopeString},
+ {"From", envelopeAddress}, {"Sender", envelopeAddress},
+ {"Reply-to", envelopeAddress}, {"To", envelopeAddress},
+ {"Cc", envelopeAddress}, {"Bcc", envelopeAddress},
+ {"In-reply-to", envelopeString}, {"Message-id", envelopeString}};
+
+void nsImapServerResponseParser::envelope_data() {
+ AdvanceToNextToken();
+ fNextToken++; // eat '('
+ for (int tableIndex = 0;
+ tableIndex < (int)(sizeof(EnvelopeTable) / sizeof(EnvelopeTable[0]));
+ tableIndex++) {
+ if (!ContinueParse()) break;
+ if (*fNextToken == ')') {
+ SetSyntaxError(true); // envelope too short
+ break;
+ }
+
+ nsAutoCString headerLine(EnvelopeTable[tableIndex].name);
+ headerLine += ": ";
+ bool headerNonNil = true;
+ if (EnvelopeTable[tableIndex].type == envelopeString) {
+ nsAutoCString strValue;
+ strValue.Adopt(CreateNilString());
+ if (!strValue.IsEmpty())
+ headerLine.Append(strValue);
+ else
+ headerNonNil = false;
+ } else {
+ nsAutoCString address;
+ parse_address(address);
+ headerLine += address;
+ if (address.IsEmpty()) headerNonNil = false;
+ }
+ if (headerNonNil)
+ fServerConnection.HandleMessageDownLoadLine(headerLine.get(), false);
+
+ if (ContinueParse()) AdvanceToNextToken();
+ }
+ // Now we should be at the end of the envelope and have *fToken == ')'.
+ // Skip this last parenthesis.
+ AdvanceToNextToken();
+}
+
+void nsImapServerResponseParser::xaolenvelope_data() {
+ // eat the opening '('
+ fNextToken++;
+
+ if (ContinueParse() && (*fNextToken != ')')) {
+ AdvanceToNextToken();
+ fNextToken++; // eat '('
+ nsAutoCString subject;
+ subject.Adopt(CreateNilString());
+ nsAutoCString subjectLine("Subject: ");
+ subjectLine += subject;
+ fServerConnection.HandleMessageDownLoadLine(subjectLine.get(), false);
+ fNextToken++; // eat the next '('
+ if (ContinueParse()) {
+ AdvanceToNextToken();
+ if (ContinueParse()) {
+ nsAutoCString fromLine;
+ if (!strcmp(GetSelectedMailboxName(), "Sent Items")) {
+ // xaol envelope switches the From with the To, so we switch them back
+ // and create a fake from line From: user@aol.com
+ fromLine.AppendLiteral("To: ");
+ nsAutoCString fakeFromLine("From: "_ns);
+ fakeFromLine.Append(fServerConnection.GetImapUserName());
+ fakeFromLine.AppendLiteral("@aol.com");
+ fServerConnection.HandleMessageDownLoadLine(fakeFromLine.get(),
+ false);
+ } else {
+ fromLine.AppendLiteral("From: ");
+ }
+ parse_address(fromLine);
+ fServerConnection.HandleMessageDownLoadLine(fromLine.get(), false);
+ if (ContinueParse()) {
+ AdvanceToNextToken(); // ge attachment size
+ int32_t attachmentSize = atoi(fNextToken);
+ if (attachmentSize != 0) {
+ nsAutoCString attachmentLine("X-attachment-size: ");
+ attachmentLine.AppendInt(attachmentSize);
+ fServerConnection.HandleMessageDownLoadLine(attachmentLine.get(),
+ false);
+ }
+ }
+ if (ContinueParse()) {
+ AdvanceToNextToken(); // skip image size
+ int32_t imageSize = atoi(fNextToken);
+ if (imageSize != 0) {
+ nsAutoCString imageLine("X-image-size: ");
+ imageLine.AppendInt(imageSize);
+ fServerConnection.HandleMessageDownLoadLine(imageLine.get(), false);
+ }
+ }
+ if (ContinueParse()) AdvanceToNextToken(); // skip )
+ }
+ }
+ }
+}
+
+void nsImapServerResponseParser::parse_address(nsAutoCString& addressLine) {
+ // NOTE: Not sure this function correctly handling group address syntax.
+ // See Bug 1609846.
+ if (!strcmp(fNextToken, "NIL")) return;
+ bool firstAddress = true;
+ // should really look at chars here
+ NS_ASSERTION(*fNextToken == '(', "address should start with '('");
+ fNextToken++; // eat the next '('
+ while (ContinueParse() && *fNextToken == '(') {
+ NS_ASSERTION(*fNextToken == '(', "address should start with '('");
+ fNextToken++; // eat the next '('
+
+ if (!firstAddress) addressLine += ", ";
+
+ firstAddress = false;
+ char* personalName = CreateNilString();
+ AdvanceToNextToken();
+ char* atDomainList = CreateNilString();
+ if (ContinueParse()) {
+ AdvanceToNextToken();
+ char* mailboxName = CreateNilString();
+ if (ContinueParse()) {
+ AdvanceToNextToken();
+ char* hostName = CreateNilString();
+ AdvanceToNextToken();
+ if (mailboxName) {
+ addressLine += mailboxName;
+ }
+ if (hostName) {
+ addressLine += '@';
+ addressLine += hostName;
+ PR_Free(hostName);
+ }
+ if (personalName) {
+ addressLine += " (";
+ addressLine += personalName;
+ addressLine += ')';
+ }
+ }
+ PR_Free(mailboxName);
+ }
+ PR_Free(personalName);
+ PR_Free(atDomainList);
+
+ if (*fNextToken == ')') fNextToken++;
+ // if the next token isn't a ')' for the address term,
+ // then we must have another address pair left....so get the next
+ // token and continue parsing in this loop...
+ if (*fNextToken == '\0') AdvanceToNextToken();
+ }
+ if (*fNextToken == ')') fNextToken++;
+ // AdvanceToNextToken(); // skip "))"
+}
+
+void nsImapServerResponseParser::internal_date() {
+ AdvanceToNextToken();
+ if (ContinueParse()) {
+ nsAutoCString dateLine("Date: ");
+ char* strValue = CreateNilString();
+ if (strValue) {
+ dateLine += strValue;
+ free(strValue);
+ }
+ fServerConnection.HandleMessageDownLoadLine(dateLine.get(), false);
+ }
+ // advance the parser.
+ AdvanceToNextToken();
+}
+
+void nsImapServerResponseParser::flags() {
+ imapMessageFlagsType messageFlags = kNoImapMsgFlag;
+ fCustomFlags.Clear();
+
+ // clear the custom flags for this message
+ // otherwise the old custom flags will stay around
+ // see bug #191042
+ if (fFlagState && CurrentResponseUID() != nsMsgKey_None)
+ fFlagState->ClearCustomFlags(CurrentResponseUID());
+
+ // eat the opening '('
+ fNextToken++;
+ while (ContinueParse() && (*fNextToken != ')')) {
+ bool knownFlag = false;
+ if (*fNextToken == '\\') {
+ switch (NS_ToUpper(fNextToken[1])) {
+ case 'S':
+ if (!PL_strncasecmp(fNextToken, "\\Seen", 5)) {
+ messageFlags |= kImapMsgSeenFlag;
+ knownFlag = true;
+ }
+ break;
+ case 'A':
+ if (!PL_strncasecmp(fNextToken, "\\Answered", 9)) {
+ messageFlags |= kImapMsgAnsweredFlag;
+ knownFlag = true;
+ }
+ break;
+ case 'F':
+ if (!PL_strncasecmp(fNextToken, "\\Flagged", 8)) {
+ messageFlags |= kImapMsgFlaggedFlag;
+ knownFlag = true;
+ }
+ break;
+ case 'D':
+ if (!PL_strncasecmp(fNextToken, "\\Deleted", 8)) {
+ messageFlags |= kImapMsgDeletedFlag;
+ knownFlag = true;
+ } else if (!PL_strncasecmp(fNextToken, "\\Draft", 6)) {
+ messageFlags |= kImapMsgDraftFlag;
+ knownFlag = true;
+ }
+ break;
+ case 'R':
+ if (!PL_strncasecmp(fNextToken, "\\Recent", 7)) {
+ messageFlags |= kImapMsgRecentFlag;
+ knownFlag = true;
+ }
+ break;
+ default:
+ break;
+ }
+ } else if (*fNextToken == '$') {
+ switch (NS_ToUpper(fNextToken[1])) {
+ case 'M':
+ if ((fSupportsUserDefinedFlags &
+ (kImapMsgSupportUserFlag | kImapMsgSupportMDNSentFlag)) &&
+ !PL_strncasecmp(fNextToken, "$MDNSent", 8)) {
+ messageFlags |= kImapMsgMDNSentFlag;
+ knownFlag = true;
+ }
+ break;
+ case 'F':
+ if ((fSupportsUserDefinedFlags &
+ (kImapMsgSupportUserFlag | kImapMsgSupportForwardedFlag)) &&
+ !PL_strncasecmp(fNextToken, "$Forwarded", 10)) {
+ messageFlags |= kImapMsgForwardedFlag;
+ knownFlag = true;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ if (!knownFlag && fFlagState) {
+ nsAutoCString flag(fNextToken);
+ int32_t parenIndex = flag.FindChar(')');
+ if (parenIndex > 0) flag.SetLength(parenIndex);
+ messageFlags |= kImapMsgCustomKeywordFlag;
+ if (CurrentResponseUID() != nsMsgKey_None && CurrentResponseUID() != 0)
+ fFlagState->AddUidCustomFlagPair(CurrentResponseUID(), flag.get());
+ else
+ fCustomFlags.AppendElement(flag);
+ }
+ if (PL_strcasestr(fNextToken, ")")) {
+ // eat token chars until we get the ')'
+ while (*fNextToken != ')') fNextToken++;
+ } else
+ AdvanceToNextToken();
+ }
+
+ if (ContinueParse())
+ while (*fNextToken != ')') fNextToken++;
+
+ fCurrentLineContainedFlagInfo = true; // handled in PostProcessEndOfLine
+ fSavedFlagInfo = messageFlags;
+}
+
+// RFC3501: resp-cond-state = ("OK" / "NO" / "BAD") SP resp-text
+// ; Status condition
+void nsImapServerResponseParser::resp_cond_state(bool isTagged) {
+ // According to RFC3501, Sec. 7.1, the untagged NO response "indicates a
+ // warning; the command can still complete successfully."
+ // However, the untagged BAD response "indicates a protocol-level error for
+ // which the associated command can not be determined; it can also indicate an
+ // internal server failure."
+ // Thus, we flag an error for a tagged NO response and for any BAD response.
+ if ((isTagged && !PL_strcasecmp(fNextToken, "NO")) ||
+ !PL_strcasecmp(fNextToken, "BAD"))
+ fCurrentCommandFailed = true;
+
+ AdvanceToNextToken();
+ if (ContinueParse()) resp_text();
+}
+
+/*
+resp_text ::= ["[" resp_text_code "]" SPACE] (text_mime2 / text)
+
+ was changed to in order to enable a one symbol look ahead predictive
+ parser.
+
+ resp_text ::= ["[" resp_text_code SPACE] (text_mime2 / text)
+*/
+void nsImapServerResponseParser::resp_text() {
+ if (ContinueParse() && (*fNextToken == '[')) resp_text_code();
+
+ if (ContinueParse()) {
+ if (!PL_strcmp(fNextToken, "=?"))
+ text_mime2();
+ else
+ text();
+ }
+}
+/*
+ text_mime2 ::= "=?" <charset> "?" <encoding> "?"
+ <encoded-text> "?="
+ ;; Syntax defined in [MIME-2]
+*/
+void nsImapServerResponseParser::text_mime2() { skip_to_CRLF(); }
+
+/*
+ text ::= 1*TEXT_CHAR
+
+*/
+void nsImapServerResponseParser::text() { skip_to_CRLF(); }
+
+void nsImapServerResponseParser::parse_folder_flags(bool calledForFlags) {
+ uint16_t junkNotJunkFlags = 0;
+
+ do {
+ AdvanceToNextToken();
+ if (*fNextToken == '(') fNextToken++;
+ if (!PL_strncasecmp(fNextToken, "\\Seen", 5))
+ fSettablePermanentFlags |= kImapMsgSeenFlag;
+ else if (!PL_strncasecmp(fNextToken, "\\Answered", 9))
+ fSettablePermanentFlags |= kImapMsgAnsweredFlag;
+ else if (!PL_strncasecmp(fNextToken, "\\Flagged", 8))
+ fSettablePermanentFlags |= kImapMsgFlaggedFlag;
+ else if (!PL_strncasecmp(fNextToken, "\\Deleted", 8))
+ fSettablePermanentFlags |= kImapMsgDeletedFlag;
+ else if (!PL_strncasecmp(fNextToken, "\\Draft", 6))
+ fSettablePermanentFlags |= kImapMsgDraftFlag;
+ else if (!PL_strncasecmp(fNextToken, "\\*", 2)) {
+ // User defined and special keywords (tags) can be defined and set for
+ // mailbox. Should only occur in PERMANENTFLAGS response.
+ fSupportsUserDefinedFlags |= kImapMsgSupportUserFlag;
+ fSupportsUserDefinedFlags |= kImapMsgSupportForwardedFlag;
+ fSupportsUserDefinedFlags |= kImapMsgSupportMDNSentFlag;
+ }
+ // Treat special and built-in $LabelX's as user defined and include
+ // $Junk/$NotJunk too.
+ else if (!PL_strncasecmp(fNextToken, "$MDNSent", 8))
+ fSupportsUserDefinedFlags |= kImapMsgSupportMDNSentFlag;
+ else if (!PL_strncasecmp(fNextToken, "$Forwarded", 10))
+ fSupportsUserDefinedFlags |= kImapMsgSupportForwardedFlag;
+ else if (!PL_strncasecmp(fNextToken, "$Junk", 5))
+ junkNotJunkFlags |= 1;
+ else if (!PL_strncasecmp(fNextToken, "$NotJunk", 8))
+ junkNotJunkFlags |= 2;
+ } while (!fAtEndOfLine && ContinueParse());
+
+ if (fFlagState) fFlagState->OrSupportedUserFlags(fSupportsUserDefinedFlags);
+
+ if (calledForFlags) {
+ // Set true if both "$Junk" and "$NotJunk" appear in FLAGS.
+ fStdJunkNotJunkUseOk = (junkNotJunkFlags == 3);
+ }
+}
+/*
+ resp_text_code ::= ("ALERT" / "PARSE" /
+ "PERMANENTFLAGS" SPACE "(" #(flag / "\*") ")" /
+ "READ-ONLY" / "READ-WRITE" / "TRYCREATE" /
+ "UIDVALIDITY" SPACE nz_number /
+ "UNSEEN" SPACE nz_number /
+ "HIGHESTMODSEQ" SPACE nz_number /
+ "NOMODSEQ" /
+ atom [SPACE 1*<any TEXT_CHAR except "]">] )
+ "]"
+
+
+*/
+void nsImapServerResponseParser::resp_text_code() {
+ // this is a special case way of advancing the token
+ // strtok won't break up "[ALERT]" into separate tokens
+ if (strlen(fNextToken) > 1)
+ fNextToken++;
+ else
+ AdvanceToNextToken();
+
+ if (ContinueParse()) {
+ if (!PL_strcasecmp(fNextToken, "ALERT]")) {
+ char* alertMsg = fCurrentTokenPlaceHolder; // advance past ALERT]
+ if (alertMsg && *alertMsg &&
+ (!fLastAlert || PL_strcmp(fNextToken, fLastAlert))) {
+ fServerConnection.AlertUserEvent(alertMsg);
+ PR_Free(fLastAlert);
+ fLastAlert = PL_strdup(alertMsg);
+ }
+ AdvanceToNextToken();
+ } else if (!PL_strcasecmp(fNextToken, "PARSE]")) {
+ // do nothing for now
+ AdvanceToNextToken();
+ } else if (!PL_strcasecmp(fNextToken, "NETSCAPE]")) {
+ skip_to_CRLF();
+ } else if (!PL_strcasecmp(fNextToken, "PERMANENTFLAGS")) {
+ uint32_t saveSettableFlags = fSettablePermanentFlags;
+ fSupportsUserDefinedFlags = 0; // assume no unless told
+ fSettablePermanentFlags = 0; // assume none, unless told otherwise.
+ parse_folder_flags(false);
+ // if the server tells us there are no permanent flags, we're
+ // just going to pretend that the FLAGS response flags, if any, are
+ // permanent in case the server is broken. This will allow us
+ // to store delete and seen flag changes - if they're not permanent,
+ // they're not permanent, but at least we'll try to set them.
+ if (!fSettablePermanentFlags) fSettablePermanentFlags = saveSettableFlags;
+ fGotPermanentFlags = true;
+ } else if (!PL_strcasecmp(fNextToken, "READ-ONLY]")) {
+ fCurrentFolderReadOnly = true;
+ AdvanceToNextToken();
+ } else if (!PL_strcasecmp(fNextToken, "READ-WRITE]")) {
+ fCurrentFolderReadOnly = false;
+ AdvanceToNextToken();
+ } else if (!PL_strcasecmp(fNextToken, "TRYCREATE]")) {
+ // do nothing for now
+ AdvanceToNextToken();
+ } else if (!PL_strcasecmp(fNextToken, "UIDVALIDITY")) {
+ AdvanceToNextToken();
+ if (ContinueParse()) {
+ fFolderUIDValidity = strtoul(fNextToken, nullptr, 10);
+ fHighestRecordedUID = 0;
+ AdvanceToNextToken();
+ }
+ } else if (!PL_strcasecmp(fNextToken, "UNSEEN")) {
+ AdvanceToNextToken();
+ if (ContinueParse()) {
+ // Note: As a response code, "UNSEEN" is NOT the number of
+ // unseen/unread messages. It is the lowest sequence number of the first
+ // unseen/unread message in the mailbox.
+ fSeqNumOfFirstUnseenMsg = strtoul(fNextToken, nullptr, 10);
+ AdvanceToNextToken();
+ }
+ } else if (!PL_strcasecmp(fNextToken, "UIDNEXT")) {
+ AdvanceToNextToken();
+ if (ContinueParse()) {
+ fNextUID = strtoul(fNextToken, nullptr, 10);
+ AdvanceToNextToken();
+ }
+ } else if (!PL_strcasecmp(fNextToken, "APPENDUID")) {
+ AdvanceToNextToken();
+ if (ContinueParse()) {
+ // ** jt -- the returned uidvalidity is the destination folder
+ // uidvalidity; don't use it for current folder
+ // fFolderUIDValidity = atoi(fNextToken);
+ // fHighestRecordedUID = 0; ??? this should be wrong
+ AdvanceToNextToken();
+ if (ContinueParse()) {
+ fCurrentResponseUID = strtoul(fNextToken, nullptr, 10);
+ AdvanceToNextToken();
+ }
+ }
+ } else if (!PL_strcasecmp(fNextToken, "COPYUID")) {
+ AdvanceToNextToken();
+ if (ContinueParse()) {
+ // ** jt -- destination folder uidvalidity
+ // fFolderUIDValidity = atoi(fNextToken);
+ // original message set; ignore it
+ AdvanceToNextToken();
+ if (ContinueParse()) {
+ // the resulting message set; should be in the form of
+ // either uid or uid1:uid2
+ AdvanceToNextToken();
+ // clear copy response uid
+ fServerConnection.SetCopyResponseUid(fNextToken);
+ }
+ if (ContinueParse()) AdvanceToNextToken();
+ }
+ } else if (!PL_strcasecmp(fNextToken, "HIGHESTMODSEQ")) {
+ AdvanceToNextToken();
+ if (ContinueParse()) {
+ fHighestModSeq = ParseUint64Str(fNextToken);
+ fUseModSeq = true;
+ AdvanceToNextToken();
+ }
+ } else if (!PL_strcasecmp(fNextToken, "NOMODSEQ]")) {
+ fHighestModSeq = 0;
+ fUseModSeq = false;
+ skip_to_CRLF();
+ } else if (!PL_strcasecmp(fNextToken, "CAPABILITY")) {
+ capability_data();
+ } else if (!PL_strcasecmp(fNextToken, "MYRIGHTS")) {
+ myrights_data(true);
+ } else // just text
+ {
+ // do nothing but eat tokens until we see the ] or CRLF
+ // we should see the ] but we don't want to go into an
+ // endless loop if the CRLF is not there
+ do {
+ AdvanceToNextToken();
+ } while (!PL_strcasestr(fNextToken, "]") && !fAtEndOfLine &&
+ ContinueParse());
+ }
+ }
+}
+
+// RFC3501: response-done = response-tagged / response-fatal
+void nsImapServerResponseParser::response_done() {
+ if (ContinueParse()) {
+ if (!PL_strcmp(fCurrentCommandTag, fNextToken))
+ response_tagged();
+ else
+ response_fatal();
+ }
+}
+
+// RFC3501: response-tagged = tag SP resp-cond-state CRLF
+void nsImapServerResponseParser::response_tagged() {
+ // eat the tag
+ AdvanceToNextToken();
+ if (ContinueParse()) {
+ resp_cond_state(true);
+ if (ContinueParse()) {
+ if (!fAtEndOfLine)
+ SetSyntaxError(true);
+ else if (!fCurrentCommandFailed)
+ ResetLexAnalyzer();
+ }
+ }
+}
+
+// RFC3501: response-fatal = "*" SP resp-cond-bye CRLF
+// ; Server closes connection immediately
+void nsImapServerResponseParser::response_fatal() {
+ // eat the "*"
+ AdvanceToNextToken();
+ if (ContinueParse()) resp_cond_bye();
+}
+
+// RFC3501: resp-cond-bye = "BYE" SP resp-text
+void nsImapServerResponseParser::resp_cond_bye() {
+ SetConnected(false);
+ fIMAPstate = kNonAuthenticated;
+}
+
+void nsImapServerResponseParser::msg_fetch_headers(const char* partNum) {
+ msg_fetch_content(false, 0, MESSAGE_RFC822);
+}
+
+/* nstring ::= string / nil
+string ::= quoted / literal
+nil ::= "NIL"
+
+*/
+void nsImapServerResponseParser::msg_fetch_content(bool chunk, int32_t origin,
+ const char* content_type) {
+ // setup the stream for downloading this message.
+ // Note: no longer concerned with body shell issues since now we only fetch
+ // full message.
+ if ((!chunk || (origin == 0)) && !GetDownloadingHeaders()) {
+ if (NS_FAILED(BeginMessageDownload(content_type))) return;
+ }
+
+ if (PL_strcasecmp(fNextToken, "NIL")) {
+ if (*fNextToken == '"')
+ fLastChunk = msg_fetch_quoted();
+ else
+ fLastChunk = msg_fetch_literal(chunk, origin);
+ } else
+ AdvanceToNextToken(); // eat "NIL"
+
+ if (fLastChunk) {
+ // complete the message download
+ if (ContinueParse()) {
+ if (fReceivedHeaderOrSizeForUID == CurrentResponseUID()) {
+ fServerConnection.NormalMessageEndDownload();
+ fReceivedHeaderOrSizeForUID = nsMsgKey_None;
+ } else
+ fReceivedHeaderOrSizeForUID = CurrentResponseUID();
+ } else
+ fServerConnection.AbortMessageDownLoad();
+ }
+}
+
+void nsImapServerResponseParser::mime_part_data() {
+ char* checkOriginToken = PL_strdup(fNextToken);
+ if (checkOriginToken) {
+ uint32_t origin = 0;
+ bool originFound = false;
+ char* whereStart = PL_strchr(checkOriginToken, '<');
+ if (whereStart) {
+ char* whereEnd = PL_strchr(whereStart, '>');
+ if (whereEnd) {
+ *whereEnd = 0;
+ whereStart++;
+ origin = atoi(whereStart);
+ originFound = true;
+ }
+ }
+ PR_Free(checkOriginToken);
+ AdvanceToNextToken();
+ msg_fetch_content(originFound, origin,
+ MESSAGE_RFC822); // keep content type as message/rfc822,
+ // even though the
+ // MIME part might not be, because then libmime will
+ // still handle and decode it.
+ } else
+ HandleMemoryFailure();
+}
+
+/*
+quoted ::= <"> *QUOTED_CHAR <">
+
+ QUOTED_CHAR ::= <any TEXT_CHAR except quoted_specials> /
+ "\" quoted_specials
+
+ quoted_specials ::= <"> / "\"
+*/
+
+bool nsImapServerResponseParser::msg_fetch_quoted() {
+ // *Should* never get a quoted string in response to a chunked download,
+ // but the RFCs don't forbid it
+ char* q = CreateQuoted();
+ if (q) {
+ numberOfCharsInThisChunk = PL_strlen(q);
+ fServerConnection.HandleMessageDownLoadLine(q, false, q);
+ PR_Free(q);
+ } else
+ numberOfCharsInThisChunk = 0;
+
+ AdvanceToNextToken();
+ bool lastChunk =
+ ((fServerConnection.GetCurFetchSize() == 0) ||
+ (numberOfCharsInThisChunk != fServerConnection.GetCurFetchSize()));
+ return lastChunk;
+}
+
+/* msg_obsolete ::= "COPY" / ("STORE" SPACE msg_fetch)
+;; OBSOLETE untagged data responses */
+void nsImapServerResponseParser::msg_obsolete() {
+ if (!PL_strcasecmp(fNextToken, "COPY"))
+ AdvanceToNextToken();
+ else if (!PL_strcasecmp(fNextToken, "STORE")) {
+ AdvanceToNextToken();
+ if (ContinueParse()) msg_fetch();
+ } else
+ SetSyntaxError(true);
+}
+
+void nsImapServerResponseParser::capability_data() {
+ int32_t endToken = -1;
+ fCapabilityFlag = kCapabilityDefined | kHasAuthOldLoginCapability;
+ do {
+ AdvanceToNextToken();
+ if (fNextToken) {
+ nsCString token(fNextToken);
+ endToken = token.FindChar(']');
+ if (endToken >= 0) token.SetLength(endToken);
+
+ if (token.Equals("AUTH=LOGIN", nsCaseInsensitiveCStringComparator))
+ fCapabilityFlag |= kHasAuthLoginCapability;
+ else if (token.Equals("AUTH=PLAIN", nsCaseInsensitiveCStringComparator))
+ fCapabilityFlag |= kHasAuthPlainCapability;
+ else if (token.Equals("AUTH=CRAM-MD5",
+ nsCaseInsensitiveCStringComparator))
+ fCapabilityFlag |= kHasCRAMCapability;
+ else if (token.Equals("AUTH=NTLM", nsCaseInsensitiveCStringComparator))
+ fCapabilityFlag |= kHasAuthNTLMCapability;
+ else if (token.Equals("AUTH=GSSAPI", nsCaseInsensitiveCStringComparator))
+ fCapabilityFlag |= kHasAuthGssApiCapability;
+ else if (token.Equals("AUTH=MSN", nsCaseInsensitiveCStringComparator))
+ fCapabilityFlag |= kHasAuthMSNCapability;
+ else if (token.Equals("AUTH=EXTERNAL",
+ nsCaseInsensitiveCStringComparator))
+ fCapabilityFlag |= kHasAuthExternalCapability;
+ else if (token.Equals("AUTH=XOAUTH2", nsCaseInsensitiveCStringComparator))
+ fCapabilityFlag |= kHasXOAuth2Capability;
+ else if (token.Equals("STARTTLS", nsCaseInsensitiveCStringComparator))
+ fCapabilityFlag |= kHasStartTLSCapability;
+ else if (token.Equals("LOGINDISABLED",
+ nsCaseInsensitiveCStringComparator))
+ fCapabilityFlag &= ~kHasAuthOldLoginCapability; // remove flag
+ else if (token.Equals("XSENDER", nsCaseInsensitiveCStringComparator))
+ fCapabilityFlag |= kHasXSenderCapability;
+ else if (token.Equals("IMAP4", nsCaseInsensitiveCStringComparator))
+ fCapabilityFlag |= kIMAP4Capability;
+ else if (token.Equals("IMAP4rev1", nsCaseInsensitiveCStringComparator))
+ fCapabilityFlag |= kIMAP4rev1Capability;
+ else if (Substring(token, 0, 5)
+ .Equals("IMAP4", nsCaseInsensitiveCStringComparator))
+ fCapabilityFlag |= kIMAP4other;
+ else if (token.Equals("X-NO-ATOMIC-RENAME",
+ nsCaseInsensitiveCStringComparator))
+ fCapabilityFlag |= kNoHierarchyRename;
+ else if (token.Equals("X-NON-HIERARCHICAL-RENAME",
+ nsCaseInsensitiveCStringComparator))
+ fCapabilityFlag |= kNoHierarchyRename;
+ else if (token.Equals("NAMESPACE", nsCaseInsensitiveCStringComparator))
+ fCapabilityFlag |= kNamespaceCapability;
+ else if (token.Equals("ID", nsCaseInsensitiveCStringComparator))
+ fCapabilityFlag |= kHasIDCapability;
+ else if (token.Equals("ACL", nsCaseInsensitiveCStringComparator))
+ fCapabilityFlag |= kACLCapability;
+ else if (token.Equals("XSERVERINFO", nsCaseInsensitiveCStringComparator))
+ fCapabilityFlag |= kXServerInfoCapability;
+ else if (token.Equals("UIDPLUS", nsCaseInsensitiveCStringComparator))
+ fCapabilityFlag |= kUidplusCapability;
+ else if (token.Equals("LITERAL+", nsCaseInsensitiveCStringComparator))
+ fCapabilityFlag |= kLiteralPlusCapability;
+ else if (token.Equals("XAOL-OPTION", nsCaseInsensitiveCStringComparator))
+ fCapabilityFlag |= kAOLImapCapability;
+ else if (token.Equals("X-GM-EXT-1", nsCaseInsensitiveCStringComparator))
+ fCapabilityFlag |= kGmailImapCapability;
+ else if (token.Equals("QUOTA", nsCaseInsensitiveCStringComparator))
+ fCapabilityFlag |= kQuotaCapability;
+ else if (token.Equals("LANGUAGE", nsCaseInsensitiveCStringComparator))
+ fCapabilityFlag |= kHasLanguageCapability;
+ else if (token.Equals("IDLE", nsCaseInsensitiveCStringComparator))
+ fCapabilityFlag |= kHasIdleCapability;
+ else if (token.Equals("CONDSTORE", nsCaseInsensitiveCStringComparator))
+ fCapabilityFlag |= kHasCondStoreCapability;
+ else if (token.Equals("ENABLE", nsCaseInsensitiveCStringComparator))
+ fCapabilityFlag |= kHasEnableCapability;
+ else if (token.Equals("LIST-EXTENDED",
+ nsCaseInsensitiveCStringComparator))
+ fCapabilityFlag |= kHasListExtendedCapability;
+ else if (token.Equals("XLIST", nsCaseInsensitiveCStringComparator))
+ fCapabilityFlag |= kHasXListCapability;
+ else if (token.Equals("SPECIAL-USE", nsCaseInsensitiveCStringComparator))
+ fCapabilityFlag |= kHasSpecialUseCapability;
+ else if (token.Equals("COMPRESS=DEFLATE",
+ nsCaseInsensitiveCStringComparator))
+ fCapabilityFlag |= kHasCompressDeflateCapability;
+ else if (token.Equals("MOVE", nsCaseInsensitiveCStringComparator))
+ fCapabilityFlag |= kHasMoveCapability;
+ else if (token.Equals("HIGHESTMODSEQ",
+ nsCaseInsensitiveCStringComparator))
+ fCapabilityFlag |= kHasHighestModSeqCapability;
+ else if (token.Equals("CLIENTID", nsCaseInsensitiveCStringComparator))
+ fCapabilityFlag |= kHasClientIDCapability;
+ else if (token.Equals("UTF8=ACCEPT",
+ nsCaseInsensitiveCStringComparator) ||
+ token.Equals("UTF8=ONLY", nsCaseInsensitiveCStringComparator))
+ fCapabilityFlag |= kHasUTF8AcceptCapability;
+ }
+ } while (fNextToken && endToken < 0 && !fAtEndOfLine && ContinueParse());
+
+ nsImapProtocol* navCon = &fServerConnection;
+ NS_ASSERTION(navCon,
+ "null imap protocol connection while parsing capability "
+ "response"); // we should always have this
+ if (navCon) {
+ navCon->CommitCapability();
+ fServerConnection.SetCapabilityResponseOccurred();
+ }
+ skip_to_CRLF();
+}
+
+void nsImapServerResponseParser::xmailboxinfo_data() {
+ AdvanceToNextToken();
+ if (!fNextToken) return;
+
+ char* mailboxName = CreateAstring(); // PL_strdup(fNextToken);
+ if (mailboxName) {
+ do {
+ AdvanceToNextToken();
+ if (fNextToken) {
+ if (!PL_strcmp("MANAGEURL", fNextToken)) {
+ AdvanceToNextToken();
+ fFolderAdminUrl = CreateAstring();
+ } else if (!PL_strcmp("POSTURL", fNextToken)) {
+ AdvanceToNextToken();
+ // ignore this for now...
+ }
+ }
+ } while (fNextToken && !fAtEndOfLine && ContinueParse());
+ }
+}
+
+void nsImapServerResponseParser::xserverinfo_data() {
+ do {
+ AdvanceToNextToken();
+ if (!fNextToken) break;
+ if (!PL_strcmp("MANAGEACCOUNTURL", fNextToken)) {
+ AdvanceToNextToken();
+ fMailAccountUrl.Adopt(CreateNilString());
+ } else if (!PL_strcmp("MANAGELISTSURL", fNextToken)) {
+ AdvanceToNextToken();
+ fManageListsUrl.Adopt(CreateNilString());
+ } else if (!PL_strcmp("MANAGEFILTERSURL", fNextToken)) {
+ AdvanceToNextToken();
+ fManageFiltersUrl.Adopt(CreateNilString());
+ }
+ } while (fNextToken && !fAtEndOfLine && ContinueParse());
+}
+
+void nsImapServerResponseParser::enable_data() {
+ do {
+ // eat each enable response;
+ AdvanceToNextToken();
+ if (!PL_strcasecmp("UTF8=ACCEPT", fNextToken)) fUtf8AcceptEnabled = true;
+ } while (fNextToken && !fAtEndOfLine && ContinueParse());
+}
+
+void nsImapServerResponseParser::language_data() {
+ // we may want to go out and store the language returned to us
+ // by the language command in the host info session stuff.
+
+ // for now, just eat the language....
+ do {
+ // eat each language returned to us
+ AdvanceToNextToken();
+ } while (fNextToken && !fAtEndOfLine && ContinueParse());
+}
+
+// cram/auth response data ::= "+" SPACE challenge CRLF
+// the server expects more client data after issuing its challenge
+
+void nsImapServerResponseParser::authChallengeResponse_data() {
+ AdvanceToNextToken();
+ fAuthChallenge = strdup(fNextToken);
+ fWaitingForMoreClientInput = true;
+
+ skip_to_CRLF();
+}
+
+void nsImapServerResponseParser::namespace_data() {
+ EIMAPNamespaceType namespaceType = kPersonalNamespace;
+ bool namespacesCommitted = false;
+ const char* serverKey = fServerConnection.GetImapServerKey();
+ while ((namespaceType != kUnknownNamespace) && ContinueParse()) {
+ AdvanceToNextToken();
+ while (fAtEndOfLine && ContinueParse()) AdvanceToNextToken();
+ if (!PL_strcasecmp(fNextToken, "NIL")) {
+ // No namespace for this type.
+ // Don't add anything to the Namespace object.
+ } else if (fNextToken[0] == '(') {
+ // There may be multiple namespaces of the same type.
+ // Go through each of them and add them to our Namespace object.
+
+ fNextToken++;
+ while (fNextToken[0] == '(' && ContinueParse()) {
+ // we have another namespace for this namespace type
+ fNextToken++;
+ if (fNextToken[0] != '"') {
+ SetSyntaxError(true);
+ } else {
+ char* namespacePrefix = CreateQuoted(false);
+
+ AdvanceToNextToken();
+ const char* quotedDelimiter = fNextToken;
+ char namespaceDelimiter = '\0';
+
+ if (quotedDelimiter[0] == '"') {
+ quotedDelimiter++;
+ namespaceDelimiter = quotedDelimiter[0];
+ } else if (!PL_strncasecmp(quotedDelimiter, "NIL", 3)) {
+ // NIL hierarchy delimiter. Leave namespace delimiter nullptr.
+ } else {
+ // not quoted or NIL.
+ SetSyntaxError(true);
+ }
+ if (ContinueParse()) {
+ // Add code to parse the TRANSLATE attribute if it is present....
+ // we'll also need to expand the name space code to take in the
+ // translated prefix name.
+
+ // Add it to a temporary list in the host.
+ if (fHostSessionList) {
+ nsImapNamespace* newNamespace = new nsImapNamespace(
+ namespaceType, namespacePrefix, namespaceDelimiter, false);
+ fHostSessionList->AddNewNamespaceForHost(serverKey, newNamespace);
+ }
+
+ skip_to_close_paren(); // Ignore any extension data
+
+ bool endOfThisNamespaceType = (fNextToken[0] == ')');
+ if (!endOfThisNamespaceType &&
+ fNextToken[0] !=
+ '(') // no space between namespaces of the same type
+ {
+ SetSyntaxError(true);
+ }
+ }
+ PR_Free(namespacePrefix);
+ }
+ }
+ } else {
+ SetSyntaxError(true);
+ }
+ switch (namespaceType) {
+ case kPersonalNamespace:
+ namespaceType = kOtherUsersNamespace;
+ break;
+ case kOtherUsersNamespace:
+ namespaceType = kPublicNamespace;
+ break;
+ default:
+ namespaceType = kUnknownNamespace;
+ break;
+ }
+ }
+ if (ContinueParse()) {
+ nsImapProtocol* navCon = &fServerConnection;
+ NS_ASSERTION(
+ navCon,
+ "null protocol connection while parsing namespace"); // we should
+ // always have
+ // this
+ if (navCon) {
+ navCon->CommitNamespacesForHostEvent();
+ namespacesCommitted = true;
+ }
+ }
+ skip_to_CRLF();
+
+ if (!namespacesCommitted && fHostSessionList) {
+ bool success;
+ fHostSessionList->FlushUncommittedNamespacesForHost(serverKey, success);
+ }
+}
+
+void nsImapServerResponseParser::myrights_data(bool unsolicited) {
+ AdvanceToNextToken();
+ if (ContinueParse() && !fAtEndOfLine) {
+ char* mailboxName;
+ // an unsolicited myrights response won't have the mailbox name in
+ // the response, so we use the selected mailbox name.
+ if (unsolicited) {
+ mailboxName = strdup(fSelectedMailboxName);
+ } else {
+ mailboxName = CreateAstring();
+ if (mailboxName) AdvanceToNextToken();
+ }
+ if (mailboxName) {
+ if (ContinueParse()) {
+ char* myrights = CreateAstring();
+ if (myrights) {
+ nsImapProtocol* navCon = &fServerConnection;
+ NS_ASSERTION(
+ navCon, "null connection parsing my rights"); // we should always
+ // have this
+ if (navCon)
+ navCon->AddFolderRightsForUser(mailboxName,
+ nullptr /* means "me" */, myrights);
+ PR_Free(myrights);
+ } else {
+ HandleMemoryFailure();
+ }
+ if (ContinueParse()) AdvanceToNextToken();
+ }
+ PR_Free(mailboxName);
+ } else {
+ HandleMemoryFailure();
+ }
+ } else {
+ SetSyntaxError(true);
+ }
+}
+
+void nsImapServerResponseParser::acl_data() {
+ AdvanceToNextToken();
+ if (ContinueParse() && !fAtEndOfLine) {
+ char* mailboxName = CreateAstring(); // PL_strdup(fNextToken);
+ if (mailboxName && ContinueParse()) {
+ AdvanceToNextToken();
+ while (ContinueParse() && !fAtEndOfLine) {
+ char* userName = CreateAstring(); // PL_strdup(fNextToken);
+ if (userName && ContinueParse()) {
+ AdvanceToNextToken();
+ if (ContinueParse()) {
+ char* rights = CreateAstring(); // PL_strdup(fNextToken);
+ if (rights) {
+ fServerConnection.AddFolderRightsForUser(mailboxName, userName,
+ rights);
+ PR_Free(rights);
+ } else
+ HandleMemoryFailure();
+
+ if (ContinueParse()) AdvanceToNextToken();
+ }
+ PR_Free(userName);
+ } else
+ HandleMemoryFailure();
+ }
+ PR_Free(mailboxName);
+ } else
+ HandleMemoryFailure();
+ }
+}
+
+// RFC2087: quotaroot_response = "QUOTAROOT" SP astring *(SP astring)
+// quota_response = "QUOTA" SP astring SP quota_list
+// quota_list = "(" [quota_resource *(SP quota_resource)] ")"
+// quota_resource = atom SP number SP number
+// draft-melnikov-extra-quota-00 proposes some additions to RFC2087 and
+// improves the documentation. We still only support RFC2087 capability QUOTA
+// and command GETQUOTAROOT and its untagged QUOTAROOT and QUOTA responses.
+void nsImapServerResponseParser::quota_data() {
+ if (!PL_strcasecmp(fNextToken, "QUOTAROOT")) {
+ // Ignore QUOTAROOT response (except to invalidate previously stored data).
+ nsCString quotaroot;
+ AdvanceToNextToken();
+ while (ContinueParse() && !fAtEndOfLine) {
+ quotaroot.Adopt(CreateAstring());
+ AdvanceToNextToken();
+ }
+ // Invalidate any previously stored quota data. Updated QUOTA data follows.
+ fServerConnection.UpdateFolderQuotaData(kInvalidateQuota, quotaroot, 0, 0);
+ } else if (!PL_strcasecmp(fNextToken, "QUOTA")) {
+ // Should have one QUOTA response per QUOTAROOT.
+ uint64_t usage, limit;
+ AdvanceToNextToken();
+ if (ContinueParse()) {
+ nsCString quotaroot;
+ quotaroot.Adopt(CreateAstring());
+ nsCString resource;
+ AdvanceToNextToken();
+ if (fNextToken) {
+ if (fNextToken[0] == '(') fNextToken++;
+ // Should have zero or more "resource|usage|limit" triplet per quotaroot
+ // name. See draft-melnikov-extra-quota-00 for specific examples. Well
+ // known resources are STORAGE (in Kbytes), number of MESSAGEs and
+ // number of MAILBOXes. However, servers typically only set a quota on
+ // STORAGE in KBytes. A mailbox can have multiple quotaroots but
+ // typically only one and with a single resource.
+ while (ContinueParse() && !fAtEndOfLine) {
+ resource.Adopt(CreateAstring());
+ AdvanceToNextToken();
+ usage = atoll(fNextToken);
+ AdvanceToNextToken();
+ nsAutoCString limitToken(fNextToken);
+ if (fNextToken[strlen(fNextToken) - 1] == ')')
+ limitToken.SetLength(strlen(fNextToken) - 1);
+ limit = atoll(limitToken.get());
+ // Some servers don't define a quotaroot name which we displays as
+ // blank.
+ nsCString quotaRootResource(quotaroot);
+ if (!quotaRootResource.IsEmpty()) {
+ quotaRootResource.AppendLiteral(" / ");
+ }
+ quotaRootResource.Append(resource);
+ fServerConnection.UpdateFolderQuotaData(
+ kStoreQuota, quotaRootResource, usage, limit);
+ AdvanceToNextToken();
+ }
+ }
+ }
+ } else {
+ SetSyntaxError(true);
+ }
+}
+
+void nsImapServerResponseParser::id_data() {
+ AdvanceToNextToken();
+ if (!PL_strcasecmp(fNextToken, "NIL"))
+ AdvanceToNextToken();
+ else
+ fServerIdResponse.Adopt(CreateParenGroup());
+ skip_to_CRLF();
+}
+
+bool nsImapServerResponseParser::GetDownloadingHeaders() {
+ return fDownloadingHeaders;
+}
+
+void nsImapServerResponseParser::ResetCapabilityFlag() {}
+
+/*
+ literal ::= "{" number "}" CRLF *CHAR8
+ Number represents the number of CHAR8 octets
+ */
+
+// Processes a message body, header or message part fetch response. Typically
+// the full message, header or part are processed in one call (effectively, one
+// chunk), and parameter `chunk` is false and `origin` (offset into the
+// response) is 0. But under some conditions and larger messages, multiple calls
+// will occur to process the message in multiple chunks and parameter `chunk`
+// will be true and parameter `origin` will increase by the chunk size from
+// initially 0 with each call. This function returns true if this is the last or
+// only chunk. This signals the caller that the stream should be closed since
+// the message response has been processed.
+bool nsImapServerResponseParser::msg_fetch_literal(bool chunk, int32_t origin) {
+ numberOfCharsInThisChunk = atoi(fNextToken + 1);
+ // If we didn't request a specific size, or the server isn't returning exactly
+ // as many octets as we requested, this must be the last or only chunk
+ bool lastChunk = (!chunk || (numberOfCharsInThisChunk !=
+ fServerConnection.GetCurFetchSize()));
+
+ // clang-format off
+ if (lastChunk)
+ MOZ_LOG(IMAP, mozilla::LogLevel::Debug,
+ ("PARSER: msg_fetch_literal() chunking=%s, requested=%d, receiving=%d",
+ chunk ? "true":"false", fServerConnection.GetCurFetchSize(),
+ numberOfCharsInThisChunk));
+ // clang-format on
+
+ charsReadSoFar = 0;
+
+ while (ContinueParse() && !fServerConnection.DeathSignalReceived() &&
+ (charsReadSoFar < numberOfCharsInThisChunk)) {
+ AdvanceToNextLine();
+ if (ContinueParse()) {
+ // When "\r\n" (CRLF) is split across two chunks, the '\n' at the
+ // beginning of the next chunk might be set to an empty line consisting
+ // only of "\r\n". This is observed while running unit tests with the imap
+ // "fake" server. The unexpected '\r' is discarded here. However, with
+ // several real world servers tested, e.g., Dovecot, Gmail, Outlook, Yahoo
+ // etc., the leading
+ // '\r' is not inserted so the beginning line of the next chunk remains
+ // just '\n' and no discard is required.
+ // In any case, this "orphan" line is ignored and not processed below.
+ if (fNextChunkStartsWithNewline && (*fCurrentLine == '\r')) {
+ // Cause fCurrentLine to point to '\n' which discards the '\r'.
+ char* usableCurrentLine = PL_strdup(fCurrentLine + 1);
+ PR_Free(fCurrentLine);
+ fCurrentLine = usableCurrentLine;
+ }
+
+ // strlen() *would* fail on data containing \0, but the above
+ // AdvanceToNextLine() in nsMsgLineStreamBuffer::ReadNextLine() we replace
+ // '\0' with ' ' (blank) because who cares about binary transparency, and
+ // anyway \0 in this context violates RFCs.
+ charsReadSoFar += strlen(fCurrentLine);
+ if (!fDownloadingHeaders && fCurrentCommandIsSingleMessageFetch) {
+ fServerConnection.ProgressEventFunctionUsingName(
+ "imapDownloadingMessage");
+ if (fTotalDownloadSize > 0)
+ fServerConnection.PercentProgressUpdateEvent(
+ ""_ns, u""_ns, charsReadSoFar + origin, fTotalDownloadSize);
+ }
+ if (charsReadSoFar > numberOfCharsInThisChunk) {
+ // This is the last line of a chunk. "Literal" here means actual email
+ // data and its EOLs, without imap protocol elements and their EOLs. End
+ // of line is defined by two characters \r\n (i.e., CRLF, 0xd,0xa)
+ // specified by RFC822. Here is an example the most typical last good
+ // line of a chunk: "1s8AA5i4AAvF4QAG6+sAAD0bAPsAAAAA1OAAC)\r\n", where
+ // ")\r\n" are non-literals. This an example of the last "good" line of
+ // a chunk that terminates with \r\n
+ // "FxcA/wAAAALN2gADu80ACS0nAPpVVAD1wNAABF5YAPhAJgD31+QABAAAAP8oMQD+HBwA/umj\r\n"
+ // followed by another line of non-literal data:
+ // " UID 1004)\r\n". These two are concatenated into a single string
+ // pointed to by fCurrentLine. The extra "non-literal data" on the last
+ // chunk line makes the charsReadSoFar greater than
+ // numberOfCharsInThisChunk (the configured chunk size). A problem
+ // occurs if the \r\n of the long line above is split between chunks and
+ // \n is contained in the next chunk. For example, if last lines of
+ // chunk X are:
+ // "/gAOC/wA/QAAAAAAAAAA8wACCvz+AgIEAAD8/P4ABQUAAPoAAAD+AAEA/voHAAQGBQD/BAQA\r"
+ // ")\r\n"
+ // and the first two lines of chunk X+1 are:
+ // "\n"
+ // "APwAAAAAmZkA/wAAAAAREQD/AAAAAquVAAbk8QAHCBAAAPD0AAP5+wABRCoA+0BgAP0AAAAA\r\n"
+ // The missing '\n' on the last line of chunk X must be added back and
+ // the line consisting only of "\n" in chunk X+1 must be ignored in
+ // order to produce the the correct output. This is needed to insure
+ // that the signature verification of cryptographically signed emails
+ // does not fail due to missing or extra EOL characters. Otherwise, the
+ // extra or missing \n or \r doesn't really matter.
+ //
+ // Special case observed only with the "fake" imap server used with TB
+ // unit test. When the "\r\n" at the end of a chunk is split as
+ // described above, the \n at the beginning of the next chunk may
+ // actually be "\r\n" like this example: Last lines of chunk X
+ // "/gAOC/wA/QAAAAAAAAAA8wACCvz+AgIEAAD8/P4ABQUAAPoAAAD+AAEA/voHAAQGBQD/BAQA\r"
+ // ")\r\n"
+ // and the first two lines of chunk X+1:
+ // "\r\n" <-- The code changes this to just "\n" like it should be.
+ // "APwAAAAAmZkA/wAAAAAREQD/AAAAAquVAAbk8QAHCBAAAPD0AAP5+wABRCoA+0BgAP0AAAAA\r\n"
+ //
+ // Implementation:
+ // Obtain pointer to last literal in chunk X, e.g., 'C' in 1st example
+ // above, or to the \n or \r in the other examples.
+ char* displayEndOfLine =
+ (fCurrentLine + strlen(fCurrentLine) -
+ (charsReadSoFar - numberOfCharsInThisChunk + 1));
+ // Save so original unmodified fCurrentLine is restored below.
+ char saveit1 = displayEndOfLine[1];
+ char saveit2 = 0; // Keep compiler happy.
+ // Determine if EOL is split such that Chunk X has the \r and chunk
+ // X+1 has the \n.
+ fNextChunkStartsWithNewline = (displayEndOfLine[0] == '\r');
+ if (fNextChunkStartsWithNewline) {
+ saveit2 = displayEndOfLine[2];
+ // Add the missing newline and terminate the string.
+ displayEndOfLine[1] = '\n';
+ displayEndOfLine[2] = 0;
+ // This is a good thing to log.
+ MOZ_LOG(IMAP, mozilla::LogLevel::Info,
+ ("PARSER: CR/LF split at chunk boundary"));
+ } else {
+ // Typical case where EOLs are not split. Terminate the string.
+ displayEndOfLine[1] = 0;
+ }
+ // Process this modified string pointed to by fCurrentLine.
+ fServerConnection.HandleMessageDownLoadLine(fCurrentLine, !lastChunk);
+ // Restore fCurrentLine's original content.
+ displayEndOfLine[1] = saveit1;
+ if (fNextChunkStartsWithNewline) displayEndOfLine[2] = saveit2;
+ } else {
+ // Not the last line of a chunk.
+ bool processTheLine = true;
+ if (fNextChunkStartsWithNewline && origin > 0) {
+ // A split of the \r\n between chunks was detected. Ignore orphan \n
+ // on line by itself which can occur on the first line of a 2nd or
+ // later chunk. Line length should be 1 and the only character should
+ // be \n. Note: If previous message ended with just \r, don't expect
+ // the first chunk of a message (origin == 0) to begin with \n.
+ // (Typically, there is only one chunk required for a message or
+ // header response unless its size exceeds the chunking threshold.)
+ if (strlen(fCurrentLine) > 1 || fCurrentLine[0] != '\n') {
+ // In case expected orphan \n is not really there, go ahead and
+ // process the line. This should theoretically not occur but rarely,
+ // and for yet to be determined reasons, it does. Logging may help.
+ NS_WARNING(
+ "'\\n' is not the only character in this line as expected!");
+ MOZ_LOG(IMAP, mozilla::LogLevel::Debug,
+ ("PARSER: expecting just '\\n' but line is = |%s|",
+ fCurrentLine));
+ } else {
+ // Discard the line containing only \n.
+ processTheLine = false;
+ MOZ_LOG(IMAP, mozilla::LogLevel::Debug,
+ ("PARSER: discarding lone '\\n'"));
+ }
+ }
+ if (processTheLine) {
+ fServerConnection.HandleMessageDownLoadLine(
+ fCurrentLine,
+ !lastChunk && (charsReadSoFar == numberOfCharsInThisChunk),
+ fCurrentLine);
+ }
+ fNextChunkStartsWithNewline = false;
+ }
+ }
+ }
+
+ if (ContinueParse()) {
+ if (charsReadSoFar > numberOfCharsInThisChunk) {
+ // move the lexical analyzer state to the end of this message because this
+ // message fetch ends in the middle of this line.
+ AdvanceTokenizerStartingPoint(
+ strlen(fCurrentLine) - (charsReadSoFar - numberOfCharsInThisChunk));
+ AdvanceToNextToken();
+ } else {
+ skip_to_CRLF();
+ AdvanceToNextToken();
+ }
+ } else {
+ // Don't typically (maybe never?) see this.
+ fNextChunkStartsWithNewline = false;
+ }
+ return lastChunk;
+}
+
+bool nsImapServerResponseParser::CurrentFolderReadOnly() {
+ return fCurrentFolderReadOnly;
+}
+
+int32_t nsImapServerResponseParser::NumberOfMessages() {
+ return fNumberOfExistingMessages;
+}
+
+int32_t nsImapServerResponseParser::NumberOfRecentMessages() {
+ return fNumberOfRecentMessages;
+}
+
+int32_t nsImapServerResponseParser::FolderUID() { return fFolderUIDValidity; }
+
+void nsImapServerResponseParser::SetCurrentResponseUID(uint32_t uid) {
+ if (uid > 0) fCurrentResponseUID = uid;
+}
+
+uint32_t nsImapServerResponseParser::CurrentResponseUID() {
+ return fCurrentResponseUID;
+}
+
+uint32_t nsImapServerResponseParser::HighestRecordedUID() {
+ return fHighestRecordedUID;
+}
+
+void nsImapServerResponseParser::ResetHighestRecordedUID() {
+ fHighestRecordedUID = 0;
+}
+
+bool nsImapServerResponseParser::IsNumericString(const char* string) {
+ int i;
+ for (i = 0; i < (int)PL_strlen(string); i++) {
+ if (!isdigit(string[i])) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+// Capture the mailbox state for folder select/update and for status.
+// If mailboxName is null, we've done imap SELECT; otherwise STATUS.
+already_AddRefed<nsImapMailboxSpec>
+nsImapServerResponseParser::CreateCurrentMailboxSpec(
+ const char* mailboxName /* = nullptr */) {
+ RefPtr<nsImapMailboxSpec> returnSpec = new nsImapMailboxSpec;
+ const char* mailboxNameToConvert =
+ (mailboxName) ? mailboxName : fSelectedMailboxName;
+ if (mailboxNameToConvert) {
+ const char* serverKey = fServerConnection.GetImapServerKey();
+ nsImapNamespace* ns = nullptr;
+ if (serverKey && fHostSessionList)
+ fHostSessionList->GetNamespaceForMailboxForHost(
+ serverKey, mailboxNameToConvert, ns); // for
+ // delimiter
+ returnSpec->mHierarchySeparator = (ns) ? ns->GetDelimiter() : '/';
+ }
+
+ returnSpec->mFolderSelected = !mailboxName;
+ returnSpec->mFolder_UIDVALIDITY = fFolderUIDValidity;
+ returnSpec->mHighestModSeq = fHighestModSeq;
+ // clang-format off
+ returnSpec->mNumOfMessages =
+ (mailboxName) ? fStatusExistingMessages : fNumberOfExistingMessages;
+ returnSpec->mNumOfUnseenMessages =
+ (mailboxName) ? fStatusUnseenMessages : -1;
+ returnSpec->mNumOfRecentMessages =
+ (mailboxName) ? fStatusRecentMessages : fNumberOfRecentMessages;
+ returnSpec->mNextUID =
+ (mailboxName) ? fStatusNextUID : fNextUID;
+ // clang-format on
+
+ returnSpec->mSupportedUserFlags = fSupportsUserDefinedFlags;
+
+ returnSpec->mBoxFlags = kNoFlags; // stub
+ returnSpec->mOnlineVerified = false; // Fabricated. Flags aren't verified.
+ returnSpec->mAllocatedPathName.Assign(mailboxNameToConvert);
+ returnSpec->mConnection = &fServerConnection;
+ if (returnSpec->mConnection) {
+ nsIURI* aUrl = nullptr;
+ nsresult rv = NS_OK;
+ returnSpec->mConnection->GetCurrentUrl()->QueryInterface(NS_GET_IID(nsIURI),
+ (void**)&aUrl);
+ if (NS_SUCCEEDED(rv) && aUrl) aUrl->GetHost(returnSpec->mHostName);
+
+ NS_IF_RELEASE(aUrl);
+ } else
+ returnSpec->mHostName.Truncate();
+
+ if (fFlagState)
+ returnSpec->mFlagState = fFlagState; // copies flag state
+ else
+ returnSpec->mFlagState = nullptr;
+
+ return returnSpec.forget();
+}
+// Reset the flag state.
+void nsImapServerResponseParser::ResetFlagInfo() {
+ if (fFlagState) fFlagState->Reset();
+}
+
+bool nsImapServerResponseParser::GetLastFetchChunkReceived() {
+ return fLastChunk;
+}
+
+void nsImapServerResponseParser::ClearLastFetchChunkReceived() {
+ fLastChunk = false;
+}
+
+int32_t nsImapServerResponseParser::GetNumBytesFetched() {
+ return numberOfCharsInThisChunk;
+}
+
+void nsImapServerResponseParser::ClearNumBytesFetched() {
+ numberOfCharsInThisChunk = 0;
+}
+
+void nsImapServerResponseParser::SetHostSessionList(
+ nsIImapHostSessionList* aHostSessionList) {
+ fHostSessionList = aHostSessionList;
+}
+
+void nsImapServerResponseParser::SetSyntaxError(bool error, const char* msg) {
+ nsImapGenericParser::SetSyntaxError(error, msg);
+ if (error) {
+ if (!fCurrentLine) {
+ HandleMemoryFailure();
+ fServerConnection.Log("PARSER", ("Internal Syntax Error: %s: <no line>"),
+ msg);
+ } else {
+ if (!strcmp(fCurrentLine, CRLF))
+ fServerConnection.Log("PARSER", "Internal Syntax Error: %s: <CRLF>",
+ msg);
+ else {
+ if (msg)
+ fServerConnection.Log("PARSER", "Internal Syntax Error: %s:", msg);
+ fServerConnection.Log("PARSER", "Internal Syntax Error on line: %s",
+ fCurrentLine);
+ }
+ }
+ }
+}
+
+nsresult nsImapServerResponseParser::BeginMessageDownload(
+ const char* content_type) {
+ nsresult rv = fServerConnection.BeginMessageDownLoad(fSizeOfMostRecentMessage,
+ content_type);
+ if (NS_FAILED(rv)) {
+ skip_to_CRLF();
+ fServerConnection.PseudoInterrupt(true);
+ fServerConnection.AbortMessageDownLoad();
+ }
+ return rv;
+}
diff --git a/comm/mailnews/imap/src/nsImapServerResponseParser.h b/comm/mailnews/imap/src/nsImapServerResponseParser.h
new file mode 100644
index 0000000000..43d6a73611
--- /dev/null
+++ b/comm/mailnews/imap/src/nsImapServerResponseParser.h
@@ -0,0 +1,275 @@
+/* -*- 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 _nsIMAPServerResponseParser_H_
+#define _nsIMAPServerResponseParser_H_
+
+#include "mozilla/Attributes.h"
+#include "../public/nsIImapHostSessionList.h"
+#include "nsImapSearchResults.h"
+#include "nsString.h"
+#include "MailNewsTypes.h"
+#include "nsTArray.h"
+#include "nsImapUtils.h"
+
+class nsImapSearchResultIterator;
+class nsIImapFlagAndUidState;
+
+#include "nsImapGenericParser.h"
+
+class nsImapServerResponseParser : public nsImapGenericParser {
+ public:
+ explicit nsImapServerResponseParser(nsImapProtocol& imapConnection);
+ virtual ~nsImapServerResponseParser();
+
+ // Overridden from the base parser class
+ virtual bool LastCommandSuccessful() override;
+ virtual void HandleMemoryFailure() override;
+
+ // aignoreBadAndNOResponses --> don't throw a error dialog if this command
+ // results in a NO or Bad response from the server..in other words the command
+ // is "exploratory" and we don't really care if it succeeds or fails. This
+ // value is typically FALSE for almost all cases.
+ virtual void ParseIMAPServerResponse(const char* aCurrentCommand,
+ bool aIgnoreBadAndNOResponses,
+ char* aGreetingWithCapability = NULL);
+ virtual void InitializeState();
+ bool CommandFailed();
+ void SetCommandFailed(bool failed);
+ bool UntaggedResponse();
+
+ enum eIMAPstate { kNonAuthenticated, kAuthenticated, kFolderSelected };
+
+ virtual eIMAPstate GetIMAPstate();
+ virtual bool WaitingForMoreClientInput() {
+ return fWaitingForMoreClientInput;
+ }
+ const char* GetSelectedMailboxName(); // can be NULL
+ bool IsStdJunkNotJunkUseOk() { return fStdJunkNotJunkUseOk; }
+
+ // if we get a PREAUTH greeting from the server, initialize the parser to
+ // begin in the kAuthenticated state
+ void PreauthSetAuthenticatedState();
+
+ // these functions represent the state of the currently selected
+ // folder
+ bool CurrentFolderReadOnly();
+ int32_t NumberOfMessages();
+ int32_t NumberOfRecentMessages();
+ int32_t FolderUID();
+ uint32_t CurrentResponseUID();
+ uint32_t HighestRecordedUID();
+ void ResetHighestRecordedUID();
+ void SetCurrentResponseUID(uint32_t uid);
+ bool IsNumericString(const char* string);
+ uint32_t SizeOfMostRecentMessage();
+ void SetTotalDownloadSize(int32_t newSize) { fTotalDownloadSize = newSize; }
+
+ nsImapSearchResultIterator* CreateSearchResultIterator();
+ void ResetSearchResultSequence() { fSearchResults->ResetSequence(); }
+
+ // create a struct mailbox_spec from our info, used in
+ // libmsg c interface
+ already_AddRefed<nsImapMailboxSpec> CreateCurrentMailboxSpec(
+ const char* mailboxName = nullptr);
+
+ // Resets the flags state.
+ void ResetFlagInfo();
+
+ // set this to false if you don't want to alert the user to server
+ // error messages
+ void SetReportingErrors(bool reportThem) { fReportingErrors = reportThem; }
+ bool GetReportingErrors() { return fReportingErrors; }
+
+ eIMAPCapabilityFlags GetCapabilityFlag() { return fCapabilityFlag; }
+ void SetCapabilityFlag(eIMAPCapabilityFlags capability) {
+ fCapabilityFlag = capability;
+ }
+ bool ServerHasIMAP4Rev1Capability() {
+ return ((fCapabilityFlag & kIMAP4rev1Capability) != 0);
+ }
+ bool ServerHasACLCapability() {
+ return ((fCapabilityFlag & kACLCapability) != 0);
+ }
+ bool ServerHasNamespaceCapability() {
+ return ((fCapabilityFlag & kNamespaceCapability) != 0);
+ }
+ bool ServerIsNetscape3xServer() { return fServerIsNetscape3xServer; }
+ bool ServerHasServerInfo() {
+ return ((fCapabilityFlag & kXServerInfoCapability) != 0);
+ }
+ bool ServerIsAOLServer() {
+ return ((fCapabilityFlag & kAOLImapCapability) != 0);
+ }
+ void SetFetchingFlags(bool aFetchFlags) { fFetchingAllFlags = aFetchFlags; }
+ void ResetCapabilityFlag();
+
+ nsCString& GetMailAccountUrl() { return fMailAccountUrl; }
+ const char* GetXSenderInfo() { return fXSenderInfo; }
+ void FreeXSenderInfo() { PR_FREEIF(fXSenderInfo); }
+ nsCString& GetManageListsUrl() { return fManageListsUrl; }
+ nsCString& GetManageFiltersUrl() { return fManageFiltersUrl; }
+ const char* GetManageFolderUrl() { return fFolderAdminUrl; }
+ nsCString& GetServerID() { return fServerIdResponse; }
+
+ // Call this when adding a pipelined command to the session
+ void IncrementNumberOfTaggedResponsesExpected(const char* newExpectedTag);
+
+ // Interrupt a Fetch, without really Interrupting (through netlib)
+ bool GetLastFetchChunkReceived();
+ void ClearLastFetchChunkReceived();
+ int32_t GetNumBytesFetched();
+ void ClearNumBytesFetched();
+ virtual uint16_t SupportsUserFlags() { return fSupportsUserDefinedFlags; }
+ virtual uint16_t SettablePermanentFlags() { return fSettablePermanentFlags; }
+ void SetFlagState(nsIImapFlagAndUidState* state);
+ bool GetDownloadingHeaders();
+ void SetHostSessionList(nsIImapHostSessionList* aHostSession);
+ char* fAuthChallenge; // the challenge returned by the server in
+ // response to authenticate using CRAM-MD5 or NTLM
+ bool fUtf8AcceptEnabled;
+ bool fUseModSeq; // can use mod seq for currently selected folder
+ uint64_t fHighestModSeq;
+
+ protected:
+ virtual void flags();
+ virtual void envelope_data();
+ virtual void xaolenvelope_data();
+ virtual void parse_address(nsAutoCString& addressLine);
+ virtual void internal_date();
+ virtual nsresult BeginMessageDownload(const char* content_type);
+
+ virtual void response_data();
+ virtual void resp_text();
+ virtual void resp_cond_state(bool isTagged);
+ virtual void text_mime2();
+ virtual void text();
+ virtual void parse_folder_flags(bool calledForFlags);
+ virtual void enable_data();
+ virtual void language_data();
+ virtual void authChallengeResponse_data();
+ virtual void resp_text_code();
+ virtual void response_done();
+ virtual void response_tagged();
+ virtual void response_fatal();
+ virtual void resp_cond_bye();
+ virtual void id_data();
+ virtual void mailbox_data();
+ virtual void numeric_mailbox_data();
+ virtual void capability_data();
+ virtual void xserverinfo_data();
+ virtual void xmailboxinfo_data();
+ virtual void namespace_data();
+ virtual void myrights_data(bool unsolicited);
+ virtual void acl_data();
+ virtual void mime_part_data();
+ virtual void quota_data();
+ virtual void msg_fetch();
+ virtual void msg_obsolete();
+ virtual void msg_fetch_headers(const char* partNum);
+ virtual void msg_fetch_content(bool chunk, int32_t origin,
+ const char* content_type);
+ virtual bool msg_fetch_quoted();
+ virtual bool msg_fetch_literal(bool chunk, int32_t origin);
+ virtual void mailbox_list(bool discoveredFromLsub);
+ virtual void mailbox(nsImapMailboxSpec* boxSpec);
+
+ virtual void ProcessOkCommand(const char* commandToken);
+ virtual void ProcessBadCommand(const char* commandToken);
+ virtual void PreProcessCommandToken(const char* commandToken,
+ const char* currentCommand);
+ virtual void PostProcessEndOfLine();
+
+ // Overridden from the nsImapGenericParser, to retrieve the next line
+ // from the open socket.
+ virtual bool GetNextLineForParser(char** nextLine) override;
+ // overridden to do logging
+ virtual void SetSyntaxError(bool error, const char* msg = nullptr) override;
+
+ private:
+ bool fCurrentCommandFailed;
+ bool fUntaggedResponse;
+ bool fReportingErrors;
+
+ bool fCurrentFolderReadOnly;
+ bool fCurrentLineContainedFlagInfo;
+ bool fFetchingAllFlags;
+ bool fWaitingForMoreClientInput;
+ // Is the server a Netscape 3.x Messaging Server?
+ bool fServerIsNetscape3xServer;
+ bool fDownloadingHeaders;
+ bool fCurrentCommandIsSingleMessageFetch;
+ bool fGotPermanentFlags;
+ bool fStdJunkNotJunkUseOk;
+ imapMessageFlagsType fSavedFlagInfo;
+ nsTArray<nsCString> fCustomFlags;
+
+ uint16_t fSupportsUserDefinedFlags;
+ uint16_t fSettablePermanentFlags;
+
+ int32_t fFolderUIDValidity;
+ int32_t fSeqNumOfFirstUnseenMsg;
+ int32_t fNumberOfExistingMessages;
+ int32_t fNumberOfRecentMessages;
+ uint32_t fCurrentResponseUID;
+ uint32_t fHighestRecordedUID;
+ // used to handle server that sends msg size after headers
+ uint32_t fReceivedHeaderOrSizeForUID;
+ int32_t fSizeOfMostRecentMessage;
+ int32_t fTotalDownloadSize;
+
+ int32_t fStatusUnseenMessages;
+ int32_t fStatusRecentMessages;
+ uint32_t fStatusNextUID;
+ int32_t fStatusExistingMessages;
+ uint32_t fNextUID;
+
+ int fNumberOfTaggedResponsesExpected;
+
+ char* fCurrentCommandTag;
+
+ nsCString fZeroLengthMessageUidString;
+
+ char* fSelectedMailboxName;
+
+ nsImapSearchResultSequence* fSearchResults;
+
+ nsCOMPtr<nsIImapFlagAndUidState>
+ fFlagState; // NOT owned by us, it's a copy, do not destroy
+
+ eIMAPstate fIMAPstate;
+
+ eIMAPCapabilityFlags fCapabilityFlag;
+ nsCString fMailAccountUrl;
+ char* fNetscapeServerVersionString;
+ char* fXSenderInfo; /* changed per message download */
+ char* fLastAlert; /* used to avoid displaying the same alert over and over */
+ char* fMsgID; /* MessageID for Gmail only (X-GM-MSGID) */
+ char* fThreadID; /* ThreadID for Gmail only (X-GM-THRID) */
+ char* fLabels; /* Labels for Gmail only (X-GM-LABELS) [will include parens,
+ removed while passing to hashTable ]*/
+ nsCString fManageListsUrl;
+ nsCString fManageFiltersUrl;
+ char* fFolderAdminUrl;
+ nsCString fServerIdResponse; // RFC
+
+ int32_t fFetchResponseIndex;
+
+ // used for aborting a fetch stream when we're pseudo-Interrupted
+ int32_t numberOfCharsInThisChunk;
+ int32_t charsReadSoFar;
+ bool fLastChunk;
+
+ // Flags split of \r and \n between chunks in msg_fetch_literal().
+ bool fNextChunkStartsWithNewline;
+
+ // The connection object
+ nsImapProtocol& fServerConnection;
+
+ RefPtr<nsIImapHostSessionList> fHostSessionList;
+ nsTArray<nsMsgKey> fCopyResponseKeyArray;
+};
+
+#endif
diff --git a/comm/mailnews/imap/src/nsImapService.cpp b/comm/mailnews/imap/src/nsImapService.cpp
new file mode 100644
index 0000000000..8c02d02dcd
--- /dev/null
+++ b/comm/mailnews/imap/src/nsImapService.cpp
@@ -0,0 +1,3091 @@
+/* -*- 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 "nsImapService.h"
+#include "nsImapCore.h"
+#include "netCore.h"
+
+#include "nsImapUrl.h"
+#include "nsCOMPtr.h"
+#include "nsIMsgFolder.h"
+#include "nsIMsgImapMailFolder.h"
+#include "nsIImapIncomingServer.h"
+#include "nsIImapMailFolderSink.h"
+#include "nsIImapMessageSink.h"
+#include "nsIImapServerSink.h"
+#include "nsIImapMockChannel.h"
+#include "nsImapUtils.h"
+#include "nsImapNamespace.h"
+#include "nsIDocShell.h"
+#include "nsIProgressEventSink.h"
+#include "nsIPrefBranch.h"
+#include "nsIPrefService.h"
+#include "nsILoadGroup.h"
+#include "nsIMsgAccountManager.h"
+#include "nsMsgFolderFlags.h"
+#include "nsMailDirServiceDefs.h"
+#include "nsIWebNavigation.h"
+#include "nsImapStringBundle.h"
+#include "plbase64.h"
+#include "nsImapOfflineSync.h"
+#include "nsIMsgHdr.h"
+#include "nsMsgUtils.h"
+#include "nsICacheStorage.h"
+#include "nsICacheStorageService.h"
+#include "nsIStreamListener.h"
+#include "nsIUrlListener.h"
+#include "nsNetCID.h"
+#include "nsMsgI18N.h"
+#include "nsIOutputStream.h"
+#include "nsIInputStream.h"
+#include "nsMsgLineBuffer.h"
+#include "nsIMsgParseMailMsgState.h"
+#include "nsIOutputStream.h"
+#include "nsIDocShell.h"
+#include "nsIMessengerWindowService.h"
+#include "nsIWindowMediator.h"
+#include "nsIPrompt.h"
+#include "nsIWindowWatcher.h"
+#include "nsIMsgMailSession.h"
+#include "nsIStreamConverterService.h"
+#include "nsIAutoSyncManager.h"
+#include "nsThreadUtils.h"
+#include "nsNetUtil.h"
+#include "nsMsgMessageFlags.h"
+#include "nsIMsgPluggableStore.h"
+#include "../../base/src/MailnewsLoadContextInfo.h"
+#include "nsDocShellLoadState.h"
+#include "nsContentUtils.h"
+#include "mozilla/LoadInfo.h"
+
+#define PREF_MAIL_ROOT_IMAP_REL "mail.root.imap-rel"
+// old - for backward compatibility only
+#define PREF_MAIL_ROOT_IMAP "mail.root.imap"
+
+#define NS_IMAPURL_CID \
+ { \
+ 0x21a89611, 0xdc0d, 0x11d2, { \
+ 0x80, 0x6c, 0x0, 0x60, 0x8, 0x12, 0x8c, 0x4e \
+ } \
+ }
+static NS_DEFINE_CID(kImapUrlCID, NS_IMAPURL_CID);
+
+#define NS_IMAPMOCKCHANNEL_CID \
+ { \
+ 0x4eca51df, 0x6734, 0x11d3, { \
+ 0x98, 0x9a, 0x0, 0x10, 0x83, 0x1, 0xe, 0x9b \
+ } \
+ }
+static NS_DEFINE_CID(kCImapMockChannel, NS_IMAPMOCKCHANNEL_CID);
+
+static const char sequenceString[] = "SEQUENCE";
+static const char uidString[] = "UID";
+
+static bool gInitialized = false;
+
+NS_IMPL_ISUPPORTS(nsImapService, nsIImapService, nsIMsgMessageService,
+ nsIProtocolHandler, nsIMsgProtocolInfo,
+ nsIMsgMessageFetchPartService, nsIContentHandler)
+
+nsImapService::nsImapService() {
+ if (!gInitialized) {
+ nsresult rv;
+
+ nsCOMPtr<nsIIOService> ioServ = do_GetIOService();
+ ioServ->RegisterProtocolHandler(
+ "imap"_ns, this,
+ nsIProtocolHandler::URI_NORELATIVE |
+ nsIProtocolHandler::URI_FORBIDS_AUTOMATIC_DOCUMENT_REPLACEMENT |
+ nsIProtocolHandler::URI_DANGEROUS_TO_LOAD |
+ nsIProtocolHandler::ALLOWS_PROXY |
+ nsIProtocolHandler::URI_FORBIDS_COOKIE_ACCESS |
+ nsIProtocolHandler::ORIGIN_IS_FULL_SPEC,
+ nsIImapUrl::DEFAULT_IMAP_PORT);
+
+ // initialize auto-sync service
+ nsCOMPtr<nsIAutoSyncManager> autoSyncMgr =
+ do_GetService(NS_AUTOSYNCMANAGER_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv) && autoSyncMgr) {
+ // auto-sync manager initialization goes here
+ // assign new strategy objects here...
+ }
+ NS_ASSERTION(autoSyncMgr != nullptr,
+ "*** Cannot initialize nsAutoSyncManager service.");
+
+ gInitialized = true;
+ }
+}
+
+nsImapService::~nsImapService() {}
+
+char nsImapService::GetHierarchyDelimiter(nsIMsgFolder* aMsgFolder) {
+ char delimiter = '/';
+ if (aMsgFolder) {
+ nsCOMPtr<nsIMsgImapMailFolder> imapFolder = do_QueryInterface(aMsgFolder);
+ if (imapFolder) imapFolder->GetHierarchyDelimiter(&delimiter);
+ }
+ return delimiter;
+}
+
+// N.B., this returns an escaped folder name, appropriate for putting in a url.
+nsresult nsImapService::GetFolderName(nsIMsgFolder* aImapFolder,
+ nsACString& aFolderName) {
+ nsresult rv;
+ nsCOMPtr<nsIMsgImapMailFolder> aFolder(do_QueryInterface(aImapFolder, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCString onlineName;
+ // Online name is in MUTF-7 or UTF-8.
+ rv = aFolder->GetOnlineName(onlineName);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (onlineName.IsEmpty()) {
+ nsCString uri;
+ rv = aImapFolder->GetURI(uri);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCString hostname;
+ rv = aImapFolder->GetHostname(hostname);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = nsImapURI2FullName(kImapRootURI, hostname.get(), uri.get(),
+ getter_Copies(onlineName));
+ }
+ // if the hierarchy delimiter is not '/', then we want to escape slashes;
+ // otherwise, we do want to escape slashes.
+ // we want to escape slashes and '^' first, otherwise, nsEscape will lose them
+ bool escapeSlashes = (GetHierarchyDelimiter(aImapFolder) != '/');
+ if (escapeSlashes && !onlineName.IsEmpty()) {
+ char* escapedOnlineName;
+ rv = nsImapUrl::EscapeSlashes(onlineName.get(), &escapedOnlineName);
+ if (NS_SUCCEEDED(rv)) onlineName.Adopt(escapedOnlineName);
+ }
+ // need to escape everything else
+ MsgEscapeString(onlineName, nsINetUtil::ESCAPE_URL_PATH, aFolderName);
+ return rv;
+}
+
+NS_IMETHODIMP nsImapService::SelectFolder(nsIMsgFolder* aImapMailFolder,
+ nsIUrlListener* aUrlListener,
+ nsIMsgWindow* aMsgWindow,
+ nsIURI** aURL) {
+ NS_ENSURE_ARG_POINTER(aImapMailFolder);
+
+ if (WeAreOffline()) return NS_MSG_ERROR_OFFLINE;
+
+ bool canOpenThisFolder = true;
+ nsCOMPtr<nsIMsgImapMailFolder> imapFolder =
+ do_QueryInterface(aImapMailFolder);
+ if (imapFolder) imapFolder->GetCanOpenFolder(&canOpenThisFolder);
+
+ if (!canOpenThisFolder) return NS_OK;
+
+ nsresult rv;
+ nsCOMPtr<nsIImapUrl> imapUrl;
+ nsAutoCString urlSpec;
+ char hierarchyDelimiter = GetHierarchyDelimiter(aImapMailFolder);
+ rv = CreateStartOfImapUrl(EmptyCString(), getter_AddRefs(imapUrl),
+ aImapMailFolder, aUrlListener, urlSpec,
+ hierarchyDelimiter);
+
+ if (NS_SUCCEEDED(rv) && imapUrl) {
+ // nsImapUrl::SetSpec() will set the imap action properly
+ rv = imapUrl->SetImapAction(nsIImapUrl::nsImapSelectFolder);
+
+ nsCOMPtr<nsIMsgMailNewsUrl> mailNewsUrl = do_QueryInterface(imapUrl);
+ // if no msg window, we won't put up error messages (this is almost
+ // certainly a biff-inspired get new msgs)
+ if (!aMsgWindow) mailNewsUrl->SetSuppressErrorMsgs(true);
+ mailNewsUrl->SetMsgWindow(aMsgWindow);
+ mailNewsUrl->SetUpdatingFolder(true);
+ rv = SetImapUrlSink(aImapMailFolder, imapUrl);
+
+ if (NS_SUCCEEDED(rv)) {
+ nsAutoCString folderName;
+ GetFolderName(aImapMailFolder, folderName);
+ urlSpec.AppendLiteral("/select>");
+ urlSpec.Append(hierarchyDelimiter);
+ urlSpec.Append(folderName);
+ rv = mailNewsUrl->SetSpecInternal(urlSpec);
+ if (NS_SUCCEEDED(rv))
+ rv = GetImapConnectionAndLoadUrl(imapUrl, nullptr, aURL);
+ }
+ } // if we have a url to run....
+
+ return rv;
+}
+
+// lite select, used to verify UIDVALIDITY while going on/offline
+NS_IMETHODIMP nsImapService::LiteSelectFolder(nsIMsgFolder* aImapMailFolder,
+ nsIUrlListener* aUrlListener,
+ nsIMsgWindow* aMsgWindow,
+ nsIURI** aURL) {
+ NS_ENSURE_ARG_POINTER(aImapMailFolder);
+
+ return FolderCommand(aImapMailFolder, aUrlListener, "/liteselect>",
+ nsIImapUrl::nsImapLiteSelectFolder, aMsgWindow, aURL);
+}
+
+NS_IMETHODIMP nsImapService::GetUrlForUri(const nsACString& aMessageURI,
+ nsIMsgWindow* aMsgWindow,
+ nsIURI** aURL) {
+ nsAutoCString messageURI(aMessageURI);
+
+ if (messageURI.Find("&type=application/x-message-display"_ns) != kNotFound)
+ return NS_NewURI(aURL, aMessageURI);
+
+ nsCOMPtr<nsIMsgFolder> folder;
+ nsAutoCString msgKey;
+ nsresult rv = DecomposeImapURI(messageURI, getter_AddRefs(folder), msgKey);
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIImapUrl> imapUrl;
+ nsAutoCString urlSpec;
+ char hierarchyDelimiter = GetHierarchyDelimiter(folder);
+ rv = CreateStartOfImapUrl(messageURI, getter_AddRefs(imapUrl), folder,
+ nullptr, urlSpec, hierarchyDelimiter);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = SetImapUrlSink(folder, imapUrl);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(imapUrl);
+ bool useLocalCache = false;
+ folder->HasMsgOffline(strtoul(msgKey.get(), nullptr, 10), &useLocalCache);
+ mailnewsUrl->SetMsgIsInLocalCache(useLocalCache);
+
+ nsCOMPtr<nsIURI> url = do_QueryInterface(imapUrl);
+ rv = url->GetSpec(urlSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ urlSpec.AppendLiteral("fetch>UID>");
+ urlSpec.Append(hierarchyDelimiter);
+
+ nsAutoCString folderName;
+ GetFolderName(folder, folderName);
+ urlSpec.Append(folderName);
+ urlSpec.Append('>');
+ urlSpec.Append(msgKey);
+ rv = mailnewsUrl->SetSpecInternal(urlSpec);
+ imapUrl->QueryInterface(NS_GET_IID(nsIURI), (void**)aURL);
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP nsImapService::FetchMimePart(
+ nsIURI* aURI, const nsACString& aMessageURI, nsISupports* aDisplayConsumer,
+ nsIMsgWindow* aMsgWindow, nsIUrlListener* aUrlListener, nsIURI** aURL) {
+ nsCOMPtr<nsIMsgFolder> folder;
+ nsAutoCString messageURI(aMessageURI);
+ nsAutoCString msgKey;
+ nsAutoCString mimePart;
+ nsAutoCString folderURI;
+ nsMsgKey key;
+
+ nsresult rv = DecomposeImapURI(messageURI, getter_AddRefs(folder), msgKey);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = nsParseImapMessageURI(aMessageURI, folderURI, &key,
+ getter_Copies(mimePart));
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIImapMessageSink> imapMessageSink(
+ do_QueryInterface(folder, &rv));
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIImapUrl> imapUrl = do_QueryInterface(aURI);
+ nsCOMPtr<nsIMsgMailNewsUrl> msgurl(do_QueryInterface(aURI, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ msgurl->SetMsgWindow(aMsgWindow);
+ msgurl->RegisterListener(aUrlListener);
+
+ if (!mimePart.IsEmpty()) {
+ return FetchMimePart(imapUrl, nsIImapUrl::nsImapMsgFetch, folder,
+ imapMessageSink, aURL, aDisplayConsumer, msgKey,
+ mimePart);
+ }
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapService::LoadMessage(const nsACString& aMessageURI,
+ nsISupports* aDisplayConsumer,
+ nsIMsgWindow* aMsgWindow,
+ nsIUrlListener* aUrlListener,
+ bool aAutodetectCharset) {
+ nsresult rv;
+
+ nsCOMPtr<nsIMsgFolder> folder;
+ nsAutoCString msgKey;
+ nsAutoCString mimePart;
+ nsAutoCString folderURI;
+ nsMsgKey key;
+ nsAutoCString messageURI(aMessageURI);
+
+ int32_t typeIndex = messageURI.Find("&type=application/x-message-display");
+ if (typeIndex != kNotFound) {
+ // This happens with forward inline of a message/rfc822 attachment opened in
+ // a standalone msg window.
+ // So, just cut to the chase and call AsyncOpen on a channel.
+ nsCOMPtr<nsIURI> uri;
+ messageURI.Cut(typeIndex,
+ sizeof("&type=application/x-message-display") - 1);
+ rv = NS_NewURI(getter_AddRefs(uri), messageURI.get());
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIStreamListener> aStreamListener =
+ do_QueryInterface(aDisplayConsumer, &rv);
+ if (NS_SUCCEEDED(rv) && aStreamListener) {
+ nsCOMPtr<nsIChannel> aChannel;
+ nsCOMPtr<nsILoadGroup> aLoadGroup;
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(uri, &rv);
+ if (NS_SUCCEEDED(rv) && mailnewsUrl)
+ mailnewsUrl->GetLoadGroup(getter_AddRefs(aLoadGroup));
+
+ nsCOMPtr<nsILoadInfo> loadInfo = new mozilla::net::LoadInfo(
+ nsContentUtils::GetSystemPrincipal(), nullptr, nullptr,
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER);
+ rv = NewChannel(uri, loadInfo, getter_AddRefs(aChannel));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // now try to open the channel passing in our display consumer as the
+ // listener
+ rv = aChannel->AsyncOpen(aStreamListener);
+ return rv;
+ }
+ }
+
+ rv = DecomposeImapURI(messageURI, getter_AddRefs(folder), msgKey);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (msgKey.IsEmpty()) return NS_MSG_MESSAGE_NOT_FOUND;
+
+ rv = nsParseImapMessageURI(aMessageURI, folderURI, &key,
+ getter_Copies(mimePart));
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIImapMessageSink> imapMessageSink(
+ do_QueryInterface(folder, &rv));
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIImapUrl> imapUrl;
+ nsAutoCString urlSpec;
+ char hierarchyDelimiter = GetHierarchyDelimiter(folder);
+ rv = CreateStartOfImapUrl(messageURI, getter_AddRefs(imapUrl), folder,
+ aUrlListener, urlSpec, hierarchyDelimiter);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!mimePart.IsEmpty()) {
+ nsresult rv;
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsurl = do_QueryInterface(imapUrl);
+
+ rv = AddImapFetchToUrl(mailnewsurl, folder, msgKey + mimePart,
+ EmptyCString());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIURI> dummyURI;
+ return FetchMimePart(imapUrl, nsIImapUrl::nsImapMsgFetch, folder,
+ imapMessageSink, getter_AddRefs(dummyURI),
+ aDisplayConsumer, msgKey, mimePart);
+ }
+
+ nsCOMPtr<nsIMsgMailNewsUrl> msgurl(do_QueryInterface(imapUrl));
+ nsCOMPtr<nsIMsgI18NUrl> i18nurl(do_QueryInterface(imapUrl));
+ i18nurl->SetAutodetectCharset(aAutodetectCharset);
+
+ bool shouldStoreMsgOffline = false;
+ bool hasMsgOffline = false;
+
+ msgurl->SetMsgWindow(aMsgWindow);
+
+ if (folder) {
+ folder->ShouldStoreMsgOffline(key, &shouldStoreMsgOffline);
+ folder->HasMsgOffline(key, &hasMsgOffline);
+ }
+ imapUrl->SetStoreResultsOffline(shouldStoreMsgOffline);
+
+ if (hasMsgOffline) msgurl->SetMsgIsInLocalCache(true);
+
+ nsCOMPtr<nsIPrefBranch> prefBranch(
+ do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ // Should the message fetch force a peek or a traditional fetch?
+ // Force peek if there is a delay in marking read (or no auto-marking at
+ // all). This is because a FETCH (BODY[]) will implicitly set the \Seen
+ // flag on the msg, but a FETCH (BODY.PEEK[]) won't.
+ bool forcePeek = false;
+ if (NS_SUCCEEDED(rv) && prefBranch) {
+ nsAutoCString uriStr(aMessageURI);
+ int32_t dontMarkAsReadPos = uriStr.Find("&markRead=false");
+ bool markReadAuto = true;
+ prefBranch->GetBoolPref("mailnews.mark_message_read.auto",
+ &markReadAuto);
+ bool markReadDelay = false;
+ prefBranch->GetBoolPref("mailnews.mark_message_read.delay",
+ &markReadDelay);
+ forcePeek = (!markReadAuto || markReadDelay ||
+ (dontMarkAsReadPos != kNotFound));
+ }
+
+ if (!forcePeek) {
+ // If we're loading a message in an inactive docShell, don't let it
+ // be marked as read immediately.
+ nsCOMPtr<nsIDocShell> docShell =
+ do_QueryInterface(aDisplayConsumer, &rv);
+ if (NS_SUCCEEDED(rv) && docShell) {
+ auto* bc = docShell->GetBrowsingContext();
+ forcePeek = !bc->IsActive();
+ }
+ }
+
+ nsCOMPtr<nsIURI> dummyURI;
+ rv = FetchMessage(imapUrl,
+ forcePeek ? nsIImapUrl::nsImapMsgFetchPeek
+ : nsIImapUrl::nsImapMsgFetch,
+ folder, imapMessageSink, aMsgWindow, aDisplayConsumer,
+ msgKey, false, getter_AddRefs(dummyURI));
+ }
+ }
+ return rv;
+}
+
+nsresult nsImapService::FetchMimePart(
+ nsIImapUrl* aImapUrl, nsImapAction aImapAction,
+ nsIMsgFolder* aImapMailFolder, nsIImapMessageSink* aImapMessage,
+ nsIURI** aURL, nsISupports* aDisplayConsumer,
+ const nsACString& messageIdentifierList, const nsACString& mimePart) {
+ NS_ENSURE_ARG_POINTER(aImapUrl);
+ NS_ENSURE_ARG_POINTER(aImapMailFolder);
+ NS_ENSURE_ARG_POINTER(aImapMessage);
+
+ // create a protocol instance to handle the request.
+ // NOTE: once we start working with multiple connections, this step will be
+ // much more complicated...but for now just create a connection and process
+ // the request.
+ nsAutoCString urlSpec;
+ nsresult rv = SetImapUrlSink(aImapMailFolder, aImapUrl);
+ nsImapAction actionToUse = aImapAction;
+ if (actionToUse == nsImapUrl::nsImapOpenMimePart)
+ actionToUse = nsIImapUrl::nsImapMsgFetch;
+
+ nsCOMPtr<nsIMsgMailNewsUrl> msgurl(do_QueryInterface(aImapUrl));
+ if (aImapMailFolder && msgurl && !messageIdentifierList.IsEmpty()) {
+ bool useLocalCache = false;
+ aImapMailFolder->HasMsgOffline(
+ strtoul(PromiseFlatCString(messageIdentifierList).get(), nullptr, 10),
+ &useLocalCache);
+ msgurl->SetMsgIsInLocalCache(useLocalCache);
+ }
+ rv = aImapUrl->SetImapMessageSink(aImapMessage);
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIURI> url = do_QueryInterface(aImapUrl);
+ if (aURL) NS_IF_ADDREF(*aURL = url);
+
+ rv = url->GetSpec(urlSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = msgurl->SetSpecInternal(urlSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aImapUrl->SetImapAction(actionToUse /* nsIImapUrl::nsImapMsgFetch */);
+ if (aImapMailFolder && aDisplayConsumer) {
+ nsCOMPtr<nsIMsgIncomingServer> aMsgIncomingServer;
+ rv = aImapMailFolder->GetServer(getter_AddRefs(aMsgIncomingServer));
+ if (NS_SUCCEEDED(rv) && aMsgIncomingServer) {
+ bool interrupted;
+ nsCOMPtr<nsIImapIncomingServer> aImapServer(
+ do_QueryInterface(aMsgIncomingServer, &rv));
+ if (NS_SUCCEEDED(rv) && aImapServer)
+ aImapServer->PseudoInterruptMsgLoad(aImapMailFolder, nullptr,
+ &interrupted);
+ }
+ }
+ // if the display consumer is a docshell, then we should run the url in the
+ // docshell. otherwise, it should be a stream listener....so open a channel
+ // using AsyncRead and the provided stream listener....
+
+ nsCOMPtr<nsIDocShell> docShell(do_QueryInterface(aDisplayConsumer, &rv));
+ if (NS_SUCCEEDED(rv) && docShell) {
+ // DIRTY LITTLE HACK --> if we are opening an attachment we want the
+ // docshell to treat this load as if it were a user click event. Then the
+ // dispatching stuff will be much happier.
+ RefPtr<nsDocShellLoadState> loadState = new nsDocShellLoadState(url);
+ loadState->SetLoadFlags(aImapAction == nsImapUrl::nsImapOpenMimePart
+ ? nsIWebNavigation::LOAD_FLAGS_IS_LINK
+ : nsIWebNavigation::LOAD_FLAGS_NONE);
+ if (aImapAction == nsImapUrl::nsImapOpenMimePart)
+ loadState->SetLoadType(LOAD_LINK);
+ loadState->SetFirstParty(false);
+ loadState->SetTriggeringPrincipal(nsContentUtils::GetSystemPrincipal());
+ rv = docShell->LoadURI(loadState, false);
+ } else {
+ nsCOMPtr<nsIStreamListener> aStreamListener =
+ do_QueryInterface(aDisplayConsumer, &rv);
+ if (NS_SUCCEEDED(rv) && aStreamListener) {
+ nsCOMPtr<nsIChannel> aChannel;
+ nsCOMPtr<nsILoadGroup> loadGroup;
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl =
+ do_QueryInterface(aImapUrl, &rv);
+ if (NS_SUCCEEDED(rv) && mailnewsUrl)
+ mailnewsUrl->GetLoadGroup(getter_AddRefs(loadGroup));
+
+ nsCOMPtr<nsILoadInfo> loadInfo = new mozilla::net::LoadInfo(
+ nsContentUtils::GetSystemPrincipal(), nullptr, nullptr,
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER);
+ rv = NewChannel(url, loadInfo, getter_AddRefs(aChannel));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // we need a load group to hold onto the channel. When the request is
+ // finished, it'll get removed from the load group, and the channel will
+ // go away, which will free the load group.
+ if (!loadGroup) loadGroup = do_CreateInstance(NS_LOADGROUP_CONTRACTID);
+
+ aChannel->SetLoadGroup(loadGroup);
+
+ // now try to open the channel passing in our display consumer as the
+ // listener
+ rv = aChannel->AsyncOpen(aStreamListener);
+ } else // do what we used to do before
+ {
+ // I'd like to get rid of this code as I believe that we always get a
+ // docshell or stream listener passed into us in this method but i'm not
+ // sure yet... I'm going to use an assert for now to figure out if this
+ // is ever getting called
+#if defined(DEBUG_mscott) || defined(DEBUG_bienvenu)
+ NS_ERROR("oops...someone still is reaching this part of the code");
+#endif
+ rv = GetImapConnectionAndLoadUrl(aImapUrl, aDisplayConsumer, aURL);
+ }
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapService::CopyMessage(const nsACString& aSrcMailboxURI,
+ nsIStreamListener* aMailboxCopy,
+ bool moveMessage,
+ nsIUrlListener* aUrlListener,
+ nsIMsgWindow* aMsgWindow) {
+ NS_ENSURE_ARG_POINTER(aMailboxCopy);
+
+ nsresult rv;
+ nsCOMPtr<nsIMsgFolder> folder;
+ nsAutoCString msgKey;
+ rv = DecomposeImapURI(aSrcMailboxURI, getter_AddRefs(folder), msgKey);
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIImapMessageSink> imapMessageSink(
+ do_QueryInterface(folder, &rv));
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIImapUrl> imapUrl;
+ nsAutoCString urlSpec;
+ char hierarchyDelimiter = GetHierarchyDelimiter(folder);
+ bool hasMsgOffline = false;
+ nsMsgKey key = strtoul(msgKey.get(), nullptr, 10);
+
+ rv = CreateStartOfImapUrl(aSrcMailboxURI, getter_AddRefs(imapUrl), folder,
+ aUrlListener, urlSpec, hierarchyDelimiter);
+ if (folder) {
+ nsCOMPtr<nsIMsgMailNewsUrl> msgurl(do_QueryInterface(imapUrl));
+ folder->HasMsgOffline(key, &hasMsgOffline);
+ if (msgurl) msgurl->SetMsgIsInLocalCache(hasMsgOffline);
+ }
+ // now try to download the message
+ nsImapAction imapAction = nsIImapUrl::nsImapOnlineToOfflineCopy;
+ if (moveMessage) imapAction = nsIImapUrl::nsImapOnlineToOfflineMove;
+ nsCOMPtr<nsIURI> dummyURI;
+ rv =
+ FetchMessage(imapUrl, imapAction, folder, imapMessageSink, aMsgWindow,
+ aMailboxCopy, msgKey, false, getter_AddRefs(dummyURI));
+ } // if we got an imap message sink
+ } // if we decomposed the imap message
+ return rv;
+}
+
+NS_IMETHODIMP nsImapService::CopyMessages(
+ const nsTArray<nsMsgKey>& aKeys, nsIMsgFolder* srcFolder,
+ nsIStreamListener* aMailboxCopy, bool moveMessage,
+ nsIUrlListener* aUrlListener, nsIMsgWindow* aMsgWindow, nsIURI** aURL) {
+ NS_ENSURE_ARG_POINTER(aMailboxCopy);
+ NS_ENSURE_TRUE(!aKeys.IsEmpty(), NS_ERROR_INVALID_ARG);
+
+ nsresult rv;
+ nsCOMPtr<nsIMsgFolder> folder = srcFolder;
+ nsCOMPtr<nsIImapMessageSink> imapMessageSink(do_QueryInterface(folder, &rv));
+ if (NS_SUCCEEDED(rv)) {
+ // we generate the uri for the first message so that way on down the line,
+ // GetMessage in nsCopyMessageStreamListener will get an unescaped
+ // username and be able to find the msg hdr. See bug 259656 for details
+ nsCString uri;
+ srcFolder->GenerateMessageURI(aKeys[0], uri);
+
+ nsCString messageIds;
+ // TODO: AllocateImapUidString() maxes out at 950 keys or so... it
+ // updates the numKeys passed in, but here the resulting value is
+ // ignored. Does this need sorting out?
+ uint32_t numKeys = aKeys.Length();
+ AllocateImapUidString(aKeys.Elements(), numKeys, nullptr, messageIds);
+ nsCOMPtr<nsIImapUrl> imapUrl;
+ nsAutoCString urlSpec;
+ char hierarchyDelimiter = GetHierarchyDelimiter(folder);
+ rv = CreateStartOfImapUrl(uri, getter_AddRefs(imapUrl), folder,
+ aUrlListener, urlSpec, hierarchyDelimiter);
+ nsImapAction action;
+ if (moveMessage) // don't use ?: syntax here, it seems to break the Mac.
+ action = nsIImapUrl::nsImapOnlineToOfflineMove;
+ else
+ action = nsIImapUrl::nsImapOnlineToOfflineCopy;
+ imapUrl->SetCopyState(aMailboxCopy);
+ // now try to display the message
+ rv = FetchMessage(imapUrl, action, folder, imapMessageSink, aMsgWindow,
+ aMailboxCopy, messageIds, false, aURL);
+ // ### end of copy operation should know how to do the delete.if this is a
+ // move
+
+ } // if we got an imap message sink
+ return rv;
+}
+
+NS_IMETHODIMP nsImapService::Search(nsIMsgSearchSession* aSearchSession,
+ nsIMsgWindow* aMsgWindow,
+ nsIMsgFolder* aMsgFolder,
+ const nsACString& aSearchUri) {
+ NS_ENSURE_ARG_POINTER(aMsgFolder);
+ nsresult rv;
+
+ nsCOMPtr<nsIImapUrl> imapUrl;
+ nsCOMPtr<nsIUrlListener> urlListener = do_QueryInterface(aSearchSession, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString urlSpec;
+ char hierarchyDelimiter = GetHierarchyDelimiter(aMsgFolder);
+ rv = CreateStartOfImapUrl(EmptyCString(), getter_AddRefs(imapUrl), aMsgFolder,
+ urlListener, urlSpec, hierarchyDelimiter);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIMsgMailNewsUrl> msgurl(do_QueryInterface(imapUrl));
+
+ msgurl->SetMsgWindow(aMsgWindow);
+ msgurl->SetSearchSession(aSearchSession);
+ rv = SetImapUrlSink(aMsgFolder, imapUrl);
+
+ if (NS_SUCCEEDED(rv)) {
+ nsCString folderName;
+ GetFolderName(aMsgFolder, folderName);
+
+ nsCOMPtr<nsIMsgMailNewsUrl> mailNewsUrl = do_QueryInterface(imapUrl);
+ if (!aMsgWindow) mailNewsUrl->SetSuppressErrorMsgs(true);
+
+ urlSpec.AppendLiteral("/search>UID>");
+ urlSpec.Append(hierarchyDelimiter);
+ urlSpec.Append(folderName);
+ urlSpec.Append('>');
+ // escape aSearchUri so that IMAP special characters (i.e. '\')
+ // won't be replaced with '/' in NECKO.
+ // it will be unescaped in nsImapUrl::ParseUrl().
+ nsCString escapedSearchUri;
+
+ MsgEscapeString(aSearchUri, nsINetUtil::ESCAPE_XALPHAS, escapedSearchUri);
+ urlSpec.Append(escapedSearchUri);
+ rv = mailNewsUrl->SetSpecInternal(urlSpec);
+ if (NS_SUCCEEDED(rv))
+ rv = GetImapConnectionAndLoadUrl(imapUrl, nullptr, nullptr);
+ }
+ return rv;
+}
+
+// just a helper method to break down imap message URIs....
+nsresult nsImapService::DecomposeImapURI(const nsACString& aMessageURI,
+ nsIMsgFolder** aFolder,
+ nsACString& aMsgKey) {
+ nsMsgKey msgKey;
+ nsresult rv = DecomposeImapURI(aMessageURI, aFolder, &msgKey);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (msgKey) {
+ nsAutoCString messageIdString;
+ messageIdString.AppendInt(msgKey);
+ aMsgKey = messageIdString;
+ }
+
+ return rv;
+}
+
+// just a helper method to break down imap message URIs....
+nsresult nsImapService::DecomposeImapURI(const nsACString& aMessageURI,
+ nsIMsgFolder** aFolder,
+ nsMsgKey* aMsgKey) {
+ NS_ENSURE_ARG_POINTER(aFolder);
+ NS_ENSURE_ARG_POINTER(aMsgKey);
+
+ nsAutoCString folderURI;
+ nsresult rv = nsParseImapMessageURI(aMessageURI, folderURI, aMsgKey, nullptr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgFolder> folder;
+ rv = GetOrCreateFolder(folderURI, aFolder);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapService::SaveMessageToDisk(
+ const nsACString& aMessageURI, nsIFile* aFile, bool aAddDummyEnvelope,
+ nsIUrlListener* aUrlListener, nsIURI** aURL, bool canonicalLineEnding,
+ nsIMsgWindow* aMsgWindow) {
+ nsCOMPtr<nsIMsgFolder> folder;
+ nsCOMPtr<nsIImapUrl> imapUrl;
+ nsAutoCString msgKey;
+
+ nsresult rv = DecomposeImapURI(aMessageURI, getter_AddRefs(folder), msgKey);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool hasMsgOffline = false;
+
+ if (folder)
+ folder->HasMsgOffline(strtoul(msgKey.get(), nullptr, 10), &hasMsgOffline);
+
+ nsAutoCString urlSpec;
+ char hierarchyDelimiter = GetHierarchyDelimiter(folder);
+ rv = CreateStartOfImapUrl(aMessageURI, getter_AddRefs(imapUrl), folder,
+ aUrlListener, urlSpec, hierarchyDelimiter);
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIImapMessageSink> imapMessageSink(
+ do_QueryInterface(folder, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIMsgMessageUrl> msgUrl = do_QueryInterface(imapUrl, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ msgUrl->SetMessageFile(aFile);
+ msgUrl->SetAddDummyEnvelope(aAddDummyEnvelope);
+ msgUrl->SetCanonicalLineEnding(canonicalLineEnding);
+
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(msgUrl);
+ if (mailnewsUrl) mailnewsUrl->SetMsgIsInLocalCache(hasMsgOffline);
+
+ nsCOMPtr<nsIStreamListener> saveAsListener;
+ mailnewsUrl->GetSaveAsListener(aAddDummyEnvelope, aFile,
+ getter_AddRefs(saveAsListener));
+
+ return FetchMessage(imapUrl, nsIImapUrl::nsImapSaveMessageToDisk, folder,
+ imapMessageSink, aMsgWindow, saveAsListener, msgKey,
+ false, aURL);
+ }
+ return rv;
+}
+
+/* fetching RFC822 messages */
+/* imap4://HOST>fetch><UID>>MAILBOXPATH>x */
+/* 'x' is the message UID */
+/* will set the 'SEEN' flag */
+NS_IMETHODIMP nsImapService::AddImapFetchToUrl(
+ nsIMsgMailNewsUrl* aUrl, nsIMsgFolder* aImapMailFolder,
+ const nsACString& aMessageIdentifierList,
+ const nsACString& aAdditionalHeader) {
+ NS_ENSURE_ARG_POINTER(aUrl);
+
+ nsAutoCString urlSpec;
+ nsresult rv = aUrl->GetSpec(urlSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ char hierarchyDelimiter = GetHierarchyDelimiter(aImapMailFolder);
+
+ urlSpec.AppendLiteral("fetch>UID>");
+ urlSpec.Append(hierarchyDelimiter);
+
+ nsAutoCString folderName;
+ GetFolderName(aImapMailFolder, folderName);
+ urlSpec.Append(folderName);
+
+ urlSpec.Append('>');
+ urlSpec.Append(aMessageIdentifierList);
+
+ if (!aAdditionalHeader.IsEmpty()) {
+ urlSpec.AppendLiteral("?header=");
+ urlSpec.Append(aAdditionalHeader);
+ }
+
+ return aUrl->SetSpecInternal(urlSpec);
+}
+
+NS_IMETHODIMP nsImapService::FetchMessage(
+ nsIImapUrl* aImapUrl, nsImapAction aImapAction,
+ nsIMsgFolder* aImapMailFolder, nsIImapMessageSink* aImapMessage,
+ nsIMsgWindow* aMsgWindow, nsISupports* aDisplayConsumer,
+ const nsACString& messageIdentifierList, bool aConvertDataToText,
+ nsIURI** aURL) {
+ NS_ENSURE_ARG_POINTER(aImapUrl);
+ NS_ENSURE_ARG_POINTER(aImapMailFolder);
+ NS_ENSURE_ARG_POINTER(aImapMessage);
+
+ nsresult rv;
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsurl = do_QueryInterface(aImapUrl);
+
+ rv = AddImapFetchToUrl(mailnewsurl, aImapMailFolder, messageIdentifierList,
+ ""_ns);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (WeAreOffline()) {
+ bool msgIsInCache = false;
+ nsCOMPtr<nsIMsgMailNewsUrl> msgUrl(do_QueryInterface(aImapUrl));
+ msgUrl->GetMsgIsInLocalCache(&msgIsInCache);
+ if (!msgIsInCache)
+ IsMsgInMemCache(mailnewsurl, aImapMailFolder, &msgIsInCache);
+
+ // Display the "offline" message if we didn't find it in the memory cache
+ // either
+ if (!msgIsInCache) {
+ return NS_ERROR_OFFLINE;
+ }
+ }
+
+ if (aURL) mailnewsurl.forget(aURL);
+
+ return GetMessageFromUrl(aImapUrl, aImapAction, aImapMailFolder, aImapMessage,
+ aMsgWindow, aDisplayConsumer, aConvertDataToText,
+ aURL);
+}
+
+nsresult nsImapService::GetMessageFromUrl(
+ nsIImapUrl* aImapUrl, nsImapAction aImapAction,
+ nsIMsgFolder* aImapMailFolder, nsIImapMessageSink* aImapMessage,
+ nsIMsgWindow* aMsgWindow, nsISupports* aDisplayConsumer,
+ bool aConvertDataToText, nsIURI** aURL) {
+ nsresult rv = SetImapUrlSink(aImapMailFolder, aImapUrl);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aImapUrl->SetImapMessageSink(aImapMessage);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aImapUrl->SetImapAction(aImapAction);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIURI> url(do_QueryInterface(aImapUrl));
+
+ // if the display consumer is a docshell, then we should run the url in the
+ // docshell. otherwise, it should be a stream listener....so open a channel
+ // using AsyncRead and the provided stream listener....
+
+ nsCOMPtr<nsIDocShell> docShell(do_QueryInterface(aDisplayConsumer, &rv));
+ if (aImapMailFolder && docShell) {
+ nsCOMPtr<nsIMsgIncomingServer> aMsgIncomingServer;
+ rv = aImapMailFolder->GetServer(getter_AddRefs(aMsgIncomingServer));
+ if (NS_SUCCEEDED(rv) && aMsgIncomingServer) {
+ bool interrupted;
+ nsCOMPtr<nsIImapIncomingServer> aImapServer(
+ do_QueryInterface(aMsgIncomingServer, &rv));
+ if (NS_SUCCEEDED(rv) && aImapServer)
+ aImapServer->PseudoInterruptMsgLoad(aImapMailFolder, aMsgWindow,
+ &interrupted);
+ }
+ }
+ if (NS_SUCCEEDED(rv) && docShell) {
+ NS_ASSERTION(!aConvertDataToText,
+ "can't convert to text when using docshell");
+ RefPtr<nsDocShellLoadState> loadState = new nsDocShellLoadState(url);
+ loadState->SetLoadFlags(nsIWebNavigation::LOAD_FLAGS_NONE);
+ loadState->SetFirstParty(false);
+ loadState->SetTriggeringPrincipal(nsContentUtils::GetSystemPrincipal());
+ rv = docShell->LoadURI(loadState, false);
+ } else {
+ nsCOMPtr<nsIStreamListener> streamListener =
+ do_QueryInterface(aDisplayConsumer, &rv);
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(aImapUrl, &rv);
+ if (aMsgWindow && mailnewsUrl) mailnewsUrl->SetMsgWindow(aMsgWindow);
+ if (NS_SUCCEEDED(rv) && streamListener) {
+ nsCOMPtr<nsIChannel> channel;
+ nsCOMPtr<nsILoadGroup> loadGroup;
+ if (NS_SUCCEEDED(rv) && mailnewsUrl)
+ mailnewsUrl->GetLoadGroup(getter_AddRefs(loadGroup));
+
+ nsCOMPtr<nsILoadInfo> loadInfo = new mozilla::net::LoadInfo(
+ nsContentUtils::GetSystemPrincipal(), nullptr, nullptr,
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER);
+ rv = NewChannel(url, loadInfo, getter_AddRefs(channel));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // we need a load group to hold onto the channel. When the request is
+ // finished, it'll get removed from the load group, and the channel will
+ // go away, which will free the load group.
+ if (!loadGroup) loadGroup = do_CreateInstance(NS_LOADGROUP_CONTRACTID);
+
+ rv = channel->SetLoadGroup(loadGroup);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (aConvertDataToText) {
+ nsCOMPtr<nsIStreamListener> conversionListener;
+ nsCOMPtr<nsIStreamConverterService> streamConverter =
+ do_GetService("@mozilla.org/streamConverters;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = streamConverter->AsyncConvertData(
+ "message/rfc822", "*/*", streamListener, channel,
+ getter_AddRefs(conversionListener));
+ NS_ENSURE_SUCCESS(rv, rv);
+ streamListener = conversionListener; // this is our new listener.
+ }
+
+ // now try to open the channel passing in our display consumer as the
+ // listener
+ rv = channel->AsyncOpen(streamListener);
+ } else // do what we used to do before
+ {
+ // I'd like to get rid of this code as I believe that we always get a
+ // docshell or stream listener passed into us in this method but i'm not
+ // sure yet... I'm going to use an assert for now to figure out if this is
+ // ever getting called
+#if defined(DEBUG_mscott) || defined(DEBUG_bienvenu)
+ NS_ERROR("oops...someone still is reaching this part of the code");
+#endif
+ rv = GetImapConnectionAndLoadUrl(aImapUrl, aDisplayConsumer, aURL);
+ }
+ }
+ return rv;
+}
+
+// this method streams a message to the passed in consumer, with an optional
+// stream converter and additional header (e.g., "header=filter")
+NS_IMETHODIMP nsImapService::StreamMessage(
+ const nsACString& aMessageURI, nsISupports* aConsumer,
+ nsIMsgWindow* aMsgWindow, nsIUrlListener* aUrlListener, bool aConvertData,
+ const nsACString& aAdditionalHeader, bool aLocalOnly, nsIURI** aURL) {
+ nsCOMPtr<nsIMsgFolder> folder;
+ nsAutoCString msgKey;
+ nsAutoCString mimePart;
+ nsAutoCString folderURI;
+ nsMsgKey key;
+ nsAutoCString messageURI(aMessageURI);
+
+ int32_t typeIndex = messageURI.Find("&type=application/x-message-display");
+ if (typeIndex != kNotFound) {
+ // This happens with forward inline of a message/rfc822 attachment opened in
+ // a standalone msg window.
+ // So, just cut to the chase and call AsyncOpen on a channel.
+ nsCOMPtr<nsIURI> uri;
+ messageURI.Cut(typeIndex,
+ sizeof("&type=application/x-message-display") - 1);
+ nsresult rv = NS_NewURI(getter_AddRefs(uri), messageURI.get());
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (aURL) NS_IF_ADDREF(*aURL = uri);
+ nsCOMPtr<nsIStreamListener> aStreamListener =
+ do_QueryInterface(aConsumer, &rv);
+ if (NS_SUCCEEDED(rv) && aStreamListener) {
+ nsCOMPtr<nsIChannel> aChannel;
+ nsCOMPtr<nsILoadGroup> aLoadGroup;
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(uri, &rv);
+ if (NS_SUCCEEDED(rv) && mailnewsUrl)
+ mailnewsUrl->GetLoadGroup(getter_AddRefs(aLoadGroup));
+
+ nsCOMPtr<nsILoadInfo> loadInfo = new mozilla::net::LoadInfo(
+ nsContentUtils::GetSystemPrincipal(), nullptr, nullptr,
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER);
+ rv = NewChannel(uri, loadInfo, getter_AddRefs(aChannel));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // now try to open the channel passing in our display consumer as the
+ // listener
+ rv = aChannel->AsyncOpen(aStreamListener);
+ return rv;
+ }
+ }
+
+ nsresult rv = DecomposeImapURI(aMessageURI, getter_AddRefs(folder), msgKey);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (msgKey.IsEmpty()) return NS_MSG_MESSAGE_NOT_FOUND;
+ rv = nsParseImapMessageURI(aMessageURI, folderURI, &key,
+ getter_Copies(mimePart));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIImapMessageSink> imapMessageSink(do_QueryInterface(folder, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIImapUrl> imapUrl;
+ nsAutoCString urlSpec;
+ char hierarchyDelimiter = GetHierarchyDelimiter(folder);
+ rv = CreateStartOfImapUrl(aMessageURI, getter_AddRefs(imapUrl), folder,
+ aUrlListener, urlSpec, hierarchyDelimiter);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsurl(do_QueryInterface(imapUrl));
+
+ // This option is used by the JS Mime Emitter, in case we want a cheap
+ // streaming, for example, if we just want a quick look at some header,
+ // without having to download all the attachments...
+
+ // We need to add the fetch command here for the cache lookup to behave
+ // correctly
+ nsAutoCString additionalHeader(aAdditionalHeader);
+ rv = AddImapFetchToUrl(mailnewsurl, folder, msgKey, additionalHeader);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgIncomingServer> aMsgIncomingServer;
+
+ mailnewsurl->SetMsgWindow(aMsgWindow);
+ rv = mailnewsurl->GetServer(getter_AddRefs(aMsgIncomingServer));
+
+ // Try to check if the message is offline
+ bool hasMsgOffline = false;
+ folder->HasMsgOffline(key, &hasMsgOffline);
+ mailnewsurl->SetMsgIsInLocalCache(hasMsgOffline);
+ imapUrl->SetLocalFetchOnly(aLocalOnly);
+
+ // If we don't have the message available locally, and we can't get it
+ // over the network, return with an error
+ if (aLocalOnly || WeAreOffline()) {
+ bool isMsgInMemCache = false;
+ if (!hasMsgOffline) {
+ rv = IsMsgInMemCache(mailnewsurl, folder, &isMsgInMemCache);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!isMsgInMemCache) return NS_ERROR_FAILURE;
+ }
+ }
+
+ bool shouldStoreMsgOffline = false;
+ folder->ShouldStoreMsgOffline(key, &shouldStoreMsgOffline);
+ imapUrl->SetStoreResultsOffline(shouldStoreMsgOffline);
+ rv = GetMessageFromUrl(imapUrl, nsIImapUrl::nsImapMsgFetchPeek, folder,
+ imapMessageSink, aMsgWindow, aConsumer, aConvertData,
+ aURL);
+ return rv;
+}
+
+// this method streams a message's headers to the passed in consumer.
+NS_IMETHODIMP nsImapService::StreamHeaders(const nsACString& aMessageURI,
+ nsIStreamListener* aConsumer,
+ nsIUrlListener* aUrlListener,
+ bool aLocalOnly, nsIURI** aURL) {
+ NS_ENSURE_ARG_POINTER(aConsumer);
+ nsCOMPtr<nsIMsgFolder> folder;
+ nsAutoCString msgKey;
+ nsAutoCString folderURI;
+ nsCString mimePart;
+ nsMsgKey key;
+
+ nsresult rv = DecomposeImapURI(aMessageURI, getter_AddRefs(folder), msgKey);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (msgKey.IsEmpty()) return NS_MSG_MESSAGE_NOT_FOUND;
+ rv = nsParseImapMessageURI(aMessageURI, folderURI, &key,
+ getter_Copies(mimePart));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIInputStream> inputStream;
+ bool hasMsgOffline = false;
+ folder->HasMsgOffline(key, &hasMsgOffline);
+ if (hasMsgOffline) {
+ nsCOMPtr<nsIMsgDBHdr> hdr;
+ rv = folder->GetMessageHeader(key, getter_AddRefs(hdr));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = folder->GetLocalMsgStream(hdr, getter_AddRefs(inputStream));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return MsgStreamMsgHeaders(inputStream, aConsumer);
+ }
+
+ if (aLocalOnly) return NS_ERROR_FAILURE;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapService::IsMsgInMemCache(nsIURI* aUrl,
+ nsIMsgFolder* aImapMailFolder,
+ bool* aResult) {
+ NS_ENSURE_ARG_POINTER(aUrl);
+ NS_ENSURE_ARG_POINTER(aImapMailFolder);
+ *aResult = false;
+
+ // Poke around in the memory cache
+ if (mCacheStorage) {
+ nsAutoCString urlSpec;
+ aUrl->GetSpec(urlSpec);
+
+ // Strip any query qualifiers.
+ bool truncated = false;
+ int32_t ind = urlSpec.FindChar('?');
+ if (ind != kNotFound) {
+ urlSpec.SetLength(ind);
+ truncated = true;
+ }
+ ind = urlSpec.Find("/;");
+ if (ind != kNotFound) {
+ urlSpec.SetLength(ind);
+ truncated = true;
+ }
+
+ nsresult rv;
+ nsCOMPtr<nsIImapMailFolderSink> folderSink(
+ do_QueryInterface(aImapMailFolder, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t uidValidity = -1;
+ folderSink->GetUidValidity(&uidValidity);
+ // stick the uid validity in front of the url, so that if the uid validity
+ // changes, we won't re-use the wrong cache entries.
+ nsAutoCString extension;
+ extension.AppendInt(uidValidity, 16);
+
+ bool exists;
+ if (truncated) {
+ nsCOMPtr<nsIURI> newUri;
+ rv = NS_NewURI(getter_AddRefs(newUri), urlSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = mCacheStorage->Exists(newUri, extension, &exists);
+ } else {
+ rv = mCacheStorage->Exists(aUrl, extension, &exists);
+ }
+ if (NS_SUCCEEDED(rv) && exists) {
+ *aResult = true;
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult nsImapService::CreateStartOfImapUrl(const nsACString& aImapURI,
+ nsIImapUrl** imapUrl,
+ nsIMsgFolder* aImapMailFolder,
+ nsIUrlListener* aUrlListener,
+ nsACString& urlSpec,
+ char& hierarchyDelimiter) {
+ NS_ENSURE_ARG_POINTER(aImapMailFolder);
+
+ nsCString hostname;
+ nsCString username;
+ nsCString escapedUsername;
+
+ nsresult rv = aImapMailFolder->GetHostname(hostname);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = aImapMailFolder->GetUsername(username);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!username.IsEmpty())
+ MsgEscapeString(username, nsINetUtil::ESCAPE_XALPHAS, escapedUsername);
+
+ int32_t port = nsIImapUrl::DEFAULT_IMAP_PORT;
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = aImapMailFolder->GetServer(getter_AddRefs(server));
+ if (NS_SUCCEEDED(rv)) {
+ server->GetPort(&port);
+ if (port == -1 || port == 0) port = nsIImapUrl::DEFAULT_IMAP_PORT;
+ }
+
+ // now we need to create an imap url to load into the connection. The url
+ // needs to represent a select folder action.
+ rv = CallCreateInstance(kImapUrlCID, imapUrl);
+ if (NS_SUCCEEDED(rv) && *imapUrl) {
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(*imapUrl, &rv);
+ if (NS_SUCCEEDED(rv) && mailnewsUrl && aUrlListener)
+ mailnewsUrl->RegisterListener(aUrlListener);
+ nsCOMPtr<nsIMsgMessageUrl> msgurl(do_QueryInterface(*imapUrl));
+ (*imapUrl)->SetExternalLinkUrl(false);
+ msgurl->SetUri(aImapURI);
+
+ urlSpec = "imap://";
+ urlSpec.Append(escapedUsername);
+ urlSpec.Append('@');
+ urlSpec.Append(hostname);
+ urlSpec.Append(':');
+
+ nsAutoCString portStr;
+ portStr.AppendInt(port);
+ urlSpec.Append(portStr);
+
+ // *** jefft - force to parse the urlSpec in order to search for
+ // the correct incoming server
+ rv = mailnewsUrl->SetSpecInternal(urlSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ hierarchyDelimiter = kOnlineHierarchySeparatorUnknown;
+ nsCOMPtr<nsIMsgImapMailFolder> imapFolder =
+ do_QueryInterface(aImapMailFolder);
+ if (imapFolder) imapFolder->GetHierarchyDelimiter(&hierarchyDelimiter);
+ }
+ return rv;
+}
+
+/* fetching the headers of RFC822 messages */
+/* imap4://HOST>header><UID/SEQUENCE>>MAILBOXPATH>x */
+/* 'x' is the message UID or sequence number list */
+/* will not affect the 'SEEN' flag */
+NS_IMETHODIMP nsImapService::GetHeaders(nsIMsgFolder* aImapMailFolder,
+ nsIUrlListener* aUrlListener,
+ nsIURI** aURL,
+ const nsACString& messageIdentifierList,
+ bool messageIdsAreUID) {
+ // create a protocol instance to handle the request.
+ // NOTE: once we start working with multiple connections, this step will be
+ // much more complicated...but for now just create a connection and process
+ // the request.
+ NS_ENSURE_ARG_POINTER(aImapMailFolder);
+
+ nsCOMPtr<nsIImapUrl> imapUrl;
+ nsAutoCString urlSpec;
+ char hierarchyDelimiter = GetHierarchyDelimiter(aImapMailFolder);
+
+ nsresult rv = CreateStartOfImapUrl(EmptyCString(), getter_AddRefs(imapUrl),
+ aImapMailFolder, aUrlListener, urlSpec,
+ hierarchyDelimiter);
+ if (NS_SUCCEEDED(rv) && imapUrl) {
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(imapUrl);
+
+ rv = imapUrl->SetImapAction(nsIImapUrl::nsImapMsgFetch);
+ rv = SetImapUrlSink(aImapMailFolder, imapUrl);
+
+ if (NS_SUCCEEDED(rv)) {
+ urlSpec.AppendLiteral("/header>");
+ urlSpec.Append(messageIdsAreUID ? uidString : sequenceString);
+ urlSpec.Append('>');
+ urlSpec.Append(char(hierarchyDelimiter));
+
+ nsCString folderName;
+
+ GetFolderName(aImapMailFolder, folderName);
+ urlSpec.Append(folderName);
+ urlSpec.Append('>');
+ urlSpec.Append(messageIdentifierList);
+ rv = mailnewsUrl->SetSpecInternal(urlSpec);
+
+ if (NS_SUCCEEDED(rv))
+ rv = GetImapConnectionAndLoadUrl(imapUrl, nullptr, aURL);
+ }
+ }
+ return rv;
+}
+
+/* peeking at the start of msg bodies */
+/* imap4://HOST>header><UID>>MAILBOXPATH>x>n */
+/* 'x' is the message UID */
+/* 'n' is the number of bytes to fetch */
+/* will not affect the 'SEEN' flag */
+NS_IMETHODIMP nsImapService::GetBodyStart(
+ nsIMsgFolder* aImapMailFolder, nsIUrlListener* aUrlListener,
+ const nsACString& messageIdentifierList, int32_t numBytes, nsIURI** aURL) {
+ NS_ENSURE_ARG_POINTER(aImapMailFolder);
+
+ nsresult rv;
+ nsCOMPtr<nsIImapUrl> imapUrl;
+ nsAutoCString urlSpec;
+
+ char hierarchyDelimiter = GetHierarchyDelimiter(aImapMailFolder);
+ rv = CreateStartOfImapUrl(EmptyCString(), getter_AddRefs(imapUrl),
+ aImapMailFolder, aUrlListener, urlSpec,
+ hierarchyDelimiter);
+ if (NS_SUCCEEDED(rv) && imapUrl) {
+ rv = imapUrl->SetImapAction(nsIImapUrl::nsImapMsgPreview);
+ rv = SetImapUrlSink(aImapMailFolder, imapUrl);
+
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(imapUrl);
+
+ urlSpec.AppendLiteral("/previewBody>");
+ urlSpec.Append(uidString);
+ urlSpec.Append('>');
+ urlSpec.Append(hierarchyDelimiter);
+
+ nsCString folderName;
+ GetFolderName(aImapMailFolder, folderName);
+ urlSpec.Append(folderName);
+ urlSpec.Append('>');
+ urlSpec.Append(messageIdentifierList);
+ urlSpec.Append('>');
+ urlSpec.AppendInt(numBytes);
+ rv = mailnewsUrl->SetSpecInternal(urlSpec);
+ if (NS_SUCCEEDED(rv))
+ rv = GetImapConnectionAndLoadUrl(imapUrl, nullptr, aURL);
+ }
+ }
+ return rv;
+}
+
+nsresult nsImapService::FolderCommand(nsIMsgFolder* imapMailFolder,
+ nsIUrlListener* urlListener,
+ const char* aCommand,
+ nsImapAction imapAction,
+ nsIMsgWindow* msgWindow, nsIURI** url) {
+ NS_ENSURE_ARG_POINTER(imapMailFolder);
+
+ nsCOMPtr<nsIImapUrl> imapUrl;
+ nsAutoCString urlSpec;
+
+ char hierarchyDelimiter = GetHierarchyDelimiter(imapMailFolder);
+ nsresult rv = CreateStartOfImapUrl(EmptyCString(), getter_AddRefs(imapUrl),
+ imapMailFolder, urlListener, urlSpec,
+ hierarchyDelimiter);
+ if (NS_SUCCEEDED(rv) && imapUrl) {
+ rv = imapUrl->SetImapAction(imapAction);
+ rv = SetImapUrlSink(imapMailFolder, imapUrl);
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsurl = do_QueryInterface(imapUrl);
+ if (mailnewsurl) mailnewsurl->SetMsgWindow(msgWindow);
+
+ if (NS_SUCCEEDED(rv)) {
+ urlSpec.Append(aCommand);
+ urlSpec.Append(hierarchyDelimiter);
+
+ nsCString folderName;
+ GetFolderName(imapMailFolder, folderName);
+ urlSpec.Append(folderName);
+ rv = mailnewsurl->SetSpecInternal(urlSpec);
+ if (NS_SUCCEEDED(rv))
+ rv = GetImapConnectionAndLoadUrl(imapUrl, nullptr, url);
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsImapService::VerifyLogon(nsIMsgFolder* aFolder, nsIUrlListener* aUrlListener,
+ nsIMsgWindow* aMsgWindow, nsIURI** aURL) {
+ nsCOMPtr<nsIImapUrl> imapUrl;
+ nsAutoCString urlSpec;
+
+ char delimiter = '/'; // shouldn't matter what is is.
+ nsresult rv = CreateStartOfImapUrl(EmptyCString(), getter_AddRefs(imapUrl),
+ aFolder, aUrlListener, urlSpec, delimiter);
+ if (NS_SUCCEEDED(rv) && imapUrl) {
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsurl = do_QueryInterface(imapUrl);
+
+ nsCOMPtr<nsIMsgMailNewsUrl> mailNewsUrl = do_QueryInterface(imapUrl);
+ mailNewsUrl->SetSuppressErrorMsgs(true);
+ mailNewsUrl->SetMsgWindow(aMsgWindow);
+ rv = SetImapUrlSink(aFolder, imapUrl);
+ urlSpec.AppendLiteral("/verifyLogon");
+ rv = mailnewsurl->SetSpecInternal(urlSpec);
+ if (NS_SUCCEEDED(rv))
+ rv = GetImapConnectionAndLoadUrl(imapUrl, nullptr, nullptr);
+ if (aURL) mailnewsurl.forget(aURL);
+ }
+ return rv;
+}
+
+// Noop, used to update a folder (causes server to send changes).
+NS_IMETHODIMP nsImapService::Noop(nsIMsgFolder* aImapMailFolder,
+ nsIUrlListener* aUrlListener, nsIURI** aURL) {
+ NS_ENSURE_ARG_POINTER(aImapMailFolder);
+
+ return FolderCommand(aImapMailFolder, aUrlListener, "/selectnoop>",
+ nsIImapUrl::nsImapSelectNoopFolder, nullptr, aURL);
+}
+
+// FolderStatus, used to update message counts
+NS_IMETHODIMP nsImapService::UpdateFolderStatus(nsIMsgFolder* aImapMailFolder,
+ nsIUrlListener* aUrlListener,
+ nsIURI** aURL) {
+ NS_ENSURE_ARG_POINTER(aImapMailFolder);
+
+ return FolderCommand(aImapMailFolder, aUrlListener, "/folderstatus>",
+ nsIImapUrl::nsImapFolderStatus, nullptr, aURL);
+}
+
+// Expunge, used to "compress" an imap folder,removes deleted messages.
+NS_IMETHODIMP nsImapService::Expunge(nsIMsgFolder* aImapMailFolder,
+ nsIUrlListener* aUrlListener,
+ nsIMsgWindow* aMsgWindow) {
+ NS_ENSURE_ARG_POINTER(aImapMailFolder);
+
+ return FolderCommand(aImapMailFolder, aUrlListener, "/Expunge>",
+ nsIImapUrl::nsImapExpungeFolder, aMsgWindow, nullptr);
+}
+
+/* old-stle biff that doesn't download headers */
+NS_IMETHODIMP nsImapService::Biff(nsIMsgFolder* aImapMailFolder,
+ nsIUrlListener* aUrlListener, nsIURI** aURL,
+ uint32_t uidHighWater) {
+ NS_ENSURE_ARG_POINTER(aImapMailFolder);
+
+ // static const char *formatString = "biff>%c%s>%ld";
+ nsCOMPtr<nsIImapUrl> imapUrl;
+ nsAutoCString urlSpec;
+
+ char hierarchyDelimiter = GetHierarchyDelimiter(aImapMailFolder);
+ nsresult rv = CreateStartOfImapUrl(EmptyCString(), getter_AddRefs(imapUrl),
+ aImapMailFolder, aUrlListener, urlSpec,
+ hierarchyDelimiter);
+ if (NS_SUCCEEDED(rv) && imapUrl) {
+ rv = imapUrl->SetImapAction(nsIImapUrl::nsImapExpungeFolder);
+ rv = SetImapUrlSink(aImapMailFolder, imapUrl);
+
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsurl = do_QueryInterface(imapUrl);
+ if (NS_SUCCEEDED(rv)) {
+ urlSpec.AppendLiteral("/Biff>");
+ urlSpec.Append(hierarchyDelimiter);
+
+ nsCString folderName;
+ GetFolderName(aImapMailFolder, folderName);
+ urlSpec.Append(folderName);
+ urlSpec.Append('>');
+ urlSpec.AppendInt(uidHighWater);
+ rv = mailnewsurl->SetSpecInternal(urlSpec);
+ if (NS_SUCCEEDED(rv))
+ rv = GetImapConnectionAndLoadUrl(imapUrl, nullptr, aURL);
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapService::DeleteFolder(nsIMsgFolder* aImapMailFolder,
+ nsIUrlListener* aUrlListener,
+ nsIMsgWindow* aMsgWindow) {
+ NS_ENSURE_ARG_POINTER(aImapMailFolder);
+
+ // If it's an aol server then use 'deletefolder' url to
+ // remove all msgs first and then remove the folder itself.
+ bool removeFolderAndMsgs = false;
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ if (NS_SUCCEEDED(aImapMailFolder->GetServer(getter_AddRefs(server))) &&
+ server) {
+ nsCOMPtr<nsIImapIncomingServer> imapServer = do_QueryInterface(server);
+ if (imapServer) imapServer->GetIsAOLServer(&removeFolderAndMsgs);
+ }
+
+ return FolderCommand(aImapMailFolder, aUrlListener,
+ removeFolderAndMsgs ? "/deletefolder>" : "/delete>",
+ nsIImapUrl::nsImapDeleteFolder, aMsgWindow, nullptr);
+}
+
+NS_IMETHODIMP nsImapService::DeleteMessages(
+ nsIMsgFolder* aImapMailFolder, nsIUrlListener* aUrlListener, nsIURI** aURL,
+ const nsACString& messageIdentifierList, bool messageIdsAreUID) {
+ NS_ENSURE_ARG_POINTER(aImapMailFolder);
+
+ // create a protocol instance to handle the request.
+ // NOTE: once we start working with multiple connections, this step will be
+ // much more complicated...but for now just create a connection and process
+ // the request.
+ nsresult rv;
+ nsCOMPtr<nsIImapUrl> imapUrl;
+ nsAutoCString urlSpec;
+
+ char hierarchyDelimiter = GetHierarchyDelimiter(aImapMailFolder);
+ rv = CreateStartOfImapUrl(EmptyCString(), getter_AddRefs(imapUrl),
+ aImapMailFolder, aUrlListener, urlSpec,
+ hierarchyDelimiter);
+ if (NS_SUCCEEDED(rv) && imapUrl) {
+ rv = imapUrl->SetImapAction(nsIImapUrl::nsImapMsgFetch);
+ rv = SetImapUrlSink(aImapMailFolder, imapUrl);
+
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsurl = do_QueryInterface(imapUrl);
+
+ urlSpec.AppendLiteral("/deletemsg>");
+ urlSpec.Append(messageIdsAreUID ? uidString : sequenceString);
+ urlSpec.Append('>');
+ urlSpec.Append(hierarchyDelimiter);
+
+ nsCString folderName;
+ GetFolderName(aImapMailFolder, folderName);
+ urlSpec.Append(folderName);
+ urlSpec.Append('>');
+ urlSpec.Append(messageIdentifierList);
+ rv = mailnewsurl->SetSpecInternal(urlSpec);
+ if (NS_SUCCEEDED(rv))
+ rv = GetImapConnectionAndLoadUrl(imapUrl, nullptr, aURL);
+ }
+ }
+ return rv;
+}
+
+// Delete all messages in a folder, used to empty trash
+NS_IMETHODIMP nsImapService::DeleteAllMessages(nsIMsgFolder* aImapMailFolder,
+ nsIUrlListener* aUrlListener) {
+ NS_ENSURE_ARG_POINTER(aImapMailFolder);
+
+ return FolderCommand(aImapMailFolder, aUrlListener, "/deleteallmsgs>",
+ nsIImapUrl::nsImapSelectNoopFolder, nullptr, nullptr);
+}
+
+NS_IMETHODIMP nsImapService::AddMessageFlags(
+ nsIMsgFolder* aImapMailFolder, nsIUrlListener* aUrlListener,
+ const nsACString& messageIdentifierList, imapMessageFlagsType flags,
+ bool messageIdsAreUID) {
+ NS_ENSURE_ARG_POINTER(aImapMailFolder);
+
+ return DiddleFlags(aImapMailFolder, aUrlListener, nullptr,
+ messageIdentifierList, "addmsgflags", flags,
+ messageIdsAreUID);
+}
+
+NS_IMETHODIMP nsImapService::SubtractMessageFlags(
+ nsIMsgFolder* aImapMailFolder, nsIUrlListener* aUrlListener,
+ const nsACString& messageIdentifierList, imapMessageFlagsType flags,
+ bool messageIdsAreUID) {
+ NS_ENSURE_ARG_POINTER(aImapMailFolder);
+
+ return DiddleFlags(aImapMailFolder, aUrlListener, nullptr,
+ messageIdentifierList, "subtractmsgflags", flags,
+ messageIdsAreUID);
+}
+
+NS_IMETHODIMP nsImapService::SetMessageFlags(
+ nsIMsgFolder* aImapMailFolder, nsIUrlListener* aUrlListener, nsIURI** aURL,
+ const nsACString& messageIdentifierList, imapMessageFlagsType flags,
+ bool messageIdsAreUID) {
+ NS_ENSURE_ARG_POINTER(aImapMailFolder);
+
+ return DiddleFlags(aImapMailFolder, aUrlListener, aURL, messageIdentifierList,
+ "setmsgflags", flags, messageIdsAreUID);
+}
+
+nsresult nsImapService::DiddleFlags(nsIMsgFolder* aImapMailFolder,
+ nsIUrlListener* aUrlListener, nsIURI** aURL,
+ const nsACString& messageIdentifierList,
+ const char* howToDiddle,
+ imapMessageFlagsType flags,
+ bool messageIdsAreUID) {
+ NS_ENSURE_ARG_POINTER(aImapMailFolder);
+
+ // create a protocol instance to handle the request.
+ // NOTE: once we start working with multiple connections,
+ // this step will be much more complicated...but for now
+ // just create a connection and process the request.
+ nsCOMPtr<nsIImapUrl> imapUrl;
+ nsAutoCString urlSpec;
+
+ char hierarchyDelimiter = GetHierarchyDelimiter(aImapMailFolder);
+ nsresult rv = CreateStartOfImapUrl(EmptyCString(), getter_AddRefs(imapUrl),
+ aImapMailFolder, aUrlListener, urlSpec,
+ hierarchyDelimiter);
+ if (NS_SUCCEEDED(rv) && imapUrl) {
+ rv = imapUrl->SetImapAction(nsIImapUrl::nsImapMsgFetch);
+ rv = SetImapUrlSink(aImapMailFolder, imapUrl);
+
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsurl = do_QueryInterface(imapUrl);
+
+ urlSpec.Append('/');
+ urlSpec.Append(howToDiddle);
+ urlSpec.Append('>');
+ urlSpec.Append(messageIdsAreUID ? uidString : sequenceString);
+ urlSpec.Append('>');
+ urlSpec.Append(hierarchyDelimiter);
+ nsCString folderName;
+ GetFolderName(aImapMailFolder, folderName);
+ urlSpec.Append(folderName);
+ urlSpec.Append('>');
+ urlSpec.Append(messageIdentifierList);
+ urlSpec.Append('>');
+ urlSpec.AppendInt(flags);
+ rv = mailnewsurl->SetSpecInternal(urlSpec);
+ if (NS_SUCCEEDED(rv))
+ rv = GetImapConnectionAndLoadUrl(imapUrl, nullptr, aURL);
+ }
+ }
+ return rv;
+}
+
+nsresult nsImapService::SetImapUrlSink(nsIMsgFolder* aMsgFolder,
+ nsIImapUrl* aImapUrl) {
+ NS_ENSURE_ARG_POINTER(aMsgFolder);
+ NS_ENSURE_ARG_POINTER(aImapUrl);
+
+ nsresult rv;
+ nsCOMPtr<nsIMsgIncomingServer> incomingServer;
+ nsCOMPtr<nsIImapServerSink> imapServerSink;
+
+ rv = aMsgFolder->GetServer(getter_AddRefs(incomingServer));
+ if (NS_SUCCEEDED(rv) && incomingServer) {
+ imapServerSink = do_QueryInterface(incomingServer);
+ if (imapServerSink) aImapUrl->SetImapServerSink(imapServerSink);
+ }
+
+ nsCOMPtr<nsIImapMailFolderSink> imapMailFolderSink =
+ do_QueryInterface(aMsgFolder);
+ if (NS_SUCCEEDED(rv) && imapMailFolderSink)
+ aImapUrl->SetImapMailFolderSink(imapMailFolderSink);
+
+ nsCOMPtr<nsIImapMessageSink> imapMessageSink = do_QueryInterface(aMsgFolder);
+ if (NS_SUCCEEDED(rv) && imapMessageSink)
+ aImapUrl->SetImapMessageSink(imapMessageSink);
+
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(aImapUrl);
+ mailnewsUrl->SetFolder(aMsgFolder);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapService::DiscoverAllFolders(nsIMsgFolder* aImapMailFolder,
+ nsIUrlListener* aUrlListener,
+ nsIMsgWindow* aMsgWindow) {
+ NS_ENSURE_ARG_POINTER(aImapMailFolder);
+
+ nsCOMPtr<nsIImapUrl> imapUrl;
+ nsAutoCString urlSpec;
+
+ char hierarchyDelimiter = GetHierarchyDelimiter(aImapMailFolder);
+ nsresult rv = CreateStartOfImapUrl(EmptyCString(), getter_AddRefs(imapUrl),
+ aImapMailFolder, aUrlListener, urlSpec,
+ hierarchyDelimiter);
+ if (NS_SUCCEEDED(rv)) {
+ rv = SetImapUrlSink(aImapMailFolder, imapUrl);
+
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsurl = do_QueryInterface(imapUrl);
+ mailnewsurl->SetMsgWindow(aMsgWindow);
+ urlSpec.AppendLiteral("/discoverallboxes");
+ rv = mailnewsurl->SetSpecInternal(urlSpec);
+ if (NS_SUCCEEDED(rv))
+ rv = GetImapConnectionAndLoadUrl(imapUrl, nullptr, nullptr);
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapService::DiscoverAllAndSubscribedFolders(
+ nsIMsgFolder* aImapMailFolder, nsIUrlListener* aUrlListener,
+ nsIMsgWindow* aMsgWindow) {
+ NS_ENSURE_ARG_POINTER(aImapMailFolder);
+
+ nsCOMPtr<nsIImapUrl> aImapUrl;
+ nsAutoCString urlSpec;
+
+ char hierarchyDelimiter = GetHierarchyDelimiter(aImapMailFolder);
+ nsresult rv = CreateStartOfImapUrl(EmptyCString(), getter_AddRefs(aImapUrl),
+ aImapMailFolder, aUrlListener, urlSpec,
+ hierarchyDelimiter);
+ if (NS_SUCCEEDED(rv) && aImapUrl) {
+ rv = SetImapUrlSink(aImapMailFolder, aImapUrl);
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsurl = do_QueryInterface(aImapUrl);
+ urlSpec.AppendLiteral("/discoverallandsubscribedboxes");
+ rv = mailnewsurl->SetSpecInternal(urlSpec);
+
+ if (aMsgWindow) mailnewsurl->SetMsgWindow(aMsgWindow);
+
+ if (NS_SUCCEEDED(rv))
+ rv = GetImapConnectionAndLoadUrl(aImapUrl, nullptr, nullptr);
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapService::DiscoverChildren(nsIMsgFolder* aImapMailFolder,
+ nsIUrlListener* aUrlListener,
+ const nsACString& folderPath) {
+ NS_ENSURE_ARG_POINTER(aImapMailFolder);
+
+ nsCOMPtr<nsIImapUrl> aImapUrl;
+ nsAutoCString urlSpec;
+
+ char hierarchyDelimiter = GetHierarchyDelimiter(aImapMailFolder);
+ nsresult rv = CreateStartOfImapUrl(EmptyCString(), getter_AddRefs(aImapUrl),
+ aImapMailFolder, aUrlListener, urlSpec,
+ hierarchyDelimiter);
+ if (NS_SUCCEEDED(rv)) {
+ rv = SetImapUrlSink(aImapMailFolder, aImapUrl);
+ if (NS_SUCCEEDED(rv)) {
+ if (!folderPath.IsEmpty()) {
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsurl = do_QueryInterface(aImapUrl);
+ urlSpec.AppendLiteral("/discoverchildren>");
+ urlSpec.Append(hierarchyDelimiter);
+ urlSpec.Append(folderPath);
+ rv = mailnewsurl->SetSpecInternal(urlSpec);
+
+ // Make sure the uri has the same hierarchy separator as the one in msg
+ // folder obj if it's not kOnlineHierarchySeparatorUnknown (ie, '^').
+ char uriDelimiter;
+ nsresult rv1 = aImapUrl->GetOnlineSubDirSeparator(&uriDelimiter);
+ if (NS_SUCCEEDED(rv1) &&
+ hierarchyDelimiter != kOnlineHierarchySeparatorUnknown &&
+ uriDelimiter != hierarchyDelimiter)
+ aImapUrl->SetOnlineSubDirSeparator(hierarchyDelimiter);
+
+ if (NS_SUCCEEDED(rv))
+ rv = GetImapConnectionAndLoadUrl(aImapUrl, nullptr, nullptr);
+ } else
+ rv = NS_ERROR_FAILURE;
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapService::OnlineMessageCopy(
+ nsIMsgFolder* aSrcFolder, const nsACString& messageIds,
+ nsIMsgFolder* aDstFolder, bool idsAreUids, bool isMove,
+ nsIUrlListener* aUrlListener, nsIURI** aURL, nsISupports* copyState,
+ nsIMsgWindow* aMsgWindow) {
+ NS_ENSURE_ARG_POINTER(aSrcFolder);
+ NS_ENSURE_ARG_POINTER(aDstFolder);
+
+ nsresult rv;
+ nsCOMPtr<nsIMsgIncomingServer> srcServer;
+ nsCOMPtr<nsIMsgIncomingServer> dstServer;
+
+ rv = aSrcFolder->GetServer(getter_AddRefs(srcServer));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aDstFolder->GetServer(getter_AddRefs(dstServer));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool sameServer;
+ rv = dstServer->Equals(srcServer, &sameServer);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!sameServer) {
+ NS_ASSERTION(false, "can't use this method to copy across servers");
+ // *** can only take message from the same imap host and user accnt
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIImapUrl> imapUrl;
+ nsAutoCString urlSpec;
+
+ char hierarchyDelimiter = GetHierarchyDelimiter(aSrcFolder);
+ rv = CreateStartOfImapUrl(EmptyCString(), getter_AddRefs(imapUrl), aSrcFolder,
+ aUrlListener, urlSpec, hierarchyDelimiter);
+ if (NS_SUCCEEDED(rv)) {
+ SetImapUrlSink(aSrcFolder, imapUrl);
+ imapUrl->SetCopyState(copyState);
+
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsurl(do_QueryInterface(imapUrl));
+ mailnewsurl->SetMsgWindow(aMsgWindow);
+
+ if (isMove)
+ urlSpec.AppendLiteral("/onlinemove>");
+ else
+ urlSpec.AppendLiteral("/onlinecopy>");
+ if (idsAreUids)
+ urlSpec.Append(uidString);
+ else
+ urlSpec.Append(sequenceString);
+ urlSpec.Append('>');
+ urlSpec.Append(hierarchyDelimiter);
+
+ nsCString folderName;
+ GetFolderName(aSrcFolder, folderName);
+ urlSpec.Append(folderName);
+ urlSpec.Append('>');
+ urlSpec.Append(messageIds);
+ urlSpec.Append('>');
+ urlSpec.Append(hierarchyDelimiter);
+ folderName.Adopt(strdup(""));
+ GetFolderName(aDstFolder, folderName);
+ urlSpec.Append(folderName);
+
+ rv = mailnewsurl->SetSpecInternal(urlSpec);
+ if (NS_SUCCEEDED(rv))
+ rv = GetImapConnectionAndLoadUrl(imapUrl, nullptr, aURL);
+ }
+ return rv;
+}
+
+nsresult nsImapService::OfflineAppendFromFile(
+ nsIFile* aFile, nsIURI* aUrl, nsIMsgFolder* aDstFolder,
+ const nsACString& messageId, // to be replaced
+ bool inSelectedState, // needs to be in
+ nsIUrlListener* aListener, nsIURI** aURL, nsISupports* aCopyState) {
+ nsCOMPtr<nsIMsgDatabase> destDB;
+ nsresult rv = aDstFolder->GetMsgDatabase(getter_AddRefs(destDB));
+ // ### might need to send some notifications instead of just returning
+
+ bool isLocked;
+ aDstFolder->GetLocked(&isLocked);
+ if (isLocked) return NS_MSG_FOLDER_BUSY;
+
+ if (NS_SUCCEEDED(rv) && destDB) {
+ nsMsgKey fakeKey;
+ destDB->GetNextFakeOfflineMsgKey(&fakeKey);
+
+ nsCOMPtr<nsIMsgOfflineImapOperation> op;
+ nsCOMPtr<nsIMsgOfflineOpsDatabase> opsDb = do_QueryInterface(destDB, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = opsDb->GetOfflineOpForKey(fakeKey, true, getter_AddRefs(op));
+ if (NS_SUCCEEDED(rv) && op) {
+ nsCString destFolderUri;
+ aDstFolder->GetURI(destFolderUri);
+ op->SetOperation(
+ nsIMsgOfflineImapOperation::kAppendDraft); // ### do we care if it's
+ // a template?
+ op->SetDestinationFolderURI(destFolderUri);
+ nsCOMPtr<nsIOutputStream> outputStream;
+ nsCOMPtr<nsIMsgPluggableStore> msgStore;
+ nsCOMPtr<nsIMsgIncomingServer> dstServer;
+ nsCOMPtr<nsIMsgDBHdr> newMsgHdr;
+
+ aDstFolder->GetServer(getter_AddRefs(dstServer));
+ rv = dstServer->GetMsgStore(getter_AddRefs(msgStore));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = destDB->CreateNewHdr(fakeKey, getter_AddRefs(newMsgHdr));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = aDstFolder->GetOfflineStoreOutputStream(
+ newMsgHdr, getter_AddRefs(outputStream));
+
+ if (NS_SUCCEEDED(rv) && outputStream) {
+ nsCOMPtr<nsIInputStream> inputStream;
+ nsCOMPtr<nsIMsgParseMailMsgState> msgParser = do_CreateInstance(
+ "@mozilla.org/messenger/messagestateparser;1", &rv);
+ msgParser->SetMailDB(destDB);
+
+ rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream), aFile);
+ if (NS_SUCCEEDED(rv) && inputStream) {
+ // now, copy the temp file to the offline store for the dest folder.
+ RefPtr<nsMsgLineStreamBuffer> inputStreamBuffer =
+ new nsMsgLineStreamBuffer(
+ FILE_IO_BUFFER_SIZE,
+ true, // allocate new lines
+ false); // leave CRLFs on the returned string
+ int64_t fileSize;
+ aFile->GetFileSize(&fileSize);
+ uint32_t bytesWritten;
+ rv = NS_OK;
+ // rv = inputStream->Read(inputBuffer, inputBufferSize, &bytesRead);
+ // if (NS_SUCCEEDED(rv) && bytesRead > 0)
+ msgParser->SetState(nsIMsgParseMailMsgState::ParseHeadersState);
+ msgParser->SetNewMsgHdr(newMsgHdr);
+ // set the new key to fake key so the msg hdr will have that for a key
+ msgParser->SetNewKey(fakeKey);
+ bool needMoreData = false;
+ char* newLine = nullptr;
+ uint32_t numBytesInLine = 0;
+ do {
+ newLine = inputStreamBuffer->ReadNextLine(
+ inputStream, numBytesInLine, needMoreData);
+ if (newLine) {
+ msgParser->ParseAFolderLine(newLine, numBytesInLine);
+ rv = outputStream->Write(newLine, numBytesInLine, &bytesWritten);
+ free(newLine);
+ }
+ } while (newLine);
+ msgParser->FinishHeader();
+
+ if (NS_SUCCEEDED(rv)) {
+ uint32_t resultFlags;
+ newMsgHdr->OrFlags(
+ nsMsgMessageFlags::Offline | nsMsgMessageFlags::Read,
+ &resultFlags);
+ newMsgHdr->SetOfflineMessageSize(fileSize);
+ destDB->AddNewHdrToDB(newMsgHdr, true /* notify */);
+ aDstFolder->SetFlag(nsMsgFolderFlags::OfflineEvents);
+ if (msgStore) msgStore->FinishNewMessage(outputStream, newMsgHdr);
+ }
+ // tell the listener we're done.
+ inputStream->Close();
+ inputStream = nullptr;
+ aListener->OnStopRunningUrl(aUrl, NS_OK);
+ }
+ outputStream->Close();
+ }
+ }
+ }
+
+ if (destDB) destDB->Close(true);
+ return rv;
+}
+
+/* append message from file url */
+/* imap://HOST>appendmsgfromfile>DESTINATIONMAILBOXPATH */
+/* imap://HOST>appenddraftfromfile>DESTINATIONMAILBOXPATH>UID>messageId */
+NS_IMETHODIMP nsImapService::AppendMessageFromFile(
+ nsIFile* aFile, nsIMsgFolder* aDstFolder,
+ const nsACString& messageId, // to be replaced
+ bool idsAreUids,
+ bool inSelectedState, // needs to be in
+ nsIUrlListener* aListener, nsISupports* aCopyState,
+ nsIMsgWindow* aMsgWindow) {
+ NS_ENSURE_ARG_POINTER(aFile);
+ NS_ENSURE_ARG_POINTER(aDstFolder);
+
+ nsresult rv;
+ nsCOMPtr<nsIImapUrl> imapUrl;
+ nsAutoCString urlSpec;
+
+ char hierarchyDelimiter = GetHierarchyDelimiter(aDstFolder);
+ rv = CreateStartOfImapUrl(EmptyCString(), getter_AddRefs(imapUrl), aDstFolder,
+ aListener, urlSpec, hierarchyDelimiter);
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIMsgMailNewsUrl> msgUrl = do_QueryInterface(imapUrl);
+ if (msgUrl && aMsgWindow) {
+ // we get the loadGroup from msgWindow
+ msgUrl->SetMsgWindow(aMsgWindow);
+ }
+
+ SetImapUrlSink(aDstFolder, imapUrl);
+ imapUrl->SetMsgFile(aFile);
+ imapUrl->SetCopyState(aCopyState);
+
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsurl = do_QueryInterface(imapUrl);
+
+ if (inSelectedState)
+ urlSpec.AppendLiteral("/appenddraftfromfile>");
+ else
+ urlSpec.AppendLiteral("/appendmsgfromfile>");
+
+ urlSpec.Append(hierarchyDelimiter);
+
+ nsCString folderName;
+ GetFolderName(aDstFolder, folderName);
+ urlSpec.Append(folderName);
+
+ if (inSelectedState) {
+ urlSpec.Append('>');
+ if (idsAreUids)
+ urlSpec.Append(uidString);
+ else
+ urlSpec.Append(sequenceString);
+ urlSpec.Append('>');
+ if (!messageId.IsEmpty()) urlSpec.Append(messageId);
+ }
+
+ rv = mailnewsurl->SetSpecInternal(urlSpec);
+ if (WeAreOffline()) {
+ // handle offline append to drafts or templates folder here.
+ return OfflineAppendFromFile(aFile, mailnewsurl, aDstFolder, messageId,
+ inSelectedState, aListener, nullptr,
+ aCopyState);
+ }
+ if (NS_SUCCEEDED(rv))
+ rv = GetImapConnectionAndLoadUrl(imapUrl, nullptr, nullptr);
+ }
+ return rv;
+}
+
+nsresult nsImapService::GetImapConnectionAndLoadUrl(nsIImapUrl* aImapUrl,
+ nsISupports* aConsumer,
+ nsIURI** aURL) {
+ NS_ENSURE_ARG_POINTER(aImapUrl);
+
+ bool isValidUrl;
+ aImapUrl->GetValidUrl(&isValidUrl);
+ if (!isValidUrl) return NS_ERROR_FAILURE;
+
+ if (WeAreOffline()) {
+ nsImapAction imapAction;
+
+ // the only thing we can do offline is fetch messages.
+ // ### TODO - need to look at msg copy, save attachment, etc. when we
+ // have offline message bodies.
+ aImapUrl->GetImapAction(&imapAction);
+ if (imapAction != nsIImapUrl::nsImapMsgFetch &&
+ imapAction != nsIImapUrl::nsImapSaveMessageToDisk)
+ return NS_MSG_ERROR_OFFLINE;
+ }
+
+ nsCOMPtr<nsIMsgIncomingServer> aMsgIncomingServer;
+ nsCOMPtr<nsIMsgMailNewsUrl> msgUrl = do_QueryInterface(aImapUrl);
+ nsresult rv = msgUrl->GetServer(getter_AddRefs(aMsgIncomingServer));
+
+ if (aURL) {
+ msgUrl.forget(aURL);
+ }
+
+ if (NS_SUCCEEDED(rv) && aMsgIncomingServer) {
+ nsCOMPtr<nsIImapIncomingServer> aImapServer(
+ do_QueryInterface(aMsgIncomingServer, &rv));
+ if (NS_SUCCEEDED(rv) && aImapServer)
+ rv = aImapServer->GetImapConnectionAndLoadUrl(aImapUrl, aConsumer);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapService::MoveFolder(nsIMsgFolder* srcFolder,
+ nsIMsgFolder* dstFolder,
+ nsIUrlListener* urlListener,
+ nsIMsgWindow* msgWindow) {
+ NS_ENSURE_ARG_POINTER(srcFolder);
+ NS_ENSURE_ARG_POINTER(dstFolder);
+
+ nsCOMPtr<nsIImapUrl> imapUrl;
+ nsAutoCString urlSpec;
+ nsresult rv;
+
+ char default_hierarchyDelimiter = GetHierarchyDelimiter(dstFolder);
+ rv = CreateStartOfImapUrl(EmptyCString(), getter_AddRefs(imapUrl), dstFolder,
+ urlListener, urlSpec, default_hierarchyDelimiter);
+ if (NS_SUCCEEDED(rv) && imapUrl) {
+ rv = SetImapUrlSink(dstFolder, imapUrl);
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIMsgMailNewsUrl> mailNewsUrl = do_QueryInterface(imapUrl);
+ if (mailNewsUrl) mailNewsUrl->SetMsgWindow(msgWindow);
+ char hierarchyDelimiter = kOnlineHierarchySeparatorUnknown;
+ nsCString folderName;
+
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsurl = do_QueryInterface(imapUrl);
+ GetFolderName(srcFolder, folderName);
+ urlSpec.AppendLiteral("/movefolderhierarchy>");
+ urlSpec.Append(hierarchyDelimiter);
+ urlSpec.Append(folderName);
+ urlSpec.Append('>');
+ GetFolderName(dstFolder, folderName);
+ if (!folderName.IsEmpty()) {
+ urlSpec.Append(hierarchyDelimiter);
+ urlSpec.Append(folderName);
+ }
+ rv = mailnewsurl->SetSpecInternal(urlSpec);
+ if (NS_SUCCEEDED(rv)) {
+ GetFolderName(srcFolder, folderName);
+ rv = GetImapConnectionAndLoadUrl(imapUrl, nullptr, nullptr);
+ }
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapService::RenameLeaf(nsIMsgFolder* srcFolder,
+ const nsAString& newLeafName,
+ nsIUrlListener* urlListener,
+ nsIMsgWindow* msgWindow) {
+ NS_ENSURE_ARG_POINTER(srcFolder);
+
+ nsCOMPtr<nsIImapUrl> imapUrl;
+ nsAutoCString urlSpec;
+
+ char hierarchyDelimiter = GetHierarchyDelimiter(srcFolder);
+ nsresult rv =
+ CreateStartOfImapUrl(EmptyCString(), getter_AddRefs(imapUrl), srcFolder,
+ urlListener, urlSpec, hierarchyDelimiter);
+ if (NS_SUCCEEDED(rv)) {
+ rv = SetImapUrlSink(srcFolder, imapUrl);
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIMsgMailNewsUrl> mailNewsUrl = do_QueryInterface(imapUrl);
+ mailNewsUrl->SetMsgWindow(msgWindow);
+ nsCString folderName;
+ GetFolderName(srcFolder, folderName);
+ urlSpec.AppendLiteral("/rename>");
+ urlSpec.Append(hierarchyDelimiter);
+ urlSpec.Append(folderName);
+ urlSpec.Append('>');
+ urlSpec.Append(hierarchyDelimiter);
+ nsAutoCString cStrFolderName;
+ // Unescape the name before looking for parent path
+ MsgUnescapeString(folderName, 0, cStrFolderName);
+ int32_t leafNameStart = cStrFolderName.RFindChar(hierarchyDelimiter);
+ if (leafNameStart != -1) {
+ cStrFolderName.SetLength(leafNameStart + 1);
+ urlSpec.Append(cStrFolderName);
+ }
+
+ nsAutoCString utfNewName;
+ bool utf8AcceptEnabled;
+ nsCOMPtr<nsIMsgImapMailFolder> imapFolder = do_QueryInterface(srcFolder);
+ rv = imapFolder->GetShouldUseUtf8FolderName(&utf8AcceptEnabled);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (utf8AcceptEnabled) {
+ CopyUTF16toUTF8(newLeafName, utfNewName);
+ } else {
+ CopyUTF16toMUTF7(newLeafName, utfNewName);
+ }
+ nsCString escapedNewName;
+ MsgEscapeString(utfNewName, nsINetUtil::ESCAPE_URL_PATH, escapedNewName);
+ nsCString escapedSlashName;
+ rv = nsImapUrl::EscapeSlashes(escapedNewName.get(),
+ getter_Copies(escapedSlashName));
+ NS_ENSURE_SUCCESS(rv, rv);
+ urlSpec.Append(escapedSlashName);
+
+ rv = mailNewsUrl->SetSpecInternal(urlSpec);
+ if (NS_SUCCEEDED(rv))
+ rv = GetImapConnectionAndLoadUrl(imapUrl, nullptr, nullptr);
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapService::CreateFolder(nsIMsgFolder* parent,
+ const nsAString& newFolderName,
+ nsIUrlListener* urlListener,
+ nsIURI** url) {
+ NS_ENSURE_ARG_POINTER(parent);
+
+ nsCOMPtr<nsIImapUrl> imapUrl;
+ nsAutoCString urlSpec;
+ nsresult rv;
+
+ char hierarchyDelimiter = GetHierarchyDelimiter(parent);
+ rv = CreateStartOfImapUrl(EmptyCString(), getter_AddRefs(imapUrl), parent,
+ urlListener, urlSpec, hierarchyDelimiter);
+ if (NS_SUCCEEDED(rv) && imapUrl) {
+ rv = SetImapUrlSink(parent, imapUrl);
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsurl = do_QueryInterface(imapUrl);
+
+ nsCString folderName;
+ GetFolderName(parent, folderName);
+ urlSpec.AppendLiteral("/create>");
+ urlSpec.Append(hierarchyDelimiter);
+ if (!folderName.IsEmpty()) {
+ nsCString canonicalName;
+ nsImapUrl::ConvertToCanonicalFormat(
+ folderName.get(), hierarchyDelimiter, getter_Copies(canonicalName));
+ urlSpec.Append(canonicalName);
+ urlSpec.Append(hierarchyDelimiter);
+ }
+
+ nsAutoCString utfNewName;
+ bool utf8AcceptEnabled;
+ nsCOMPtr<nsIMsgImapMailFolder> imapFolder = do_QueryInterface(parent);
+ rv = imapFolder->GetShouldUseUtf8FolderName(&utf8AcceptEnabled);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (utf8AcceptEnabled) {
+ CopyUTF16toUTF8(newFolderName, utfNewName);
+ } else {
+ CopyUTF16toMUTF7(newFolderName, utfNewName);
+ }
+ nsCString escapedFolderName;
+ MsgEscapeString(utfNewName, nsINetUtil::ESCAPE_URL_PATH,
+ escapedFolderName);
+ urlSpec.Append(escapedFolderName);
+
+ rv = mailnewsurl->SetSpecInternal(urlSpec);
+ if (NS_SUCCEEDED(rv))
+ rv = GetImapConnectionAndLoadUrl(imapUrl, nullptr, url);
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapService::EnsureFolderExists(nsIMsgFolder* parent,
+ const nsAString& newFolderName,
+ nsIMsgWindow* msgWindow,
+ nsIUrlListener* urlListener) {
+ NS_ENSURE_ARG_POINTER(parent);
+
+ nsCOMPtr<nsIImapUrl> imapUrl;
+ nsAutoCString urlSpec;
+ nsresult rv;
+
+ char hierarchyDelimiter = GetHierarchyDelimiter(parent);
+ rv = CreateStartOfImapUrl(EmptyCString(), getter_AddRefs(imapUrl), parent,
+ urlListener, urlSpec, hierarchyDelimiter);
+ if (NS_SUCCEEDED(rv) && imapUrl) {
+ rv = SetImapUrlSink(parent, imapUrl);
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsurl = do_QueryInterface(imapUrl);
+
+ nsCString folderName;
+ GetFolderName(parent, folderName);
+ urlSpec.AppendLiteral("/ensureExists>");
+ urlSpec.Append(hierarchyDelimiter);
+ if (!folderName.IsEmpty()) {
+ urlSpec.Append(folderName);
+ urlSpec.Append(hierarchyDelimiter);
+ }
+ nsAutoCString utfNewName;
+ bool utf8AcceptEnabled;
+ nsCOMPtr<nsIMsgImapMailFolder> imapFolder = do_QueryInterface(parent);
+ rv = imapFolder->GetShouldUseUtf8FolderName(&utf8AcceptEnabled);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (utf8AcceptEnabled) {
+ CopyUTF16toUTF8(newFolderName, utfNewName);
+ } else {
+ CopyUTF16toMUTF7(newFolderName, utfNewName);
+ }
+ nsCString escapedFolderName;
+ MsgEscapeString(utfNewName, nsINetUtil::ESCAPE_URL_PATH,
+ escapedFolderName);
+ urlSpec.Append(escapedFolderName);
+
+ rv = mailnewsurl->SetSpecInternal(urlSpec);
+
+ if (msgWindow) mailnewsurl->SetMsgWindow(msgWindow);
+
+ if (NS_SUCCEEDED(rv))
+ rv = GetImapConnectionAndLoadUrl(imapUrl, nullptr, nullptr);
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapService::ListFolder(nsIMsgFolder* aImapMailFolder,
+ nsIUrlListener* aUrlListener) {
+ NS_ENSURE_ARG_POINTER(aImapMailFolder);
+
+ return FolderCommand(aImapMailFolder, aUrlListener, "/listfolder>",
+ nsIImapUrl::nsImapListFolder, nullptr, nullptr);
+}
+
+NS_IMETHODIMP nsImapService::GetScheme(nsACString& aScheme) {
+ aScheme.AssignLiteral("imap");
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapService::AllowPort(int32_t port, const char* scheme,
+ bool* aRetVal) {
+ // allow imap to run on any port
+ *aRetVal = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapService::GetDefaultDoBiff(bool* aDoBiff) {
+ NS_ENSURE_ARG_POINTER(aDoBiff);
+ // by default, do biff for IMAP servers
+ *aDoBiff = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapService::GetDefaultServerPort(bool isSecure,
+ int32_t* aDefaultPort) {
+ // Return Secure IMAP Port if secure option chosen i.e., if isSecure is TRUE
+ if (isSecure)
+ *aDefaultPort = nsIImapUrl::DEFAULT_IMAPS_PORT;
+ else
+ *aDefaultPort = nsIImapUrl::DEFAULT_IMAP_PORT;
+
+ return NS_OK;
+}
+
+// this method first tries to find an exact username and hostname match with the
+// given url then, tries to find any account on the passed in imap host in case
+// this is a url to a shared imap folder.
+nsresult nsImapService::GetServerFromUrl(nsIImapUrl* aImapUrl,
+ nsIMsgIncomingServer** aServer) {
+ nsresult rv;
+ nsCString folderName;
+ nsAutoCString userPass;
+ nsAutoCString hostName;
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(aImapUrl);
+
+ // if we can't get a folder name out of the url then I think this is an error
+ aImapUrl->CreateCanonicalSourceFolderPathString(getter_Copies(folderName));
+ if (folderName.IsEmpty()) {
+ rv = mailnewsUrl->GetFileName(folderName);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ nsCOMPtr<nsIMsgAccountManager> accountManager =
+ do_GetService("@mozilla.org/messenger/account-manager;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = accountManager->FindServerByURI(mailnewsUrl, aServer);
+
+ // look for server with any user name, in case we're trying to subscribe
+ // to a folder with some one else's user name like the following
+ // "IMAP://userSharingFolder@server1/SharedFolderName"
+ if (NS_FAILED(rv) || !aServer) {
+ nsAutoCString turl;
+ rv = mailnewsUrl->GetSpec(turl);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIURL> url;
+ rv = NS_MutateURI(NS_STANDARDURLMUTATOR_CONTRACTID)
+ .SetSpec(turl)
+ .SetUserPass(EmptyCString())
+ .Finalize(url);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = accountManager->FindServerByURI(url, aServer);
+ if (*aServer) aImapUrl->SetExternalLinkUrl(true);
+ }
+
+ // if we can't extract the imap server from this url then give up!!!
+ NS_ENSURE_TRUE(*aServer, NS_ERROR_FAILURE);
+ return rv;
+}
+
+nsresult nsImapService::NewURI(const nsACString& aSpec,
+ const char* aOriginCharset, // ignored
+ nsIURI* aBaseURI, nsIURI** aRetVal) {
+ NS_ENSURE_ARG_POINTER(aRetVal);
+
+ nsresult rv;
+ nsCOMPtr<nsIImapUrl> aImapUrl = do_CreateInstance(kImapUrlCID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // now extract lots of fun information...
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(aImapUrl);
+ // nsAutoCString unescapedSpec(aSpec);
+ // nsUnescape(unescapedSpec.BeginWriting());
+
+ // set the spec
+ if (aBaseURI) {
+ nsAutoCString newSpec;
+ aBaseURI->Resolve(aSpec, newSpec);
+ rv = mailnewsUrl->SetSpecInternal(newSpec);
+ } else {
+ rv = mailnewsUrl->SetSpecInternal(aSpec);
+ }
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCString folderName;
+ // if we can't get a folder name out of the url then I think this is an error
+ aImapUrl->CreateCanonicalSourceFolderPathString(getter_Copies(folderName));
+ if (folderName.IsEmpty()) {
+ rv = mailnewsUrl->GetFileName(folderName);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = GetServerFromUrl(aImapUrl, getter_AddRefs(server));
+ // if we can't extract the imap server from this url then give up!!!
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_TRUE(server, NS_ERROR_FAILURE);
+
+ // now try to get the folder in question...
+ nsCOMPtr<nsIMsgFolder> rootFolder;
+ server->GetRootFolder(getter_AddRefs(rootFolder));
+ bool ready;
+ if (rootFolder && !folderName.IsEmpty() &&
+ // Skip folder processing if folder names aren't ready yet.
+ // They may not be available during early initialization.
+ // XXX TODO: This hack can be removed when the localization system gets
+ // initialized in M-C code before, for example, the permission manager
+ // which creates all sorts of URIs incl. imap: URIs.
+ NS_SUCCEEDED(rootFolder->FolderNamesReady(&ready)) && ready) {
+ nsCOMPtr<nsIMsgFolder> folder;
+ nsCOMPtr<nsIMsgImapMailFolder> imapRoot = do_QueryInterface(rootFolder);
+ nsCOMPtr<nsIMsgImapMailFolder> subFolder;
+ if (imapRoot) {
+ imapRoot->FindOnlineSubFolder(folderName, getter_AddRefs(subFolder));
+ folder = do_QueryInterface(subFolder);
+ }
+
+ // If we can't find the folder, we can still create the URI
+ // in this low-level service. Cloning URIs where the folder
+ // isn't found is common when folders are renamed or moved.
+ // We also ignore return statuses here.
+ if (folder) {
+ nsCOMPtr<nsIImapMessageSink> msgSink = do_QueryInterface(folder);
+ (void)aImapUrl->SetImapMessageSink(msgSink);
+
+ (void)SetImapUrlSink(folder, aImapUrl);
+
+ nsCString messageIdString;
+ aImapUrl->GetListOfMessageIds(messageIdString);
+ if (!messageIdString.IsEmpty()) {
+ bool useLocalCache = false;
+ folder->HasMsgOffline(strtoul(messageIdString.get(), nullptr, 10),
+ &useLocalCache);
+ mailnewsUrl->SetMsgIsInLocalCache(useLocalCache);
+ }
+ }
+ }
+
+ // we got an imap url, so be sure to return it...
+ nsCOMPtr<nsIURI> imapUri = do_QueryInterface(aImapUrl);
+
+ imapUri.forget(aRetVal);
+
+ return rv;
+}
+
+NS_IMETHODIMP nsImapService::NewChannel(nsIURI* aURI, nsILoadInfo* aLoadInfo,
+ nsIChannel** aRetVal) {
+ NS_ENSURE_ARG_POINTER(aURI);
+ NS_ENSURE_ARG_POINTER(aRetVal);
+ MOZ_ASSERT(aLoadInfo);
+ *aRetVal = nullptr;
+
+ nsresult rv;
+ nsCOMPtr<nsIImapUrl> imapUrl = do_QueryInterface(aURI, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(imapUrl, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // imap can't open and return a channel right away...the url needs to go in
+ // the imap url queue until we find a connection which can run the url..in
+ // order to satisfy necko, we're going to return a mock imap channel....
+ nsCOMPtr<nsIImapMockChannel> channel =
+ do_CreateInstance(kCImapMockChannel, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ channel->SetURI(aURI);
+
+ rv = channel->SetLoadInfo(aLoadInfo);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString spec;
+ rv = aURI->GetSpec(spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Add the attachment disposition. This forces docShell to open the
+ // attachment instead of displaying it. Content types we have special
+ // handlers for are white-listed. This white list also exists in
+ // nsMailboxService::NewChannel and nsNntpService::NewChannel, so if you're
+ // changing this, update those too.
+ if (spec.Find("part=") >= 0 && spec.Find("type=message/rfc822") < 0 &&
+ spec.Find("type=application/x-message-display") < 0 &&
+ spec.Find("type=application/pdf") < 0) {
+ rv = channel->SetContentDisposition(nsIChannel::DISPOSITION_ATTACHMENT);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ nsCOMPtr<nsIMsgWindow> msgWindow;
+ mailnewsUrl->GetMsgWindow(getter_AddRefs(msgWindow));
+ if (msgWindow) {
+ nsCOMPtr<nsIDocShell> msgDocShell;
+ msgWindow->GetRootDocShell(getter_AddRefs(msgDocShell));
+ if (msgDocShell) {
+ nsCOMPtr<nsIProgressEventSink> prevEventSink;
+ channel->GetProgressEventSink(getter_AddRefs(prevEventSink));
+ nsCOMPtr<nsIInterfaceRequestor> docIR(do_QueryInterface(msgDocShell));
+ channel->SetNotificationCallbacks(docIR);
+ // we want to use our existing event sink.
+ if (prevEventSink) channel->SetProgressEventSink(prevEventSink);
+ }
+ } else {
+ // This might not be a call resulting from user action (e.g. we might be
+ // getting a new message via nsImapMailFolder::OnNewIdleMessages(), or via
+ // nsAutoSyncManager, etc). In this case, try to retrieve the top-most
+ // message window to update its status feedback.
+ nsCOMPtr<nsIMsgMailSession> mailSession =
+ do_GetService("@mozilla.org/messenger/services/session;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgWindow> msgWindow;
+ rv = mailSession->GetTopmostMsgWindow(getter_AddRefs(msgWindow));
+ if (NS_SUCCEEDED(rv) && msgWindow) {
+ // If we could retrieve a window, get its nsIMsgStatusFeedback and set it
+ // to the URL so that other components interacting with it can correctly
+ // feed status updates to the UI.
+ nsCOMPtr<nsIMsgStatusFeedback> statusFeedback;
+ msgWindow->GetStatusFeedback(getter_AddRefs(statusFeedback));
+ mailnewsUrl->SetStatusFeedback(statusFeedback);
+ // We also need to set the status feedback as the channel's progress event
+ // sink, since that's how nsImapProtocol feeds some of the progress
+ // changes (e.g. downloading incoming messages) to the UI.
+ nsCOMPtr<nsIProgressEventSink> eventSink =
+ do_QueryInterface(statusFeedback);
+ channel->SetProgressEventSink(eventSink);
+ }
+
+ // This function ends by checking the final value of rv and deciding whether
+ // to set aRetVal to our channel according to it. We don't want this to be
+ // impacted if we fail to retrieve a window (which might not work if we're
+ // being called through the command line, or through a test), so let's just
+ // reset rv to an OK value.
+ rv = NS_OK;
+ }
+
+ // the imap url holds a weak reference so we can pass the channel into the
+ // imap protocol when we actually run the url.
+ imapUrl->SetMockChannel(channel);
+
+ bool externalLinkUrl;
+ imapUrl->GetExternalLinkUrl(&externalLinkUrl);
+
+ // Only external imap links with no action are supported. Ignore links that
+ // attempt to cause an effect such as fetching a mime part. This avoids
+ // spurious prompts to subscribe to folders due to "imap://...Fetch..." links
+ // residing in legacy emails residing in an imap mailbox.
+ if (externalLinkUrl) {
+ nsImapAction imapAction;
+ imapUrl->GetImapAction(&imapAction);
+ if (imapAction != 0) externalLinkUrl = false;
+ }
+
+ if (externalLinkUrl) {
+ // Everything after here is to handle clicking on an external link. We only
+ // want to do this if we didn't run the url through the various
+ // nsImapService methods, which we can tell by seeing if the sinks have been
+ // setup on the url or not.
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = GetServerFromUrl(imapUrl, getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCString folderName;
+ imapUrl->CreateCanonicalSourceFolderPathString(getter_Copies(folderName));
+ if (folderName.IsEmpty()) {
+ nsCString escapedFolderName;
+ rv = mailnewsUrl->GetFileName(escapedFolderName);
+ if (!escapedFolderName.IsEmpty()) {
+ MsgUnescapeString(escapedFolderName, 0, folderName);
+ }
+ }
+ // if the parent is null, then the folder doesn't really exist, so see if
+ // the user wants to subscribe to it./
+ nsCOMPtr<nsIMsgFolder> urlFolder;
+ // now try to get the folder in question...
+ nsCOMPtr<nsIMsgFolder> rootFolder;
+ server->GetRootFolder(getter_AddRefs(rootFolder));
+ nsCOMPtr<nsIMsgImapMailFolder> imapRoot = do_QueryInterface(rootFolder);
+ nsCOMPtr<nsIMsgImapMailFolder> subFolder;
+ if (imapRoot) {
+ imapRoot->FindOnlineSubFolder(folderName, getter_AddRefs(subFolder));
+ urlFolder = do_QueryInterface(subFolder);
+ }
+ nsCOMPtr<nsIMsgFolder> parent;
+ if (urlFolder) urlFolder->GetParent(getter_AddRefs(parent));
+ nsCString serverKey;
+ nsAutoCString userPass;
+ rv = mailnewsUrl->GetUserPass(userPass);
+ server->GetKey(serverKey);
+ nsCString fullFolderName;
+ if (parent) fullFolderName = folderName;
+ if (!parent && !folderName.IsEmpty() && imapRoot) {
+ // Check if this folder is another user's folder.
+ fullFolderName =
+ nsImapNamespaceList::GenerateFullFolderNameWithDefaultNamespace(
+ serverKey.get(), folderName.get(), userPass.get(),
+ kOtherUsersNamespace, nullptr);
+ // if this is another user's folder, let's see if we're already subscribed
+ // to it.
+ rv = imapRoot->FindOnlineSubFolder(fullFolderName,
+ getter_AddRefs(subFolder));
+ urlFolder = do_QueryInterface(subFolder);
+ if (urlFolder) urlFolder->GetParent(getter_AddRefs(parent));
+ }
+ // if we couldn't get the fullFolderName, then we probably couldn't find
+ // the other user's namespace, in which case, we shouldn't try to subscribe
+ // to it.
+ if (!parent && !folderName.IsEmpty() && !fullFolderName.IsEmpty()) {
+ // this folder doesn't exist - check if the user wants to subscribe to
+ // this folder.
+ nsCOMPtr<nsIPrompt> dialog;
+ nsCOMPtr<nsIWindowWatcher> wwatch(
+ do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ wwatch->GetNewPrompter(nullptr, getter_AddRefs(dialog));
+
+ nsString statusString, confirmText;
+ nsCOMPtr<nsIStringBundle> bundle;
+ rv = IMAPGetStringBundle(getter_AddRefs(bundle));
+ NS_ENSURE_SUCCESS(rv, rv);
+ // Need to convert folder name, can be MUTF-7 or UTF-8 depending on the
+ // server.
+ nsAutoString unescapedName;
+ if (NS_FAILED(CopyFolderNameToUTF16(fullFolderName, unescapedName)))
+ CopyASCIItoUTF16(fullFolderName, unescapedName);
+ AutoTArray<nsString, 1> formatStrings = {unescapedName};
+
+ rv = bundle->FormatStringFromName("imapSubscribePrompt", formatStrings,
+ confirmText);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool confirmResult = false;
+ rv = dialog->Confirm(nullptr, confirmText.get(), &confirmResult);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (confirmResult) {
+ nsCOMPtr<nsIImapIncomingServer> imapServer = do_QueryInterface(server);
+ if (imapServer) {
+ nsCOMPtr<nsIURI> subscribeURI;
+ // Now we have the real folder name to try to subscribe to. Let's try
+ // running a subscribe url and returning that as the uri we've
+ // created. We need to convert this to unicode because that's what
+ // subscribe wants.
+ nsAutoString unicodeName;
+ CopyFolderNameToUTF16(fullFolderName, unicodeName);
+ rv = imapServer->SubscribeToFolder(unicodeName, true,
+ getter_AddRefs(subscribeURI));
+ if (NS_SUCCEEDED(rv) && subscribeURI) {
+ nsCOMPtr<nsIImapUrl> imapSubscribeUrl =
+ do_QueryInterface(subscribeURI);
+ if (imapSubscribeUrl) imapSubscribeUrl->SetExternalLinkUrl(true);
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl =
+ do_QueryInterface(subscribeURI);
+ if (mailnewsUrl) {
+ nsCOMPtr<nsIMsgMailSession> mailSession = do_GetService(
+ "@mozilla.org/messenger/services/session;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIMsgWindow> msgWindow;
+ rv = mailSession->GetTopmostMsgWindow(getter_AddRefs(msgWindow));
+ if (NS_SUCCEEDED(rv) && msgWindow) {
+ mailnewsUrl->SetMsgWindow(msgWindow);
+ nsCOMPtr<nsIUrlListener> listener =
+ do_QueryInterface(rootFolder);
+ if (listener) mailnewsUrl->RegisterListener(listener);
+ }
+ }
+ }
+ }
+ }
+ // error out this channel, so it'll stop trying to run the url.
+ rv = NS_ERROR_FAILURE;
+ *aRetVal = nullptr;
+ }
+ // this folder exists - check if this is a click on a link to the folder
+ // in which case, we'll select it.
+ else if (!fullFolderName.IsEmpty()) {
+ nsCOMPtr<nsIMsgFolder> imapFolder;
+ mailnewsUrl->GetFolder(getter_AddRefs(imapFolder));
+ NS_ASSERTION(
+ imapFolder,
+ nsPrintfCString("No folder for imap url: %s", spec.get()).get());
+
+ nsCOMPtr<nsIMsgMailSession> mailSession =
+ do_GetService("@mozilla.org/messenger/services/session;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIMsgWindow> msgWindow;
+ rv = mailSession->GetTopmostMsgWindow(getter_AddRefs(msgWindow));
+ if (NS_SUCCEEDED(rv) && msgWindow) {
+ // Clicked IMAP folder URL in the window.
+ nsCOMPtr<nsIObserverService> obsServ =
+ mozilla::services::GetObserverService();
+ obsServ->NotifyObservers(imapFolder, "folder-attention", nullptr);
+ // null out this channel, so it'll stop trying to run the url.
+ *aRetVal = nullptr;
+ rv = NS_OK;
+ } else {
+ // Got IMAP folder URL from command line (most likely).
+ // Set action to nsImapSelectFolder (x-application-imapfolder), so
+ // ::HandleContent will handle it.
+ imapUrl->SetImapAction(nsIImapUrl::nsImapSelectFolder);
+ HandleContent("x-application-imapfolder", nullptr, channel);
+ }
+ }
+ }
+ if (NS_SUCCEEDED(rv)) channel.forget(aRetVal);
+ return rv;
+}
+
+NS_IMETHODIMP nsImapService::SetDefaultLocalPath(nsIFile* aPath) {
+ NS_ENSURE_ARG_POINTER(aPath);
+
+ return NS_SetPersistentFile(PREF_MAIL_ROOT_IMAP_REL, PREF_MAIL_ROOT_IMAP,
+ aPath);
+}
+
+NS_IMETHODIMP nsImapService::GetDefaultLocalPath(nsIFile** aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = nullptr;
+
+ bool havePref;
+ nsCOMPtr<nsIFile> localFile;
+ nsresult rv = NS_GetPersistentFile(
+ PREF_MAIL_ROOT_IMAP_REL, PREF_MAIL_ROOT_IMAP, NS_APP_IMAP_MAIL_50_DIR,
+ havePref, getter_AddRefs(localFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_TRUE(localFile, NS_ERROR_FAILURE);
+
+ bool exists;
+ rv = localFile->Exists(&exists);
+ if (NS_SUCCEEDED(rv) && !exists)
+ rv = localFile->Create(nsIFile::DIRECTORY_TYPE, 0775);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!havePref || !exists) {
+ rv = NS_SetPersistentFile(PREF_MAIL_ROOT_IMAP_REL, PREF_MAIL_ROOT_IMAP,
+ localFile);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "Failed to set root dir pref.");
+ }
+
+ localFile.forget(aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapService::GetServerIID(nsIID** aServerIID) {
+ *aServerIID = new nsIID(NS_GET_IID(nsIImapIncomingServer));
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapService::GetRequiresUsername(bool* aRequiresUsername) {
+ NS_ENSURE_ARG_POINTER(aRequiresUsername);
+
+ *aRequiresUsername = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapService::GetPreflightPrettyNameWithEmailAddress(
+ bool* aPreflightPrettyNameWithEmailAddress) {
+ NS_ENSURE_ARG_POINTER(aPreflightPrettyNameWithEmailAddress);
+
+ *aPreflightPrettyNameWithEmailAddress = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapService::GetCanLoginAtStartUp(bool* aCanLoginAtStartUp) {
+ NS_ENSURE_ARG_POINTER(aCanLoginAtStartUp);
+ *aCanLoginAtStartUp = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapService::GetCanDelete(bool* aCanDelete) {
+ NS_ENSURE_ARG_POINTER(aCanDelete);
+ *aCanDelete = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapService::GetCanDuplicate(bool* aCanDuplicate) {
+ NS_ENSURE_ARG_POINTER(aCanDuplicate);
+ *aCanDuplicate = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapService::GetCanGetMessages(bool* aCanGetMessages) {
+ NS_ENSURE_ARG_POINTER(aCanGetMessages);
+ *aCanGetMessages = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapService::GetCanGetIncomingMessages(
+ bool* aCanGetIncomingMessages) {
+ NS_ENSURE_ARG_POINTER(aCanGetIncomingMessages);
+ *aCanGetIncomingMessages = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapService::GetShowComposeMsgLink(bool* showComposeMsgLink) {
+ NS_ENSURE_ARG_POINTER(showComposeMsgLink);
+ *showComposeMsgLink = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapService::GetFoldersCreatedAsync(bool* aAsyncCreation) {
+ NS_ENSURE_ARG_POINTER(aAsyncCreation);
+ *aAsyncCreation = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapService::GetListOfFoldersWithPath(
+ nsIImapIncomingServer* aServer, nsIMsgWindow* aMsgWindow,
+ const nsACString& folderPath) {
+ nsresult rv;
+ nsCOMPtr<nsIMsgIncomingServer> server = do_QueryInterface(aServer);
+ if (!server) return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIMsgFolder> rootMsgFolder;
+ rv = server->GetRootMsgFolder(getter_AddRefs(rootMsgFolder));
+
+ NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && rootMsgFolder, NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsIUrlListener> listener = do_QueryInterface(aServer, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!listener) return NS_ERROR_FAILURE;
+
+ // Locate the folder so that the correct hierarchical delimiter is used in the
+ // folder pathnames, otherwise root's (ie, '^') is used and this is wrong.
+ nsCOMPtr<nsIMsgFolder> msgFolder;
+ if (rootMsgFolder && !folderPath.IsEmpty()) {
+ // If the folder path contains 'INBOX' of any forms, we need to convert it
+ // to uppercase before finding it under the root folder. We do the same in
+ // PossibleImapMailbox().
+ nsAutoCString tempFolderName(folderPath);
+ nsAutoCString tokenStr, remStr, changedStr;
+ int32_t slashPos = tempFolderName.FindChar('/');
+ if (slashPos > 0) {
+ tokenStr = StringHead(tempFolderName, slashPos);
+ remStr = Substring(tempFolderName, slashPos);
+ } else
+ tokenStr.Assign(tempFolderName);
+
+ if (tokenStr.LowerCaseEqualsLiteral("inbox") &&
+ !tokenStr.EqualsLiteral("INBOX"))
+ changedStr.AppendLiteral("INBOX");
+ else
+ changedStr.Append(tokenStr);
+
+ if (slashPos > 0) changedStr.Append(remStr);
+
+ rv = rootMsgFolder->FindSubFolder(changedStr, getter_AddRefs(msgFolder));
+ }
+ return DiscoverChildren(msgFolder, listener, folderPath);
+}
+
+NS_IMETHODIMP nsImapService::GetListOfFoldersOnServer(
+ nsIImapIncomingServer* aServer, nsIMsgWindow* aMsgWindow) {
+ nsresult rv;
+
+ nsCOMPtr<nsIMsgIncomingServer> server = do_QueryInterface(aServer);
+ if (!server) return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIMsgFolder> rootMsgFolder;
+ rv = server->GetRootMsgFolder(getter_AddRefs(rootMsgFolder));
+
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!rootMsgFolder) return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIUrlListener> listener = do_QueryInterface(aServer, &rv);
+ NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && listener, NS_ERROR_FAILURE);
+
+ return DiscoverAllAndSubscribedFolders(rootMsgFolder, listener, aMsgWindow);
+}
+
+NS_IMETHODIMP nsImapService::SubscribeFolder(nsIMsgFolder* aFolder,
+ const nsAString& aFolderName,
+ nsIUrlListener* urlListener,
+ nsIURI** url) {
+ return ChangeFolderSubscription(aFolder, aFolderName, "/subscribe>",
+ urlListener, url);
+}
+
+nsresult nsImapService::ChangeFolderSubscription(nsIMsgFolder* folder,
+ const nsAString& folderName,
+ const char* command,
+ nsIUrlListener* urlListener,
+ nsIURI** url) {
+ NS_ENSURE_ARG_POINTER(folder);
+
+ nsCOMPtr<nsIImapUrl> imapUrl;
+ nsAutoCString urlSpec;
+ nsresult rv;
+ char hierarchyDelimiter = GetHierarchyDelimiter(folder);
+ rv = CreateStartOfImapUrl(EmptyCString(), getter_AddRefs(imapUrl), folder,
+ urlListener, urlSpec, hierarchyDelimiter);
+ if (NS_SUCCEEDED(rv) && imapUrl) {
+ rv = SetImapUrlSink(folder, imapUrl);
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsurl = do_QueryInterface(imapUrl);
+ urlSpec.Append(command);
+ urlSpec.Append(hierarchyDelimiter);
+ // `folderName` contains MUFT-7 or UTF-8 as required by the server here.
+ NS_ConvertUTF16toUTF8 utfFolderName(folderName);
+ nsCString escapedFolderName;
+ MsgEscapeString(utfFolderName, nsINetUtil::ESCAPE_URL_PATH,
+ escapedFolderName);
+ urlSpec.Append(escapedFolderName);
+ rv = mailnewsurl->SetSpecInternal(urlSpec);
+ if (NS_SUCCEEDED(rv))
+ rv = GetImapConnectionAndLoadUrl(imapUrl, nullptr, url);
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapService::UnsubscribeFolder(nsIMsgFolder* aFolder,
+ const nsAString& aFolderName,
+ nsIUrlListener* aUrlListener,
+ nsIURI** aUrl) {
+ return ChangeFolderSubscription(aFolder, aFolderName, "/unsubscribe>",
+ aUrlListener, aUrl);
+}
+
+NS_IMETHODIMP nsImapService::GetFolderAdminUrl(nsIMsgFolder* aImapMailFolder,
+ nsIMsgWindow* aMsgWindow,
+ nsIUrlListener* aUrlListener,
+ nsIURI** aURL) {
+ NS_ENSURE_ARG_POINTER(aImapMailFolder);
+
+ return FolderCommand(aImapMailFolder, aUrlListener, "/refreshfolderurls>",
+ nsIImapUrl::nsImapRefreshFolderUrls, aMsgWindow, aURL);
+}
+
+NS_IMETHODIMP nsImapService::IssueCommandOnMsgs(nsIMsgFolder* anImapFolder,
+ nsIMsgWindow* aMsgWindow,
+ const nsACString& aCommand,
+ const nsACString& uids,
+ nsIURI** aURL) {
+ NS_ENSURE_ARG_POINTER(anImapFolder);
+ NS_ENSURE_ARG_POINTER(aMsgWindow);
+
+ nsCOMPtr<nsIImapUrl> imapUrl;
+ nsAutoCString urlSpec;
+ nsresult rv;
+ char hierarchyDelimiter = GetHierarchyDelimiter(anImapFolder);
+ rv = CreateStartOfImapUrl(EmptyCString(), getter_AddRefs(imapUrl),
+ anImapFolder, nullptr, urlSpec, hierarchyDelimiter);
+
+ if (NS_SUCCEEDED(rv) && imapUrl) {
+ // nsImapUrl::SetSpec() will set the imap action properly
+ rv = imapUrl->SetImapAction(nsIImapUrl::nsImapUserDefinedMsgCommand);
+
+ nsCOMPtr<nsIMsgMailNewsUrl> mailNewsUrl = do_QueryInterface(imapUrl);
+ mailNewsUrl->SetMsgWindow(aMsgWindow);
+ mailNewsUrl->SetUpdatingFolder(true);
+ rv = SetImapUrlSink(anImapFolder, imapUrl);
+
+ if (NS_SUCCEEDED(rv)) {
+ nsCString folderName;
+ GetFolderName(anImapFolder, folderName);
+ urlSpec.Append('/');
+ urlSpec.Append(aCommand);
+ urlSpec.Append('>');
+ urlSpec.Append(uidString);
+ urlSpec.Append('>');
+ urlSpec.Append(hierarchyDelimiter);
+ urlSpec.Append(folderName);
+ urlSpec.Append('>');
+ urlSpec.Append(uids);
+ rv = mailNewsUrl->SetSpecInternal(urlSpec);
+ if (NS_SUCCEEDED(rv))
+ rv = GetImapConnectionAndLoadUrl(imapUrl, nullptr, aURL);
+ }
+ } // if we have a url to run....
+
+ return rv;
+}
+
+NS_IMETHODIMP nsImapService::FetchCustomMsgAttribute(
+ nsIMsgFolder* anImapFolder, nsIMsgWindow* aMsgWindow,
+ const nsACString& aAttribute, const nsACString& uids, nsIURI** aURL) {
+ NS_ENSURE_ARG_POINTER(anImapFolder);
+ NS_ENSURE_ARG_POINTER(aMsgWindow);
+
+ nsCOMPtr<nsIImapUrl> imapUrl;
+ nsAutoCString urlSpec;
+ nsresult rv;
+ char hierarchyDelimiter = GetHierarchyDelimiter(anImapFolder);
+ rv = CreateStartOfImapUrl(EmptyCString(), getter_AddRefs(imapUrl),
+ anImapFolder, nullptr, urlSpec, hierarchyDelimiter);
+ if (NS_SUCCEEDED(rv) && imapUrl) {
+ // nsImapUrl::SetSpec() will set the imap action properly
+ rv = imapUrl->SetImapAction(nsIImapUrl::nsImapUserDefinedFetchAttribute);
+
+ nsCOMPtr<nsIMsgMailNewsUrl> mailNewsUrl = do_QueryInterface(imapUrl);
+ mailNewsUrl->SetMsgWindow(aMsgWindow);
+ mailNewsUrl->SetUpdatingFolder(true);
+ rv = SetImapUrlSink(anImapFolder, imapUrl);
+
+ if (NS_SUCCEEDED(rv)) {
+ nsCString folderName;
+ GetFolderName(anImapFolder, folderName);
+ urlSpec.AppendLiteral("/customFetch>UID>");
+ urlSpec.Append(hierarchyDelimiter);
+ urlSpec.Append(folderName);
+ urlSpec.Append('>');
+ urlSpec.Append(uids);
+ urlSpec.Append('>');
+ urlSpec.Append(aAttribute);
+ rv = mailNewsUrl->SetSpecInternal(urlSpec);
+ if (NS_SUCCEEDED(rv))
+ rv = GetImapConnectionAndLoadUrl(imapUrl, nullptr, aURL);
+ }
+ } // if we have a url to run....
+
+ return rv;
+}
+
+NS_IMETHODIMP nsImapService::StoreCustomKeywords(
+ nsIMsgFolder* anImapFolder, nsIMsgWindow* aMsgWindow,
+ const nsACString& flagsToAdd, const nsACString& flagsToSubtract,
+ const nsACString& uids, nsIURI** aURL) {
+ NS_ENSURE_ARG_POINTER(anImapFolder);
+
+ nsCOMPtr<nsIImapUrl> imapUrl;
+ nsAutoCString urlSpec;
+ nsresult rv;
+ char hierarchyDelimiter = GetHierarchyDelimiter(anImapFolder);
+ rv = CreateStartOfImapUrl(EmptyCString(), getter_AddRefs(imapUrl),
+ anImapFolder, nullptr, urlSpec, hierarchyDelimiter);
+
+ if (NS_SUCCEEDED(rv) && imapUrl) {
+ // nsImapUrl::SetSpec() will set the imap action properly
+ rv = imapUrl->SetImapAction(nsIImapUrl::nsImapMsgStoreCustomKeywords);
+
+ nsCOMPtr<nsIMsgMailNewsUrl> mailNewsUrl = do_QueryInterface(imapUrl);
+ mailNewsUrl->SetMsgWindow(aMsgWindow);
+ mailNewsUrl->SetUpdatingFolder(true);
+ rv = SetImapUrlSink(anImapFolder, imapUrl);
+
+ if (NS_SUCCEEDED(rv)) {
+ nsCString folderName;
+ GetFolderName(anImapFolder, folderName);
+ urlSpec.AppendLiteral("/customKeywords>UID>");
+ urlSpec.Append(hierarchyDelimiter);
+ urlSpec.Append(folderName);
+ urlSpec.Append('>');
+ urlSpec.Append(uids);
+ urlSpec.Append('>');
+ urlSpec.Append(flagsToAdd);
+ urlSpec.Append('>');
+ urlSpec.Append(flagsToSubtract);
+ rv = mailNewsUrl->SetSpecInternal(urlSpec);
+ if (NS_SUCCEEDED(rv))
+ rv = GetImapConnectionAndLoadUrl(imapUrl, nullptr, aURL);
+ }
+ } // if we have a url to run....
+
+ return rv;
+}
+
+NS_IMETHODIMP nsImapService::DownloadMessagesForOffline(
+ const nsACString& messageIds, nsIMsgFolder* aFolder,
+ nsIUrlListener* aUrlListener, nsIMsgWindow* aMsgWindow) {
+ NS_ENSURE_ARG_POINTER(aFolder);
+
+ nsCOMPtr<nsIImapUrl> imapUrl;
+ nsAutoCString urlSpec;
+ nsresult rv;
+ char hierarchyDelimiter = GetHierarchyDelimiter(aFolder);
+ rv = CreateStartOfImapUrl(EmptyCString(), getter_AddRefs(imapUrl), aFolder,
+ nullptr, urlSpec, hierarchyDelimiter);
+ if (NS_SUCCEEDED(rv) && imapUrl) {
+ nsCOMPtr<nsIURI> runningURI;
+ // need to pass in stream listener in order to get the channel created
+ // correctly
+ nsCOMPtr<nsIImapMessageSink> imapMessageSink(
+ do_QueryInterface(aFolder, &rv));
+ rv = FetchMessage(imapUrl, nsImapUrl::nsImapMsgDownloadForOffline, aFolder,
+ imapMessageSink, aMsgWindow, nullptr, messageIds, false,
+ getter_AddRefs(runningURI));
+ if (runningURI && aUrlListener) {
+ nsCOMPtr<nsIMsgMailNewsUrl> msgurl(do_QueryInterface(runningURI));
+ nsCOMPtr<nsIImapUrl> imapUrl(do_QueryInterface(runningURI));
+ if (msgurl) msgurl->RegisterListener(aUrlListener);
+ if (imapUrl) imapUrl->SetStoreResultsOffline(true);
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapService::MessageURIToMsgHdr(const nsACString& uri,
+ nsIMsgDBHdr** aRetVal) {
+ NS_ENSURE_ARG_POINTER(aRetVal);
+
+ nsCOMPtr<nsIMsgFolder> folder;
+ nsMsgKey msgKey;
+ nsresult rv = DecomposeImapURI(uri, getter_AddRefs(folder), &msgKey);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = folder->GetMessageHeader(msgKey, aRetVal);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapService::PlaybackAllOfflineOperations(
+ nsIMsgWindow* aMsgWindow, nsIUrlListener* aListener,
+ nsISupports** aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ nsresult rv;
+ nsImapOfflineSync* goOnline = new nsImapOfflineSync();
+ goOnline->Init(aMsgWindow, aListener, nullptr, false);
+ rv = goOnline->QueryInterface(NS_GET_IID(nsISupports), (void**)aResult);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (NS_SUCCEEDED(rv) && *aResult) return goOnline->ProcessNextOperation();
+ return NS_ERROR_OUT_OF_MEMORY;
+}
+
+NS_IMETHODIMP nsImapService::DownloadAllOffineImapFolders(
+ nsIMsgWindow* aMsgWindow, nsIUrlListener* aListener) {
+ RefPtr<nsImapOfflineDownloader> downloadForOffline =
+ new nsImapOfflineDownloader(aMsgWindow, aListener);
+ if (downloadForOffline) {
+ // hold reference to this so it won't get deleted out from under itself.
+ nsresult rv = downloadForOffline->ProcessNextOperation();
+ return rv;
+ }
+ return NS_ERROR_OUT_OF_MEMORY;
+}
+
+NS_IMETHODIMP nsImapService::GetCacheStorage(nsICacheStorage** result) {
+ nsresult rv = NS_OK;
+ if (!mCacheStorage) {
+ nsCOMPtr<nsICacheStorageService> cacheStorageService =
+ do_GetService("@mozilla.org/netwerk/cache-storage-service;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<MailnewsLoadContextInfo> lci =
+ new MailnewsLoadContextInfo(false, false, mozilla::OriginAttributes());
+
+ // Determine if disk cache or memory cache is in use.
+ // Note: This is mozilla system cache, not offline storage (mbox, maildir)
+ // which is also sometimes referred to as cache at places in the code.
+ if (mozilla::Preferences::GetBool("mail.imap.use_disk_cache2", true))
+ rv = cacheStorageService->DiskCacheStorage(lci,
+ getter_AddRefs(mCacheStorage));
+ else
+ rv = cacheStorageService->MemoryCacheStorage(
+ lci, getter_AddRefs(mCacheStorage));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ NS_IF_ADDREF(*result = mCacheStorage);
+ return rv;
+}
+
+NS_IMETHODIMP nsImapService::HandleContent(
+ const char* aContentType, nsIInterfaceRequestor* aWindowContext,
+ nsIRequest* request) {
+ NS_ENSURE_ARG_POINTER(request);
+
+ nsresult rv;
+ nsCOMPtr<nsIChannel> aChannel = do_QueryInterface(request, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (PL_strcasecmp(aContentType, "x-application-imapfolder") == 0) {
+ nsCOMPtr<nsIURI> uri;
+ rv = aChannel->GetURI(getter_AddRefs(uri));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (uri) {
+ request->Cancel(NS_BINDING_ABORTED);
+ nsCOMPtr<nsIWindowMediator> mediator(
+ do_GetService(NS_WINDOWMEDIATOR_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString uriStr;
+ rv = uri->GetSpec(uriStr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // imap uri's are unescaped, so unescape the url.
+ nsCString unescapedUriStr;
+ MsgUnescapeString(uriStr, 0, unescapedUriStr);
+ nsCOMPtr<nsIMessengerWindowService> messengerWindowService =
+ do_GetService("@mozilla.org/messenger/windowservice;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = messengerWindowService->OpenMessengerWindowWithUri(
+ "mail:3pane", unescapedUriStr, nsMsgKey_None);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ } else {
+ // The content-type was not x-application-imapfolder
+ return NS_ERROR_WONT_HANDLE_CONTENT;
+ }
+
+ return rv;
+}
diff --git a/comm/mailnews/imap/src/nsImapService.h b/comm/mailnews/imap/src/nsImapService.h
new file mode 100644
index 0000000000..86d38aa949
--- /dev/null
+++ b/comm/mailnews/imap/src/nsImapService.h
@@ -0,0 +1,122 @@
+/* -*- 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 nsImapService_h___
+#define nsImapService_h___
+
+#include "nsIImapService.h"
+#include "nsIMsgMessageService.h"
+#include "nsCOMPtr.h"
+#include "nsIFile.h"
+#include "nsString.h"
+#include "nsIProtocolHandler.h"
+#include "nsIMsgProtocolInfo.h"
+#include "nsIContentHandler.h"
+#include "nsICacheStorage.h"
+
+class nsIImapUrl;
+class nsIMsgFolder;
+class nsIMsgIncomingServer;
+
+/**
+ * nsImapService implements the IMAP protocol.
+ * So, whenever someone opens an "imap://" url, the resultant nsIChannel
+ * is created here (via newChannel()).
+ *
+ * It also provides a bunch of methods to provide more egonomic ways to
+ * initiate IMAP operations, rather than manually composing an "imap://..."
+ * URL. See nsIImapService for these.
+ */
+class nsImapService : public nsIImapService,
+ public nsIMsgMessageService,
+ public nsIMsgMessageFetchPartService,
+ public nsIProtocolHandler,
+ public nsIMsgProtocolInfo,
+ public nsIContentHandler {
+ public:
+ nsImapService();
+ static nsresult NewURI(const nsACString& aSpec,
+ const char* aOriginCharset, // ignored
+ nsIURI* aBaseURI, nsIURI** aRetVal);
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIMSGPROTOCOLINFO
+ NS_DECL_NSIIMAPSERVICE
+ NS_DECL_NSIMSGMESSAGESERVICE
+ NS_DECL_NSIPROTOCOLHANDLER
+ NS_DECL_NSIMSGMESSAGEFETCHPARTSERVICE
+ NS_DECL_NSICONTENTHANDLER
+
+ protected:
+ virtual ~nsImapService();
+ char GetHierarchyDelimiter(nsIMsgFolder* aMsgFolder);
+
+ nsresult GetFolderName(nsIMsgFolder* aImapFolder, nsACString& aFolderName);
+
+ // This is called by both FetchMessage and StreamMessage
+ nsresult GetMessageFromUrl(nsIImapUrl* aImapUrl, nsImapAction aImapAction,
+ nsIMsgFolder* aImapMailFolder,
+ nsIImapMessageSink* aImapMessage,
+ nsIMsgWindow* aMsgWindow,
+ nsISupports* aDisplayConsumer,
+ bool aConvertDataToText, nsIURI** aURL);
+
+ nsresult CreateStartOfImapUrl(
+ const nsACString&
+ aImapURI, // a RDF URI for the current message/folder, can be empty
+ nsIImapUrl** imapUrl, nsIMsgFolder* aImapFolder,
+ nsIUrlListener* aUrlListener, nsACString& urlSpec,
+ char& hierarchyDelimiter);
+
+ nsresult GetImapConnectionAndLoadUrl(nsIImapUrl* aImapUrl,
+ nsISupports* aConsumer, nsIURI** aURL);
+
+ static nsresult SetImapUrlSink(nsIMsgFolder* aMsgFolder,
+ nsIImapUrl* aImapUrl);
+
+ nsresult FetchMimePart(nsIImapUrl* aImapUrl, nsImapAction aImapAction,
+ nsIMsgFolder* aImapMailFolder,
+ nsIImapMessageSink* aImapMessage, nsIURI** aURL,
+ nsISupports* aDisplayConsumer,
+ const nsACString& messageIdentifierList,
+ const nsACString& mimePart);
+
+ nsresult FolderCommand(nsIMsgFolder* imapMailFolder,
+ nsIUrlListener* urlListener, const char* aCommand,
+ nsImapAction imapAction, nsIMsgWindow* msgWindow,
+ nsIURI** url);
+
+ nsresult ChangeFolderSubscription(nsIMsgFolder* folder,
+ const nsAString& folderName,
+ const char* aCommand,
+ nsIUrlListener* urlListener, nsIURI** url);
+
+ nsresult DiddleFlags(nsIMsgFolder* aImapMailFolder,
+ nsIUrlListener* aUrlListener, nsIURI** aURL,
+ const nsACString& messageIdentifierList,
+ const char* howToDiddle, imapMessageFlagsType flags,
+ bool messageIdsAreUID);
+
+ nsresult OfflineAppendFromFile(nsIFile* aFile, nsIURI* aUrl,
+ nsIMsgFolder* aDstFolder,
+ const nsACString& messageId, // to be replaced
+ bool inSelectedState, // needs to be in
+ nsIUrlListener* aListener, nsIURI** aURL,
+ nsISupports* aCopyState);
+
+ static nsresult GetServerFromUrl(nsIImapUrl* aImapUrl,
+ nsIMsgIncomingServer** aServer);
+
+ // just a little helper method...maybe it should be a macro? which helps break
+ // down a imap message uri into the folder and message key equivalents
+ nsresult DecomposeImapURI(const nsACString& aMessageURI,
+ nsIMsgFolder** aFolder, nsACString& msgKey);
+ nsresult DecomposeImapURI(const nsACString& aMessageURI,
+ nsIMsgFolder** aFolder, nsMsgKey* msgKey);
+
+ nsCOMPtr<nsICacheStorage> mCacheStorage;
+};
+
+#endif /* nsImapService_h___ */
diff --git a/comm/mailnews/imap/src/nsImapStringBundle.cpp b/comm/mailnews/imap/src/nsImapStringBundle.cpp
new file mode 100644
index 0000000000..ab20f6e20f
--- /dev/null
+++ b/comm/mailnews/imap/src/nsImapStringBundle.cpp
@@ -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/. */
+#include "prprf.h"
+#include "prmem.h"
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "nsIStringBundle.h"
+#include "nsImapStringBundle.h"
+#include "nsServiceManagerUtils.h"
+#include "mozilla/Components.h"
+
+#define IMAP_MSGS_URL "chrome://messenger/locale/imapMsgs.properties"
+
+extern "C" nsresult IMAPGetStringByName(const char* stringName,
+ char16_t** aString) {
+ nsCOMPtr<nsIStringBundle> sBundle;
+ nsresult rv = IMAPGetStringBundle(getter_AddRefs(sBundle));
+ if (NS_SUCCEEDED(rv) && sBundle) {
+ nsAutoString string;
+ rv = sBundle->GetStringFromName(stringName, string);
+ *aString = ToNewUnicode(string);
+ }
+ return rv;
+}
+
+nsresult IMAPGetStringBundle(nsIStringBundle** aBundle) {
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIStringBundleService> stringService =
+ mozilla::components::StringBundle::Service();
+ if (!stringService) return NS_ERROR_NULL_POINTER;
+ nsCOMPtr<nsIStringBundle> stringBundle;
+ rv = stringService->CreateBundle(IMAP_MSGS_URL, getter_AddRefs(stringBundle));
+ stringBundle.forget(aBundle);
+ return rv;
+}
diff --git a/comm/mailnews/imap/src/nsImapStringBundle.h b/comm/mailnews/imap/src/nsImapStringBundle.h
new file mode 100644
index 0000000000..a929391e43
--- /dev/null
+++ b/comm/mailnews/imap/src/nsImapStringBundle.h
@@ -0,0 +1,17 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 _nsImapStringBundle_H__
+#define _nsImapStringBundle_H__
+
+#include "nsIStringBundle.h"
+
+PR_BEGIN_EXTERN_C
+
+nsresult IMAPGetStringByName(const char* stringName, char16_t** aString);
+nsresult IMAPGetStringBundle(nsIStringBundle** aBundle);
+
+PR_END_EXTERN_C
+
+#endif /* _nsImapStringBundle_H__ */
diff --git a/comm/mailnews/imap/src/nsImapUndoTxn.cpp b/comm/mailnews/imap/src/nsImapUndoTxn.cpp
new file mode 100644
index 0000000000..04fa74c90a
--- /dev/null
+++ b/comm/mailnews/imap/src/nsImapUndoTxn.cpp
@@ -0,0 +1,647 @@
+/* -*- 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 precompiled headers
+#include "nsIMsgHdr.h"
+#include "nsImapUndoTxn.h"
+#include "nsIMsgIncomingServer.h"
+#include "nsImapMailFolder.h"
+#include "nsIImapService.h"
+#include "nsIDBFolderInfo.h"
+#include "nsIMsgDatabase.h"
+#include "nsMsgUtils.h"
+#include "nsThreadUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "nsComponentManagerUtils.h"
+
+nsImapMoveCopyMsgTxn::nsImapMoveCopyMsgTxn()
+ : m_idsAreUids(false), m_isMove(false), m_srcIsPop3(false) {}
+
+nsresult nsImapMoveCopyMsgTxn::Init(nsIMsgFolder* srcFolder,
+ nsTArray<nsMsgKey>* srcKeyArray,
+ const char* srcMsgIdString,
+ nsIMsgFolder* dstFolder, bool idsAreUids,
+ bool isMove) {
+ m_srcMsgIdString = srcMsgIdString;
+ m_idsAreUids = idsAreUids;
+ m_isMove = isMove;
+ m_srcFolder = do_GetWeakReference(srcFolder);
+ m_dstFolder = do_GetWeakReference(dstFolder);
+ m_srcKeyArray = srcKeyArray->Clone();
+ m_dupKeyArray = srcKeyArray->Clone();
+ nsCString uri;
+ nsresult rv = srcFolder->GetURI(uri);
+ nsCString protocolType(uri);
+ protocolType.SetLength(protocolType.FindChar(':'));
+ nsCOMPtr<nsIMsgDatabase> srcDB;
+ rv = srcFolder->GetMsgDatabase(getter_AddRefs(srcDB));
+ NS_ENSURE_SUCCESS(rv, rv);
+ uint32_t i, count = m_srcKeyArray.Length();
+ nsCOMPtr<nsIMsgDBHdr> srcHdr;
+ nsCOMPtr<nsIMsgDBHdr> copySrcHdr;
+ nsCString messageId;
+
+ for (i = 0; i < count; i++) {
+ rv = srcDB->GetMsgHdrForKey(m_srcKeyArray[i], getter_AddRefs(srcHdr));
+ if (NS_SUCCEEDED(rv)) {
+ // ** jt -- only do this for mailbox protocol
+ if (protocolType.LowerCaseEqualsLiteral("mailbox")) {
+ m_srcIsPop3 = true;
+ uint32_t msgSize;
+ rv = srcHdr->GetMessageSize(&msgSize);
+ if (NS_SUCCEEDED(rv)) m_srcSizeArray.AppendElement(msgSize);
+ if (isMove) {
+ rv = srcDB->CopyHdrFromExistingHdr(nsMsgKey_None, srcHdr, false,
+ getter_AddRefs(copySrcHdr));
+ nsMsgKey pseudoKey = nsMsgKey_None;
+ if (NS_SUCCEEDED(rv)) {
+ copySrcHdr->GetMessageKey(&pseudoKey);
+ m_srcHdrs.AppendObject(copySrcHdr);
+ }
+ m_dupKeyArray[i] = pseudoKey;
+ }
+ }
+ srcHdr->GetMessageId(getter_Copies(messageId));
+ m_srcMessageIds.AppendElement(messageId);
+ }
+ }
+ return nsMsgTxn::Init();
+}
+
+nsImapMoveCopyMsgTxn::~nsImapMoveCopyMsgTxn() {}
+
+NS_IMPL_ISUPPORTS_INHERITED(nsImapMoveCopyMsgTxn, nsMsgTxn, nsIUrlListener)
+
+NS_IMETHODIMP
+nsImapMoveCopyMsgTxn::UndoTransaction(void) {
+ nsresult rv;
+ nsCOMPtr<nsIImapService> imapService =
+ do_GetService("@mozilla.org/messenger/imapservice;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool finishInOnStopRunningUrl = false;
+
+ if (m_isMove || !m_dstFolder) {
+ if (m_srcIsPop3) {
+ rv = UndoMailboxDelete();
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ nsCOMPtr<nsIMsgFolder> srcFolder = do_QueryReferent(m_srcFolder, &rv);
+ if (NS_FAILED(rv) || !srcFolder) return rv;
+ nsCOMPtr<nsIUrlListener> srcListener = do_QueryInterface(srcFolder, &rv);
+ if (NS_FAILED(rv)) return rv;
+ m_onStopListener = do_GetWeakReference(srcListener);
+
+ // ** make sure we are in the selected state; use lite select
+ // folder so we won't hit performance hard
+ nsCOMPtr<nsIURI> outUri;
+ rv = imapService->LiteSelectFolder(srcFolder, srcListener, nullptr,
+ getter_AddRefs(outUri));
+ if (NS_FAILED(rv)) return rv;
+ bool deletedMsgs = true; // default is true unless imapDelete model
+ nsMsgImapDeleteModel deleteModel;
+ rv = GetImapDeleteModel(srcFolder, &deleteModel);
+
+ // protect against a bogus undo txn without any source keys
+ // see bug #179856 for details
+ NS_ASSERTION(!m_srcKeyArray.IsEmpty(), "no source keys");
+ if (m_srcKeyArray.IsEmpty()) return NS_ERROR_UNEXPECTED;
+
+ if (!m_srcMsgIdString.IsEmpty()) {
+ if (NS_SUCCEEDED(rv) &&
+ deleteModel == nsMsgImapDeleteModels::IMAPDelete)
+ CheckForToggleDelete(srcFolder, m_srcKeyArray[0], &deletedMsgs);
+
+ if (deletedMsgs)
+ rv = imapService->SubtractMessageFlags(
+ srcFolder, this, m_srcMsgIdString, kImapMsgDeletedFlag,
+ m_idsAreUids);
+ else
+ rv = imapService->AddMessageFlags(srcFolder, srcListener,
+ m_srcMsgIdString,
+ kImapMsgDeletedFlag, m_idsAreUids);
+ if (NS_FAILED(rv)) return rv;
+
+ finishInOnStopRunningUrl = true;
+ if (deleteModel != nsMsgImapDeleteModels::IMAPDelete)
+ nsCOMPtr<nsIURI> outUri;
+ rv = imapService->GetHeaders(srcFolder, srcListener,
+ getter_AddRefs(outUri), m_srcMsgIdString,
+ true);
+ }
+ }
+ }
+ if (!finishInOnStopRunningUrl && !m_dstMsgIdString.IsEmpty()) {
+ nsCOMPtr<nsIMsgFolder> dstFolder = do_QueryReferent(m_dstFolder, &rv);
+ if (NS_FAILED(rv) || !dstFolder) return rv;
+
+ nsCOMPtr<nsIUrlListener> dstListener;
+
+ dstListener = do_QueryInterface(dstFolder, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // ** make sure we are in the selected state; use lite select folder
+ // so we won't potentially download a bunch of headers.
+ nsCOMPtr<nsIURI> outUri;
+ rv = imapService->LiteSelectFolder(dstFolder, dstListener, nullptr,
+ getter_AddRefs(outUri));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = imapService->AddMessageFlags(dstFolder, dstListener, m_dstMsgIdString,
+ kImapMsgDeletedFlag, m_idsAreUids);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsImapMoveCopyMsgTxn::RedoTransaction(void) {
+ nsresult rv;
+ nsCOMPtr<nsIImapService> imapService =
+ do_GetService("@mozilla.org/messenger/imapservice;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (m_isMove || !m_dstFolder) {
+ if (m_srcIsPop3) {
+ rv = RedoMailboxDelete();
+ if (NS_FAILED(rv)) return rv;
+ } else if (!m_srcMsgIdString.IsEmpty()) {
+ nsCOMPtr<nsIMsgFolder> srcFolder = do_QueryReferent(m_srcFolder, &rv);
+ if (NS_FAILED(rv) || !srcFolder) return rv;
+ nsCOMPtr<nsIUrlListener> srcListener = do_QueryInterface(srcFolder, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool deletedMsgs = false; // default will be false unless
+ // imapDeleteModel;
+ nsMsgImapDeleteModel deleteModel;
+ rv = GetImapDeleteModel(srcFolder, &deleteModel);
+
+ // protect against a bogus undo txn without any source keys
+ // see bug #179856 for details
+ NS_ASSERTION(!m_srcKeyArray.IsEmpty(), "no source keys");
+ if (m_srcKeyArray.IsEmpty()) return NS_ERROR_UNEXPECTED;
+
+ if (NS_SUCCEEDED(rv) && deleteModel == nsMsgImapDeleteModels::IMAPDelete)
+ rv = CheckForToggleDelete(srcFolder, m_srcKeyArray[0], &deletedMsgs);
+
+ // Make sure we are in the selected state; use lite select
+ // folder so performance won't suffer.
+ nsCOMPtr<nsIURI> outUri;
+ rv = imapService->LiteSelectFolder(srcFolder, srcListener, nullptr,
+ getter_AddRefs(outUri));
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (deletedMsgs) {
+ rv = imapService->SubtractMessageFlags(
+ srcFolder, srcListener, m_srcMsgIdString, kImapMsgDeletedFlag,
+ m_idsAreUids);
+ } else {
+ rv = imapService->AddMessageFlags(srcFolder, srcListener,
+ m_srcMsgIdString, kImapMsgDeletedFlag,
+ m_idsAreUids);
+ }
+ }
+ }
+ if (!m_dstMsgIdString.IsEmpty()) {
+ nsCOMPtr<nsIMsgFolder> dstFolder = do_QueryReferent(m_dstFolder, &rv);
+ if (NS_FAILED(rv) || !dstFolder) return rv;
+
+ nsCOMPtr<nsIUrlListener> dstListener;
+
+ dstListener = do_QueryInterface(dstFolder, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // ** make sure we are in the selected state; use lite select
+ // folder so we won't hit performance hard
+ nsCOMPtr<nsIURI> outUri;
+ rv = imapService->LiteSelectFolder(dstFolder, dstListener, nullptr,
+ getter_AddRefs(outUri));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = imapService->SubtractMessageFlags(dstFolder, dstListener,
+ m_dstMsgIdString,
+ kImapMsgDeletedFlag, m_idsAreUids);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsMsgImapDeleteModel deleteModel;
+ rv = GetImapDeleteModel(dstFolder, &deleteModel);
+ if (NS_FAILED(rv) || deleteModel == nsMsgImapDeleteModels::MoveToTrash) {
+ rv = imapService->GetHeaders(dstFolder, dstListener, nullptr,
+ m_dstMsgIdString, true);
+ }
+ }
+ return rv;
+}
+
+nsresult nsImapMoveCopyMsgTxn::SetCopyResponseUid(const char* aMsgIdString) {
+ if (!aMsgIdString) return NS_ERROR_NULL_POINTER;
+ m_dstMsgIdString = aMsgIdString;
+ if (m_dstMsgIdString.Last() == ']') {
+ int32_t len = m_dstMsgIdString.Length();
+ m_dstMsgIdString.SetLength(len - 1);
+ }
+ return NS_OK;
+}
+
+nsresult nsImapMoveCopyMsgTxn::GetSrcKeyArray(nsTArray<nsMsgKey>& srcKeyArray) {
+ srcKeyArray = m_srcKeyArray.Clone();
+ return NS_OK;
+}
+
+nsresult nsImapMoveCopyMsgTxn::AddDstKey(nsMsgKey aKey) {
+ if (!m_dstMsgIdString.IsEmpty()) m_dstMsgIdString.Append(',');
+ m_dstMsgIdString.AppendInt((int32_t)aKey);
+ return NS_OK;
+}
+
+nsresult nsImapMoveCopyMsgTxn::UndoMailboxDelete() {
+ nsresult rv = NS_ERROR_FAILURE;
+ // ** jt -- only do this for mailbox protocol
+ if (m_srcIsPop3) {
+ nsCOMPtr<nsIMsgFolder> srcFolder = do_QueryReferent(m_srcFolder, &rv);
+ if (NS_FAILED(rv) || !srcFolder) return rv;
+
+ nsCOMPtr<nsIMsgFolder> dstFolder = do_QueryReferent(m_dstFolder, &rv);
+ if (NS_FAILED(rv) || !dstFolder) return rv;
+
+ nsCOMPtr<nsIMsgDatabase> srcDB;
+ nsCOMPtr<nsIMsgDatabase> dstDB;
+ rv = srcFolder->GetMsgDatabase(getter_AddRefs(srcDB));
+ if (NS_FAILED(rv)) return rv;
+ rv = dstFolder->GetMsgDatabase(getter_AddRefs(dstDB));
+ if (NS_FAILED(rv)) return rv;
+
+ uint32_t count = m_srcKeyArray.Length();
+ uint32_t i;
+ nsCOMPtr<nsIMsgDBHdr> oldHdr;
+ nsCOMPtr<nsIMsgDBHdr> newHdr;
+ for (i = 0; i < count; i++) {
+ oldHdr = m_srcHdrs[i];
+ NS_ASSERTION(oldHdr, "fatal ... cannot get old msg header");
+ rv = srcDB->CopyHdrFromExistingHdr(m_srcKeyArray[i], oldHdr, true,
+ getter_AddRefs(newHdr));
+ NS_ASSERTION(newHdr, "fatal ... cannot create new header");
+
+ if (NS_SUCCEEDED(rv) && newHdr) {
+ if (i < m_srcSizeArray.Length())
+ newHdr->SetMessageSize(m_srcSizeArray[i]);
+ srcDB->UndoDelete(newHdr);
+ }
+ }
+ srcDB->SetSummaryValid(true);
+ return NS_OK; // always return NS_OK
+ }
+ return NS_ERROR_FAILURE;
+}
+
+nsresult nsImapMoveCopyMsgTxn::RedoMailboxDelete() {
+ nsresult rv = NS_ERROR_FAILURE;
+ if (m_srcIsPop3) {
+ nsCOMPtr<nsIMsgDatabase> srcDB;
+ nsCOMPtr<nsIMsgFolder> srcFolder = do_QueryReferent(m_srcFolder, &rv);
+ if (NS_FAILED(rv) || !srcFolder) return rv;
+ rv = srcFolder->GetMsgDatabase(getter_AddRefs(srcDB));
+ if (NS_SUCCEEDED(rv)) {
+ srcDB->DeleteMessages(m_srcKeyArray, nullptr);
+ srcDB->SetSummaryValid(true);
+ }
+ return NS_OK; // always return NS_OK
+ }
+ return NS_ERROR_FAILURE;
+}
+
+nsresult nsImapMoveCopyMsgTxn::GetImapDeleteModel(
+ nsIMsgFolder* aFolder, nsMsgImapDeleteModel* aDeleteModel) {
+ nsresult rv;
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ if (!aFolder) return NS_ERROR_NULL_POINTER;
+ rv = aFolder->GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIImapIncomingServer> imapServer = do_QueryInterface(server, &rv);
+ if (NS_SUCCEEDED(rv) && imapServer)
+ rv = imapServer->GetDeleteModel(aDeleteModel);
+ return rv;
+}
+
+NS_IMETHODIMP nsImapMoveCopyMsgTxn::OnStartRunningUrl(nsIURI* aUrl) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMoveCopyMsgTxn::OnStopRunningUrl(nsIURI* aUrl,
+ nsresult aExitCode) {
+ nsCOMPtr<nsIUrlListener> urlListener = do_QueryReferent(m_onStopListener);
+ if (urlListener) urlListener->OnStopRunningUrl(aUrl, aExitCode);
+
+ nsCOMPtr<nsIImapUrl> imapUrl = do_QueryInterface(aUrl);
+ if (imapUrl) {
+ nsresult rv;
+ nsCOMPtr<nsIImapService> imapService =
+ do_GetService("@mozilla.org/messenger/imapservice;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsImapAction imapAction;
+ imapUrl->GetImapAction(&imapAction);
+ nsCOMPtr<nsIMsgFolder> dstFolder = do_QueryReferent(m_dstFolder, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIMsgFolder> srcFolder = do_QueryReferent(m_srcFolder, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (imapAction == nsIImapUrl::nsImapSubtractMsgFlags) {
+ int32_t extraStatus;
+ imapUrl->GetExtraStatus(&extraStatus);
+ if (extraStatus != nsIImapUrl::ImapStatusNone) {
+ // If subtracting the deleted flag didn't work, try
+ // moving the message back from the target folder to the src folder
+ if (!m_dstMsgIdString.IsEmpty())
+ imapService->OnlineMessageCopy(dstFolder, m_dstMsgIdString, srcFolder,
+ true, true, nullptr, /* listener */
+ nullptr, nullptr, nullptr);
+ else {
+ // server doesn't support COPYUID, so we're going to update the dest
+ // folder, and when that's done, use the db to find the messages
+ // to move back, looking them up by message-id.
+ nsCOMPtr<nsIMsgImapMailFolder> imapDest =
+ do_QueryInterface(dstFolder);
+ if (imapDest) imapDest->UpdateFolderWithListener(nullptr, this);
+ }
+ } else if (!m_dstMsgIdString.IsEmpty()) {
+ nsCOMPtr<nsIUrlListener> dstListener;
+
+ dstListener = do_QueryInterface(dstFolder, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // ** make sure we are in the selected state; use lite select folder
+ // so we won't potentially download a bunch of headers.
+ nsCOMPtr<nsIURI> outUri;
+ rv = imapService->LiteSelectFolder(dstFolder, dstListener, nullptr,
+ getter_AddRefs(outUri));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = imapService->AddMessageFlags(dstFolder, dstListener,
+ m_dstMsgIdString, kImapMsgDeletedFlag,
+ m_idsAreUids);
+ }
+ } else if (imapAction == nsIImapUrl::nsImapSelectFolder) {
+ // Now we should have the headers from the dest folder.
+ // Look them up and move them back to the source folder.
+ uint32_t count = m_srcMessageIds.Length();
+ uint32_t i;
+ nsCString messageId;
+ nsTArray<nsMsgKey> dstKeys;
+ nsCOMPtr<nsIMsgDatabase> destDB;
+ nsCOMPtr<nsIMsgDBHdr> dstHdr;
+
+ rv = dstFolder->GetMsgDatabase(getter_AddRefs(destDB));
+ NS_ENSURE_SUCCESS(rv, rv);
+ for (i = 0; i < count; i++) {
+ rv = destDB->GetMsgHdrForMessageID(m_srcMessageIds[i].get(),
+ getter_AddRefs(dstHdr));
+ if (NS_SUCCEEDED(rv) && dstHdr) {
+ nsMsgKey dstKey;
+ dstHdr->GetMessageKey(&dstKey);
+ dstKeys.AppendElement(dstKey);
+ }
+ }
+ if (dstKeys.Length()) {
+ nsAutoCString uids;
+ nsImapMailFolder::AllocateUidStringFromKeys(dstKeys, uids);
+ rv = imapService->OnlineMessageCopy(dstFolder, uids, srcFolder, true,
+ true, nullptr, nullptr, nullptr,
+ nullptr);
+ }
+ }
+ }
+ return NS_OK;
+}
+
+nsImapOfflineTxn::nsImapOfflineTxn(nsIMsgFolder* srcFolder,
+ nsTArray<nsMsgKey>* srcKeyArray,
+ const char* srcMsgIdString,
+ nsIMsgFolder* dstFolder, bool isMove,
+ nsOfflineImapOperationType opType,
+ nsCOMArray<nsIMsgDBHdr>& srcHdrs) {
+ Init(srcFolder, srcKeyArray, srcMsgIdString, dstFolder, true, isMove);
+
+ m_opType = opType;
+ m_flags = 0;
+ m_addFlags = false;
+ if (opType == nsIMsgOfflineImapOperation::kDeletedMsg) {
+ nsCOMPtr<nsIMsgDatabase> srcDB;
+ nsCOMPtr<nsIDBFolderInfo> folderInfo;
+
+ nsresult rv = srcFolder->GetDBFolderInfoAndDB(getter_AddRefs(folderInfo),
+ getter_AddRefs(srcDB));
+ if (NS_SUCCEEDED(rv) && srcDB) {
+ nsMsgKey pseudoKey;
+ nsCOMPtr<nsIMsgDBHdr> copySrcHdr;
+
+ // Imap protocols have conflated key/UUID so we cannot use
+ // auto key with them.
+ nsCString protocolType;
+ srcFolder->GetURI(protocolType);
+ protocolType.SetLength(protocolType.FindChar(':'));
+ for (int32_t i = 0; i < srcHdrs.Count(); i++) {
+ if (protocolType.EqualsLiteral("imap")) {
+ srcDB->GetNextPseudoMsgKey(&pseudoKey);
+ pseudoKey--;
+ } else {
+ pseudoKey = nsMsgKey_None;
+ }
+ rv = srcDB->CopyHdrFromExistingHdr(pseudoKey, srcHdrs[i], false,
+ getter_AddRefs(copySrcHdr));
+ if (NS_SUCCEEDED(rv)) {
+ copySrcHdr->GetMessageKey(&pseudoKey);
+ m_srcHdrs.AppendObject(copySrcHdr);
+ }
+ m_dupKeyArray[i] = pseudoKey;
+ }
+ }
+ } else
+ m_srcHdrs.AppendObjects(srcHdrs);
+}
+
+nsImapOfflineTxn::~nsImapOfflineTxn() {}
+
+// Open the database and find the key for the offline operation that we want to
+// undo, then remove it from the database, we also hold on to this
+// data for a redo operation.
+NS_IMETHODIMP nsImapOfflineTxn::UndoTransaction(void) {
+ nsresult rv;
+
+ nsCOMPtr<nsIMsgFolder> srcFolder = do_QueryReferent(m_srcFolder, &rv);
+ if (NS_FAILED(rv) || !srcFolder) return rv;
+ nsCOMPtr<nsIMsgOfflineImapOperation> op;
+ nsCOMPtr<nsIDBFolderInfo> folderInfo;
+ nsCOMPtr<nsIMsgDatabase> srcDB;
+ nsCOMPtr<nsIMsgDatabase> destDB;
+
+ rv = srcFolder->GetDBFolderInfoAndDB(getter_AddRefs(folderInfo),
+ getter_AddRefs(srcDB));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ switch (m_opType) {
+ case nsIMsgOfflineImapOperation::kMsgMoved:
+ case nsIMsgOfflineImapOperation::kMsgCopy:
+ case nsIMsgOfflineImapOperation::kAddedHeader:
+ case nsIMsgOfflineImapOperation::kFlagsChanged:
+ case nsIMsgOfflineImapOperation::kDeletedMsg: {
+ if (m_srcHdrs.IsEmpty()) {
+ NS_ASSERTION(false, "No msg header to apply undo.");
+ break;
+ }
+ nsCOMPtr<nsIMsgDBHdr> firstHdr = m_srcHdrs[0];
+ nsMsgKey hdrKey;
+ firstHdr->GetMessageKey(&hdrKey);
+ nsCOMPtr<nsIMsgOfflineOpsDatabase> opsDb = do_QueryInterface(srcDB, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = opsDb->GetOfflineOpForKey(hdrKey, false, getter_AddRefs(op));
+ bool offlineOpPlayedBack = true;
+ if (NS_SUCCEEDED(rv) && op) {
+ op->GetPlayingBack(&offlineOpPlayedBack);
+ opsDb->RemoveOfflineOp(op);
+ op = nullptr;
+ }
+ if (!WeAreOffline() && offlineOpPlayedBack) {
+ // couldn't find offline op - it must have been played back already
+ // so we should undo the transaction online.
+ return nsImapMoveCopyMsgTxn::UndoTransaction();
+ }
+
+ if (!firstHdr) break;
+ nsMsgKey msgKey;
+ if (m_opType == nsIMsgOfflineImapOperation::kAddedHeader) {
+ for (int32_t i = 0; i < m_srcHdrs.Count(); i++) {
+ m_srcHdrs[i]->GetMessageKey(&msgKey);
+ nsCOMPtr<nsIMsgDBHdr> mailHdr;
+ rv = srcDB->GetMsgHdrForKey(msgKey, getter_AddRefs(mailHdr));
+ if (mailHdr) srcDB->DeleteHeader(mailHdr, nullptr, false, false);
+ }
+ srcDB->Commit(true);
+ } else if (m_opType == nsIMsgOfflineImapOperation::kDeletedMsg) {
+ for (int32_t i = 0; i < m_srcHdrs.Count(); i++) {
+ nsCOMPtr<nsIMsgDBHdr> undeletedHdr = m_srcHdrs[i];
+ m_srcHdrs[i]->GetMessageKey(&msgKey);
+ if (undeletedHdr) {
+ nsCOMPtr<nsIMsgDBHdr> newHdr;
+ srcDB->CopyHdrFromExistingHdr(msgKey, undeletedHdr, true,
+ getter_AddRefs(newHdr));
+ }
+ }
+ srcDB->Close(true);
+ srcFolder->SummaryChanged();
+ }
+ break;
+ }
+ case nsIMsgOfflineImapOperation::kMsgMarkedDeleted: {
+ nsMsgKey msgKey;
+ for (int32_t i = 0; i < m_srcHdrs.Count(); i++) {
+ m_srcHdrs[i]->GetMessageKey(&msgKey);
+ srcDB->MarkImapDeleted(msgKey, false, nullptr);
+ }
+ } break;
+ default:
+ break;
+ }
+ srcDB->Close(true);
+ srcFolder->SummaryChanged();
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapOfflineTxn::RedoTransaction(void) {
+ nsresult rv;
+
+ nsCOMPtr<nsIMsgFolder> srcFolder = do_QueryReferent(m_srcFolder, &rv);
+ if (NS_FAILED(rv) || !srcFolder) return rv;
+ nsCOMPtr<nsIMsgOfflineImapOperation> op;
+ nsCOMPtr<nsIDBFolderInfo> folderInfo;
+ nsCOMPtr<nsIMsgDatabase> srcDB;
+ nsCOMPtr<nsIMsgDatabase> destDB;
+ rv = srcFolder->GetDBFolderInfoAndDB(getter_AddRefs(folderInfo),
+ getter_AddRefs(srcDB));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ switch (m_opType) {
+ case nsIMsgOfflineImapOperation::kMsgMoved:
+ case nsIMsgOfflineImapOperation::kMsgCopy: {
+ nsCOMPtr<nsIMsgOfflineOpsDatabase> opsDb = do_QueryInterface(srcDB, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ for (int32_t i = 0; i < m_srcHdrs.Count(); i++) {
+ nsMsgKey hdrKey;
+ m_srcHdrs[i]->GetMessageKey(&hdrKey);
+ rv = opsDb->GetOfflineOpForKey(hdrKey, false, getter_AddRefs(op));
+ if (NS_SUCCEEDED(rv) && op) {
+ nsCOMPtr<nsIMsgFolder> dstFolder = do_QueryReferent(m_dstFolder, &rv);
+ if (dstFolder) {
+ nsCString folderURI;
+ dstFolder->GetURI(folderURI);
+
+ if (m_opType == nsIMsgOfflineImapOperation::kMsgMoved)
+ op->SetDestinationFolderURI(folderURI); // offline move
+ if (m_opType == nsIMsgOfflineImapOperation::kMsgCopy) {
+ op->SetOperation(nsIMsgOfflineImapOperation::kMsgMoved);
+ op->AddMessageCopyOperation(folderURI); // offline copy
+ }
+ dstFolder->SummaryChanged();
+ }
+ } else if (!WeAreOffline()) {
+ // couldn't find offline op - it must have been played back already
+ // so we should redo the transaction online.
+ return nsImapMoveCopyMsgTxn::RedoTransaction();
+ }
+ }
+ break;
+ }
+ case nsIMsgOfflineImapOperation::kAddedHeader: {
+ nsCOMPtr<nsIMsgFolder> dstFolder = do_QueryReferent(m_dstFolder, &rv);
+ rv = srcFolder->GetDBFolderInfoAndDB(getter_AddRefs(folderInfo),
+ getter_AddRefs(destDB));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIMsgOfflineOpsDatabase> opsDb = do_QueryInterface(destDB, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ for (int32_t i = 0; i < m_srcHdrs.Count(); i++) {
+ nsCOMPtr<nsIMsgDBHdr> restoreHdr;
+ nsMsgKey msgKey;
+ m_srcHdrs[i]->GetMessageKey(&msgKey);
+ destDB->CopyHdrFromExistingHdr(msgKey, m_srcHdrs[i], true,
+ getter_AddRefs(restoreHdr));
+ rv = opsDb->GetOfflineOpForKey(msgKey, true, getter_AddRefs(op));
+ if (NS_SUCCEEDED(rv) && op) {
+ nsCString folderURI;
+ srcFolder->GetURI(folderURI);
+ op->SetSourceFolderURI(folderURI);
+ }
+ }
+ dstFolder->SummaryChanged();
+ destDB->Close(true);
+ } break;
+ case nsIMsgOfflineImapOperation::kDeletedMsg:
+ for (int32_t i = 0; i < m_srcHdrs.Count(); i++) {
+ nsMsgKey msgKey;
+ m_srcHdrs[i]->GetMessageKey(&msgKey);
+ srcDB->DeleteMessage(msgKey, nullptr, true);
+ }
+ break;
+ case nsIMsgOfflineImapOperation::kMsgMarkedDeleted:
+ for (int32_t i = 0; i < m_srcHdrs.Count(); i++) {
+ nsMsgKey msgKey;
+ m_srcHdrs[i]->GetMessageKey(&msgKey);
+ srcDB->MarkImapDeleted(msgKey, true, nullptr);
+ }
+ break;
+ case nsIMsgOfflineImapOperation::kFlagsChanged: {
+ nsCOMPtr<nsIMsgOfflineOpsDatabase> opsDb = do_QueryInterface(srcDB, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ for (int32_t i = 0; i < m_srcHdrs.Count(); i++) {
+ nsMsgKey msgKey;
+ m_srcHdrs[i]->GetMessageKey(&msgKey);
+ rv = opsDb->GetOfflineOpForKey(msgKey, true, getter_AddRefs(op));
+ if (NS_SUCCEEDED(rv) && op) {
+ imapMessageFlagsType newMsgFlags;
+ op->GetNewFlags(&newMsgFlags);
+ if (m_addFlags)
+ op->SetFlagOperation(newMsgFlags | m_flags);
+ else
+ op->SetFlagOperation(newMsgFlags & ~m_flags);
+ }
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ srcDB->Close(true);
+ srcDB = nullptr;
+ srcFolder->SummaryChanged();
+ return NS_OK;
+}
diff --git a/comm/mailnews/imap/src/nsImapUndoTxn.h b/comm/mailnews/imap/src/nsImapUndoTxn.h
new file mode 100644
index 0000000000..9b0f2b30f9
--- /dev/null
+++ b/comm/mailnews/imap/src/nsImapUndoTxn.h
@@ -0,0 +1,87 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsImapUndoTxn_h__
+#define nsImapUndoTxn_h__
+
+#include "mozilla/Attributes.h"
+#include "nsIMsgFolder.h"
+#include "nsIImapIncomingServer.h"
+#include "nsIUrlListener.h"
+#include "nsMsgTxn.h"
+#include "MailNewsTypes.h"
+#include "nsTArray.h"
+#include "nsIMsgOfflineImapOperation.h"
+#include "nsCOMPtr.h"
+#include "nsIWeakReferenceUtils.h"
+#include "nsCOMArray.h"
+
+class nsImapMoveCopyMsgTxn : public nsMsgTxn, nsIUrlListener {
+ public:
+ nsImapMoveCopyMsgTxn();
+ nsImapMoveCopyMsgTxn(nsIMsgFolder* srcFolder, nsTArray<nsMsgKey>* srcKeyArray,
+ const char* srcMsgIdString, nsIMsgFolder* dstFolder,
+ bool isMove);
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIURLLISTENER
+
+ NS_IMETHOD UndoTransaction(void) override;
+ NS_IMETHOD RedoTransaction(void) override;
+
+ // helper
+ nsresult SetCopyResponseUid(const char* msgIdString);
+ nsresult GetSrcKeyArray(nsTArray<nsMsgKey>& srcKeyArray);
+ void GetSrcMsgIds(nsCString& srcMsgIds) { srcMsgIds = m_srcMsgIdString; }
+ nsresult AddDstKey(nsMsgKey aKey);
+ nsresult UndoMailboxDelete();
+ nsresult RedoMailboxDelete();
+ nsresult Init(nsIMsgFolder* srcFolder, nsTArray<nsMsgKey>* srcKeyArray,
+ const char* srcMsgIdString, nsIMsgFolder* dstFolder,
+ bool idsAreUids, bool isMove);
+
+ protected:
+ virtual ~nsImapMoveCopyMsgTxn();
+
+ nsWeakPtr m_srcFolder;
+ nsCOMArray<nsIMsgDBHdr> m_srcHdrs;
+ nsTArray<nsMsgKey> m_dupKeyArray;
+ nsTArray<nsMsgKey> m_srcKeyArray;
+ nsTArray<nsCString> m_srcMessageIds;
+ nsCString m_srcMsgIdString;
+ nsWeakPtr m_dstFolder;
+ nsCString m_dstMsgIdString;
+ bool m_idsAreUids;
+ bool m_isMove;
+ bool m_srcIsPop3;
+ nsTArray<uint32_t> m_srcSizeArray;
+ // this is used when we chain urls for imap undo, since "this" needs
+ // to be the listener, but the folder may need to also be notified.
+ nsWeakPtr m_onStopListener;
+
+ nsresult GetImapDeleteModel(nsIMsgFolder* aFolder,
+ nsMsgImapDeleteModel* aDeleteModel);
+};
+
+class nsImapOfflineTxn : public nsImapMoveCopyMsgTxn {
+ public:
+ nsImapOfflineTxn(nsIMsgFolder* srcFolder, nsTArray<nsMsgKey>* srcKeyArray,
+ const char* srcMsgIdString, nsIMsgFolder* dstFolder,
+ bool isMove, nsOfflineImapOperationType opType,
+ nsCOMArray<nsIMsgDBHdr>& srcHdrs);
+
+ NS_IMETHOD UndoTransaction(void) override;
+ NS_IMETHOD RedoTransaction(void) override;
+ void SetAddFlags(bool addFlags) { m_addFlags = addFlags; }
+ void SetFlags(uint32_t flags) { m_flags = flags; }
+
+ protected:
+ virtual ~nsImapOfflineTxn();
+ nsOfflineImapOperationType m_opType;
+ // these two are used to undo flag changes, which we don't currently do.
+ bool m_addFlags;
+ uint32_t m_flags;
+};
+#endif
diff --git a/comm/mailnews/imap/src/nsImapUrl.cpp b/comm/mailnews/imap/src/nsImapUrl.cpp
new file mode 100644
index 0000000000..4460b5ab05
--- /dev/null
+++ b/comm/mailnews/imap/src/nsImapUrl.cpp
@@ -0,0 +1,1276 @@
+/* -*- 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 "nsImapUrl.h"
+#include "../public/nsIImapHostSessionList.h"
+#include "nsThreadUtils.h"
+#include "nsString.h"
+#include "prmem.h"
+#include "plstr.h"
+#include "prprf.h"
+#include "nsMemory.h"
+#include "nsCOMPtr.h"
+#include "nsImapUtils.h"
+#include "nsIImapMockChannel.h"
+#include "nsIImapMailFolderSink.h"
+#include "nsIImapMessageSink.h"
+#include "nsIImapServerSink.h"
+#include "nsImapNamespace.h"
+#include "nsICacheEntry.h"
+#include "nsIMsgFolder.h"
+#include "nsMsgUtils.h"
+#include "nsIMsgHdr.h"
+#include "nsServiceManagerUtils.h"
+#include "mozilla/Logging.h"
+
+using namespace mozilla;
+extern LazyLogModule IMAPCache; // defined in nsImapProtocol.cpp
+
+#define NS_IIMAPHOSTSESSIONLIST_CID \
+ { \
+ 0x479ce8fc, 0xe725, 0x11d2, { \
+ 0xa5, 0x05, 0x00, 0x60, 0xb0, 0xfc, 0x04, 0xb7 \
+ } \
+ }
+static NS_DEFINE_CID(kCImapHostSessionListCID, NS_IIMAPHOSTSESSIONLIST_CID);
+
+nsImapUrl::nsImapUrl() : mLock("nsImapUrl.mLock") {
+ m_listOfMessageIds = nullptr;
+ m_sourceCanonicalFolderPathSubString = nullptr;
+ m_destinationCanonicalFolderPathSubString = nullptr;
+ m_listOfMessageIds = nullptr;
+ m_tokenPlaceHolder = nullptr;
+ m_urlidSubString = nullptr;
+ m_searchCriteriaString = nullptr;
+ m_idsAreUids = false;
+ m_mimePartSelectorDetected = false;
+ m_msgLoadingFromCache = false;
+ m_storeResultsOffline = false;
+ m_storeOfflineOnFallback = false;
+ m_localFetchOnly = false;
+ m_rerunningUrl = false;
+ m_moreHeadersToDownload = false;
+ m_externalLinkUrl = true; // we'll start this at true, and set it false in
+ // nsImapService::CreateStartOfImapUrl
+ m_numBytesToFetch = 0;
+ m_validUrl = true; // assume the best.
+ m_runningUrl = false;
+ m_flags = 0;
+ m_extraStatus = ImapStatusNone;
+ m_onlineSubDirSeparator = '/';
+ m_imapAction = 0;
+ mAutodetectCharset = false;
+
+ // ** jt - the following are not ref counted
+ m_copyState = nullptr;
+ m_file = nullptr;
+ m_imapMailFolderSink = nullptr;
+ m_imapMessageSink = nullptr;
+ m_addDummyEnvelope = false;
+ m_canonicalLineEnding = false;
+}
+
+nsImapUrl::~nsImapUrl() {
+ PR_FREEIF(m_listOfMessageIds);
+ PR_FREEIF(m_destinationCanonicalFolderPathSubString);
+ PR_FREEIF(m_sourceCanonicalFolderPathSubString);
+ PR_FREEIF(m_searchCriteriaString);
+}
+
+NS_IMPL_ADDREF_INHERITED(nsImapUrl, nsMsgMailNewsUrl)
+
+NS_IMPL_RELEASE_INHERITED(nsImapUrl, nsMsgMailNewsUrl)
+
+NS_INTERFACE_MAP_BEGIN(nsImapUrl)
+ NS_INTERFACE_MAP_ENTRY(nsIImapUrl)
+ NS_INTERFACE_MAP_ENTRY(nsIMsgMessageUrl)
+ NS_INTERFACE_MAP_ENTRY(nsIMsgI18NUrl)
+NS_INTERFACE_MAP_END_INHERITING(nsMsgMailNewsUrl)
+
+////////////////////////////////////////////////////////////////////////////////////
+// Begin nsIImapUrl specific support
+////////////////////////////////////////////////////////////////////////////////////
+
+NS_IMETHODIMP nsImapUrl::GetRequiredImapState(nsImapState* aImapUrlState) {
+ if (aImapUrlState) {
+ // the imap action determines the state we must be in...check the
+ // the imap action.
+
+ if (m_imapAction & 0x10000000)
+ *aImapUrlState = nsImapSelectedState;
+ else
+ *aImapUrlState = nsImapAuthenticatedState;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapUrl::GetImapAction(nsImapAction* aImapAction) {
+ *aImapAction = m_imapAction;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapUrl::SetImapAction(nsImapAction aImapAction) {
+ m_imapAction = aImapAction;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapUrl::GetFolder(nsIMsgFolder** aMsgFolder) {
+ NS_ENSURE_ARG_POINTER(aMsgFolder);
+ NS_ENSURE_ARG_POINTER(m_imapFolder);
+
+ nsCOMPtr<nsIMsgFolder> folder = do_QueryReferent(m_imapFolder);
+ folder.forget(aMsgFolder);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapUrl::SetFolder(nsIMsgFolder* aMsgFolder) {
+ nsresult rv;
+ m_imapFolder = do_GetWeakReference(aMsgFolder, &rv);
+ if (aMsgFolder) {
+ nsCOMPtr<nsIMsgIncomingServer> incomingServer;
+ aMsgFolder->GetServer(getter_AddRefs(incomingServer));
+ if (incomingServer) incomingServer->GetKey(m_serverKey);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapUrl::GetImapMailFolderSink(
+ nsIImapMailFolderSink** aImapMailFolderSink) {
+ NS_ENSURE_ARG_POINTER(aImapMailFolderSink);
+ if (!m_imapMailFolderSink)
+ return NS_ERROR_NULL_POINTER; // no assert, so don't use NS_ENSURE_POINTER.
+
+ nsCOMPtr<nsIImapMailFolderSink> folderSink =
+ do_QueryReferent(m_imapMailFolderSink);
+ folderSink.forget(aImapMailFolderSink);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapUrl::SetImapMailFolderSink(
+ nsIImapMailFolderSink* aImapMailFolderSink) {
+ nsresult rv;
+ m_imapMailFolderSink = do_GetWeakReference(aImapMailFolderSink, &rv);
+ return rv;
+}
+
+NS_IMETHODIMP nsImapUrl::GetImapMessageSink(
+ nsIImapMessageSink** aImapMessageSink) {
+ NS_ENSURE_ARG_POINTER(aImapMessageSink);
+ NS_ENSURE_ARG_POINTER(m_imapMessageSink);
+
+ nsCOMPtr<nsIImapMessageSink> messageSink =
+ do_QueryReferent(m_imapMessageSink);
+ messageSink.forget(aImapMessageSink);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapUrl::SetImapMessageSink(
+ nsIImapMessageSink* aImapMessageSink) {
+ nsresult rv;
+ m_imapMessageSink = do_GetWeakReference(aImapMessageSink, &rv);
+ return rv;
+}
+
+NS_IMETHODIMP nsImapUrl::GetImapServerSink(
+ nsIImapServerSink** aImapServerSink) {
+ NS_ENSURE_ARG_POINTER(aImapServerSink);
+ NS_ENSURE_ARG_POINTER(m_imapServerSink);
+
+ nsCOMPtr<nsIImapServerSink> serverSink = do_QueryReferent(m_imapServerSink);
+ serverSink.forget(aImapServerSink);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapUrl::SetImapServerSink(nsIImapServerSink* aImapServerSink) {
+ nsresult rv;
+ m_imapServerSink = do_GetWeakReference(aImapServerSink, &rv);
+ return rv;
+}
+
+////////////////////////////////////////////////////////////////////////////////////
+// End nsIImapUrl specific support
+////////////////////////////////////////////////////////////////////////////////////
+
+nsresult nsImapUrl::SetSpecInternal(const nsACString& aSpec) {
+ nsresult rv = nsMsgMailNewsUrl::SetSpecInternal(aSpec);
+ if (NS_SUCCEEDED(rv)) {
+ m_validUrl = true; // assume the best.
+ rv = ParseUrl();
+ }
+ return rv;
+}
+
+nsresult nsImapUrl::SetQuery(const nsACString& aQuery) {
+ nsresult rv = nsMsgMailNewsUrl::SetQuery(aQuery);
+ if (NS_SUCCEEDED(rv)) rv = ParseUrl();
+ return rv;
+}
+
+nsresult nsImapUrl::ParseUrl() {
+ nsresult rv = NS_OK;
+ // extract the user name
+ GetUserPass(m_userName);
+
+ nsAutoCString imapPartOfUrl;
+ rv = GetPathQueryRef(imapPartOfUrl);
+ nsAutoCString unescapedImapPartOfUrl;
+ MsgUnescapeString(imapPartOfUrl, 0, unescapedImapPartOfUrl);
+ if (NS_SUCCEEDED(rv) && !unescapedImapPartOfUrl.IsEmpty()) {
+ ParseImapPart(unescapedImapPartOfUrl.BeginWriting() +
+ 1); // GetPath leaves leading '/' in the path!!!
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapUrl::CreateSearchCriteriaString(char** aResult) {
+ // this method should only be called from the imap thread...
+ // o.t. add lock protection..
+ if (nullptr == aResult || !m_searchCriteriaString)
+ return NS_ERROR_NULL_POINTER;
+ *aResult = strdup(m_searchCriteriaString);
+ return NS_OK;
+}
+
+// this method gets called from the UI thread and the imap thread
+NS_IMETHODIMP nsImapUrl::GetListOfMessageIds(nsACString& aResult) {
+ MutexAutoLock mon(mLock);
+ if (!m_listOfMessageIds) return NS_ERROR_NULL_POINTER;
+
+ int32_t bytesToCopy = strlen(m_listOfMessageIds);
+
+ // mime may have glommed a "&part=" for a part download
+ // we return the entire message and let mime extract
+ // the part. Pop and news work this way also.
+ // this algorithm truncates the "&part" string.
+ char* currentChar = m_listOfMessageIds;
+ while (*currentChar && (*currentChar != '?')) currentChar++;
+ if (*currentChar == '?') bytesToCopy = currentChar - m_listOfMessageIds;
+
+ // we should also strip off anything after "/;section="
+ // since that can specify an IMAP MIME part
+ char* wherePart = PL_strstr(m_listOfMessageIds, "/;section=");
+ if (wherePart)
+ bytesToCopy =
+ std::min(bytesToCopy, int32_t(wherePart - m_listOfMessageIds));
+
+ aResult.Assign(m_listOfMessageIds, bytesToCopy);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapUrl::GetCommand(nsACString& result) {
+ result = m_command;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapUrl::GetCustomAttributeToFetch(nsACString& result) {
+ result = m_msgFetchAttribute;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapUrl::GetCustomAttributeResult(nsACString& result) {
+ result = m_customAttributeResult;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapUrl::SetCustomAttributeResult(const nsACString& result) {
+ m_customAttributeResult = result;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapUrl::GetCustomCommandResult(nsACString& result) {
+ result = m_customCommandResult;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapUrl::SetCustomCommandResult(const nsACString& result) {
+ m_customCommandResult = result;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapUrl::GetCustomAddFlags(nsACString& aResult) {
+ aResult = m_customAddFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapUrl::GetCustomSubtractFlags(nsACString& aResult) {
+ aResult = m_customSubtractFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapUrl::GetImapPartToFetch(char** result) {
+ // here's the old code....
+
+ // unfortunately an imap part can have the form: /;section= OR
+ // it can have the form ?section=. We need to look for both.
+ if (m_listOfMessageIds) {
+ char* wherepart = PL_strstr(m_listOfMessageIds, ";section=");
+ if (!wherepart) // look for ?section too....
+ wherepart = PL_strstr(m_listOfMessageIds, "?section=");
+ if (wherepart) {
+ wherepart += 9; // strlen("/;section=")
+ char* wherelibmimepart = PL_strstr(wherepart, "&part=");
+ if (!wherelibmimepart) wherelibmimepart = PL_strstr(wherepart, "?part=");
+ int numCharsToCopy = (wherelibmimepart)
+ ? wherelibmimepart - wherepart
+ : PL_strlen(m_listOfMessageIds) -
+ (wherepart - m_listOfMessageIds);
+ if (numCharsToCopy) {
+ *result = (char*)PR_Malloc(sizeof(char) * (numCharsToCopy + 1));
+ if (*result) {
+ PL_strncpy(*result, wherepart, numCharsToCopy + 1); // appends a \0
+ (*result)[numCharsToCopy] = '\0';
+ }
+ }
+ } // if we got a wherepart
+ } // if we got a m_listOfMessageIds
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapUrl::GetOnlineSubDirSeparator(char* separator) {
+ if (separator) {
+ *separator = m_onlineSubDirSeparator;
+ return NS_OK;
+ }
+ return NS_ERROR_NULL_POINTER;
+}
+
+NS_IMETHODIMP nsImapUrl::GetNumBytesToFetch(int32_t* aNumBytesToFetch) {
+ NS_ENSURE_ARG_POINTER(aNumBytesToFetch);
+ *aNumBytesToFetch = m_numBytesToFetch;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapUrl::SetOnlineSubDirSeparator(char onlineDirSeparator) {
+ m_onlineSubDirSeparator = onlineDirSeparator;
+ return NS_OK;
+}
+
+// this method is only called from the imap thread
+NS_IMETHODIMP nsImapUrl::MessageIdsAreUids(bool* result) {
+ *result = m_idsAreUids;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapUrl::SetExtraStatus(int32_t aExtraStatus) {
+ m_extraStatus = aExtraStatus;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapUrl::GetExtraStatus(int32_t* aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = m_extraStatus;
+ return NS_OK;
+}
+
+// this method is only called from the imap thread
+NS_IMETHODIMP nsImapUrl::GetMsgFlags(
+ imapMessageFlagsType* result) // kAddMsgFlags or kSubtractMsgFlags only
+{
+ *result = m_flags;
+ return NS_OK;
+}
+
+void nsImapUrl::ParseImapPart(char* imapPartOfUrl) {
+ m_tokenPlaceHolder = imapPartOfUrl;
+ m_urlidSubString = m_tokenPlaceHolder ? NS_strtok(IMAP_URL_TOKEN_SEPARATOR,
+ &m_tokenPlaceHolder)
+ : (char*)NULL;
+
+ if (!m_urlidSubString) {
+ m_validUrl = false;
+ return;
+ }
+
+ if (!PL_strcasecmp(m_urlidSubString, "fetch")) {
+ m_imapAction = nsImapMsgFetch;
+ ParseUidChoice();
+ PR_FREEIF(m_sourceCanonicalFolderPathSubString);
+ ParseFolderPath(&m_sourceCanonicalFolderPathSubString);
+ ParseListOfMessageIds();
+ // if fetched by spam filter, the action will be changed to
+ // nsImapMsgFetchPeek
+ } else {
+ if (!PL_strcasecmp(m_urlidSubString, "header")) {
+ m_imapAction = nsImapMsgHeader;
+ ParseUidChoice();
+ ParseFolderPath(&m_sourceCanonicalFolderPathSubString);
+ ParseListOfMessageIds();
+ } else if (!PL_strcasecmp(m_urlidSubString, "customFetch")) {
+ ParseUidChoice();
+ ParseFolderPath(&m_sourceCanonicalFolderPathSubString);
+ ParseListOfMessageIds();
+ ParseCustomMsgFetchAttribute();
+ } else if (!PL_strcasecmp(m_urlidSubString, "previewBody")) {
+ ParseUidChoice();
+ ParseFolderPath(&m_sourceCanonicalFolderPathSubString);
+ ParseListOfMessageIds();
+ ParseNumBytes();
+ } else if (!PL_strcasecmp(m_urlidSubString, "deletemsg")) {
+ m_imapAction = nsImapDeleteMsg;
+ ParseUidChoice();
+ ParseFolderPath(&m_sourceCanonicalFolderPathSubString);
+ ParseListOfMessageIds();
+ } else if (!PL_strcasecmp(m_urlidSubString, "uidexpunge")) {
+ m_imapAction = nsImapUidExpunge;
+ ParseFolderPath(&m_sourceCanonicalFolderPathSubString);
+ ParseListOfMessageIds();
+ } else if (!PL_strcasecmp(m_urlidSubString, "deleteallmsgs")) {
+ m_imapAction = nsImapDeleteAllMsgs;
+ ParseFolderPath(&m_sourceCanonicalFolderPathSubString);
+ } else if (!PL_strcasecmp(m_urlidSubString, "addmsgflags")) {
+ m_imapAction = nsImapAddMsgFlags;
+ ParseUidChoice();
+ ParseFolderPath(&m_sourceCanonicalFolderPathSubString);
+ ParseListOfMessageIds();
+ ParseMsgFlags();
+ } else if (!PL_strcasecmp(m_urlidSubString, "subtractmsgflags")) {
+ m_imapAction = nsImapSubtractMsgFlags;
+ ParseUidChoice();
+ ParseFolderPath(&m_sourceCanonicalFolderPathSubString);
+ ParseListOfMessageIds();
+ ParseMsgFlags();
+ } else if (!PL_strcasecmp(m_urlidSubString, "setmsgflags")) {
+ m_imapAction = nsImapSetMsgFlags;
+ ParseUidChoice();
+ ParseFolderPath(&m_sourceCanonicalFolderPathSubString);
+ ParseListOfMessageIds();
+ ParseMsgFlags();
+ } else if (!PL_strcasecmp(m_urlidSubString, "onlinecopy")) {
+ m_imapAction = nsImapOnlineCopy;
+ ParseUidChoice();
+ ParseFolderPath(&m_sourceCanonicalFolderPathSubString);
+ ParseListOfMessageIds();
+ ParseFolderPath(&m_destinationCanonicalFolderPathSubString);
+ } else if (!PL_strcasecmp(m_urlidSubString, "onlinemove")) {
+ m_imapAction = nsImapOnlineMove;
+ ParseUidChoice();
+ ParseFolderPath(&m_sourceCanonicalFolderPathSubString);
+ ParseListOfMessageIds();
+ ParseFolderPath(&m_destinationCanonicalFolderPathSubString);
+ } else if (!PL_strcasecmp(m_urlidSubString, "onlinetoofflinecopy")) {
+ m_imapAction = nsImapOnlineToOfflineCopy;
+ ParseUidChoice();
+ ParseFolderPath(&m_sourceCanonicalFolderPathSubString);
+ ParseListOfMessageIds();
+ ParseFolderPath(&m_destinationCanonicalFolderPathSubString);
+ } else if (!PL_strcasecmp(m_urlidSubString, "onlinetoofflinemove")) {
+ m_imapAction = nsImapOnlineToOfflineMove;
+ ParseUidChoice();
+ ParseFolderPath(&m_sourceCanonicalFolderPathSubString);
+ ParseListOfMessageIds();
+ ParseFolderPath(&m_destinationCanonicalFolderPathSubString);
+ } else if (!PL_strcasecmp(m_urlidSubString, "offlinetoonlinecopy")) {
+ m_imapAction = nsImapOfflineToOnlineMove;
+ ParseFolderPath(&m_destinationCanonicalFolderPathSubString);
+ } else if (!PL_strcasecmp(m_urlidSubString, "search")) {
+ m_imapAction = nsImapSearch;
+ ParseUidChoice();
+ ParseFolderPath(&m_sourceCanonicalFolderPathSubString);
+ ParseSearchCriteriaString();
+ } else if (!PL_strcasecmp(m_urlidSubString, "test")) {
+ m_imapAction = nsImapTest;
+ } else if (!PL_strcasecmp(m_urlidSubString, "select")) {
+ m_imapAction = nsImapSelectFolder;
+ ParseFolderPath(&m_sourceCanonicalFolderPathSubString);
+ if (m_tokenPlaceHolder && *m_tokenPlaceHolder)
+ ParseListOfMessageIds();
+ else
+ m_listOfMessageIds = PL_strdup("");
+ } else if (!PL_strcasecmp(m_urlidSubString, "liteselect")) {
+ m_imapAction = nsImapLiteSelectFolder;
+ ParseFolderPath(&m_sourceCanonicalFolderPathSubString);
+ } else if (!PL_strcasecmp(m_urlidSubString, "selectnoop")) {
+ m_imapAction = nsImapSelectNoopFolder;
+ m_listOfMessageIds = PL_strdup("");
+ ParseFolderPath(&m_sourceCanonicalFolderPathSubString);
+ } else if (!PL_strcasecmp(m_urlidSubString, "expunge")) {
+ m_imapAction = nsImapExpungeFolder;
+ ParseFolderPath(&m_sourceCanonicalFolderPathSubString);
+ m_listOfMessageIds = PL_strdup(""); // no ids to UNDO
+ } else if (!PL_strcasecmp(m_urlidSubString, "create")) {
+ m_imapAction = nsImapCreateFolder;
+ ParseFolderPath(&m_sourceCanonicalFolderPathSubString);
+ } else if (!PL_strcasecmp(m_urlidSubString, "ensureExists")) {
+ m_imapAction = nsImapEnsureExistsFolder;
+ ParseFolderPath(&m_sourceCanonicalFolderPathSubString);
+ } else if (!PL_strcasecmp(m_urlidSubString, "discoverchildren")) {
+ m_imapAction = nsImapDiscoverChildrenUrl;
+ ParseFolderPath(&m_sourceCanonicalFolderPathSubString);
+ } else if (!PL_strcasecmp(m_urlidSubString, "discoverallboxes")) {
+ m_imapAction = nsImapDiscoverAllBoxesUrl;
+ } else if (!PL_strcasecmp(m_urlidSubString,
+ "discoverallandsubscribedboxes")) {
+ m_imapAction = nsImapDiscoverAllAndSubscribedBoxesUrl;
+ } else if (!PL_strcasecmp(m_urlidSubString, "delete")) {
+ m_imapAction = nsImapDeleteFolder;
+ ParseFolderPath(&m_sourceCanonicalFolderPathSubString);
+ } else if (!PL_strcasecmp(m_urlidSubString, "deletefolder")) {
+ m_imapAction = nsImapDeleteFolderAndMsgs;
+ ParseFolderPath(&m_sourceCanonicalFolderPathSubString);
+ } else if (!PL_strcasecmp(m_urlidSubString, "rename")) {
+ m_imapAction = nsImapRenameFolder;
+ ParseFolderPath(&m_sourceCanonicalFolderPathSubString);
+ ParseFolderPath(&m_destinationCanonicalFolderPathSubString);
+ } else if (!PL_strcasecmp(m_urlidSubString, "movefolderhierarchy")) {
+ m_imapAction = nsImapMoveFolderHierarchy;
+ ParseFolderPath(&m_sourceCanonicalFolderPathSubString);
+ if (m_tokenPlaceHolder && *m_tokenPlaceHolder) // handle promote to root
+ ParseFolderPath(&m_destinationCanonicalFolderPathSubString);
+ } else if (!PL_strcasecmp(m_urlidSubString, "list")) {
+ m_imapAction = nsImapLsubFolders;
+ ParseFolderPath(&m_destinationCanonicalFolderPathSubString);
+ } else if (!PL_strcasecmp(m_urlidSubString, "biff")) {
+ m_imapAction = nsImapBiff;
+ ParseFolderPath(&m_sourceCanonicalFolderPathSubString);
+ ParseListOfMessageIds();
+ } else if (!PL_strcasecmp(m_urlidSubString, "netscape")) {
+ m_imapAction = nsImapGetMailAccountUrl;
+ } else if (!PL_strcasecmp(m_urlidSubString, "appendmsgfromfile")) {
+ m_imapAction = nsImapAppendMsgFromFile;
+ ParseFolderPath(&m_sourceCanonicalFolderPathSubString);
+ } else if (!PL_strcasecmp(m_urlidSubString, "appenddraftfromfile")) {
+ m_imapAction = nsImapAppendDraftFromFile;
+ ParseFolderPath(&m_sourceCanonicalFolderPathSubString);
+ ParseUidChoice();
+ if (m_tokenPlaceHolder && *m_tokenPlaceHolder)
+ ParseListOfMessageIds();
+ else
+ m_listOfMessageIds = strdup("");
+ } else if (!PL_strcasecmp(m_urlidSubString, "subscribe")) {
+ m_imapAction = nsImapSubscribe;
+ ParseFolderPath(&m_sourceCanonicalFolderPathSubString);
+ } else if (!PL_strcasecmp(m_urlidSubString, "unsubscribe")) {
+ m_imapAction = nsImapUnsubscribe;
+ ParseFolderPath(&m_sourceCanonicalFolderPathSubString);
+ } else if (!PL_strcasecmp(m_urlidSubString, "refreshacl")) {
+ m_imapAction = nsImapRefreshACL;
+ ParseFolderPath(&m_sourceCanonicalFolderPathSubString);
+ } else if (!PL_strcasecmp(m_urlidSubString, "refreshfolderurls")) {
+ m_imapAction = nsImapRefreshFolderUrls;
+ ParseFolderPath(&m_sourceCanonicalFolderPathSubString);
+ } else if (!PL_strcasecmp(m_urlidSubString, "refreshallacls")) {
+ m_imapAction = nsImapRefreshAllACLs;
+ } else if (!PL_strcasecmp(m_urlidSubString, "listfolder")) {
+ m_imapAction = nsImapListFolder;
+ ParseFolderPath(&m_sourceCanonicalFolderPathSubString);
+ } else if (!PL_strcasecmp(m_urlidSubString, "upgradetosubscription")) {
+ m_imapAction = nsImapUpgradeToSubscription;
+ ParseFolderPath(&m_sourceCanonicalFolderPathSubString);
+ } else if (!PL_strcasecmp(m_urlidSubString, "folderstatus")) {
+ m_imapAction = nsImapFolderStatus;
+ ParseFolderPath(&m_sourceCanonicalFolderPathSubString);
+ } else if (!PL_strcasecmp(m_urlidSubString, "verifyLogon")) {
+ m_imapAction = nsImapVerifylogon;
+ } else if (m_imapAction == nsIImapUrl::nsImapUserDefinedMsgCommand) {
+ m_command = m_urlidSubString; // save this
+ ParseUidChoice();
+ ParseFolderPath(&m_sourceCanonicalFolderPathSubString);
+ ParseListOfMessageIds();
+ } else if (m_imapAction == nsIImapUrl::nsImapMsgStoreCustomKeywords) {
+ ParseUidChoice();
+ ParseFolderPath(&m_sourceCanonicalFolderPathSubString);
+ ParseListOfMessageIds();
+ bool addKeyword = (m_tokenPlaceHolder && *m_tokenPlaceHolder != '>');
+ // if we're not adding a keyword, m_tokenPlaceHolder will now look like
+ // >keywordToSubtract> and strtok will leave flagsPtr pointing to
+ // keywordToSubtract. So detect this case and only set the
+ // customSubtractFlags.
+ char* flagsPtr = m_tokenPlaceHolder ? NS_strtok(IMAP_URL_TOKEN_SEPARATOR,
+ &m_tokenPlaceHolder)
+ : (char*)nullptr;
+ if (addKeyword) {
+ m_customAddFlags.Assign(flagsPtr);
+ flagsPtr = m_tokenPlaceHolder ? NS_strtok(IMAP_URL_TOKEN_SEPARATOR,
+ &m_tokenPlaceHolder)
+ : (char*)nullptr;
+ }
+ m_customSubtractFlags.Assign(flagsPtr);
+ } else {
+ m_validUrl = false;
+ }
+ }
+}
+
+// Returns NULL if nothing was done.
+// Otherwise, returns a newly allocated name.
+NS_IMETHODIMP nsImapUrl::AddOnlineDirectoryIfNecessary(
+ const char* onlineMailboxName, char** directory) {
+ nsresult rv;
+ nsString onlineDirString;
+ char* newOnlineName = nullptr;
+
+ nsCOMPtr<nsIImapHostSessionList> hostSessionList =
+ do_GetService(kCImapHostSessionListCID, &rv);
+ if (NS_FAILED(rv)) return rv;
+ rv = hostSessionList->GetOnlineDirForHost(m_serverKey.get(), onlineDirString);
+ nsAutoCString onlineDir;
+ LossyCopyUTF16toASCII(onlineDirString, onlineDir);
+
+ nsImapNamespace* ns = nullptr;
+ rv = hostSessionList->GetNamespaceForMailboxForHost(m_serverKey.get(),
+ onlineMailboxName, ns);
+ if (!ns)
+ hostSessionList->GetDefaultNamespaceOfTypeForHost(m_serverKey.get(),
+ kPersonalNamespace, ns);
+
+ if (onlineDir.IsEmpty() && ns) onlineDir = ns->GetPrefix();
+
+ // If this host has an online server directory configured
+ if (onlineMailboxName && !onlineDir.IsEmpty()) {
+ if (PL_strcasecmp(onlineMailboxName, "INBOX")) {
+ NS_ASSERTION(ns, "couldn't find namespace for host");
+ nsAutoCString onlineDirWithDelimiter(onlineDir);
+ // make sure the onlineDir ends with the hierarchy delimiter
+ if (ns) {
+ char delimiter = ns->GetDelimiter();
+ if (delimiter && delimiter != kOnlineHierarchySeparatorUnknown) {
+ // try to change the canonical online dir name to real dir name first
+ onlineDirWithDelimiter.ReplaceChar('/', delimiter);
+ // make sure the last character is the delimiter
+ if (onlineDirWithDelimiter.Last() != delimiter)
+ onlineDirWithDelimiter += delimiter;
+ if (!*onlineMailboxName)
+ onlineDirWithDelimiter.SetLength(onlineDirWithDelimiter.Length() -
+ 1);
+ }
+ }
+ if (ns && (PL_strlen(ns->GetPrefix()) != 0) &&
+ !onlineDirWithDelimiter.Equals(ns->GetPrefix())) {
+ // check that onlineMailboxName doesn't start with the namespace. If
+ // that's the case, we don't want to prepend the online dir.
+ if (PL_strncmp(onlineMailboxName, ns->GetPrefix(),
+ PL_strlen(ns->GetPrefix()))) {
+ // The namespace for this mailbox is the root ("").
+ // Prepend the online server directory
+ int finalLen =
+ onlineDirWithDelimiter.Length() + strlen(onlineMailboxName) + 1;
+ newOnlineName = (char*)PR_Malloc(finalLen);
+ if (newOnlineName) {
+ PL_strcpy(newOnlineName, onlineDirWithDelimiter.get());
+ PL_strcat(newOnlineName, onlineMailboxName);
+ }
+ }
+ }
+ // just prepend the online server directory if it doesn't start with it
+ // already
+ else if (strncmp(onlineMailboxName, onlineDirWithDelimiter.get(),
+ onlineDirWithDelimiter.Length())) {
+ newOnlineName = (char*)PR_Malloc(strlen(onlineMailboxName) +
+ onlineDirWithDelimiter.Length() + 1);
+ if (newOnlineName) {
+ PL_strcpy(newOnlineName, onlineDirWithDelimiter.get());
+ PL_strcat(newOnlineName, onlineMailboxName);
+ }
+ }
+ }
+ }
+ if (directory)
+ *directory = newOnlineName;
+ else if (newOnlineName)
+ free(newOnlineName);
+ return rv;
+}
+
+// Converts from canonical format (hierarchy is indicated by '/' and all real
+// slashes ('/') are escaped) to the real online name on the server.
+NS_IMETHODIMP nsImapUrl::AllocateServerPath(const char* canonicalPath,
+ char onlineDelimiter,
+ char** aAllocatedPath) {
+ nsresult retVal = NS_OK;
+ char* rv = NULL;
+ char delimiterToUse = onlineDelimiter;
+ if (onlineDelimiter == kOnlineHierarchySeparatorUnknown)
+ GetOnlineSubDirSeparator(&delimiterToUse);
+ NS_ASSERTION(delimiterToUse != kOnlineHierarchySeparatorUnknown,
+ "hierarchy separator unknown");
+ if (canonicalPath)
+ rv = ReplaceCharsInCopiedString(canonicalPath, '/', delimiterToUse);
+ else
+ rv = strdup("");
+
+ if (delimiterToUse != '/') UnescapeSlashes(rv);
+ char* onlineNameAdded = nullptr;
+ AddOnlineDirectoryIfNecessary(rv, &onlineNameAdded);
+ if (onlineNameAdded) {
+ free(rv);
+ rv = onlineNameAdded;
+ }
+
+ if (aAllocatedPath)
+ *aAllocatedPath = rv;
+ else
+ free(rv);
+
+ return retVal;
+}
+
+// escape '/' as ^, ^ -> ^^ - use UnescapeSlashes to revert
+/* static */ nsresult nsImapUrl::EscapeSlashes(const char* sourcePath,
+ char** resultPath) {
+ NS_ENSURE_ARG(sourcePath);
+ NS_ENSURE_ARG(resultPath);
+ int32_t extra = 0;
+ int32_t len = strlen(sourcePath);
+ const char* src = sourcePath;
+ int32_t i;
+ for (i = 0; i < len; i++) {
+ if (*src == '^') extra += 1; /* ^ -> ^^ */
+ src++;
+ }
+ char* result = (char*)moz_xmalloc(len + extra + 1);
+ if (!result) return NS_ERROR_OUT_OF_MEMORY;
+
+ unsigned char* dst = (unsigned char*)result;
+ src = sourcePath;
+ for (i = 0; i < len; i++) {
+ unsigned char c = *src++;
+ if (c == '/')
+ *dst++ = '^';
+ else if (c == '^') {
+ *dst++ = '^';
+ *dst++ = '^';
+ } else
+ *dst++ = c;
+ }
+ *dst = '\0'; /* tack on eos */
+ *resultPath = result;
+ return NS_OK;
+}
+
+static void unescapeSlashes(char* path, size_t* newLength) {
+ char* src = path;
+ char* start = src;
+ char* dst = path;
+
+ while (*src) {
+ if (*src == '^') {
+ if (*(src + 1) == '^') {
+ *dst++ = '^';
+ src++; // skip over second '^'
+ } else
+ *dst++ = '/';
+ src++;
+ } else
+ *dst++ = *src++;
+ }
+
+ *newLength = dst - start;
+}
+
+/* static */ nsresult nsImapUrl::UnescapeSlashes(char* path) {
+ size_t newLength;
+ unescapeSlashes(path, &newLength);
+ path[newLength] = 0;
+ return NS_OK;
+}
+
+/* static */ nsresult nsImapUrl::UnescapeSlashes(nsACString& path) {
+ size_t newLength;
+ unescapeSlashes(path.BeginWriting(), &newLength);
+ path.SetLength(newLength);
+ return NS_OK;
+}
+
+/* static */ nsresult nsImapUrl::ConvertToCanonicalFormat(
+ const char* folderName, char onlineDelimiter,
+ char** resultingCanonicalPath) {
+ // Now, start the conversion to canonical form.
+
+ char* canonicalPath;
+ if (onlineDelimiter != '/') {
+ nsCString escapedPath;
+
+ EscapeSlashes(folderName, getter_Copies(escapedPath));
+ canonicalPath =
+ ReplaceCharsInCopiedString(escapedPath.get(), onlineDelimiter, '/');
+ } else {
+ canonicalPath = strdup(folderName);
+ }
+ if (canonicalPath) *resultingCanonicalPath = canonicalPath;
+
+ return (canonicalPath) ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
+}
+
+// Converts the real online name on the server to canonical format:
+// result is hierarchy is indicated by '/' and all real slashes ('/') are
+// escaped. This method is only called from the IMAP thread.
+NS_IMETHODIMP nsImapUrl::AllocateCanonicalPath(const char* serverPath,
+ char onlineDelimiter,
+ char** allocatedPath) {
+ NS_ENSURE_ARG_POINTER(serverPath);
+
+ char delimiterToUse = onlineDelimiter;
+ *allocatedPath = nullptr;
+
+ char* currentPath = (char*)serverPath;
+
+ nsresult rv;
+ nsCOMPtr<nsIImapHostSessionList> hostSessionList =
+ do_GetService(kCImapHostSessionListCID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (onlineDelimiter == kOnlineHierarchySeparatorUnknown ||
+ onlineDelimiter == 0)
+ GetOnlineSubDirSeparator(&delimiterToUse);
+
+ nsString dir;
+ hostSessionList->GetOnlineDirForHost(m_serverKey.get(), dir);
+ // First we have to check to see if we should strip off an online server
+ // subdirectory
+ // If this host has an online server directory configured
+ nsAutoCString onlineDir;
+ LossyCopyUTF16toASCII(dir, onlineDir);
+
+ if (!onlineDir.IsEmpty()) {
+ // By definition, the online dir must be at the root.
+ if (delimiterToUse && delimiterToUse != kOnlineHierarchySeparatorUnknown) {
+ // try to change the canonical online dir name to real dir name first
+ onlineDir.ReplaceChar('/', delimiterToUse);
+ // Add the delimiter
+ if (onlineDir.Last() != delimiterToUse) onlineDir += delimiterToUse;
+ }
+ int len = onlineDir.Length();
+ if (!PL_strncmp(onlineDir.get(), currentPath, len)) {
+ // This online path begins with the server sub directory
+ currentPath += len;
+
+ // This might occur, but it's most likely something not good.
+ // Basically, it means we're doing something on the online sub directory
+ // itself.
+ NS_ASSERTION(*currentPath, "Oops ... null currentPath");
+ // Also make sure that the first character in the mailbox name is not '/'.
+ NS_ASSERTION(*currentPath != '/',
+ "Oops ... currentPath starts with a slash");
+ }
+ }
+
+ return ConvertToCanonicalFormat(currentPath, delimiterToUse, allocatedPath);
+}
+
+// this method is only called from the imap thread
+NS_IMETHODIMP nsImapUrl::CreateServerSourceFolderPathString(char** result) {
+ NS_ENSURE_ARG_POINTER(result);
+ AllocateServerPath(m_sourceCanonicalFolderPathSubString,
+ kOnlineHierarchySeparatorUnknown, result);
+ return NS_OK;
+}
+
+// this method is called from the imap thread AND the UI thread...
+NS_IMETHODIMP nsImapUrl::CreateCanonicalSourceFolderPathString(char** result) {
+ NS_ENSURE_ARG_POINTER(result);
+ MutexAutoLock mon(mLock);
+ *result = strdup(m_sourceCanonicalFolderPathSubString
+ ? m_sourceCanonicalFolderPathSubString
+ : "");
+ return (*result) ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
+}
+
+// this method is called from the imap thread AND the UI thread...
+NS_IMETHODIMP nsImapUrl::CreateServerDestinationFolderPathString(
+ char** result) {
+ NS_ENSURE_ARG_POINTER(result);
+ MutexAutoLock mon(mLock);
+ nsresult rv = AllocateServerPath(m_destinationCanonicalFolderPathSubString,
+ kOnlineHierarchySeparatorUnknown, result);
+ return (*result) ? rv : NS_ERROR_OUT_OF_MEMORY;
+}
+
+NS_IMETHODIMP nsImapUrl::SetMimePartSelectorDetected(
+ bool mimePartSelectorDetected) {
+ m_mimePartSelectorDetected = mimePartSelectorDetected;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapUrl::GetMimePartSelectorDetected(
+ bool* mimePartSelectorDetected) {
+ if (!mimePartSelectorDetected) return NS_ERROR_NULL_POINTER;
+
+ *mimePartSelectorDetected = m_mimePartSelectorDetected;
+ return NS_OK;
+}
+
+// this method is only called from the UI thread.
+NS_IMETHODIMP nsImapUrl::SetCopyState(nsISupports* copyState) {
+ MutexAutoLock mon(mLock);
+ m_copyState = copyState;
+ return NS_OK;
+}
+
+// this method is only called from the imap thread..but we still
+// need a monitor 'cause the setter is called from the UI thread.
+NS_IMETHODIMP nsImapUrl::GetCopyState(nsISupports** copyState) {
+ NS_ENSURE_ARG_POINTER(copyState);
+ MutexAutoLock mon(mLock);
+ NS_IF_ADDREF(*copyState = m_copyState);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapUrl::SetMsgFile(nsIFile* aFile) {
+ nsresult rv = NS_OK;
+ MutexAutoLock mon(mLock);
+ m_file = aFile;
+ return rv;
+}
+
+NS_IMETHODIMP
+nsImapUrl::GetMsgFile(nsIFile** aFile) {
+ NS_ENSURE_ARG_POINTER(aFile);
+
+ MutexAutoLock mon(mLock);
+ NS_IF_ADDREF(*aFile = m_file);
+ return NS_OK;
+}
+
+// this method is called from the UI thread..
+NS_IMETHODIMP nsImapUrl::GetMockChannel(nsIImapMockChannel** aChannel) {
+ NS_ENSURE_ARG_POINTER(aChannel);
+ MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread(),
+ "should only access mock channel on ui thread");
+ *aChannel = nullptr;
+ nsCOMPtr<nsIImapMockChannel> channel(do_QueryReferent(m_channelWeakPtr));
+ channel.forget(aChannel);
+ return *aChannel ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP nsImapUrl::SetMockChannel(nsIImapMockChannel* aChannel) {
+ MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread(),
+ "should only access mock channel on ui thread");
+ m_channelWeakPtr = do_GetWeakReference(aChannel);
+ return NS_OK;
+}
+
+nsresult nsImapUrl::Clone(nsIURI** _retval) {
+ nsresult rv = nsMsgMailNewsUrl::Clone(_retval);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // also clone the mURI member, because GetUri below won't work if
+ // mURI isn't set due to escaping issues.
+ nsCOMPtr<nsIMsgMessageUrl> clonedUrl = do_QueryInterface(*_retval);
+ if (clonedUrl) clonedUrl->SetUri(mURI);
+ return rv;
+}
+
+NS_IMETHODIMP nsImapUrl::GetNormalizedSpec(nsACString& aPrincipalSpec) {
+ // URLs look like this:
+ // imap://user@domain@server:port/fetch>UID>folder>nn
+ // We simply strip any query part beginning with ? & or /;
+ // Normalized spec: imap://user@domain@server:port/fetch>UID>folder>nn
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsURL;
+ QueryInterface(NS_GET_IID(nsIMsgMailNewsUrl), getter_AddRefs(mailnewsURL));
+
+ nsAutoCString spec;
+ mailnewsURL->GetSpecIgnoringRef(spec);
+
+ // Strip any query part beginning with ? or /;
+ MsgRemoveQueryPart(spec);
+
+ aPrincipalSpec.Assign(spec);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapUrl::SetUri(const nsACString& aURI) {
+ mURI = aURI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapUrl::GetUri(nsACString& aURI) {
+ nsresult rv = NS_OK;
+ if (!mURI.IsEmpty())
+ aURI = mURI;
+ else {
+ uint32_t key =
+ m_listOfMessageIds ? strtoul(m_listOfMessageIds, nullptr, 10) : 0;
+ nsCString canonicalPath;
+ AllocateCanonicalPath(m_sourceCanonicalFolderPathSubString,
+ m_onlineSubDirSeparator,
+ (getter_Copies(canonicalPath)));
+ nsCString fullFolderPath("/");
+ fullFolderPath.Append(m_userName);
+ nsAutoCString hostName;
+ rv = GetHost(hostName);
+ fullFolderPath.Append('@');
+ fullFolderPath.Append(hostName);
+ fullFolderPath.Append('/');
+ fullFolderPath.Append(canonicalPath);
+
+ nsCString baseMessageURI;
+ nsCreateImapBaseMessageURI(fullFolderPath, baseMessageURI);
+ rv = nsBuildImapMessageURI(baseMessageURI.get(), key, aURI);
+ }
+ return rv;
+}
+
+NS_IMPL_GETSET(nsImapUrl, AddDummyEnvelope, bool, m_addDummyEnvelope)
+NS_IMPL_GETSET(nsImapUrl, CanonicalLineEnding, bool, m_canonicalLineEnding)
+NS_IMETHODIMP nsImapUrl::GetMsgLoadingFromCache(bool* result) {
+ NS_ENSURE_ARG_POINTER(result);
+ *result = m_msgLoadingFromCache;
+ return NS_OK;
+}
+NS_IMPL_GETSET(nsImapUrl, LocalFetchOnly, bool, m_localFetchOnly)
+NS_IMPL_GETSET(nsImapUrl, ExternalLinkUrl, bool, m_externalLinkUrl)
+NS_IMPL_GETSET(nsImapUrl, RerunningUrl, bool, m_rerunningUrl)
+NS_IMPL_GETSET(nsImapUrl, ValidUrl, bool, m_validUrl)
+NS_IMPL_GETSET(nsImapUrl, MoreHeadersToDownload, bool, m_moreHeadersToDownload)
+
+NS_IMETHODIMP nsImapUrl::SetMsgLoadingFromCache(bool loadingFromCache) {
+ nsresult rv = NS_OK;
+ m_msgLoadingFromCache = loadingFromCache;
+ return rv;
+}
+
+NS_IMETHODIMP nsImapUrl::SetMessageFile(nsIFile* aFile) {
+ m_messageFile = aFile;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapUrl::GetMessageFile(nsIFile** aFile) {
+ if (aFile) NS_IF_ADDREF(*aFile = m_messageFile);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapUrl::IsUrlType(uint32_t type, bool* isType) {
+ NS_ENSURE_ARG(isType);
+
+ switch (type) {
+ case nsIMsgMailNewsUrl::eCopy:
+ *isType = ((m_imapAction == nsIImapUrl::nsImapOnlineCopy) ||
+ (m_imapAction == nsIImapUrl::nsImapOnlineToOfflineCopy) ||
+ (m_imapAction == nsIImapUrl::nsImapOfflineToOnlineCopy));
+ break;
+ case nsIMsgMailNewsUrl::eMove:
+ *isType = ((m_imapAction == nsIImapUrl::nsImapOnlineMove) ||
+ (m_imapAction == nsIImapUrl::nsImapOnlineToOfflineMove) ||
+ (m_imapAction == nsIImapUrl::nsImapOfflineToOnlineMove));
+ break;
+ case nsIMsgMailNewsUrl::eDisplay:
+ *isType = (m_imapAction == nsIImapUrl::nsImapMsgFetch ||
+ m_imapAction == nsIImapUrl::nsImapMsgFetchPeek);
+ break;
+ default:
+ *isType = false;
+ };
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapUrl::GetOriginalSpec(nsACString& aSpec) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsImapUrl::SetOriginalSpec(const nsACString& aSpec) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+char* nsImapUrl::ReplaceCharsInCopiedString(const char* stringToCopy,
+ char oldChar, char newChar) {
+ char oldCharString[2];
+ *oldCharString = oldChar;
+ *(oldCharString + 1) = 0;
+
+ char* translatedString = PL_strdup(stringToCopy);
+ char* currentSeparator = PL_strstr(translatedString, oldCharString);
+
+ while (currentSeparator) {
+ *currentSeparator = newChar;
+ currentSeparator = PL_strstr(currentSeparator + 1, oldCharString);
+ }
+
+ return translatedString;
+}
+
+////////////////////////////////////////////////////////////////////////////////////
+// End of functions which should be made obsolete after modifying nsIURI
+////////////////////////////////////////////////////////////////////////////////////
+
+void nsImapUrl::ParseFolderPath(char** resultingCanonicalPath) {
+ char* resultPath = m_tokenPlaceHolder ? NS_strtok(IMAP_URL_TOKEN_SEPARATOR,
+ &m_tokenPlaceHolder)
+ : (char*)NULL;
+
+ if (!resultPath) {
+ m_validUrl = false;
+ return;
+ }
+ NS_ASSERTION(*resultingCanonicalPath == nullptr, "whoops, mem leak");
+
+ char dirSeparator = *resultPath;
+
+ nsCString unescapedResultingCanonicalPath;
+ MsgUnescapeString(nsDependentCString(resultPath + 1), 0,
+ unescapedResultingCanonicalPath);
+ *resultingCanonicalPath = ToNewCString(unescapedResultingCanonicalPath);
+ // The delimiter will be set for a given URL, but will not be statically
+ // available from an arbitrary URL. It is the creator's responsibility to
+ // fill in the correct delimiter from the folder's namespace when creating the
+ // URL.
+ if (dirSeparator != kOnlineHierarchySeparatorUnknown)
+ SetOnlineSubDirSeparator(dirSeparator);
+
+ // if dirSeparator == kOnlineHierarchySeparatorUnknown, then this must be a
+ // create of a top level imap box. If there is an online subdir, we will
+ // automatically use its separator. If there is not an online subdir, we
+ // don't need a separator.
+}
+
+void nsImapUrl::ParseSearchCriteriaString() {
+ if (m_tokenPlaceHolder) {
+ int quotedFlag = false;
+
+ // skip initial separator
+ while (*m_tokenPlaceHolder == *IMAP_URL_TOKEN_SEPARATOR)
+ m_tokenPlaceHolder++;
+
+ char* saveTokenPlaceHolder = m_tokenPlaceHolder;
+
+ // m_searchCriteriaString = m_tokenPlaceHolder;
+
+ // looking for another separator outside quoted string
+ while (*m_tokenPlaceHolder) {
+ if (*m_tokenPlaceHolder == '\\' && *(m_tokenPlaceHolder + 1) == '"')
+ m_tokenPlaceHolder++;
+ else if (*m_tokenPlaceHolder == '"')
+ quotedFlag = !quotedFlag;
+ else if (!quotedFlag &&
+ *m_tokenPlaceHolder == *IMAP_URL_TOKEN_SEPARATOR) {
+ *m_tokenPlaceHolder = '\0';
+ m_tokenPlaceHolder++;
+ break;
+ }
+ m_tokenPlaceHolder++;
+ }
+ m_searchCriteriaString = PL_strdup(saveTokenPlaceHolder);
+ if (*m_tokenPlaceHolder == '\0') m_tokenPlaceHolder = NULL;
+
+ if (*m_searchCriteriaString == '\0') m_searchCriteriaString = (char*)NULL;
+ } else
+ m_searchCriteriaString = (char*)NULL;
+ if (!m_searchCriteriaString) m_validUrl = false;
+}
+
+void nsImapUrl::ParseUidChoice() {
+ char* uidChoiceString =
+ m_tokenPlaceHolder
+ ? NS_strtok(IMAP_URL_TOKEN_SEPARATOR, &m_tokenPlaceHolder)
+ : (char*)NULL;
+ if (!uidChoiceString)
+ m_validUrl = false;
+ else
+ m_idsAreUids = strcmp(uidChoiceString, "UID") == 0;
+}
+
+void nsImapUrl::ParseMsgFlags() {
+ char* flagsPtr = m_tokenPlaceHolder ? NS_strtok(IMAP_URL_TOKEN_SEPARATOR,
+ &m_tokenPlaceHolder)
+ : (char*)NULL;
+ if (flagsPtr) {
+ // the url is encodes the flags byte as ascii
+ int intFlags = atoi(flagsPtr);
+ m_flags = (imapMessageFlagsType)intFlags; // cast here
+ } else
+ m_flags = 0;
+}
+
+void nsImapUrl::ParseListOfMessageIds() {
+ m_listOfMessageIds = m_tokenPlaceHolder ? NS_strtok(IMAP_URL_TOKEN_SEPARATOR,
+ &m_tokenPlaceHolder)
+ : (char*)NULL;
+ if (!m_listOfMessageIds)
+ m_validUrl = false;
+ else {
+ m_listOfMessageIds = strdup(m_listOfMessageIds);
+ m_mimePartSelectorDetected = PL_strstr(m_listOfMessageIds, "&part=") != 0 ||
+ PL_strstr(m_listOfMessageIds, "?part=") != 0;
+
+ // if it's a spam filter trying to fetch the msg, don't let it get marked
+ // read.
+ if (PL_strstr(m_listOfMessageIds, "?header=filter") != 0)
+ m_imapAction = nsImapMsgFetchPeek;
+ }
+}
+
+void nsImapUrl::ParseCustomMsgFetchAttribute() {
+ m_msgFetchAttribute = m_tokenPlaceHolder ? NS_strtok(IMAP_URL_TOKEN_SEPARATOR,
+ &m_tokenPlaceHolder)
+ : (char*)nullptr;
+}
+
+void nsImapUrl::ParseNumBytes() {
+ const char* numBytes =
+ (m_tokenPlaceHolder)
+ ? NS_strtok(IMAP_URL_TOKEN_SEPARATOR, &m_tokenPlaceHolder)
+ : 0;
+ m_numBytesToFetch = numBytes ? atoi(numBytes) : 0;
+}
+
+// nsIMsgI18NUrl support
+
+nsresult nsImapUrl::GetMsgFolder(nsIMsgFolder** msgFolder) {
+ // if we have a RDF URI, then try to get the folder for that URI and then ask
+ // the folder for it's charset....
+
+ nsCString uri;
+ GetUri(uri);
+ NS_ENSURE_TRUE(!uri.IsEmpty(), NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsIMsgDBHdr> msg;
+ GetMsgDBHdrFromURI(uri, getter_AddRefs(msg));
+ NS_ENSURE_TRUE(msg, NS_ERROR_FAILURE);
+ nsresult rv = msg->GetFolder(msgFolder);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_TRUE(msgFolder, NS_ERROR_FAILURE);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapUrl::GetAutodetectCharset(bool* aAutodetectCharset) {
+ *aAutodetectCharset = mAutodetectCharset;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapUrl::SetAutodetectCharset(bool aAutodetectCharset) {
+ mAutodetectCharset = aAutodetectCharset;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapUrl::GetStoreResultsOffline(bool* aStoreResultsOffline) {
+ NS_ENSURE_ARG_POINTER(aStoreResultsOffline);
+ *aStoreResultsOffline = m_storeResultsOffline;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapUrl::SetStoreResultsOffline(bool aStoreResultsOffline) {
+ m_storeResultsOffline = aStoreResultsOffline;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapUrl::GetStoreOfflineOnFallback(
+ bool* aStoreOfflineOnFallback) {
+ NS_ENSURE_ARG_POINTER(aStoreOfflineOnFallback);
+ *aStoreOfflineOnFallback = m_storeOfflineOnFallback;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapUrl::SetStoreOfflineOnFallback(
+ bool aStoreOfflineOnFallback) {
+ m_storeOfflineOnFallback = aStoreOfflineOnFallback;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapUrl::GetMessageHeader(nsIMsgDBHdr** aMsgHdr) {
+ nsCString uri;
+ nsresult rv = GetUri(uri);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return GetMsgDBHdrFromURI(uri, aMsgHdr);
+}
diff --git a/comm/mailnews/imap/src/nsImapUrl.h b/comm/mailnews/imap/src/nsImapUrl.h
new file mode 100644
index 0000000000..c5cbdb15cf
--- /dev/null
+++ b/comm/mailnews/imap/src/nsImapUrl.h
@@ -0,0 +1,132 @@
+/* -*- 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 nsImapUrl_h___
+#define nsImapUrl_h___
+
+#include "mozilla/Attributes.h"
+#include "nsIImapUrl.h"
+#include "nsCOMPtr.h"
+#include "nsMsgMailNewsUrl.h"
+#include "nsIWeakReferenceUtils.h"
+#include "nsIFile.h"
+#include "mozilla/Mutex.h"
+
+class nsImapUrl : public nsIImapUrl,
+ public nsMsgMailNewsUrl,
+ public nsIMsgMessageUrl,
+ public nsIMsgI18NUrl {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+
+ // nsMsgMailNewsUrl overrides
+ nsresult SetSpecInternal(const nsACString& aSpec) override;
+ nsresult SetQuery(const nsACString& aQuery) override;
+ nsresult Clone(nsIURI** _retval) override;
+
+ //////////////////////////////////////////////////////////////////////////////
+ // we support the nsIImapUrl interface
+ //////////////////////////////////////////////////////////////////////////////
+ NS_DECL_NSIIMAPURL
+
+ // nsIMsgMailNewsUrl overrides
+ NS_IMETHOD IsUrlType(uint32_t type, bool* isType) override;
+ NS_IMETHOD GetFolder(nsIMsgFolder** aFolder) override;
+ NS_IMETHOD SetFolder(nsIMsgFolder* aFolder) override;
+ // nsIMsgMessageUrl
+ NS_DECL_NSIMSGMESSAGEURL
+ NS_DECL_NSIMSGI18NURL
+
+ // nsImapUrl
+ nsImapUrl();
+
+ static nsresult ConvertToCanonicalFormat(const char* folderName,
+ char onlineDelimiter,
+ char** resultingCanonicalPath);
+ static nsresult EscapeSlashes(const char* sourcePath, char** resultPath);
+ static nsresult UnescapeSlashes(nsACString& path);
+ static nsresult UnescapeSlashes(char* path);
+ static char* ReplaceCharsInCopiedString(const char* stringToCopy,
+ char oldChar, char newChar);
+
+ protected:
+ virtual ~nsImapUrl();
+ virtual nsresult ParseUrl();
+
+ char* m_listOfMessageIds;
+
+ // handle the imap specific parsing
+ void ParseImapPart(char* imapPartOfUrl);
+
+ void ParseFolderPath(char** resultingCanonicalPath);
+ void ParseSearchCriteriaString();
+ void ParseUidChoice();
+ void ParseMsgFlags();
+ void ParseListOfMessageIds();
+ void ParseCustomMsgFetchAttribute();
+ void ParseNumBytes();
+
+ nsresult GetMsgFolder(nsIMsgFolder** msgFolder);
+
+ char* m_sourceCanonicalFolderPathSubString;
+ char* m_destinationCanonicalFolderPathSubString;
+ char* m_tokenPlaceHolder;
+ char* m_urlidSubString;
+ char m_onlineSubDirSeparator;
+ char* m_searchCriteriaString; // should we use m_search, or is this special?
+ nsCString m_command; // for custom commands
+ nsCString m_msgFetchAttribute; // for fetching custom msg attributes
+ nsCString m_customAttributeResult; // for fetching custom msg attributes
+ nsCString m_customCommandResult; // custom command response
+ nsCString
+ m_customAddFlags; // these two are for setting and clearing custom flags
+ nsCString m_customSubtractFlags;
+ int32_t m_numBytesToFetch; // when doing a msg body preview, how many bytes
+ // to read
+ bool m_validUrl;
+ bool m_runningUrl;
+ bool m_idsAreUids;
+ bool m_mimePartSelectorDetected;
+ bool m_msgLoadingFromCache; // if true, we might need to mark read on server
+ bool m_externalLinkUrl; // if true, we're running this url because the user
+ // True if the fetch results should be put in the offline store.
+ bool m_storeResultsOffline;
+ bool m_storeOfflineOnFallback;
+ bool m_localFetchOnly;
+ bool m_rerunningUrl; // first attempt running this failed with connection
+ // error; retrying
+ bool m_moreHeadersToDownload;
+ nsImapContentModifiedType m_contentModified;
+
+ int32_t m_extraStatus;
+
+ nsCString m_userName;
+ nsCString m_serverKey;
+ // event sinks
+ imapMessageFlagsType m_flags;
+ nsImapAction m_imapAction;
+
+ nsWeakPtr m_imapFolder;
+ nsWeakPtr m_imapMailFolderSink;
+ nsWeakPtr m_imapMessageSink;
+
+ nsWeakPtr m_imapServerSink;
+
+ // online message copy support; i don't have a better solution yet
+ nsCOMPtr<nsISupports> m_copyState; // now, refcounted.
+ nsCOMPtr<nsIFile> m_file;
+ nsWeakPtr m_channelWeakPtr;
+
+ // used by save message to disk
+ nsCOMPtr<nsIFile> m_messageFile;
+ bool m_addDummyEnvelope;
+ bool m_canonicalLineEnding; // CRLF
+
+ nsCString mURI; // the RDF URI associated with this url.
+ bool mAutodetectCharset; // used by nsIMsgI18NUrl...
+ mozilla::Mutex mLock;
+};
+
+#endif /* nsImapUrl_h___ */
diff --git a/comm/mailnews/imap/src/nsImapUtils.cpp b/comm/mailnews/imap/src/nsImapUtils.cpp
new file mode 100644
index 0000000000..9a5811511c
--- /dev/null
+++ b/comm/mailnews/imap/src/nsImapUtils.cpp
@@ -0,0 +1,336 @@
+/* -*- 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 "nsImapUtils.h"
+#include "nsCOMPtr.h"
+#include "prsystem.h"
+#include "prprf.h"
+#include "nsNetCID.h"
+
+#include "nsMsgUtils.h"
+#include "nsImapFlagAndUidState.h"
+#include "nsImapNamespace.h"
+#include "nsIImapFlagAndUidState.h"
+
+nsresult nsImapURI2FullName(const char* rootURI, const char* hostName,
+ const char* uriStr, char** name) {
+ nsAutoCString uri(uriStr);
+ nsAutoCString fullName;
+ if (uri.Find(rootURI) != 0) return NS_ERROR_FAILURE;
+ fullName = Substring(uri, strlen(rootURI));
+ uri = fullName;
+ int32_t hostStart = uri.Find(hostName);
+ if (hostStart <= 0) return NS_ERROR_FAILURE;
+ fullName = Substring(uri, hostStart);
+ uri = fullName;
+ int32_t hostEnd = uri.FindChar('/');
+ if (hostEnd <= 0) return NS_ERROR_FAILURE;
+ fullName = Substring(uri, hostEnd + 1);
+ if (fullName.IsEmpty()) return NS_ERROR_FAILURE;
+ *name = ToNewCString(fullName);
+ return NS_OK;
+}
+
+/* parses ImapMessageURI */
+nsresult nsParseImapMessageURI(const nsACString& uri, nsCString& folderURI,
+ uint32_t* key, char** part) {
+ if (!key) return NS_ERROR_NULL_POINTER;
+
+ const nsPromiseFlatCString& uriStr = PromiseFlatCString(uri);
+ int32_t folderEnd = -1;
+ // imap-message uri's can have imap:// url strings tacked on the end,
+ // e.g., when opening/saving attachments. We don't want to look for '#'
+ // in that part of the uri, if the attachment name contains '#',
+ // so check for that here.
+ if (StringBeginsWith(uriStr, "imap-message"_ns))
+ folderEnd = uriStr.Find("imap://");
+
+ int32_t keySeparator = uriStr.RFindChar('#', folderEnd);
+ if (keySeparator != -1) {
+ int32_t keyEndSeparator = MsgFindCharInSet(uriStr, "/?&", keySeparator);
+ nsAutoString folderPath;
+ folderURI = StringHead(uriStr, keySeparator);
+ folderURI.Cut(4, 8); // cut out the _message part of imap-message:
+ // folder uri's don't have fully escaped usernames.
+ int32_t atPos = folderURI.FindChar('@');
+ if (atPos != -1) {
+ nsCString unescapedName, escapedName;
+ int32_t userNamePos = folderURI.Find("//") + 2;
+ uint32_t origUserNameLen = atPos - userNamePos;
+ if (NS_SUCCEEDED(MsgUnescapeString(
+ Substring(folderURI, userNamePos, origUserNameLen), 0,
+ unescapedName))) {
+ // Re-escape the username, matching the way we do it in uris, not the
+ // way necko escapes urls. See nsMsgIncomingServer::GetServerURI.
+ MsgEscapeString(unescapedName, nsINetUtil::ESCAPE_XALPHAS, escapedName);
+ folderURI.Replace(userNamePos, origUserNameLen, escapedName);
+ }
+ }
+ nsAutoCString keyStr;
+ if (keyEndSeparator != -1)
+ keyStr = Substring(uriStr, keySeparator + 1,
+ keyEndSeparator - (keySeparator + 1));
+ else
+ keyStr = Substring(uriStr, keySeparator + 1);
+
+ *key = strtoul(keyStr.get(), nullptr, 10);
+
+ if (part && keyEndSeparator != -1) {
+ int32_t partPos = uriStr.Find("part=", keyEndSeparator);
+ if (partPos != -1) {
+ *part = ToNewCString(Substring(uriStr, keyEndSeparator));
+ }
+ }
+ }
+ return NS_OK;
+}
+
+nsresult nsBuildImapMessageURI(const char* baseURI, uint32_t key,
+ nsACString& uri) {
+ uri.Append(baseURI);
+ uri.Append('#');
+ uri.AppendInt(key);
+ return NS_OK;
+}
+
+nsresult nsCreateImapBaseMessageURI(const nsACString& baseURI,
+ nsCString& baseMessageURI) {
+ nsAutoCString tailURI(baseURI);
+ // chop off imap:/
+ if (tailURI.Find(kImapRootURI) == 0) tailURI.Cut(0, PL_strlen(kImapRootURI));
+ baseMessageURI = kImapMessageRootURI;
+ baseMessageURI += tailURI;
+ return NS_OK;
+}
+
+// nsImapMailboxSpec definition
+NS_IMPL_ISUPPORTS(nsImapMailboxSpec, nsIMailboxSpec)
+
+nsImapMailboxSpec::nsImapMailboxSpec() {
+ mFolder_UIDVALIDITY = 0;
+ mHighestModSeq = 0;
+ mNumOfMessages = 0;
+ mNumOfUnseenMessages = 0;
+ mNumOfRecentMessages = 0;
+ mNextUID = 0;
+
+ mBoxFlags = 0;
+ mSupportedUserFlags = 0;
+
+ mHierarchySeparator = '\0';
+
+ mFolderSelected = false;
+ mDiscoveredFromLsub = false;
+
+ mOnlineVerified = false;
+ mNamespaceForFolder = nullptr;
+}
+
+nsImapMailboxSpec::~nsImapMailboxSpec() {}
+
+NS_IMPL_GETSET(nsImapMailboxSpec, Folder_UIDVALIDITY, int32_t,
+ mFolder_UIDVALIDITY)
+NS_IMPL_GETSET(nsImapMailboxSpec, HighestModSeq, uint64_t, mHighestModSeq)
+NS_IMPL_GETSET(nsImapMailboxSpec, NumMessages, int32_t, mNumOfMessages)
+NS_IMPL_GETSET(nsImapMailboxSpec, NumUnseenMessages, int32_t,
+ mNumOfUnseenMessages)
+NS_IMPL_GETSET(nsImapMailboxSpec, NumRecentMessages, int32_t,
+ mNumOfRecentMessages)
+NS_IMPL_GETSET(nsImapMailboxSpec, NextUID, int32_t, mNextUID)
+NS_IMPL_GETSET(nsImapMailboxSpec, HierarchyDelimiter, char, mHierarchySeparator)
+NS_IMPL_GETSET(nsImapMailboxSpec, FolderSelected, bool, mFolderSelected)
+NS_IMPL_GETSET(nsImapMailboxSpec, DiscoveredFromLsub, bool, mDiscoveredFromLsub)
+NS_IMPL_GETSET(nsImapMailboxSpec, OnlineVerified, bool, mOnlineVerified)
+NS_IMPL_GETSET(nsImapMailboxSpec, SupportedUserFlags, uint32_t,
+ mSupportedUserFlags)
+NS_IMPL_GETSET(nsImapMailboxSpec, Box_flags, uint32_t, mBoxFlags)
+NS_IMPL_GETSET(nsImapMailboxSpec, NamespaceForFolder, nsImapNamespace*,
+ mNamespaceForFolder)
+
+NS_IMETHODIMP nsImapMailboxSpec::GetAllocatedPathName(
+ nsACString& aAllocatedPathName) {
+ aAllocatedPathName = mAllocatedPathName;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailboxSpec::SetAllocatedPathName(
+ const nsACString& aAllocatedPathName) {
+ mAllocatedPathName = aAllocatedPathName;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailboxSpec::GetUnicharPathName(
+ nsAString& aUnicharPathName) {
+ aUnicharPathName = mUnicharPathName;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailboxSpec::SetUnicharPathName(
+ const nsAString& aUnicharPathName) {
+ mUnicharPathName = aUnicharPathName;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailboxSpec::GetHostName(nsACString& aHostName) {
+ aHostName = mHostName;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailboxSpec::SetHostName(const nsACString& aHostName) {
+ mHostName = aHostName;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailboxSpec::GetFlagState(
+ nsIImapFlagAndUidState** aFlagState) {
+ NS_ENSURE_ARG_POINTER(aFlagState);
+ NS_IF_ADDREF(*aFlagState = mFlagState);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailboxSpec::SetFlagState(
+ nsIImapFlagAndUidState* aFlagState) {
+ NS_ENSURE_ARG_POINTER(aFlagState);
+ mFlagState = aFlagState;
+ return NS_OK;
+}
+
+nsImapMailboxSpec& nsImapMailboxSpec::operator=(
+ const nsImapMailboxSpec& aCopy) {
+ mFolder_UIDVALIDITY = aCopy.mFolder_UIDVALIDITY;
+ mHighestModSeq = aCopy.mHighestModSeq;
+ mNumOfMessages = aCopy.mNumOfMessages;
+ mNumOfUnseenMessages = aCopy.mNumOfUnseenMessages;
+ mNumOfRecentMessages = aCopy.mNumOfRecentMessages;
+
+ mBoxFlags = aCopy.mBoxFlags;
+ mSupportedUserFlags = aCopy.mSupportedUserFlags;
+
+ mAllocatedPathName.Assign(aCopy.mAllocatedPathName);
+ mUnicharPathName.Assign(aCopy.mUnicharPathName);
+ mHostName.Assign(aCopy.mHostName);
+
+ mFlagState = aCopy.mFlagState;
+ mNamespaceForFolder = aCopy.mNamespaceForFolder;
+
+ mFolderSelected = aCopy.mFolderSelected;
+ mDiscoveredFromLsub = aCopy.mDiscoveredFromLsub;
+
+ mOnlineVerified = aCopy.mOnlineVerified;
+
+ return *this;
+}
+
+// use the flagState to determine if the gaps in the msgUids correspond to gaps
+// in the mailbox, in which case we can still use ranges. If flagState is null,
+// we won't do this.
+void AllocateImapUidString(const uint32_t* msgUids, uint32_t& msgCount,
+ nsImapFlagAndUidState* flagState,
+ nsCString& returnString) {
+ uint32_t startSequence = (msgCount > 0) ? msgUids[0] : 0xFFFFFFFF;
+ uint32_t curSequenceEnd = startSequence;
+ uint32_t total = msgCount;
+ int32_t curFlagStateIndex = -1;
+
+ // a partial fetch flag state doesn't help us, so don't use it.
+ if (flagState && flagState->GetPartialUIDFetch()) flagState = nullptr;
+
+ for (uint32_t keyIndex = 0; keyIndex < total; keyIndex++) {
+ uint32_t curKey = msgUids[keyIndex];
+ uint32_t nextKey =
+ (keyIndex + 1 < total) ? msgUids[keyIndex + 1] : 0xFFFFFFFF;
+ bool lastKey = (nextKey == 0xFFFFFFFF);
+
+ if (lastKey) curSequenceEnd = curKey;
+
+ if (!lastKey) {
+ if (nextKey == curSequenceEnd + 1) {
+ curSequenceEnd = nextKey;
+ curFlagStateIndex++;
+ continue;
+ }
+ if (flagState) {
+ if (curFlagStateIndex == -1) {
+ bool foundIt;
+ flagState->GetMessageFlagsFromUID(curSequenceEnd, &foundIt,
+ &curFlagStateIndex);
+ if (!foundIt) {
+ NS_WARNING("flag state missing key");
+ // The start of this sequence is missing from flag state, so move
+ // on to the next key.
+ curFlagStateIndex = -1;
+ curSequenceEnd = startSequence = nextKey;
+ continue;
+ }
+ }
+ curFlagStateIndex++;
+ uint32_t nextUidInFlagState;
+ nsresult rv =
+ flagState->GetUidOfMessage(curFlagStateIndex, &nextUidInFlagState);
+ if (NS_SUCCEEDED(rv) && nextUidInFlagState == nextKey) {
+ curSequenceEnd = nextKey;
+ continue;
+ }
+ }
+ }
+ if (curSequenceEnd > startSequence) {
+ returnString.AppendInt((int64_t)startSequence);
+ returnString += ':';
+ returnString.AppendInt((int64_t)curSequenceEnd);
+ startSequence = nextKey;
+ curSequenceEnd = startSequence;
+ curFlagStateIndex = -1;
+ } else {
+ startSequence = nextKey;
+ curSequenceEnd = startSequence;
+ returnString.AppendInt((int64_t)msgUids[keyIndex]);
+ curFlagStateIndex = -1;
+ }
+ // check if we've generated too long a string - if there's no flag state,
+ // it means we just need to go ahead and generate a too long string
+ // because the calling code won't handle breaking up the strings.
+ if (flagState && returnString.Length() > 950) {
+ msgCount = keyIndex;
+ break;
+ }
+ // If we are not the last item then we need to add the comma
+ // but it's important we do it here, after the length check
+ if (!lastKey) returnString += ',';
+ }
+}
+
+void ParseUidString(const char* uidString, nsTArray<nsMsgKey>& keys) {
+ // This is in the form <id>,<id>, or <id1>:<id2>
+ if (!uidString) return;
+
+ char curChar = *uidString;
+ bool isRange = false;
+ uint32_t curToken;
+ uint32_t saveStartToken = 0;
+
+ for (const char* curCharPtr = uidString; curChar && *curCharPtr;) {
+ const char* currentKeyToken = curCharPtr;
+ curChar = *curCharPtr;
+ while (curChar != ':' && curChar != ',' && curChar != '\0')
+ curChar = *curCharPtr++;
+
+ // we don't need to null terminate currentKeyToken because strtoul
+ // stops at non-numeric chars.
+ curToken = strtoul(currentKeyToken, nullptr, 10);
+ if (isRange) {
+ while (saveStartToken < curToken) keys.AppendElement(saveStartToken++);
+ }
+ keys.AppendElement(curToken);
+ isRange = (curChar == ':');
+ if (isRange) saveStartToken = curToken + 1;
+ }
+}
+
+void AppendUid(nsCString& msgIds, uint32_t uid) {
+ char buf[20];
+ PR_snprintf(buf, sizeof(buf), "%u", uid);
+ msgIds.Append(buf);
+}
diff --git a/comm/mailnews/imap/src/nsImapUtils.h b/comm/mailnews/imap/src/nsImapUtils.h
new file mode 100644
index 0000000000..8dc2c55ef3
--- /dev/null
+++ b/comm/mailnews/imap/src/nsImapUtils.h
@@ -0,0 +1,77 @@
+/* -*- 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 NS_IMAPUTILS_H
+#define NS_IMAPUTILS_H
+
+#include "nsString.h"
+#include "MailNewsTypes.h"
+#include "nsTArray.h"
+#include "nsIMailboxSpec.h"
+#include "nsCOMPtr.h"
+
+class nsImapFlagAndUidState;
+class nsImapProtocol;
+
+static const char kImapRootURI[] = "imap:/";
+static const char kImapMessageRootURI[] = "imap-message:/";
+static const char kModSeqPropertyName[] = "highestModSeq";
+static const char kHighestRecordedUIDPropertyName[] = "highestRecordedUID";
+static const char kDeletedHdrCountPropertyName[] = "numDeletedHeaders";
+
+extern nsresult nsImapURI2FullName(const char* rootURI, const char* hostname,
+ const char* uriStr, char** name);
+
+extern nsresult nsParseImapMessageURI(const nsACString& uri,
+ nsCString& folderURI, uint32_t* key,
+ char** part);
+
+extern nsresult nsBuildImapMessageURI(const char* baseURI, uint32_t key,
+ nsACString& uri);
+
+extern nsresult nsCreateImapBaseMessageURI(const nsACString& baseURI,
+ nsCString& baseMessageURI);
+
+void AllocateImapUidString(const uint32_t* msgUids, uint32_t& msgCount,
+ nsImapFlagAndUidState* flagState,
+ nsCString& returnString);
+void ParseUidString(const char* uidString, nsTArray<nsMsgKey>& keys);
+void AppendUid(nsCString& msgIds, uint32_t uid);
+
+class nsImapMailboxSpec : public nsIMailboxSpec {
+ public:
+ nsImapMailboxSpec();
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIMAILBOXSPEC
+
+ nsImapMailboxSpec& operator=(const nsImapMailboxSpec& aCopy);
+
+ nsCOMPtr<nsIImapFlagAndUidState> mFlagState;
+ nsImapNamespace* mNamespaceForFolder;
+
+ uint32_t mBoxFlags;
+ uint32_t mSupportedUserFlags;
+ int32_t mFolder_UIDVALIDITY;
+ uint64_t mHighestModSeq;
+ int32_t mNumOfMessages;
+ int32_t mNumOfUnseenMessages;
+ int32_t mNumOfRecentMessages;
+ int32_t mNextUID;
+ nsCString mAllocatedPathName;
+ nsCString mHostName;
+ nsString mUnicharPathName;
+ char mHierarchySeparator;
+ bool mFolderSelected;
+ bool mDiscoveredFromLsub;
+ bool mOnlineVerified;
+
+ nsImapProtocol* mConnection; // do we need this? It seems evil
+
+ private:
+ virtual ~nsImapMailboxSpec();
+};
+
+#endif // NS_IMAPUTILS_H
diff --git a/comm/mailnews/imap/src/nsSyncRunnableHelpers.cpp b/comm/mailnews/imap/src/nsSyncRunnableHelpers.cpp
new file mode 100644
index 0000000000..14028fef03
--- /dev/null
+++ b/comm/mailnews/imap/src/nsSyncRunnableHelpers.cpp
@@ -0,0 +1,596 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "nsSyncRunnableHelpers.h"
+#include "nsImapCore.h"
+#include "nsIMsgMailNewsUrl.h"
+#include "nsIMsgIncomingServer.h"
+#include "nsIMsgWindow.h"
+#include "nsIImapMailFolderSink.h"
+
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/Monitor.h"
+
+NS_IMPL_ISUPPORTS(StreamListenerProxy, nsIStreamListener)
+NS_IMPL_ISUPPORTS(ImapMailFolderSinkProxy, nsIImapMailFolderSink)
+NS_IMPL_ISUPPORTS(ImapServerSinkProxy, nsIImapServerSink)
+NS_IMPL_ISUPPORTS(ImapMessageSinkProxy, nsIImapMessageSink)
+NS_IMPL_ISUPPORTS(ImapProtocolSinkProxy, nsIImapProtocolSink)
+namespace {
+
+// Traits class for a reference type, specialized for parameters which are
+// already references.
+template <typename T>
+struct RefType {
+ typedef T& type;
+};
+
+template <>
+struct RefType<nsAString&> {
+ typedef nsAString& type;
+};
+
+template <>
+struct RefType<const nsAString&> {
+ typedef const nsAString& type;
+};
+
+template <>
+struct RefType<nsACString&> {
+ typedef nsACString& type;
+};
+
+template <>
+struct RefType<const nsACString&> {
+ typedef const nsACString& type;
+};
+
+template <>
+struct RefType<const nsIID&> {
+ typedef const nsIID& type;
+};
+
+class SyncRunnableBase : public mozilla::Runnable {
+ public:
+ nsresult Result() { return mResult; }
+
+ mozilla::Monitor& Monitor() { return mMonitor; }
+
+ protected:
+ SyncRunnableBase()
+ : mozilla::Runnable("SyncRunnableBase"),
+ mResult(NS_ERROR_UNEXPECTED),
+ mMonitor("SyncRunnableBase") {}
+
+ nsresult mResult;
+ mozilla::Monitor mMonitor;
+};
+
+template <typename Receiver>
+class SyncRunnable0 : public SyncRunnableBase {
+ public:
+ typedef nsresult (NS_STDCALL Receiver::*ReceiverMethod)();
+
+ SyncRunnable0(Receiver* receiver, ReceiverMethod method)
+ : mReceiver(receiver), mMethod(method) {}
+
+ NS_IMETHOD Run() {
+ mResult = (mReceiver->*mMethod)();
+ mozilla::MonitorAutoLock(mMonitor).Notify();
+ return NS_OK;
+ }
+
+ private:
+ Receiver* mReceiver;
+ ReceiverMethod mMethod;
+};
+
+template <typename Receiver, typename Arg1>
+class SyncRunnable1 : public SyncRunnableBase {
+ public:
+ typedef nsresult (NS_STDCALL Receiver::*ReceiverMethod)(Arg1);
+ typedef typename RefType<Arg1>::type Arg1Ref;
+
+ SyncRunnable1(Receiver* receiver, ReceiverMethod method, Arg1Ref arg1)
+ : mReceiver(receiver), mMethod(method), mArg1(arg1) {}
+
+ NS_IMETHOD Run() {
+ mResult = (mReceiver->*mMethod)(mArg1);
+ mozilla::MonitorAutoLock(mMonitor).Notify();
+ return NS_OK;
+ }
+
+ private:
+ Receiver* mReceiver;
+ ReceiverMethod mMethod;
+ Arg1Ref mArg1;
+};
+
+template <typename Receiver, typename Arg1, typename Arg2>
+class SyncRunnable2 : public SyncRunnableBase {
+ public:
+ typedef nsresult (NS_STDCALL Receiver::*ReceiverMethod)(Arg1, Arg2);
+ typedef typename RefType<Arg1>::type Arg1Ref;
+ typedef typename RefType<Arg2>::type Arg2Ref;
+
+ SyncRunnable2(Receiver* receiver, ReceiverMethod method, Arg1Ref arg1,
+ Arg2Ref arg2)
+ : mReceiver(receiver), mMethod(method), mArg1(arg1), mArg2(arg2) {}
+
+ NS_IMETHOD Run() {
+ mResult = (mReceiver->*mMethod)(mArg1, mArg2);
+ mozilla::MonitorAutoLock(mMonitor).Notify();
+ return NS_OK;
+ }
+
+ private:
+ Receiver* mReceiver;
+ ReceiverMethod mMethod;
+ Arg1Ref mArg1;
+ Arg2Ref mArg2;
+};
+
+template <typename Receiver, typename Arg1, typename Arg2, typename Arg3>
+class SyncRunnable3 : public SyncRunnableBase {
+ public:
+ typedef nsresult (NS_STDCALL Receiver::*ReceiverMethod)(Arg1, Arg2, Arg3);
+ typedef typename RefType<Arg1>::type Arg1Ref;
+ typedef typename RefType<Arg2>::type Arg2Ref;
+ typedef typename RefType<Arg3>::type Arg3Ref;
+
+ SyncRunnable3(Receiver* receiver, ReceiverMethod method, Arg1Ref arg1,
+ Arg2Ref arg2, Arg3Ref arg3)
+ : mReceiver(receiver),
+ mMethod(method),
+ mArg1(arg1),
+ mArg2(arg2),
+ mArg3(arg3) {}
+
+ NS_IMETHOD Run() {
+ mResult = (mReceiver->*mMethod)(mArg1, mArg2, mArg3);
+ mozilla::MonitorAutoLock(mMonitor).Notify();
+ return NS_OK;
+ }
+
+ private:
+ Receiver* mReceiver;
+ ReceiverMethod mMethod;
+ Arg1Ref mArg1;
+ Arg2Ref mArg2;
+ Arg3Ref mArg3;
+};
+
+template <typename Receiver, typename Arg1, typename Arg2, typename Arg3,
+ typename Arg4>
+class SyncRunnable4 : public SyncRunnableBase {
+ public:
+ typedef nsresult (NS_STDCALL Receiver::*ReceiverMethod)(Arg1, Arg2, Arg3,
+ Arg4);
+ typedef typename RefType<Arg1>::type Arg1Ref;
+ typedef typename RefType<Arg2>::type Arg2Ref;
+ typedef typename RefType<Arg3>::type Arg3Ref;
+ typedef typename RefType<Arg4>::type Arg4Ref;
+
+ SyncRunnable4(Receiver* receiver, ReceiverMethod method, Arg1Ref arg1,
+ Arg2Ref arg2, Arg3Ref arg3, Arg4Ref arg4)
+ : mReceiver(receiver),
+ mMethod(method),
+ mArg1(arg1),
+ mArg2(arg2),
+ mArg3(arg3),
+ mArg4(arg4) {}
+
+ NS_IMETHOD Run() {
+ mResult = (mReceiver->*mMethod)(mArg1, mArg2, mArg3, mArg4);
+ mozilla::MonitorAutoLock(mMonitor).Notify();
+ return NS_OK;
+ }
+
+ private:
+ Receiver* mReceiver;
+ ReceiverMethod mMethod;
+ Arg1Ref mArg1;
+ Arg2Ref mArg2;
+ Arg3Ref mArg3;
+ Arg4Ref mArg4;
+};
+
+template <typename Receiver, typename Arg1, typename Arg2, typename Arg3,
+ typename Arg4, typename Arg5>
+class SyncRunnable5 : public SyncRunnableBase {
+ public:
+ typedef nsresult (NS_STDCALL Receiver::*ReceiverMethod)(Arg1, Arg2, Arg3,
+ Arg4, Arg5);
+ typedef typename RefType<Arg1>::type Arg1Ref;
+ typedef typename RefType<Arg2>::type Arg2Ref;
+ typedef typename RefType<Arg3>::type Arg3Ref;
+ typedef typename RefType<Arg4>::type Arg4Ref;
+ typedef typename RefType<Arg5>::type Arg5Ref;
+
+ SyncRunnable5(Receiver* receiver, ReceiverMethod method, Arg1Ref arg1,
+ Arg2Ref arg2, Arg3Ref arg3, Arg4Ref arg4, Arg5Ref arg5)
+ : mReceiver(receiver),
+ mMethod(method),
+ mArg1(arg1),
+ mArg2(arg2),
+ mArg3(arg3),
+ mArg4(arg4),
+ mArg5(arg5) {}
+
+ NS_IMETHOD Run() {
+ mResult = (mReceiver->*mMethod)(mArg1, mArg2, mArg3, mArg4, mArg5);
+ mozilla::MonitorAutoLock(mMonitor).Notify();
+ return NS_OK;
+ }
+
+ private:
+ Receiver* mReceiver;
+ ReceiverMethod mMethod;
+ Arg1Ref mArg1;
+ Arg2Ref mArg2;
+ Arg3Ref mArg3;
+ Arg4Ref mArg4;
+ Arg5Ref mArg5;
+};
+
+nsresult DispatchSyncRunnable(SyncRunnableBase* r) {
+ if (NS_IsMainThread()) {
+ r->Run();
+ } else {
+ mozilla::MonitorAutoLock lock(r->Monitor());
+ nsresult rv = NS_DispatchToMainThread(r);
+ if (NS_FAILED(rv)) return rv;
+ lock.Wait();
+ }
+ return r->Result();
+}
+
+} // anonymous namespace
+
+#define NS_SYNCRUNNABLEMETHOD0(iface, method) \
+ NS_IMETHODIMP iface##Proxy::method() { \
+ RefPtr<SyncRunnableBase> r = \
+ new SyncRunnable0<nsI##iface>(mReceiver, &nsI##iface::method); \
+ return DispatchSyncRunnable(r); \
+ }
+
+#define NS_SYNCRUNNABLEMETHOD1(iface, method, arg1) \
+ NS_IMETHODIMP iface##Proxy::method(arg1 a1) { \
+ RefPtr<SyncRunnableBase> r = new SyncRunnable1<nsI##iface, arg1>( \
+ mReceiver, &nsI##iface::method, a1); \
+ return DispatchSyncRunnable(r); \
+ }
+
+#define NS_SYNCRUNNABLEMETHOD2(iface, method, arg1, arg2) \
+ NS_IMETHODIMP iface##Proxy::method(arg1 a1, arg2 a2) { \
+ RefPtr<SyncRunnableBase> r = new SyncRunnable2<nsI##iface, arg1, arg2>( \
+ mReceiver, &nsI##iface::method, a1, a2); \
+ return DispatchSyncRunnable(r); \
+ }
+
+#define NS_SYNCRUNNABLEMETHOD3(iface, method, arg1, arg2, arg3) \
+ NS_IMETHODIMP iface##Proxy::method(arg1 a1, arg2 a2, arg3 a3) { \
+ RefPtr<SyncRunnableBase> r = \
+ new SyncRunnable3<nsI##iface, arg1, arg2, arg3>( \
+ mReceiver, &nsI##iface::method, a1, a2, a3); \
+ return DispatchSyncRunnable(r); \
+ }
+
+#define NS_SYNCRUNNABLEMETHOD4(iface, method, arg1, arg2, arg3, arg4) \
+ NS_IMETHODIMP iface##Proxy::method(arg1 a1, arg2 a2, arg3 a3, arg4 a4) { \
+ RefPtr<SyncRunnableBase> r = \
+ new SyncRunnable4<nsI##iface, arg1, arg2, arg3, arg4>( \
+ mReceiver, &nsI##iface::method, a1, a2, a3, a4); \
+ return DispatchSyncRunnable(r); \
+ }
+
+#define NS_SYNCRUNNABLEMETHOD5(iface, method, arg1, arg2, arg3, arg4, arg5) \
+ NS_IMETHODIMP iface##Proxy::method(arg1 a1, arg2 a2, arg3 a3, arg4 a4, \
+ arg5 a5) { \
+ RefPtr<SyncRunnableBase> r = \
+ new SyncRunnable5<nsI##iface, arg1, arg2, arg3, arg4, arg5>( \
+ mReceiver, &nsI##iface::method, a1, a2, a3, a4, a5); \
+ return DispatchSyncRunnable(r); \
+ }
+
+#define NS_SYNCRUNNABLEATTRIBUTE(iface, attribute, type) \
+ NS_IMETHODIMP iface##Proxy::Get##attribute(type* a1) { \
+ RefPtr<SyncRunnableBase> r = new SyncRunnable1<nsI##iface, type*>( \
+ mReceiver, &nsI##iface::Get##attribute, a1); \
+ return DispatchSyncRunnable(r); \
+ } \
+ NS_IMETHODIMP iface##Proxy::Set##attribute(type a1) { \
+ RefPtr<SyncRunnableBase> r = new SyncRunnable1<nsI##iface, type>( \
+ mReceiver, &nsI##iface::Set##attribute, a1); \
+ return DispatchSyncRunnable(r); \
+ }
+
+#define NS_NOTIMPLEMENTED \
+ { \
+ NS_RUNTIMEABORT("Not implemented"); \
+ return NS_ERROR_UNEXPECTED; \
+ }
+
+NS_SYNCRUNNABLEMETHOD4(StreamListener, OnDataAvailable, nsIRequest*,
+ nsIInputStream*, uint64_t, uint32_t)
+
+NS_SYNCRUNNABLEMETHOD1(StreamListener, OnStartRequest, nsIRequest*)
+
+NS_SYNCRUNNABLEMETHOD2(StreamListener, OnStopRequest, nsIRequest*, nsresult)
+
+NS_SYNCRUNNABLEMETHOD2(ImapProtocolSink, GetUrlWindow, nsIMsgMailNewsUrl*,
+ nsIMsgWindow**)
+
+NS_SYNCRUNNABLEMETHOD0(ImapProtocolSink, CloseStreams)
+NS_SYNCRUNNABLEMETHOD0(ImapProtocolSink, SetupMainThreadProxies)
+
+NS_SYNCRUNNABLEATTRIBUTE(ImapMailFolderSink, FolderNeedsACLListed, bool)
+NS_SYNCRUNNABLEATTRIBUTE(ImapMailFolderSink, FolderNeedsSubscribing, bool)
+NS_SYNCRUNNABLEATTRIBUTE(ImapMailFolderSink, FolderNeedsAdded, bool)
+NS_SYNCRUNNABLEATTRIBUTE(ImapMailFolderSink, AclFlags, uint32_t)
+NS_SYNCRUNNABLEATTRIBUTE(ImapMailFolderSink, UidValidity, int32_t)
+NS_SYNCRUNNABLEATTRIBUTE(ImapMailFolderSink, FolderQuotaCommandIssued, bool)
+NS_SYNCRUNNABLEMETHOD4(ImapMailFolderSink, SetFolderQuotaData, uint32_t,
+ const nsACString&, uint64_t, uint64_t)
+NS_SYNCRUNNABLEMETHOD1(ImapMailFolderSink, GetShouldDownloadAllHeaders, bool*)
+NS_SYNCRUNNABLEMETHOD1(ImapMailFolderSink, GetOnlineDelimiter, char*)
+NS_SYNCRUNNABLEMETHOD0(ImapMailFolderSink, OnNewIdleMessages)
+NS_SYNCRUNNABLEMETHOD2(ImapMailFolderSink, UpdateImapMailboxStatus,
+ nsIImapProtocol*, nsIMailboxSpec*)
+NS_SYNCRUNNABLEMETHOD2(ImapMailFolderSink, UpdateImapMailboxInfo,
+ nsIImapProtocol*, nsIMailboxSpec*)
+NS_SYNCRUNNABLEMETHOD3(ImapMailFolderSink, GetMsgHdrsToDownload, bool*,
+ int32_t*, nsTArray<nsMsgKey>&)
+NS_SYNCRUNNABLEMETHOD2(ImapMailFolderSink, ParseMsgHdrs, nsIImapProtocol*,
+ nsIImapHeaderXferInfo*)
+NS_SYNCRUNNABLEMETHOD1(ImapMailFolderSink, AbortHeaderParseStream,
+ nsIImapProtocol*)
+NS_SYNCRUNNABLEMETHOD2(ImapMailFolderSink, OnlineCopyCompleted,
+ nsIImapProtocol*, ImapOnlineCopyState)
+NS_SYNCRUNNABLEMETHOD1(ImapMailFolderSink, StartMessage, nsIMsgMailNewsUrl*)
+NS_SYNCRUNNABLEMETHOD2(ImapMailFolderSink, EndMessage, nsIMsgMailNewsUrl*,
+ nsMsgKey)
+NS_SYNCRUNNABLEMETHOD2(ImapMailFolderSink, NotifySearchHit, nsIMsgMailNewsUrl*,
+ const char*)
+NS_SYNCRUNNABLEMETHOD2(ImapMailFolderSink, CopyNextStreamMessage, bool,
+ nsISupports*)
+NS_SYNCRUNNABLEMETHOD1(ImapMailFolderSink, CloseMockChannel,
+ nsIImapMockChannel*)
+NS_SYNCRUNNABLEMETHOD5(ImapMailFolderSink, SetUrlState, nsIImapProtocol*,
+ nsIMsgMailNewsUrl*, bool, bool, nsresult)
+NS_SYNCRUNNABLEMETHOD1(ImapMailFolderSink, ReleaseUrlCacheEntry,
+ nsIMsgMailNewsUrl*)
+NS_SYNCRUNNABLEMETHOD1(ImapMailFolderSink, HeaderFetchCompleted,
+ nsIImapProtocol*)
+NS_SYNCRUNNABLEMETHOD1(ImapMailFolderSink, SetBiffStateAndUpdate, int32_t)
+NS_SYNCRUNNABLEMETHOD3(ImapMailFolderSink, ProgressStatusString,
+ nsIImapProtocol*, const char*, const char16_t*)
+NS_SYNCRUNNABLEMETHOD5(ImapMailFolderSink, PercentProgress, nsIImapProtocol*,
+ nsACString const&, nsAString const&, int64_t, int64_t)
+NS_SYNCRUNNABLEMETHOD0(ImapMailFolderSink, ClearFolderRights)
+NS_SYNCRUNNABLEMETHOD2(ImapMailFolderSink, SetCopyResponseUid, const char*,
+ nsIImapUrl*)
+NS_SYNCRUNNABLEMETHOD2(ImapMailFolderSink, SetAppendMsgUid, nsMsgKey,
+ nsIImapUrl*)
+NS_SYNCRUNNABLEMETHOD2(ImapMailFolderSink, GetMessageId, nsIImapUrl*,
+ nsACString&)
+
+NS_SYNCRUNNABLEMETHOD2(ImapMessageSink, SetupMsgWriteStream, nsIFile*, bool)
+NS_SYNCRUNNABLEMETHOD3(ImapMessageSink, ParseAdoptedMsgLine, const char*,
+ nsMsgKey, nsIImapUrl*)
+NS_SYNCRUNNABLEMETHOD4(ImapMessageSink, NormalEndMsgWriteStream, nsMsgKey, bool,
+ nsIImapUrl*, int32_t)
+NS_SYNCRUNNABLEMETHOD0(ImapMessageSink, AbortMsgWriteStream)
+NS_SYNCRUNNABLEMETHOD0(ImapMessageSink, BeginMessageUpload)
+NS_SYNCRUNNABLEMETHOD4(ImapMessageSink, NotifyMessageFlags, uint32_t,
+ const nsACString&, nsMsgKey, uint64_t)
+NS_SYNCRUNNABLEMETHOD3(ImapMessageSink, NotifyMessageDeleted, const char*, bool,
+ const char*)
+NS_SYNCRUNNABLEMETHOD2(ImapMessageSink, GetMessageSizeFromDB, const char*,
+ uint32_t*)
+NS_SYNCRUNNABLEMETHOD4(ImapMessageSink, GetCurMoveCopyMessageInfo, nsIImapUrl*,
+ PRTime*, nsACString&, uint32_t*)
+
+NS_SYNCRUNNABLEMETHOD4(ImapServerSink, PossibleImapMailbox, const nsACString&,
+ char, int32_t, bool*)
+NS_SYNCRUNNABLEMETHOD2(ImapServerSink, FolderNeedsACLInitialized,
+ const nsACString&, bool*)
+NS_SYNCRUNNABLEMETHOD3(ImapServerSink, AddFolderRights, const nsACString&,
+ const nsACString&, const nsACString&)
+NS_SYNCRUNNABLEMETHOD1(ImapServerSink, RefreshFolderRights, const nsACString&)
+NS_SYNCRUNNABLEMETHOD0(ImapServerSink, DiscoveryDone)
+NS_SYNCRUNNABLEMETHOD1(ImapServerSink, OnlineFolderDelete, const nsACString&)
+NS_SYNCRUNNABLEMETHOD1(ImapServerSink, OnlineFolderCreateFailed,
+ const nsACString&)
+NS_SYNCRUNNABLEMETHOD3(ImapServerSink, OnlineFolderRename, nsIMsgWindow*,
+ const nsACString&, const nsACString&)
+NS_SYNCRUNNABLEMETHOD2(ImapServerSink, FolderIsNoSelect, const nsACString&,
+ bool*)
+NS_SYNCRUNNABLEMETHOD2(ImapServerSink, SetFolderAdminURL, const nsACString&,
+ const nsACString&)
+NS_SYNCRUNNABLEMETHOD2(ImapServerSink, FolderVerifiedOnline, const nsACString&,
+ bool*)
+NS_SYNCRUNNABLEMETHOD1(ImapServerSink, SetCapability, eIMAPCapabilityFlags)
+NS_SYNCRUNNABLEMETHOD1(ImapServerSink, SetServerID, const nsACString&)
+NS_SYNCRUNNABLEMETHOD2(ImapServerSink, LoadNextQueuedUrl, nsIImapProtocol*,
+ bool*)
+NS_SYNCRUNNABLEMETHOD2(ImapServerSink, PrepareToRetryUrl, nsIImapUrl*,
+ nsIImapMockChannel**)
+NS_SYNCRUNNABLEMETHOD1(ImapServerSink, SuspendUrl, nsIImapUrl*)
+NS_SYNCRUNNABLEMETHOD2(ImapServerSink, RetryUrl, nsIImapUrl*,
+ nsIImapMockChannel*)
+NS_SYNCRUNNABLEMETHOD0(ImapServerSink, AbortQueuedUrls)
+NS_SYNCRUNNABLEMETHOD2(ImapServerSink, GetImapStringByName, const char*,
+ nsAString&)
+NS_SYNCRUNNABLEMETHOD2(ImapServerSink, PromptLoginFailed, nsIMsgWindow*,
+ int32_t*)
+NS_SYNCRUNNABLEMETHOD2(ImapServerSink, FEAlert, const nsAString&,
+ nsIMsgMailNewsUrl*)
+NS_SYNCRUNNABLEMETHOD2(ImapServerSink, FEAlertWithName, const char*,
+ nsIMsgMailNewsUrl*)
+NS_SYNCRUNNABLEMETHOD2(ImapServerSink, FEAlertFromServer, const nsACString&,
+ nsIMsgMailNewsUrl*)
+NS_SYNCRUNNABLEMETHOD0(ImapServerSink, CommitNamespaces)
+NS_SYNCRUNNABLEMETHOD3(ImapServerSink, AsyncGetPassword, nsIImapProtocol*, bool,
+ nsAString&)
+NS_SYNCRUNNABLEMETHOD1(ImapServerSink, SyncGetPassword, nsAString&)
+NS_SYNCRUNNABLEATTRIBUTE(ImapServerSink, UserAuthenticated, bool)
+NS_SYNCRUNNABLEMETHOD3(ImapServerSink, SetMailServerUrls, const nsACString&,
+ const nsACString&, const nsACString&)
+NS_SYNCRUNNABLEMETHOD1(ImapServerSink, GetArbitraryHeaders, nsACString&)
+NS_SYNCRUNNABLEMETHOD0(ImapServerSink, ForgetPassword)
+NS_SYNCRUNNABLEMETHOD1(ImapServerSink, GetShowAttachmentsInline, bool*)
+NS_SYNCRUNNABLEMETHOD3(ImapServerSink, CramMD5Hash, const char*, const char*,
+ char**)
+NS_SYNCRUNNABLEMETHOD1(ImapServerSink, GetLoginUsername, nsACString&)
+NS_SYNCRUNNABLEMETHOD1(ImapServerSink, UpdateTrySTARTTLSPref, bool)
+NS_SYNCRUNNABLEMETHOD1(ImapServerSink, GetOriginalUsername, nsACString&)
+NS_SYNCRUNNABLEMETHOD1(ImapServerSink, GetServerKey, nsACString&)
+NS_SYNCRUNNABLEMETHOD1(ImapServerSink, GetServerPassword, nsAString&)
+NS_SYNCRUNNABLEMETHOD1(ImapServerSink, RemoveServerConnection, nsIImapProtocol*)
+NS_SYNCRUNNABLEMETHOD1(ImapServerSink, GetServerShuttingDown, bool*)
+NS_SYNCRUNNABLEMETHOD1(ImapServerSink, ResetServerConnection, const nsACString&)
+NS_SYNCRUNNABLEMETHOD1(ImapServerSink, SetServerDoingLsub, bool)
+NS_SYNCRUNNABLEMETHOD1(ImapServerSink, SetServerUtf8AcceptEnabled, bool)
+
+namespace mozilla {
+namespace mailnews {
+
+NS_IMPL_ISUPPORTS(OAuth2ThreadHelper, msgIOAuth2ModuleListener)
+
+OAuth2ThreadHelper::OAuth2ThreadHelper(nsIMsgIncomingServer* aServer)
+ : mMonitor("OAuth thread lock"), mServer(aServer) {}
+
+OAuth2ThreadHelper::~OAuth2ThreadHelper() {
+ if (mOAuth2Support) {
+ NS_ReleaseOnMainThread("OAuth2ThreadHelper::mOAuth2Support",
+ mOAuth2Support.forget());
+ }
+}
+
+bool OAuth2ThreadHelper::SupportsOAuth2() {
+ // Acquire a lock early, before reading anything. Guarantees memory visibility
+ // issues.
+ MonitorAutoLock lockGuard(mMonitor);
+
+ // If we don't have a server, we can't init, and therefore, we don't support
+ // OAuth2.
+ if (!mServer) return false;
+
+ // If we have this, then we support OAuth2.
+ if (mOAuth2Support) return true;
+
+ // Initialize. This needs to be done on-main-thread: if we're off that thread,
+ // synchronously dispatch to the main thread.
+ if (NS_IsMainThread()) {
+ MonitorAutoUnlock lockGuard(mMonitor);
+ Init();
+ } else {
+ nsCOMPtr<nsIRunnable> runInit = NewRunnableMethod(
+ "OAuth2ThreadHelper::SupportsOAuth2", this, &OAuth2ThreadHelper::Init);
+ nsresult rv = NS_DispatchToMainThread(runInit);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return false;
+ }
+ mMonitor.Wait();
+ }
+
+ // After synchronously initializing, if we didn't get an object, then we don't
+ // support XOAuth2.
+ return mOAuth2Support != nullptr;
+}
+
+void OAuth2ThreadHelper::GetXOAuth2String(nsACString& base64Str) {
+ MOZ_ASSERT(!NS_IsMainThread(), "This method cannot run on the main thread");
+
+ // Acquire a lock early, before reading anything. Guarantees memory visibility
+ // issues.
+ MonitorAutoLock lockGuard(mMonitor);
+
+ // Umm... what are you trying to do?
+ if (!mOAuth2Support) return;
+
+ nsCOMPtr<nsIRunnable> runInit =
+ NewRunnableMethod("OAuth2ThreadHelper::GetXOAuth2String", this,
+ &OAuth2ThreadHelper::Connect);
+ nsresult rv = NS_DispatchToMainThread(runInit);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+ mMonitor.Wait();
+
+ nsCOMPtr<nsIRunnable> shutdownNotify = NS_NewRunnableFunction(
+ "GetXOAuth2StringShutdownNotifier", [self = RefPtr(this)]() {
+ mozilla::RunOnShutdown([self]() {
+ // Notify anyone waiting that we're done.
+ self->mMonitor.Notify();
+ });
+ });
+ rv = NS_DispatchToMainThread(shutdownNotify.forget());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ // Now we either have the string, or we failed (in which case the string is
+ // empty).
+ base64Str = mOAuth2String;
+}
+
+void OAuth2ThreadHelper::Init() {
+ MOZ_ASSERT(NS_IsMainThread(), "Can't touch JS off-main-thread");
+ MonitorAutoLock lockGuard(mMonitor);
+
+ // Create the OAuth2 helper module and initialize it. If the preferences are
+ // not set up on this server, we don't support OAuth2, and we nullify our
+ // members to indicate this.
+ mOAuth2Support = do_CreateInstance(MSGIOAUTH2MODULE_CONTRACTID);
+ if (mOAuth2Support) {
+ bool supportsOAuth = false;
+ mOAuth2Support->InitFromMail(mServer, &supportsOAuth);
+ if (!supportsOAuth) mOAuth2Support = nullptr;
+ }
+
+ // There is now no longer any need for the server. Kill it now--this helps
+ // prevent us from maintaining a refcount cycle.
+ mServer = nullptr;
+
+ // Notify anyone waiting that we're done.
+ mMonitor.Notify();
+}
+
+void OAuth2ThreadHelper::Connect() {
+ MOZ_ASSERT(NS_IsMainThread(), "Can't touch JS off-main-thread");
+ MOZ_ASSERT(mOAuth2Support, "Should not be here if no OAuth2 support");
+
+ // OK to delay lock since mOAuth2Support is only written on main thread.
+ nsresult rv = mOAuth2Support->Connect(true, this);
+ // If the method failed, we'll never get a callback, so notify the monitor
+ // immediately so that IMAP can react.
+ if (NS_FAILED(rv)) {
+ MonitorAutoLock lockGuard(mMonitor);
+ mMonitor.Notify();
+ }
+}
+
+nsresult OAuth2ThreadHelper::OnSuccess(const nsACString& aOAuth2String) {
+ MOZ_ASSERT(NS_IsMainThread(), "Can't touch JS off-main-thread");
+ MonitorAutoLock lockGuard(mMonitor);
+
+ MOZ_ASSERT(mOAuth2Support, "Should not be here if no OAuth2 support");
+ mOAuth2String = aOAuth2String;
+ mMonitor.Notify();
+ return NS_OK;
+}
+
+nsresult OAuth2ThreadHelper::OnFailure(nsresult aError) {
+ MOZ_ASSERT(NS_IsMainThread(), "Can't touch JS off-main-thread");
+ MonitorAutoLock lockGuard(mMonitor);
+
+ mOAuth2String.Truncate();
+ mMonitor.Notify();
+ return NS_OK;
+}
+
+} // namespace mailnews
+} // namespace mozilla
diff --git a/comm/mailnews/imap/src/nsSyncRunnableHelpers.h b/comm/mailnews/imap/src/nsSyncRunnableHelpers.h
new file mode 100644
index 0000000000..9be1aafef6
--- /dev/null
+++ b/comm/mailnews/imap/src/nsSyncRunnableHelpers.h
@@ -0,0 +1,149 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 nsSyncRunnableHelpers_h
+#define nsSyncRunnableHelpers_h
+
+#include "nsThreadUtils.h"
+#include "nsProxyRelease.h"
+
+#include "mozilla/Monitor.h"
+#include "msgIOAuth2Module.h"
+#include "nsIStreamListener.h"
+#include "nsIImapMailFolderSink.h"
+#include "nsIImapServerSink.h"
+#include "nsIImapProtocolSink.h"
+#include "nsIImapMessageSink.h"
+
+// The classes in this file proxy method calls to the main thread
+// synchronously. The main thread must not block on this thread, or a
+// deadlock condition can occur.
+
+class StreamListenerProxy final : public nsIStreamListener {
+ public:
+ explicit StreamListenerProxy(nsIStreamListener* receiver)
+ : mReceiver(receiver) {
+ MOZ_DIAGNOSTIC_ASSERT(
+ receiver, "Null receiver, crash now to get feedback instead of later");
+ }
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+
+ private:
+ ~StreamListenerProxy() {
+ NS_ReleaseOnMainThread("StreamListenerProxy::mReceiver",
+ mReceiver.forget());
+ }
+ nsCOMPtr<nsIStreamListener> mReceiver;
+};
+
+class ImapMailFolderSinkProxy final : public nsIImapMailFolderSink {
+ public:
+ explicit ImapMailFolderSinkProxy(nsIImapMailFolderSink* receiver)
+ : mReceiver(receiver) {
+ MOZ_DIAGNOSTIC_ASSERT(
+ receiver, "Null receiver, crash now to get feedback instead of later");
+ }
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIIMAPMAILFOLDERSINK
+
+ private:
+ ~ImapMailFolderSinkProxy() {
+ NS_ReleaseOnMainThread("ImapMailFolderSinkProxy::mReceiver",
+ mReceiver.forget());
+ }
+ nsCOMPtr<nsIImapMailFolderSink> mReceiver;
+};
+
+class ImapServerSinkProxy final : public nsIImapServerSink {
+ public:
+ explicit ImapServerSinkProxy(nsIImapServerSink* receiver)
+ : mReceiver(receiver) {
+ MOZ_DIAGNOSTIC_ASSERT(
+ receiver, "Null receiver, crash now to get feedback instead of later");
+ }
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIIMAPSERVERSINK
+
+ private:
+ ~ImapServerSinkProxy() {
+ NS_ReleaseOnMainThread("ImapServerSinkProxy::mReceiver",
+ mReceiver.forget());
+ }
+ nsCOMPtr<nsIImapServerSink> mReceiver;
+};
+
+class ImapMessageSinkProxy final : public nsIImapMessageSink {
+ public:
+ explicit ImapMessageSinkProxy(nsIImapMessageSink* receiver)
+ : mReceiver(receiver) {
+ MOZ_DIAGNOSTIC_ASSERT(
+ receiver, "Null receiver, crash now to get feedback instead of later");
+ }
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIIMAPMESSAGESINK
+
+ private:
+ ~ImapMessageSinkProxy() {
+ NS_ReleaseOnMainThread("ImapMessageSinkProxy::mReceiver",
+ mReceiver.forget());
+ }
+ nsCOMPtr<nsIImapMessageSink> mReceiver;
+};
+
+class ImapProtocolSinkProxy final : public nsIImapProtocolSink {
+ public:
+ explicit ImapProtocolSinkProxy(nsIImapProtocolSink* receiver)
+ : mReceiver(receiver) {
+ MOZ_DIAGNOSTIC_ASSERT(
+ receiver, "Null receiver, crash now to get feedback instead of later");
+ }
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIIMAPPROTOCOLSINK
+
+ private:
+ ~ImapProtocolSinkProxy() {
+ NS_ReleaseOnMainThread("ImapProtocolSinkProxy::mReceiver",
+ mReceiver.forget());
+ }
+ nsCOMPtr<nsIImapProtocolSink> mReceiver;
+};
+
+class msgIOAuth2Module;
+class nsIMsgIncomingServer;
+
+namespace mozilla {
+namespace mailnews {
+
+class OAuth2ThreadHelper final : public msgIOAuth2ModuleListener {
+ public:
+ explicit OAuth2ThreadHelper(nsIMsgIncomingServer* aServer);
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_MSGIOAUTH2MODULELISTENER
+
+ bool SupportsOAuth2();
+ void GetXOAuth2String(nsACString& base64Str);
+
+ private:
+ ~OAuth2ThreadHelper();
+ void Init();
+ void Connect();
+
+ Monitor mMonitor;
+ nsCOMPtr<msgIOAuth2Module> mOAuth2Support;
+ nsCOMPtr<nsIMsgIncomingServer> mServer;
+ nsCString mOAuth2String;
+};
+
+} // namespace mailnews
+} // namespace mozilla
+
+#endif // nsSyncRunnableHelpers_h
diff --git a/comm/mailnews/imap/test/TestImapFlagAndUidState.cpp b/comm/mailnews/imap/test/TestImapFlagAndUidState.cpp
new file mode 100644
index 0000000000..a7658322b8
--- /dev/null
+++ b/comm/mailnews/imap/test/TestImapFlagAndUidState.cpp
@@ -0,0 +1,166 @@
+/* -*- 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 <stdio.h>
+#include "TestHarness.h"
+#include "nsCOMPtr.h"
+#include "msgCore.h"
+#include "nsImapProtocol.h"
+
+struct msgState {
+ uint32_t uid;
+ uint16_t flag;
+ uint32_t index;
+};
+
+char errorMsg[200];
+
+const char* MainChecks(nsImapFlagAndUidState* flagState,
+ struct msgState* expectedState, uint32_t numMessages,
+ uint32_t expectedNumUnread) {
+ // Verify that flag state matches the expected state.
+ for (uint32_t i = 0; i < numMessages; i++) {
+ uint32_t uid;
+ uint16_t flag;
+ flagState->GetUidOfMessage(expectedState[i].index, &uid);
+ flagState->GetMessageFlags(expectedState[i].index, &flag);
+ if (uid != expectedState[i].uid) {
+ PR_snprintf(errorMsg, sizeof(errorMsg),
+ "expected uid %d, got %d at index %d\n", expectedState[i].uid,
+ uid, i);
+ return errorMsg;
+ }
+ if (flag != expectedState[i].flag) {
+ PR_snprintf(errorMsg, sizeof(errorMsg),
+ "expected flag %d, got %d at index %d\n",
+ expectedState[i].flag, flag, i);
+ return errorMsg;
+ }
+ }
+ int32_t numMsgsInFlagState;
+ int32_t numUnread = 0;
+ int32_t expectedMsgIndex = 0;
+
+ flagState->GetNumberOfMessages(&numMsgsInFlagState);
+ for (int32_t msgIndex = 0; msgIndex < numMsgsInFlagState; msgIndex++) {
+ uint32_t uidOfMessage;
+ flagState->GetUidOfMessage(msgIndex, &uidOfMessage);
+ if (!uidOfMessage || uidOfMessage == nsMsgKey_None) continue;
+ if (uidOfMessage != expectedState[expectedMsgIndex++].uid) {
+ PR_snprintf(
+ errorMsg, sizeof(errorMsg),
+ "got a uid w/o a match in expected state, uid %d at index %d\n",
+ uidOfMessage, msgIndex);
+ return errorMsg;
+ }
+ imapMessageFlagsType flags;
+ flagState->GetMessageFlags(msgIndex, &flags);
+ if (!(flags & kImapMsgSeenFlag)) numUnread++;
+ }
+ if (numUnread != expectedNumUnread) {
+ PR_snprintf(errorMsg, sizeof(errorMsg),
+ "expected %d unread message, got %d\n", expectedNumUnread,
+ numUnread);
+ return errorMsg;
+ }
+ return nullptr;
+}
+
+// General note about return values:
+// return 1 for a setup or xpcom type failure, return 2 for a real test failure
+int main(int argc, char** argv) {
+ ScopedXPCOM xpcom("TestImapFlagAndUidState.cpp");
+ if (xpcom.failed()) return 1;
+
+ struct msgState msgState1[] = {{10, kImapMsgSeenFlag, 0},
+ {15, kImapMsgSeenFlag, 1},
+ {16, kImapMsgSeenFlag, 2},
+ {17, kImapMsgSeenFlag, 3},
+ {18, kImapMsgSeenFlag, 4}};
+
+ RefPtr<nsImapFlagAndUidState> flagState = new nsImapFlagAndUidState(10);
+ int32_t numMsgs = sizeof(msgState1) / sizeof(msgState1[0]);
+ for (int32_t i = 0; i < numMsgs; i++)
+ flagState->AddUidFlagPair(msgState1[i].uid, msgState1[i].flag,
+ msgState1[i].index);
+
+ const char* error = MainChecks(flagState, msgState1, numMsgs, 0);
+ if (error) {
+ printf("TEST-UNEXPECTED-FAIL | %s | %s\n", __FILE__, error);
+ return 1;
+ }
+
+ // Now reset all
+ flagState->Reset();
+
+ // This tests adding some messages to a partial uid flag state,
+ // i.e., CONDSTORE.
+ struct msgState msgState2[] = {{68, kImapMsgSeenFlag, 69},
+ {71, kImapMsgSeenFlag, 70},
+ {73, kImapMsgSeenFlag, 71}};
+ numMsgs = sizeof(msgState2) / sizeof(msgState2[0]);
+ for (int32_t i = 0; i < numMsgs; i++)
+ flagState->AddUidFlagPair(msgState2[i].uid, msgState2[i].flag,
+ msgState2[i].index);
+ error = MainChecks(flagState, msgState2, numMsgs, 0);
+ if (error) {
+ printf("TEST-UNEXPECTED-FAIL | %s | %s\n", __FILE__, error);
+ return 1;
+ }
+ // Reset all
+ flagState->Reset();
+ // This tests generating a uid string from a non-sequential set of
+ // messages where the first message is not in the flag state, but the
+ // missing message from the sequence is in the set. I.e., we're
+ // generating a uid string from 69,71, but only 70 and 71 are in
+ // the flag state.
+ struct msgState msgState3[] = {{10, kImapMsgSeenFlag, 0},
+ {69, kImapMsgSeenFlag, 1},
+ {70, kImapMsgSeenFlag, 2},
+ {71, kImapMsgSeenFlag, 3}};
+
+ flagState->SetPartialUIDFetch(false);
+ numMsgs = sizeof(msgState3) / sizeof(msgState3[0]);
+ for (int32_t i = 0; i < numMsgs; i++)
+ flagState->AddUidFlagPair(msgState3[i].uid, msgState3[i].flag,
+ msgState3[i].index);
+ flagState->ExpungeByIndex(2);
+ nsCString uidString;
+ uint32_t msgUids[] = {69, 71};
+ uint32_t msgCount = 2;
+ AllocateImapUidString(&msgUids[0], msgCount, flagState, uidString);
+ if (!uidString.EqualsLiteral("71")) {
+ printf("TEST-UNEXPECTED-FAIL | uid String is %s, not 71 | %s\n",
+ uidString.get(), __FILE__);
+ return -1;
+ }
+ // Reset all
+ flagState->Reset();
+ // This tests the middle message missing from the flag state.
+ struct msgState msgState4[] = {{10, kImapMsgSeenFlag, 0},
+ {69, kImapMsgSeenFlag, 1},
+ {70, kImapMsgSeenFlag, 2},
+ {71, kImapMsgSeenFlag, 3},
+ {73, kImapMsgSeenFlag, 4}};
+
+ flagState->SetPartialUIDFetch(false);
+ numMsgs = sizeof(msgState4) / sizeof(msgState4[0]);
+ for (int32_t i = 0; i < numMsgs; i++)
+ flagState->AddUidFlagPair(msgState4[i].uid, msgState4[i].flag,
+ msgState4[i].index);
+ flagState->ExpungeByIndex(4);
+ uint32_t msgUids2[] = {69, 71, 73};
+ msgCount = 3;
+ nsCString uidString2;
+
+ AllocateImapUidString(&msgUids2[0], msgCount, flagState, uidString2);
+ if (!uidString2.EqualsLiteral("69,73")) {
+ printf("TEST-UNEXPECTED-FAIL | uid String is %s, not 71 | %s\n",
+ uidString.get(), __FILE__);
+ return -1;
+ }
+
+ printf("TEST-PASS | %s | all tests passed\n", __FILE__);
+ return 0;
+}
diff --git a/comm/mailnews/imap/test/TestImapHdrXferInfo.cpp b/comm/mailnews/imap/test/TestImapHdrXferInfo.cpp
new file mode 100644
index 0000000000..442ca03567
--- /dev/null
+++ b/comm/mailnews/imap/test/TestImapHdrXferInfo.cpp
@@ -0,0 +1,101 @@
+/* -*- 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 <stdio.h>
+#include "TestHarness.h"
+#include "nsCOMPtr.h"
+#include "msgCore.h"
+#include "nsImapProtocol.h"
+
+// What to check
+enum EHdrArrayCheck { eAddNoCheck, eCheckSame };
+
+int MainChecks(nsMsgImapHdrXferInfo* hdrInfo, nsIImapHeaderInfo** hdrArray,
+ EHdrArrayCheck hdrArrayCheck) {
+ nsCOMPtr<nsIImapHeaderInfo> hdr;
+ int32_t numHdrs = -1;
+
+ // Check the number of headers initially is zero
+ if (NS_FAILED(hdrInfo->GetNumHeaders(&numHdrs))) return 1;
+
+ if (numHdrs != 0) return 2;
+
+ // Get a header that doesn't exist
+ if (hdrInfo->GetHeader(1, getter_AddRefs(hdr)) != NS_ERROR_NULL_POINTER)
+ return 3;
+
+ int32_t i;
+ for (i = 0; i < kNumHdrsToXfer; ++i) {
+ // Now kick off a new one.
+ hdr = hdrInfo->StartNewHdr();
+ if (!hdr) return 4;
+
+ // Check pointers are different or not depending on which cycle we are in
+ switch (hdrArrayCheck) {
+ case eAddNoCheck:
+ hdrArray[i] = hdr;
+ break;
+ case eCheckSame:
+ if (hdrArray[i] != hdr) return 5;
+ break;
+ default:
+ return 1;
+ }
+
+ if (NS_FAILED(hdrInfo->GetNumHeaders(&numHdrs))) return 1;
+
+ if (numHdrs != i + 1) return 7;
+ }
+
+ // Now try and get one more (this should return null)
+ if (hdrInfo->StartNewHdr()) return 8;
+
+ // Now check the number of headers
+ if (NS_FAILED(hdrInfo->GetNumHeaders(&numHdrs))) return 1;
+
+ if (numHdrs != kNumHdrsToXfer) return 9;
+
+ // Now check our pointers align with those from GetHeader
+ if (hdrArrayCheck != 2) {
+ for (i = 0; i < kNumHdrsToXfer; ++i) {
+ if (NS_FAILED(hdrInfo->GetHeader(i, getter_AddRefs(hdr)))) return 1;
+
+ if (hdr != hdrArray[i]) return 10;
+ }
+ }
+ return 0;
+}
+
+// General note about return values:
+// return 1 for a setup or xpcom type failure, return 2 for a real test failure
+int main(int argc, char** argv) {
+ ScopedXPCOM xpcom("TestImapHdrXferInfo.cpp");
+ if (xpcom.failed()) return 1;
+
+ RefPtr<nsMsgImapHdrXferInfo> hdrInfo = new nsMsgImapHdrXferInfo();
+ // Purposely not reference counted to ensure we get the same pointers the
+ // second time round MainChecks.
+ nsIImapHeaderInfo* hdrArray[kNumHdrsToXfer] = {nullptr};
+
+ int result = MainChecks(hdrInfo, hdrArray, eAddNoCheck);
+ if (result) {
+ printf("TEST-UNEXPECTED-FAIL | %s | %d\n", __FILE__, result);
+ return result;
+ }
+
+ // Now reset all
+ hdrInfo->ResetAll();
+
+ // and repeat
+ result = MainChecks(hdrInfo, hdrArray, eCheckSame);
+ if (result) {
+ // add 100 to differentiate results
+ result += 100;
+ printf("TEST-UNEXPECTED-FAIL | %s | %d\n", __FILE__, result);
+ return result;
+ }
+
+ printf("TEST-PASS | %s | all tests passed\n", __FILE__);
+ return result;
+}
diff --git a/comm/mailnews/imap/test/moz.build b/comm/mailnews/imap/test/moz.build
new file mode 100644
index 0000000000..8b6d9424ce
--- /dev/null
+++ b/comm/mailnews/imap/test/moz.build
@@ -0,0 +1,27 @@
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+XPCSHELL_TESTS_MANIFESTS += [
+ "unit/xpcshell-cpp.ini",
+ "unit/xpcshell.ini",
+ "unit/xpcshell_maildir-cpp.ini",
+ "unit/xpcshell_maildir.ini",
+]
+
+LOCAL_INCLUDES += [
+ "../../base/src",
+ "../src",
+ "/xpcom/tests",
+]
+
+USE_LIBS += [
+ "msgbsutl_s",
+ "msgimap_s",
+ "nspr",
+ "xpcomglue_s",
+ "xul",
+]
+
+OS_LIBS += CONFIG["MOZ_ZLIB_LIBS"]
diff --git a/comm/mailnews/imap/test/unit/head_imap_maildir.js b/comm/mailnews/imap/test/unit/head_imap_maildir.js
new file mode 100644
index 0000000000..676becd52a
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/head_imap_maildir.js
@@ -0,0 +1,9 @@
+/* import-globals-from head_server.js */
+load("head_server.js");
+
+info("Running test with maildir");
+
+Services.prefs.setCharPref(
+ "mail.serverDefaultStoreContractID",
+ "@mozilla.org/msgstore/maildirstore;1"
+);
diff --git a/comm/mailnews/imap/test/unit/head_server.js b/comm/mailnews/imap/test/unit/head_server.js
new file mode 100644
index 0000000000..b092b9a21b
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/head_server.js
@@ -0,0 +1,201 @@
+// We can be executed from multiple depths
+// Provide gDEPTH if not already defined
+if (typeof gDEPTH == "undefined") {
+ var gDEPTH = "../../../../";
+}
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+var { XPCOMUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/XPCOMUtils.sys.mjs"
+);
+var { mailTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/MailTestUtils.jsm"
+);
+var { localAccountUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/LocalAccountUtils.jsm"
+);
+var { IMAPPump, setupIMAPPump, teardownIMAPPump } = ChromeUtils.import(
+ "resource://testing-common/mailnews/IMAPpump.jsm"
+);
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+var CC = Components.Constructor;
+
+// WebApps.jsm called by ProxyAutoConfig (PAC) requires a valid nsIXULAppInfo.
+var { getAppInfo, newAppInfo, updateAppInfo } = ChromeUtils.importESModule(
+ "resource://testing-common/AppInfo.sys.mjs"
+);
+updateAppInfo();
+
+// Ensure the profile directory is set up
+do_get_profile();
+
+// Import fakeserver
+var {
+ nsMailServer,
+ gThreadManager,
+ fsDebugNone,
+ fsDebugAll,
+ fsDebugRecv,
+ fsDebugRecvSend,
+} = ChromeUtils.import("resource://testing-common/mailnews/Maild.jsm");
+
+var {
+ ImapDaemon,
+ ImapMessage,
+ configurations,
+ IMAP_RFC3501_handler,
+ mixinExtension,
+ IMAP_RFC2197_extension,
+ IMAP_RFC2342_extension,
+ IMAP_RFC3348_extension,
+ IMAP_RFC4315_extension,
+ IMAP_RFC5258_extension,
+ IMAP_RFC2195_extension,
+} = ChromeUtils.import("resource://testing-common/mailnews/Imapd.jsm");
+var { AuthPLAIN, AuthLOGIN, AuthCRAM } = ChromeUtils.import(
+ "resource://testing-common/mailnews/Auth.jsm"
+);
+var { SmtpDaemon, SMTP_RFC2821_handler } = ChromeUtils.import(
+ "resource://testing-common/mailnews/Smtpd.jsm"
+);
+
+function makeServer(daemon, infoString, otherProps) {
+ if (infoString in configurations) {
+ return makeServer(daemon, configurations[infoString].join(","), otherProps);
+ }
+
+ function createHandler(d) {
+ var handler = new IMAP_RFC3501_handler(d);
+ if (!infoString) {
+ infoString = "RFC2195";
+ }
+
+ var parts = infoString.split(/ *, */);
+ for (var part of parts) {
+ if (part.startsWith("RFC")) {
+ let ext;
+ switch (part) {
+ case "RFC2197":
+ ext = IMAP_RFC2197_extension;
+ break;
+ case "RFC2342":
+ ext = IMAP_RFC2342_extension;
+ break;
+ case "RFC3348":
+ ext = IMAP_RFC3348_extension;
+ break;
+ case "RFC4315":
+ ext = IMAP_RFC4315_extension;
+ break;
+ case "RFC5258":
+ ext = IMAP_RFC5258_extension;
+ break;
+ case "RFC2195":
+ ext = IMAP_RFC2195_extension;
+ break;
+ default:
+ throw new Error("Unknown extension: " + part);
+ }
+ mixinExtension(handler, ext);
+ }
+ }
+ if (otherProps) {
+ for (var prop in otherProps) {
+ handler[prop] = otherProps[prop];
+ }
+ }
+ return handler;
+ }
+ var server = new nsMailServer(createHandler, daemon);
+ server.start();
+ return server;
+}
+
+function createLocalIMAPServer(port, hostname = "localhost") {
+ let server = localAccountUtils.create_incoming_server(
+ "imap",
+ port,
+ "user",
+ "password",
+ hostname
+ );
+ server.QueryInterface(Ci.nsIImapIncomingServer);
+ return server;
+}
+
+// <copied from="head_maillocal.js">
+/**
+ * @param fromServer server.playTransaction
+ * @param expected ["command", "command", ...]
+ * @param withParams if false,
+ * everything apart from the IMAP command will the stripped.
+ * E.g. 'lsub "" "*"' will be compared as 'lsub'.
+ * Exception is "authenticate", which also get its first parameter in upper case,
+ * e.g. "authenticate CRAM-MD5".
+ */
+function do_check_transaction(fromServer, expected, withParams) {
+ // If we don't spin the event loop before starting the next test, the readers
+ // aren't expired. In this case, the "real" real transaction is the last one.
+ if (fromServer instanceof Array) {
+ fromServer = fromServer[fromServer.length - 1];
+ }
+
+ let realTransaction = [];
+ for (let i = 0; i < fromServer.them.length; i++) {
+ var line = fromServer.them[i]; // e.g. '1 login "user" "password"'
+ var components = line.split(" ");
+ if (components.length < 2) {
+ throw new Error("IMAP command in transaction log missing: " + line);
+ }
+ if (withParams) {
+ realTransaction.push(line.substr(components[0].length + 1));
+ } else if (components[1].toUpperCase() == "AUTHENTICATE") {
+ realTransaction.push(components[1] + " " + components[2].toUpperCase());
+ } else {
+ realTransaction.push(components[1]);
+ }
+ }
+
+ Assert.equal(
+ realTransaction.join(", ").toUpperCase(),
+ expected.join(", ").toUpperCase()
+ );
+}
+
+/**
+ * add a simple message to the IMAP pump mailbox
+ */
+function addImapMessage() {
+ let messages = [];
+ let messageGenerator = new MessageGenerator(); // eslint-disable-line no-undef
+ messages = messages.concat(messageGenerator.makeMessage());
+ let dataUri = Services.io.newURI(
+ "data:text/plain;base64," + btoa(messages[0].toMessageString())
+ );
+ let imapMsg = new ImapMessage(dataUri.spec, IMAPPump.mailbox.uidnext++, []);
+ IMAPPump.mailbox.addMessage(imapMsg);
+}
+
+registerCleanupFunction(function () {
+ load(gDEPTH + "mailnews/resources/mailShutdown.js");
+});
+
+// Setup the SMTP daemon and server
+function setupSmtpServerDaemon(handler) {
+ if (!handler) {
+ handler = function (d) {
+ return new SMTP_RFC2821_handler(d);
+ };
+ }
+ var server = new nsMailServer(handler, new SmtpDaemon());
+ return server;
+}
+
+// profile-after-change is not triggered in xpcshell tests, manually run the
+// getService to load the correct imap modules.
+Cc["@mozilla.org/messenger/imap-module-loader;1"].getService();
diff --git a/comm/mailnews/imap/test/unit/test_ImapResponse.js b/comm/mailnews/imap/test/unit/test_ImapResponse.js
new file mode 100644
index 0000000000..4dd60e6d32
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_ImapResponse.js
@@ -0,0 +1,288 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var { ImapResponse } = ChromeUtils.import(
+ "resource:///modules/ImapResponse.jsm"
+);
+var { ImapUtils } = ChromeUtils.import("resource:///modules/ImapUtils.jsm");
+
+/**
+ * Test CAPABILITY response can be correctly parsed.
+ */
+add_task(function test_CapabilityResponse() {
+ let response = new ImapResponse();
+ response.parse(
+ "32 OK [CAPABILITY IMAP4rev1 IDLE STARTTLS AUTH=LOGIN AUTH=PLAIN] server ready\r\n"
+ );
+
+ deepEqual(response.authMethods, ["LOGIN", "PLAIN"]);
+ deepEqual(response.capabilities, ["IMAP4REV1", "IDLE", "STARTTLS"]);
+
+ response = new ImapResponse();
+ response.parse("* CAPABILITY IMAP4rev1 ID IDLE STARTTLS AUTH=PLAIN\r\n");
+
+ deepEqual(response.authMethods, ["PLAIN"]);
+ deepEqual(response.capabilities, ["IMAP4REV1", "ID", "IDLE", "STARTTLS"]);
+});
+
+/**
+ * Test flags from a FETCH response can be correctly parsed.
+ */
+add_task(function test_FetchResponse_flags() {
+ let response = new ImapResponse();
+ response.parse(
+ [
+ "* 1 FETCH (UID 500 FLAGS (\\Answered \\Seen $Forwarded))",
+ "* 2 FETCH (UID 600 FLAGS (\\Seen))",
+ "",
+ ].join("\r\n")
+ );
+ ok(!response.done);
+
+ response.parse(
+ ["* 3 FETCH (UID 601 FLAGS ())", "40 OK Fetch completed", ""].join("\r\n")
+ );
+
+ ok(response.done);
+ deepEqual(response.messages[0], {
+ sequence: 1,
+ uid: 500,
+ flags:
+ ImapUtils.FLAG_ANSWERED | ImapUtils.FLAG_SEEN | ImapUtils.FLAG_FORWARDED,
+ keywords: "$Forwarded",
+ customAttributes: {},
+ });
+ deepEqual(response.messages[1], {
+ sequence: 2,
+ uid: 600,
+ flags: ImapUtils.FLAG_SEEN,
+ keywords: "",
+ customAttributes: {},
+ });
+ deepEqual(response.messages[2], {
+ sequence: 3,
+ uid: 601,
+ flags: 0,
+ keywords: "",
+ customAttributes: {},
+ });
+});
+
+/**
+ * Test body from a FETCH response can be correctly parsed.
+ */
+add_task(function test_messageBody() {
+ let response = new ImapResponse();
+ response.parse(
+ [
+ "* 1 FETCH (UID 500 FLAGS (\\Answered \\Seen $Forwarded) BODY[HEADER.FIELDS (FROM TO)] {12}",
+ "abcd",
+ "efgh",
+ ")",
+ "* 2 FETCH (UID 600 FLAGS (\\Seen) BODY[] {15}",
+ "Hello ",
+ "world",
+ ")",
+ "40 OK Fetch completed",
+ "",
+ ].join("\r\n")
+ );
+
+ equal(response.messages[0].body, "abcd\r\nefgh\r\n");
+ equal(response.messages[1].body, "Hello \r\nworld\r\n");
+});
+
+/**
+ * Test msg body spanning multiple chuncks can be correctly parsed.
+ */
+add_task(function test_messageBodyIncremental() {
+ let response = new ImapResponse();
+ // Chunk 1.
+ response.parse(
+ [
+ "* 1 FETCH (UID 500 FLAGS (\\Answered \\Seen $Forwarded) BODY[HEADER.FIELDS (FROM TO)] {12}",
+ "abcd",
+ "efgh",
+ ")",
+ "* 2 FETCH (UID 600 FLAGS (\\Seen) BODY[] {15}",
+ "Hel",
+ ].join("\r\n")
+ );
+ equal(response.messages[0].body, "abcd\r\nefgh\r\n");
+ ok(!response.done);
+
+ // Chunk 2.
+ response.parse("lo \r\nworld\r\n");
+ ok(!response.done);
+
+ // Chunk 3.
+ response.parse(")\r\n40 OK Fetch completed\r\n");
+ ok(response.done);
+ equal(response.messages[1].body, "Hello \r\nworld\r\n");
+});
+
+/**
+ * Test FLAGS response can be correctly parsed.
+ */
+add_task(function test_FlagsResponse() {
+ let response = new ImapResponse();
+ response.parse(
+ [
+ "* FLAGS (\\Seen \\Draft $Forwarded)",
+ "* OK [PERMANENTFLAGS (\\Seen \\Draft $Forwarded \\*)] Flags permitted.",
+ "* 6 EXISTS",
+ "* OK [UNSEEN 2] First unseen.",
+ "* OK [UIDVALIDITY 1594877893] UIDs valid",
+ "* OK [UIDNEXT 625] Predicted next UID",
+ "* OK [HIGHESTMODSEQ 1148] Highest",
+ "42 OK [READ-WRITE] Select completed",
+ "",
+ ].join("\r\n")
+ );
+
+ equal(
+ response.flags,
+ ImapUtils.FLAG_SEEN | ImapUtils.FLAG_DRAFT | ImapUtils.FLAG_FORWARDED
+ );
+ equal(
+ response.permanentflags,
+ ImapUtils.FLAG_SEEN |
+ ImapUtils.FLAG_DRAFT |
+ ImapUtils.FLAG_FORWARDED |
+ ImapUtils.FLAG_LABEL |
+ ImapUtils.FLAG_MDN_SENT |
+ ImapUtils.FLAG_FORWARDED |
+ ImapUtils.FLAG_SUPPORT_USER_FLAG
+ );
+ equal(response.highestmodseq, 1148);
+ equal(response.exists, 6);
+});
+
+/**
+ * Test mailbox updates can be correctly parsed.
+ */
+add_task(function test_MailboxResponse() {
+ let response = new ImapResponse();
+ response.parse("* 7 EXISTS\r\n");
+ response.parse("* 1 EXPUNGE\r\n* 3 EXPUNGE\r\n");
+ equal(response.exists, 7);
+ deepEqual(response.expunged, [1, 3]);
+});
+
+/**
+ * Test LIST response can be correctly parsed.
+ */
+add_task(function test_ListResponse() {
+ let response = new ImapResponse();
+ response.parse(
+ [
+ '* LIST (\\Subscribed \\NoInferiors \\Marked \\Trash) "/" "Trash"',
+ '* LIST () "/" "a \\"b\\" c"',
+ '* LIST (\\Subscribed) "/" INBOX',
+ "84 OK List completed (0.002 + 0.000 + 0.001 secs).",
+ "",
+ ].join("\r\n")
+ );
+ equal(response.mailboxes.length, 3);
+ deepEqual(response.mailboxes[0], {
+ name: "Trash",
+ delimiter: "/",
+ flags:
+ ImapUtils.FLAG_SUBSCRIBED |
+ ImapUtils.FLAG_NO_INFERIORS |
+ ImapUtils.FLAG_HAS_NO_CHILDREN |
+ ImapUtils.FLAG_MARKED |
+ ImapUtils.FLAG_IMAP_TRASH |
+ ImapUtils.FLAG_IMAP_XLIST_TRASH,
+ });
+ deepEqual(response.mailboxes[1], {
+ name: 'a "b" c',
+ delimiter: "/",
+ flags: 0,
+ });
+ deepEqual(response.mailboxes[2], {
+ name: "INBOX",
+ delimiter: "/",
+ flags: ImapUtils.FLAG_SUBSCRIBED,
+ });
+});
+
+/**
+ * Test folder names containg [] or () or "" can be correctly parsed.
+ */
+add_task(function test_parseFolderNames() {
+ let response = new ImapResponse();
+ response.parse(
+ [
+ '* LSUB () "/" "[Gmail]"',
+ '* LSUB () "/" "[Gmail]/All Mail"',
+ '* LSUB () "/" "[Gmail]/Sent"',
+ '* LSUB () "/" "[a(b)])"',
+ '* LSUB () "/" "a \\"b \\"c\\""',
+ "84 OK LSUB completed",
+ "",
+ ].join("\r\n")
+ );
+ equal(response.mailboxes.length, 5);
+ deepEqual(
+ response.mailboxes.map(x => x.name),
+ ["[Gmail]", "[Gmail]/All Mail", "[Gmail]/Sent", "[a(b)])", 'a "b "c"']
+ );
+});
+
+/**
+ * Test STATUS response can be correctly parsed.
+ */
+add_task(function test_StatusResponse() {
+ let response = new ImapResponse();
+ response.parse(
+ '* STATUS "sub folder 2" (UIDNEXT 2 MESSAGES 1 UNSEEN 1 RECENT 0)\r\n'
+ );
+ deepEqual(response.attributes, {
+ mailbox: "sub folder 2",
+ uidnext: 2,
+ messages: 1,
+ unseen: 1,
+ recent: 0,
+ });
+});
+
+/**
+ * Test GETQUOTAROOT response can be correctly parsed.
+ */
+add_task(function test_QuotaResponse() {
+ let response = new ImapResponse();
+ response.parse(
+ ["* QUOTAROOT Sent INBOX", "* QUOTA INBOX (STORAGE 123 456)", ""].join(
+ "\r\n"
+ )
+ );
+ deepEqual(response.quotaRoots, ["INBOX"]);
+ deepEqual(response.quotas, [["INBOX", "STORAGE", 123, 456]]);
+});
+
+/**
+ * Test IDLE and DONE response can be correctly parsed.
+ */
+add_task(function test_IdleDoneResponse() {
+ let response = new ImapResponse();
+ response.parse("+ idling\r\n");
+ deepEqual(
+ [response.tag, response.status, response.done],
+ ["+", "idling", true]
+ );
+
+ response = new ImapResponse();
+ response.parse(["+ idling", "75 OK Completed", ""].join("\r\n"));
+ deepEqual([response.tag, response.status, response.done], [75, "OK", true]);
+});
+
+/**
+ * Test SEARCH response can be correctly parsed.
+ */
+add_task(function test_SearchResponse() {
+ let response = new ImapResponse();
+ response.parse("* SEARCH 1 4 9\r\n90 OK SEARCH COMPLETED\r\n");
+ deepEqual(response.search, [1, 4, 9]);
+});
diff --git a/comm/mailnews/imap/test/unit/test_autosync_date_constraints.js b/comm/mailnews/imap/test/unit/test_autosync_date_constraints.js
new file mode 100644
index 0000000000..101413d491
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_autosync_date_constraints.js
@@ -0,0 +1,88 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Test autosync date constraints
+ */
+
+var { MessageGenerator } = ChromeUtils.import(
+ "resource://testing-common/mailnews/MessageGenerator.jsm"
+);
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+var gMsgImapInboxFolder;
+
+// Adds some messages directly to a mailbox (eg new mail)
+function addMessagesToServer(messages, mailbox) {
+ // Create the ImapMessages and store them on the mailbox
+ messages.forEach(function (message) {
+ let dataUri = "data:text/plain," + message.toMessageString();
+ mailbox.addMessage(new ImapMessage(dataUri, mailbox.uidnext++, []));
+ });
+}
+
+add_setup(function () {
+ Services.prefs.setIntPref("mail.server.server1.autosync_max_age_days", 4);
+
+ setupIMAPPump();
+
+ gMsgImapInboxFolder = IMAPPump.inbox.QueryInterface(Ci.nsIMsgImapMailFolder);
+ // these hacks are required because we've created the inbox before
+ // running initial folder discovery, and adding the folder bails
+ // out before we set it as verified online, so we bail out, and
+ // then remove the INBOX folder since it's not verified.
+ gMsgImapInboxFolder.hierarchyDelimiter = "/";
+ gMsgImapInboxFolder.verifiedAsOnlineFolder = true;
+
+ // Add a couple of messages to the INBOX
+ // this is synchronous, afaik
+ let messageGenerator = new MessageGenerator();
+
+ // build up a diverse list of messages
+ let messages = [];
+ messages = messages.concat(
+ messageGenerator.makeMessage({ age: { days: 2, hours: 1 } })
+ );
+ messages = messages.concat(
+ messageGenerator.makeMessage({ age: { days: 8, hours: 1 } })
+ );
+ messages = messages.concat(
+ messageGenerator.makeMessage({ age: { days: 10, hours: 1 } })
+ );
+
+ addMessagesToServer(messages, IMAPPump.daemon.getMailbox("INBOX"));
+});
+
+add_task(async function downloadForOffline() {
+ // ...and download for offline use.
+ // This downloads all messages, ignoring the autosync age constraints.
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.downloadAllForOffline(listener, null);
+ await listener.promise;
+});
+
+add_task(function test_applyRetentionSettings() {
+ IMAPPump.inbox.applyRetentionSettings();
+ let enumerator = IMAPPump.inbox.msgDatabase.enumerateMessages();
+ if (enumerator) {
+ let now = new Date();
+ let dateInSeconds = now.getSeconds();
+ let cutOffDateInSeconds = dateInSeconds - 5 * 60 * 24;
+ for (let header of enumerator) {
+ if (header instanceof Ci.nsIMsgDBHdr) {
+ if (header.dateInSeconds < cutOffDateInSeconds) {
+ Assert.equal(header.getStringProperty("pendingRemoval"), "1");
+ } else {
+ Assert.equal(header.getStringProperty("pendingRemoval"), "");
+ }
+ }
+ }
+ }
+});
+
+add_task(function endTest() {
+ teardownIMAPPump();
+});
diff --git a/comm/mailnews/imap/test/unit/test_bccProperty.js b/comm/mailnews/imap/test/unit/test_bccProperty.js
new file mode 100644
index 0000000000..42795245c7
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_bccProperty.js
@@ -0,0 +1,52 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Test to ensure that BCC gets added to message headers on IMAP download
+ *
+ * adapted from test_downloadOffline.js
+ *
+ * original author Kent James <kent@caspia.com>
+ */
+
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+var gFileName = "draft1";
+var gMsgFile = do_get_file("../../../data/" + gFileName);
+
+add_setup(async function () {
+ setupIMAPPump();
+
+ /*
+ * Ok, prelude done. Read the original message from disk
+ * (through a file URI), and add it to the Inbox.
+ */
+ let msgfileuri = Services.io
+ .newFileURI(gMsgFile)
+ .QueryInterface(Ci.nsIFileURL);
+
+ IMAPPump.mailbox.addMessage(
+ new ImapMessage(msgfileuri.spec, IMAPPump.mailbox.uidnext++, [])
+ );
+
+ // ...and download for offline use.
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.downloadAllForOffline(listener, null);
+ await listener.promise;
+});
+
+add_task(function checkBccs() {
+ // locate the new message by enumerating through the database
+ for (let hdr of IMAPPump.inbox.msgDatabase.enumerateMessages()) {
+ Assert.ok(hdr.bccList.includes("Another Person"));
+ Assert.ok(hdr.bccList.includes("<u1@example.com>"));
+ Assert.ok(!hdr.bccList.includes("IDoNotExist"));
+ }
+});
+
+add_task(function endTest() {
+ teardownIMAPPump();
+});
diff --git a/comm/mailnews/imap/test/unit/test_bug460636.js b/comm/mailnews/imap/test/unit/test_bug460636.js
new file mode 100644
index 0000000000..6cb8b60056
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_bug460636.js
@@ -0,0 +1,82 @@
+/*
+ * Test bug 460636 - nsMsgSaveAsListener sometimes inserts extra LF characters
+ */
+
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+var gSavedMsgFile;
+
+var gIMAPService = Cc[
+ "@mozilla.org/messenger/messageservice;1?type=imap"
+].getService(Ci.nsIMsgMessageService);
+
+var gFileName = "bug460636";
+var gMsgFile = do_get_file("../../../data/" + gFileName);
+
+add_task(async function run_the_test() {
+ await setup();
+ await checkSavedMessage();
+ teardown();
+});
+
+async function setup() {
+ setupIMAPPump();
+
+ // Ok, prelude done. Read the original message from disk
+ // (through a file URI), and add it to the Inbox.
+ var msgfileuri = Services.io
+ .newFileURI(gMsgFile)
+ .QueryInterface(Ci.nsIFileURL);
+
+ IMAPPump.mailbox.addMessage(
+ new ImapMessage(msgfileuri.spec, IMAPPump.mailbox.uidnext++, [])
+ );
+ let promiseUrlListener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.updateFolderWithListener(null, promiseUrlListener);
+ await promiseUrlListener.promise;
+
+ // Save the message to a local file. IMapMD corresponds to
+ // <profile_dir>/mailtest/ImapMail (where fakeserver puts the IMAP mailbox
+ // files). If we pass the test, we'll remove the file afterwards
+ // (cf. UrlListener), otherwise it's kept in IMapMD.
+ gSavedMsgFile = Services.dirsvc.get("IMapMD", Ci.nsIFile);
+ gSavedMsgFile.append(gFileName + ".eml");
+
+ // From nsIMsgMessageService.idl:
+ // void SaveMessageToDisk(in string aMessageURI, in nsIFile aFile,
+ // in boolean aGenerateDummyEnvelope,
+ // in nsIUrlListener aUrlListener, out nsIURI aURL,
+ // in boolean canonicalLineEnding,
+ // in nsIMsgWindow aMsgWindow);
+ // Enforcing canonicalLineEnding (i.e., CRLF) makes sure that the
+ let promiseUrlListener2 = new PromiseTestUtils.PromiseUrlListener();
+ gIMAPService.SaveMessageToDisk(
+ "imap-message://user@localhost/INBOX#" + (IMAPPump.mailbox.uidnext - 1),
+ gSavedMsgFile,
+ false,
+ promiseUrlListener2,
+ {},
+ true,
+ null
+ );
+ await promiseUrlListener2.promise;
+}
+
+async function checkSavedMessage() {
+ Assert.equal(
+ await IOUtils.readUTF8(gMsgFile.path),
+ await IOUtils.readUTF8(gSavedMsgFile.path)
+ );
+}
+
+function teardown() {
+ try {
+ gSavedMsgFile.remove(false);
+ } catch (ex) {
+ dump(ex);
+ do_throw(ex);
+ }
+ teardownIMAPPump();
+}
diff --git a/comm/mailnews/imap/test/unit/test_chunkLastLF.js b/comm/mailnews/imap/test/unit/test_chunkLastLF.js
new file mode 100644
index 0000000000..003320ad2d
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_chunkLastLF.js
@@ -0,0 +1,108 @@
+/* -*- Mode: JavaScript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ *
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/licenses/publicdomain/
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+/*
+ * Test content length for the IMAP protocol. This focuses on necko URLs
+ * that are run externally.
+ */
+
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+var gFile = do_get_file("../../../data/bug92111b");
+var gIMAPDaemon, gIMAPServer, gIMAPIncomingServer;
+
+// Adds some messages directly to a mailbox (eg new mail)
+function addMessageToServer(file, mailbox) {
+ let URI = Services.io.newFileURI(file).QueryInterface(Ci.nsIFileURL);
+ let msg = new ImapMessage(URI.spec, mailbox.uidnext++, []);
+ // underestimate the actual file size, like some IMAP servers do
+ msg.setSize(file.fileSize - 55);
+ mailbox.addMessage(msg);
+}
+
+add_task(async function verifyContentLength() {
+ // Disable new mail notifications
+ Services.prefs.setBoolPref("mail.biff.play_sound", false);
+ Services.prefs.setBoolPref("mail.biff.show_alert", false);
+ Services.prefs.setBoolPref("mail.biff.show_tray_icon", false);
+ Services.prefs.setBoolPref("mail.biff.animate_dock_icon", false);
+ Services.prefs.setBoolPref(
+ "mail.server.server1.autosync_offline_stores",
+ false
+ );
+
+ // Crank down the message chunk size to make test cases easier
+ Services.prefs.setBoolPref("mail.server.default.fetch_by_chunks", true);
+ Services.prefs.setIntPref("mail.imap.chunk_size", 1000);
+ Services.prefs.setIntPref("mail.imap.min_chunk_size_threshold", 1500);
+ Services.prefs.setIntPref("mail.imap.chunk_add", 0);
+
+ // set up IMAP fakeserver and incoming server
+ gIMAPDaemon = new ImapDaemon();
+ gIMAPServer = makeServer(gIMAPDaemon, "");
+ gIMAPIncomingServer = createLocalIMAPServer(gIMAPServer.port);
+
+ // The server doesn't support more than one connection
+ Services.prefs.setIntPref("mail.server.server1.max_cached_connections", 1);
+ // We aren't interested in downloading messages automatically
+ Services.prefs.setBoolPref("mail.server.server1.download_on_biff", false);
+
+ dump("adding message to server\n");
+ // Add a message to the IMAP server
+ addMessageToServer(gFile, gIMAPDaemon.getMailbox("INBOX"));
+
+ let imapS = Cc[
+ "@mozilla.org/messenger/messageservice;1?type=imap"
+ ].getService(Ci.nsIMsgMessageService);
+
+ let uri = imapS.getUrlForUri("imap-message://user@localhost/INBOX#1");
+
+ // Get a channel from this URI, and check its content length
+ let channel = Services.io.newChannelFromURI(
+ uri,
+ null,
+ Services.scriptSecurityManager.getSystemPrincipal(),
+ null,
+ Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ Ci.nsIContentPolicy.TYPE_OTHER
+ );
+
+ let promiseStreamListener = new PromiseTestUtils.PromiseStreamListener();
+
+ // Read all the contents
+ channel.asyncOpen(promiseStreamListener, null);
+ let streamData = (await promiseStreamListener.promise).replace(/\r\n/g, "\n");
+
+ // Now check whether our stream listener got the right bytes
+ // First, clean up line endings to avoid CRLF vs. LF differences
+ let origData = (await IOUtils.readUTF8(gFile.path)).replace(/\r\n/g, "\n");
+ Assert.equal(origData.length, streamData.length);
+ Assert.equal(origData, streamData);
+
+ // Now try an attachment. &part=1.2
+ // let attachmentURL = Services.io.newURI(neckoURL.value.spec + "&part=1.2",
+ // null, null);
+ // let attachmentChannel = Services.io.newChannelFromURI(attachmentURL,
+ // null,
+ // Services.scriptSecurityManager.getSystemPrincipal(),
+ // null,
+ // Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ // Ci.nsIContentPolicy.TYPE_OTHER);
+ // Currently attachments have their content length set to the length of the
+ // entire message
+ // do_check_eq(attachmentChannel.contentLength, gFile.fileSize);
+
+ gIMAPIncomingServer.closeCachedConnections();
+ gIMAPServer.stop();
+ let thread = gThreadManager.currentThread;
+ while (thread.hasPendingEvents()) {
+ thread.processNextEvent(true);
+ }
+});
diff --git a/comm/mailnews/imap/test/unit/test_compactOfflineStore.js b/comm/mailnews/imap/test/unit/test_compactOfflineStore.js
new file mode 100644
index 0000000000..9c3f0ef898
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_compactOfflineStore.js
@@ -0,0 +1,194 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Test to ensure that compacting offline stores works correctly with imap folders
+ * and returns success.
+ */
+
+var { setTimeout } = ChromeUtils.importESModule(
+ "resource://gre/modules/Timer.sys.mjs"
+);
+var { MessageGenerator } = ChromeUtils.import(
+ "resource://testing-common/mailnews/MessageGenerator.jsm"
+);
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+Services.prefs.setCharPref(
+ "mail.serverDefaultStoreContractID",
+ "@mozilla.org/msgstore/berkeleystore;1"
+);
+
+// Globals
+var gRootFolder;
+var gImapInboxOfflineStoreSize;
+
+var gMsgFile1 = do_get_file("../../../data/bugmail10");
+var gMsgFile2 = do_get_file("../../../data/bugmail11");
+// var gMsgFile3 = do_get_file("../../../data/draft1");
+var gMsgFile4 = do_get_file("../../../data/bugmail7");
+var gMsgFile5 = do_get_file("../../../data/bugmail6");
+
+// Copied straight from the example files
+var gMsgId1 = "200806061706.m56H6RWT004933@mrapp54.mozilla.org";
+var gMsgId2 = "200804111417.m3BEHTk4030129@mrapp51.mozilla.org";
+// var gMsgId3 = "4849BF7B.2030800@example.com";
+var gMsgId4 = "bugmail7.m47LtAEf007542@mrapp51.mozilla.org";
+var gMsgId5 = "bugmail6.m47LtAEf007542@mrapp51.mozilla.org";
+
+// Adds some messages directly to a mailbox (e.g. new mail).
+function addMessagesToServer(messages, mailbox) {
+ // For every message we have, we need to convert it to a file:/// URI
+ messages.forEach(function (message) {
+ let URI = Services.io
+ .newFileURI(message.file)
+ .QueryInterface(Ci.nsIFileURL);
+ // Create the ImapMessage and store it on the mailbox.
+ mailbox.addMessage(new ImapMessage(URI.spec, mailbox.uidnext++, []));
+ });
+}
+
+function addGeneratedMessagesToServer(messages, mailbox) {
+ // Create the ImapMessages and store them on the mailbox
+ messages.forEach(function (message) {
+ let dataUri = Services.io.newURI(
+ "data:text/plain;base64," + btoa(message.toMessageString())
+ );
+ mailbox.addMessage(new ImapMessage(dataUri.spec, mailbox.uidnext++, []));
+ });
+}
+
+function checkOfflineStore(prevOfflineStoreSize) {
+ let enumerator = IMAPPump.inbox.msgDatabase.enumerateMessages();
+ if (enumerator) {
+ for (let header of enumerator) {
+ // this will verify that the message in the offline store
+ // starts with "From " - otherwise, it returns an error.
+ if (
+ header instanceof Ci.nsIMsgDBHdr &&
+ header.flags & Ci.nsMsgMessageFlags.Offline
+ ) {
+ IMAPPump.inbox.getLocalMsgStream(header).close();
+ }
+ }
+ }
+ // check that the offline store shrunk by at least 100 bytes.
+ // (exact calculation might be fragile).
+ Assert.ok(prevOfflineStoreSize > IMAPPump.inbox.filePath.fileSize + 100);
+}
+
+add_setup(function () {
+ setupIMAPPump();
+
+ gRootFolder = IMAPPump.incomingServer.rootFolder;
+ // these hacks are required because we've created the inbox before
+ // running initial folder discovery, and adding the folder bails
+ // out before we set it as verified online, so we bail out, and
+ // then remove the INBOX folder since it's not verified.
+ IMAPPump.inbox.hierarchyDelimiter = "/";
+ IMAPPump.inbox.verifiedAsOnlineFolder = true;
+
+ let messageGenerator = new MessageGenerator();
+ let messages = [];
+ for (let i = 0; i < 50; i++) {
+ messages = messages.concat(messageGenerator.makeMessage());
+ }
+
+ addGeneratedMessagesToServer(messages, IMAPPump.daemon.getMailbox("INBOX"));
+
+ // Add a couple of messages to the INBOX
+ // this is synchronous, afaik
+ addMessagesToServer(
+ [
+ { file: gMsgFile1, messageId: gMsgId1 },
+ { file: gMsgFile4, messageId: gMsgId4 },
+ { file: gMsgFile2, messageId: gMsgId2 },
+ { file: gMsgFile5, messageId: gMsgId5 },
+ ],
+ IMAPPump.daemon.getMailbox("INBOX")
+ );
+});
+
+add_task(async function downloadForOffline() {
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.downloadAllForOffline(listener, null);
+ await listener.promise;
+});
+
+add_task(async function markOneMsgDeleted() {
+ // mark a message deleted, and then do a compact of just
+ // that folder.
+ let msgHdr = IMAPPump.inbox.msgDatabase.getMsgHdrForMessageID(gMsgId5);
+ // store the deleted flag
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.storeImapFlags(0x0008, true, [msgHdr.messageKey], listener);
+ await listener.promise;
+});
+
+add_task(async function compactOneFolder() {
+ IMAPPump.incomingServer.deleteModel = Ci.nsMsgImapDeleteModels.IMAPDelete;
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.compact(listener, null);
+ await listener.promise;
+});
+
+add_task(async function test_deleteOneMessage() {
+ // check that nstmp file has been cleaned up.
+ let tmpFile = gRootFolder.filePath;
+ tmpFile.append("nstmp");
+ Assert.ok(!tmpFile.exists());
+ // Deleting one message.
+ IMAPPump.incomingServer.deleteModel = Ci.nsMsgImapDeleteModels.MoveToTrash;
+ let msgHdr = IMAPPump.inbox.msgDatabase.getMsgHdrForMessageID(gMsgId1);
+ let copyListener = new PromiseTestUtils.PromiseCopyListener();
+ IMAPPump.inbox.deleteMessages(
+ [msgHdr],
+ null,
+ false,
+ true,
+ copyListener,
+ false
+ );
+ await copyListener.promise;
+
+ let trashFolder = gRootFolder.getChildNamed("Trash");
+ // hack to force uid validity to get initialized for trash.
+ trashFolder.updateFolder(null);
+});
+
+add_task(async function compactOfflineStore() {
+ gImapInboxOfflineStoreSize = IMAPPump.inbox.filePath.fileSize;
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ gRootFolder.compactAll(listener, null);
+ await listener.promise;
+});
+
+add_task(function test_checkCompactionResult1() {
+ checkOfflineStore(gImapInboxOfflineStoreSize);
+});
+
+add_task(async function pendingRemoval() {
+ let msgHdr = IMAPPump.inbox.msgDatabase.getMsgHdrForMessageID(gMsgId2);
+ IMAPPump.inbox.markPendingRemoval(msgHdr, true);
+ gImapInboxOfflineStoreSize = IMAPPump.inbox.filePath.fileSize;
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ gRootFolder.compactAll(listener, null);
+ await listener.promise;
+});
+
+add_task(function test_checkCompactionResult2() {
+ let tmpFile = gRootFolder.filePath;
+ tmpFile.append("nstmp");
+ Assert.ok(!tmpFile.exists());
+ checkOfflineStore(gImapInboxOfflineStoreSize);
+ let msgHdr = IMAPPump.inbox.msgDatabase.getMsgHdrForMessageID(gMsgId2);
+ Assert.equal(msgHdr.flags & Ci.nsMsgMessageFlags.Offline, 0);
+});
+
+add_task(function endTest() {
+ gRootFolder = null;
+ teardownIMAPPump();
+});
diff --git a/comm/mailnews/imap/test/unit/test_converterImap.js b/comm/mailnews/imap/test/unit/test_converterImap.js
new file mode 100644
index 0000000000..a05e236ce6
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_converterImap.js
@@ -0,0 +1,110 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 { convertMailStoreTo } = ChromeUtils.import(
+ "resource:///modules/mailstoreConverter.jsm"
+);
+
+var { FileUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/FileUtils.sys.mjs"
+);
+var { MessageGenerator } = ChromeUtils.import(
+ "resource://testing-common/mailnews/MessageGenerator.jsm"
+);
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+// Globals
+let gMsgFile1 = do_get_file("../../../data/bugmail10");
+let gTestMsgs = [gMsgFile1, gMsgFile1, gMsgFile1, gMsgFile1];
+
+function checkConversion(aSource, aTarget) {
+ for (let sourceContent of aSource.directoryEntries) {
+ let sourceContentName = sourceContent.leafName;
+ let ext = sourceContentName.slice(-4);
+ let targetFile = FileUtils.File(
+ PathUtils.join(aTarget.path, sourceContentName)
+ );
+
+ // Checking path.
+ if (ext == ".msf" || ext == ".dat") {
+ Assert.ok(targetFile.exists());
+ } else if (sourceContent.isDirectory()) {
+ Assert.ok(targetFile.exists());
+ checkConversion(sourceContent, targetFile);
+ } else {
+ Assert.ok(targetFile.exists());
+ let cur = FileUtils.File(PathUtils.join(targetFile.path, "cur"));
+ Assert.ok(cur.exists());
+ let tmp = FileUtils.File(PathUtils.join(targetFile.path, "tmp"));
+ Assert.ok(tmp.exists());
+ if (targetFile.leafName == "INBOX") {
+ let curContentsCount = [...cur.directoryEntries].length;
+ Assert.equal(curContentsCount, 8);
+ }
+ }
+ }
+}
+
+var EventTarget = function () {
+ this.dispatchEvent = function (aEvent) {
+ if (aEvent.type == "progress") {
+ dump("Progress: " + aEvent.detail + "\n");
+ }
+ };
+};
+
+add_setup(async function () {
+ // Force mbox.
+ Services.prefs.setCharPref(
+ "mail.serverDefaultStoreContractID",
+ "@mozilla.org/msgstore/berkeleystore;1"
+ );
+
+ setupIMAPPump();
+
+ // These hacks are required because we've created the inbox before
+ // running initial folder discovery, and adding the folder bails
+ // out before we set it as verified online, so we bail out, and
+ // then remove the INBOX folder since it's not verified.
+ IMAPPump.inbox.hierarchyDelimiter = "/";
+ IMAPPump.inbox.verifiedAsOnlineFolder = true;
+
+ // Add our test messages to the INBOX.
+ let mailbox = IMAPPump.daemon.getMailbox("INBOX");
+ for (let file of gTestMsgs) {
+ let URI = Services.io.newFileURI(file).QueryInterface(Ci.nsIFileURL);
+ mailbox.addMessage(new ImapMessage(URI.spec, mailbox.uidnext++, []));
+ }
+});
+
+add_task(async function downloadForOffline() {
+ // Download for offline use.
+ let promiseUrlListener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.downloadAllForOffline(promiseUrlListener, null);
+ await promiseUrlListener.promise;
+});
+
+add_task(async function convert() {
+ let mailstoreContractId = Services.prefs.getCharPref(
+ "mail.server." + IMAPPump.incomingServer.key + ".storeContractID"
+ );
+ let eventTarget = new EventTarget();
+ let originalRootFolder = IMAPPump.incomingServer.rootFolder.filePath;
+ await convertMailStoreTo(
+ mailstoreContractId,
+ IMAPPump.incomingServer,
+ eventTarget
+ );
+ // Conversion done.
+ let newRootFolder = IMAPPump.incomingServer.rootFolder.filePath;
+ checkConversion(originalRootFolder, newRootFolder);
+ let newRootFolderMsf = FileUtils.File(newRootFolder.path + ".msf");
+ Assert.ok(newRootFolderMsf.exists());
+});
+
+add_task(function endTest() {
+ teardownIMAPPump();
+});
diff --git a/comm/mailnews/imap/test/unit/test_copyThenMove.js b/comm/mailnews/imap/test/unit/test_copyThenMove.js
new file mode 100644
index 0000000000..9abdb5e45d
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_copyThenMove.js
@@ -0,0 +1,200 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 extends test_imapFolderCopy.js to test message
+ * moves from a local folder to an IMAP folder.
+ *
+ * Original Author: Kent James <kent@caspia.com>
+ */
+
+var { MessageGenerator } = ChromeUtils.import(
+ "resource://testing-common/mailnews/MessageGenerator.jsm"
+);
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+var gEmptyLocal1, gEmptyLocal2;
+var gLastKey;
+var gMessages = [];
+
+add_setup(function () {
+ // Turn off autosync_offline_stores because
+ // fetching messages is invoked after copying the messages.
+ // (i.e. The fetching process will be invoked after OnStopCopy)
+ // It will cause crash with an assertion
+ // (ASSERTION: tried to add duplicate listener: 'index == -1') on teardown.
+ Services.prefs.setBoolPref(
+ "mail.server.default.autosync_offline_stores",
+ false
+ );
+
+ setupIMAPPump();
+
+ gEmptyLocal1 = localAccountUtils.rootFolder.createLocalSubfolder("empty 1");
+ gEmptyLocal2 = localAccountUtils.rootFolder.createLocalSubfolder("empty 2");
+
+ // these hacks are required because we've created the inbox before
+ // running initial folder discovery, and adding the folder bails
+ // out before we set it as verified online, so we bail out, and
+ // then remove the INBOX folder since it's not verified.
+ IMAPPump.inbox.hierarchyDelimiter = "/";
+ IMAPPump.inbox.verifiedAsOnlineFolder = true;
+});
+
+add_task(async function copyFolder1() {
+ let copyListener = new PromiseTestUtils.PromiseCopyListener({
+ SetMessageKey(aKey) {
+ gLastKey = aKey;
+ },
+ });
+ MailServices.copy.copyFolder(
+ gEmptyLocal1,
+ IMAPPump.inbox,
+ false,
+ copyListener,
+ null
+ );
+ await copyListener.promise;
+});
+
+add_task(async function copyFolder2() {
+ let copyListener = new PromiseTestUtils.PromiseCopyListener({
+ SetMessageKey(aKey) {
+ gLastKey = aKey;
+ },
+ });
+ MailServices.copy.copyFolder(
+ gEmptyLocal2,
+ IMAPPump.inbox,
+ false,
+ copyListener,
+ null
+ );
+ await copyListener.promise;
+});
+
+add_task(async function getLocalMessage1() {
+ let copyListener = new PromiseTestUtils.PromiseCopyListener({
+ SetMessageKey(aKey) {
+ gLastKey = aKey;
+ },
+ });
+ let file = do_get_file("../../../data/bugmail1");
+ MailServices.copy.copyFileMessage(
+ file,
+ localAccountUtils.inboxFolder,
+ null,
+ false,
+ 0,
+ "",
+ copyListener,
+ null
+ );
+ await copyListener.promise;
+});
+
+add_task(async function getLocalMessage2() {
+ gMessages.push(localAccountUtils.inboxFolder.GetMessageHeader(gLastKey));
+ let file = do_get_file("../../../data/draft1");
+ let copyListener = new PromiseTestUtils.PromiseCopyListener({
+ SetMessageKey(aKey) {
+ gLastKey = aKey;
+ },
+ });
+ MailServices.copy.copyFileMessage(
+ file,
+ localAccountUtils.inboxFolder,
+ null,
+ false,
+ 0,
+ "",
+ copyListener,
+ null
+ );
+ await copyListener.promise;
+});
+
+add_task(async function copyMessages() {
+ gMessages.push(localAccountUtils.inboxFolder.GetMessageHeader(gLastKey));
+ let folder1 = IMAPPump.inbox.getChildNamed("empty 1");
+ let copyListener = new PromiseTestUtils.PromiseCopyListener({
+ SetMessageKey(aKey) {
+ gLastKey = aKey;
+ },
+ });
+ MailServices.copy.copyMessages(
+ localAccountUtils.inboxFolder,
+ gMessages,
+ folder1,
+ false,
+ copyListener,
+ null,
+ false
+ );
+ await copyListener.promise;
+});
+
+add_task(async function moveMessages() {
+ let folder2 = IMAPPump.inbox.getChildNamed("empty 2");
+ let copyListener = new PromiseTestUtils.PromiseCopyListener({
+ SetMessageKey(aKey) {
+ gLastKey = aKey;
+ },
+ });
+ MailServices.copy.copyMessages(
+ localAccountUtils.inboxFolder,
+ gMessages,
+ folder2,
+ true,
+ copyListener,
+ null,
+ false
+ );
+ await copyListener.promise;
+});
+
+add_task(async function update1() {
+ let folder1 = IMAPPump.inbox
+ .getChildNamed("empty 1")
+ .QueryInterface(Ci.nsIMsgImapMailFolder);
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ folder1.updateFolderWithListener(null, listener);
+ await listener.promise;
+});
+
+add_task(async function update2() {
+ let folder2 = IMAPPump.inbox
+ .getChildNamed("empty 2")
+ .QueryInterface(Ci.nsIMsgImapMailFolder);
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ folder2.updateFolderWithListener(null, listener);
+ await listener.promise;
+});
+
+add_task(function verifyFolders() {
+ let folder1 = IMAPPump.inbox.getChildNamed("empty 1");
+ Assert.equal(folderCount(folder1), 2);
+ let folder2 = IMAPPump.inbox.getChildNamed("empty 2");
+ Assert.ok(folder2 !== null);
+ // folder 1 and 2 should each now have two messages in them.
+ Assert.ok(folder1 !== null);
+ Assert.equal(folderCount(folder2), 2);
+ // The local inbox folder should now be empty, since the second
+ // operation was a move.
+ Assert.equal(folderCount(localAccountUtils.inboxFolder), 0);
+});
+add_task(function endTest() {
+ gMessages = [];
+ teardownIMAPPump();
+});
+
+function folderCount(folder) {
+ return [...folder.msgDatabase.enumerateMessages()].length;
+}
diff --git a/comm/mailnews/imap/test/unit/test_customCommandReturnsFetchResponse.js b/comm/mailnews/imap/test/unit/test_customCommandReturnsFetchResponse.js
new file mode 100644
index 0000000000..abf45ab3e3
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_customCommandReturnsFetchResponse.js
@@ -0,0 +1,134 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Test to ensure that imap customCommandResult function works properly
+ * Bug 778246
+ */
+
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+// IMAP pump
+
+// Globals
+
+// Messages to load must have CRLF line endings, that is Windows style
+var gMessageFileName = "bugmail10"; // message file used as the test message
+var gMessage, gExpectedLength;
+
+var gCustomList = ["Custom1", "Custom2", "Custom3"];
+
+var gMsgWindow = Cc["@mozilla.org/messenger/msgwindow;1"].createInstance(
+ Ci.nsIMsgWindow
+);
+
+setupIMAPPump("CUSTOM1");
+
+add_setup(async function () {
+ Services.prefs.setBoolPref(
+ "mail.server.server1.autosync_offline_stores",
+ false
+ );
+ // Load and update a message in the imap fake server.
+
+ gMessage = new ImapMessage(
+ specForFileName(gMessageFileName),
+ IMAPPump.mailbox.uidnext++,
+ []
+ );
+ gMessage.xCustomList = [];
+ IMAPPump.mailbox.addMessage(gMessage);
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.updateFolderWithListener(null, listener);
+ await listener.promise;
+});
+
+add_task(async function testStoreCustomList() {
+ let msgHdr = mailTestUtils.firstMsgHdr(IMAPPump.inbox);
+ gExpectedLength = gCustomList.length;
+ let uri = IMAPPump.inbox.issueCommandOnMsgs(
+ "STORE",
+ msgHdr.messageKey + " X-CUSTOM-LIST (" + gCustomList.join(" ") + ")",
+ gMsgWindow
+ );
+ uri.QueryInterface(Ci.nsIMsgMailNewsUrl);
+ // Listens for response from customCommandResult request for X-CUSTOM-LIST.
+ let storeCustomListSetListener = new PromiseTestUtils.PromiseUrlListener({
+ OnStopRunningUrl(aUrl, aExitCode) {
+ aUrl.QueryInterface(Ci.nsIImapUrl);
+ Assert.equal(
+ aUrl.customCommandResult,
+ "(" + gMessage.xCustomList.join(" ") + ")"
+ );
+ Assert.equal(gMessage.xCustomList.length, gExpectedLength);
+ },
+ });
+ uri.RegisterListener(storeCustomListSetListener);
+ await storeCustomListSetListener.promise;
+});
+
+add_task(async function testStoreMinusCustomList() {
+ let msgHdr = mailTestUtils.firstMsgHdr(IMAPPump.inbox);
+ gExpectedLength--;
+ let uri = IMAPPump.inbox.issueCommandOnMsgs(
+ "STORE",
+ msgHdr.messageKey + " -X-CUSTOM-LIST (" + gCustomList[0] + ")",
+ gMsgWindow
+ );
+ uri.QueryInterface(Ci.nsIMsgMailNewsUrl);
+ // Listens for response from customCommandResult request for X-CUSTOM-LIST.
+ let storeCustomListRemovedListener = new PromiseTestUtils.PromiseUrlListener({
+ OnStopRunningUrl(aUrl, aExitCode) {
+ aUrl.QueryInterface(Ci.nsIImapUrl);
+ Assert.equal(
+ aUrl.customCommandResult,
+ "(" + gMessage.xCustomList.join(" ") + ")"
+ );
+ Assert.equal(gMessage.xCustomList.length, gExpectedLength);
+ },
+ });
+ uri.RegisterListener(storeCustomListRemovedListener);
+ await storeCustomListRemovedListener.promise;
+});
+
+add_task(async function testStorePlusCustomList() {
+ let msgHdr = mailTestUtils.firstMsgHdr(IMAPPump.inbox);
+ gExpectedLength++;
+ let uri = IMAPPump.inbox.issueCommandOnMsgs(
+ "STORE",
+ msgHdr.messageKey + ' +X-CUSTOM-LIST ("Custom4")',
+ gMsgWindow
+ );
+ uri.QueryInterface(Ci.nsIMsgMailNewsUrl);
+ let storeCustomListAddedListener = new PromiseTestUtils.PromiseUrlListener({
+ OnStopRunningUrl(aUrl, aExitCode) {
+ aUrl.QueryInterface(Ci.nsIImapUrl);
+ Assert.equal(
+ aUrl.customCommandResult,
+ "(" + gMessage.xCustomList.join(" ") + ")"
+ );
+ Assert.equal(gMessage.xCustomList.length, gExpectedLength);
+ },
+ });
+ uri.RegisterListener(storeCustomListAddedListener);
+ await storeCustomListAddedListener.promise;
+});
+
+// Cleanup at end
+add_task(function endTest() {
+ teardownIMAPPump();
+});
+
+/*
+ * helper functions
+ */
+
+// given a test file, return the file uri spec
+function specForFileName(aFileName) {
+ let file = do_get_file("../../../data/" + aFileName);
+ let msgfileuri = Services.io.newFileURI(file).QueryInterface(Ci.nsIFileURL);
+ return msgfileuri.spec;
+}
diff --git a/comm/mailnews/imap/test/unit/test_dontStatNoSelect.js b/comm/mailnews/imap/test/unit/test_dontStatNoSelect.js
new file mode 100644
index 0000000000..7c05cc0c1f
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_dontStatNoSelect.js
@@ -0,0 +1,149 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 tests that checking folders for new mail with STATUS
+// doesn't try to STAT noselect folders.
+
+var gServer, gImapServer;
+var gIMAPInbox;
+var gFolder2Mailbox;
+var gFolder1, gFolder2;
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+var { MessageGenerator } = ChromeUtils.import(
+ "resource://testing-common/mailnews/MessageGenerator.jsm"
+);
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+add_setup(function () {
+ var daemon = new ImapDaemon();
+ daemon.createMailbox("folder 1", { subscribed: true });
+ let folder1Mailbox = daemon.getMailbox("folder 1");
+ folder1Mailbox.flags.push("\\Noselect");
+ daemon.createMailbox("folder 2", { subscribed: true });
+ gFolder2Mailbox = daemon.getMailbox("folder 2");
+ addMessageToFolder(gFolder2Mailbox);
+ gServer = makeServer(daemon, "");
+
+ gImapServer = createLocalIMAPServer(gServer.port);
+
+ // Bug 1050840: check a newly created server has the default number of connections
+ Assert.equal(gImapServer.maximumConnectionsNumber, 5);
+ gImapServer.maximumConnectionsNumber = 1;
+
+ localAccountUtils.loadLocalMailAccount();
+
+ // We need an identity so that updateFolder doesn't fail
+ let localAccount = MailServices.accounts.createAccount();
+ let identity = MailServices.accounts.createIdentity();
+ localAccount.addIdentity(identity);
+ localAccount.defaultIdentity = identity;
+ localAccount.incomingServer = localAccountUtils.incomingServer;
+
+ // Let's also have another account, using the same identity
+ let imapAccount = MailServices.accounts.createAccount();
+ imapAccount.addIdentity(identity);
+ imapAccount.defaultIdentity = identity;
+ imapAccount.incomingServer = gImapServer;
+ MailServices.accounts.defaultAccount = imapAccount;
+
+ // Get the folder list...
+ gImapServer.performExpand(null);
+ gServer.performTest("SUBSCRIBE");
+ // pref tuning: one connection only, turn off notifications
+ // Make sure no biff notifications happen
+ Services.prefs.setBoolPref("mail.biff.play_sound", false);
+ Services.prefs.setBoolPref("mail.biff.show_alert", false);
+ Services.prefs.setBoolPref("mail.biff.show_tray_icon", false);
+ Services.prefs.setBoolPref("mail.biff.animate_dock_icon", false);
+
+ let rootFolder = gImapServer.rootFolder;
+ gIMAPInbox = rootFolder.getFolderWithFlags(Ci.nsMsgFolderFlags.Inbox);
+ gFolder1 = rootFolder.getChildNamed("folder 1");
+ gFolder2 = rootFolder.getChildNamed("folder 2");
+ gFolder1.setFlag(Ci.nsMsgFolderFlags.CheckNew);
+ gFolder2.setFlag(Ci.nsMsgFolderFlags.CheckNew);
+});
+
+add_task(function checkStatSelect() {
+ // imap fake server's resetTest resets the authentication state - charming.
+ // So poke the _test member directly.
+ gServer._test = true;
+ gIMAPInbox.getNewMessages(null, null);
+ gServer.performTest("STATUS");
+ // We want to wait for the STATUS to be really done before we issue
+ // more STATUS commands, so we do a NOOP on the
+ // INBOX, and since we only have one connection with the fake server,
+ // that will essentially serialize things.
+ gServer._test = true;
+ gIMAPInbox.updateFolder(null);
+ gServer.performTest("NOOP");
+});
+
+add_task(async function checkStatNoSelect() {
+ // folder 2 should have been stat'd, but not folder 1. All we can really check
+ // is that folder 2 was stat'd and that its unread msg count is 1
+ Assert.equal(gFolder2.getNumUnread(false), 1);
+ addMessageToFolder(gFolder2Mailbox);
+ gFolder1.clearFlag(Ci.nsMsgFolderFlags.ImapNoselect);
+ gServer._test = true;
+
+ let folderListener = new FolderListener();
+
+ // we've cleared the ImapNoselect flag, so we will attempt to STAT folder 1,
+ // which will fail. So we verify that we go on and STAT folder 2, and that
+ // it picks up the message we added to it above.
+ MailServices.mailSession.AddFolderListener(
+ folderListener,
+ Ci.nsIFolderListener.boolPropertyChanged
+ );
+ gIMAPInbox.getNewMessages(null, null);
+ // Wait for the folder listener to get told about new messages.
+ await folderListener.promise;
+});
+
+add_task(function endTest() {
+ Assert.equal(gFolder2.getNumUnread(false), 2);
+
+ // Clean up the server in preparation
+ gServer.resetTest();
+ gImapServer.closeCachedConnections();
+ gServer.performTest();
+ gServer.stop();
+});
+
+function addMessageToFolder(mbox) {
+ // make a couple of messages
+ let messages = [];
+ let gMessageGenerator = new MessageGenerator();
+ messages = messages.concat(gMessageGenerator.makeMessage());
+
+ let msgURI = Services.io.newURI(
+ "data:text/plain;base64," + btoa(messages[0].toMessageString())
+ );
+ let message = new ImapMessage(msgURI.spec, mbox.uidnext++);
+ mbox.addMessage(message);
+}
+
+function FolderListener() {
+ this._promise = new Promise(resolve => {
+ this._resolve = resolve;
+ });
+}
+
+FolderListener.prototype = {
+ onFolderBoolPropertyChanged(aItem, aProperty, aOldValue, aNewValue) {
+ // This means that the STAT on "folder 2" has finished.
+ if (aProperty == "NewMessages" && aNewValue) {
+ this._resolve();
+ }
+ },
+ get promise() {
+ return this._promise;
+ },
+};
diff --git a/comm/mailnews/imap/test/unit/test_downloadOffline.js b/comm/mailnews/imap/test/unit/test_downloadOffline.js
new file mode 100644
index 0000000000..931995ee01
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_downloadOffline.js
@@ -0,0 +1,75 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Test to ensure that downloadAllForOffline works correctly with imap folders
+ * and returns success.
+ */
+
+/* import-globals-from ../../../test/resources/logHelper.js */
+/* import-globals-from ../../../test/resources/MessageGenerator.jsm */
+load("../../../resources/logHelper.js");
+load("../../../resources/MessageGenerator.jsm");
+
+var gFileName = "bug460636";
+var gMsgFile = do_get_file("../../../data/" + gFileName);
+
+var tests = [setup, downloadAllForOffline, verifyDownloaded, teardownIMAPPump];
+
+async function setup() {
+ setupIMAPPump();
+
+ /*
+ * Ok, prelude done. Read the original message from disk
+ * (through a file URI), and add it to the Inbox.
+ */
+ let msgfileuri = Services.io
+ .newFileURI(gMsgFile)
+ .QueryInterface(Ci.nsIFileURL);
+
+ IMAPPump.mailbox.addMessage(
+ new ImapMessage(msgfileuri.spec, IMAPPump.mailbox.uidnext++, [])
+ );
+
+ let messages = [];
+ let gMessageGenerator = new MessageGenerator();
+ messages = messages.concat(gMessageGenerator.makeMessage());
+ let dataUri = Services.io.newURI(
+ "data:text/plain;base64," + btoa(messages[0].toMessageString())
+ );
+ let imapMsg = new ImapMessage(dataUri.spec, IMAPPump.mailbox.uidnext++, []);
+ imapMsg.setSize(5000);
+ IMAPPump.mailbox.addMessage(imapMsg);
+
+ // ...and download for offline use.
+ let promiseUrlListener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.downloadAllForOffline(promiseUrlListener, null);
+ await promiseUrlListener.promise;
+}
+
+async function downloadAllForOffline() {
+ let promiseUrlListener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.downloadAllForOffline(promiseUrlListener, null);
+ await promiseUrlListener.promise;
+}
+
+function verifyDownloaded() {
+ // verify that the message headers have the offline flag set.
+ for (let header of IMAPPump.inbox.msgDatabase.enumerateMessages()) {
+ // Verify that each message has been downloaded and looks OK.
+ if (
+ header instanceof Ci.nsIMsgDBHdr &&
+ header.flags & Ci.nsMsgMessageFlags.Offline
+ ) {
+ IMAPPump.inbox.getLocalMsgStream(header).close();
+ } else {
+ do_throw("Message not downloaded for offline use");
+ }
+ }
+}
+
+function run_test() {
+ tests.forEach(x => add_task(x));
+ run_next_test();
+}
diff --git a/comm/mailnews/imap/test/unit/test_fetchCustomAttribute.js b/comm/mailnews/imap/test/unit/test_fetchCustomAttribute.js
new file mode 100644
index 0000000000..631e925cfa
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_fetchCustomAttribute.js
@@ -0,0 +1,105 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Test to ensure that imap fetchCustomMsgAttribute function works properly
+ */
+
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+// IMAP pump
+
+// Globals
+
+// Messages to load must have CRLF line endings, that is Windows style
+var gMessage = "bugmail10"; // message file used as the test message
+
+var gCustomValue = "Custom";
+var gCustomList = ["Custom1", "Custom2", "Custom3"];
+
+var gMsgWindow = Cc["@mozilla.org/messenger/msgwindow;1"].createInstance(
+ Ci.nsIMsgWindow
+);
+
+add_setup(async function () {
+ setupIMAPPump("CUSTOM1");
+ Services.prefs.setBoolPref(
+ "mail.server.server1.autosync_offline_stores",
+ false
+ );
+ // Load and update a message in the imap fake server.
+ let message = new ImapMessage(
+ specForFileName(gMessage),
+ IMAPPump.mailbox.uidnext++,
+ []
+ );
+ message.xCustomValue = gCustomValue;
+ message.xCustomList = gCustomList;
+ IMAPPump.mailbox.addMessage(message);
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.updateFolderWithListener(null, listener);
+ await listener.promise;
+});
+
+// Used to verify that nsIServerResponseParser.msg_fetch() can handle
+// not in a parenthesis group - Bug 750012
+add_task(async function testFetchCustomValue() {
+ let msgHdr = mailTestUtils.firstMsgHdr(IMAPPump.inbox);
+ let uri = IMAPPump.inbox.fetchCustomMsgAttribute(
+ "X-CUSTOM-VALUE",
+ msgHdr.messageKey,
+ gMsgWindow
+ );
+ uri.QueryInterface(Ci.nsIMsgMailNewsUrl);
+ // Listens for response from fetchCustomMsgAttribute request for X-CUSTOM-VALUE.
+ let fetchCustomValueListener = new PromiseTestUtils.PromiseUrlListener({
+ OnStopRunningUrl(aUrl, aExitCode) {
+ aUrl.QueryInterface(Ci.nsIImapUrl);
+ Assert.equal(aUrl.customAttributeResult, gCustomValue);
+ },
+ });
+ uri.RegisterListener(fetchCustomValueListener);
+ await fetchCustomValueListener.promise;
+});
+
+// Used to verify that nsIServerResponseParser.msg_fetch() can handle a parenthesis group - Bug 735542
+add_task(async function testFetchCustomList() {
+ let msgHdr = mailTestUtils.firstMsgHdr(IMAPPump.inbox);
+ let uri = IMAPPump.inbox.fetchCustomMsgAttribute(
+ "X-CUSTOM-LIST",
+ msgHdr.messageKey,
+ gMsgWindow
+ );
+ uri.QueryInterface(Ci.nsIMsgMailNewsUrl);
+ // Listens for response from fetchCustomMsgAttribute request for X-CUSTOM-VALUE.
+ let fetchCustomListListener = new PromiseTestUtils.PromiseUrlListener({
+ OnStopRunningUrl(aUrl, aExitCode) {
+ aUrl.QueryInterface(Ci.nsIImapUrl);
+ Assert.equal(
+ aUrl.customAttributeResult,
+ "(" + gCustomList.join(" ") + ")"
+ );
+ },
+ });
+ uri.RegisterListener(fetchCustomListListener);
+ await fetchCustomListListener.promise;
+});
+
+// Cleanup at end
+add_task(function endTest() {
+ teardownIMAPPump();
+});
+
+/*
+ * helper functions
+ */
+
+// given a test file, return the file uri spec
+function specForFileName(aFileName) {
+ let file = do_get_file("../../../data/" + aFileName);
+ let msgfileuri = Services.io.newFileURI(file).QueryInterface(Ci.nsIFileURL);
+ return msgfileuri.spec;
+}
diff --git a/comm/mailnews/imap/test/unit/test_filterCustomHeaders.js b/comm/mailnews/imap/test/unit/test_filterCustomHeaders.js
new file mode 100644
index 0000000000..05e2a71b66
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_filterCustomHeaders.js
@@ -0,0 +1,66 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 tests hdr parsing in the filter running context, specifically
+ * filters on custom headers.
+ * See https://bugzilla.mozilla.org/show_bug.cgi?id=655578
+ * for more info.
+ *
+ * Original author: David Bienvenu <bienvenu@mozilla.com>
+ */
+
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+// IMAP pump
+
+var { IMAPPump, setupIMAPPump, teardownIMAPPump } = ChromeUtils.import(
+ "resource://testing-common/mailnews/IMAPpump.jsm"
+);
+
+add_setup(async function () {
+ setupIMAPPump();
+ // Create a test filter.
+ let filterList = IMAPPump.incomingServer.getFilterList(null);
+ let filter = filterList.createFilter("test list-id");
+ let searchTerm = filter.createTerm();
+ searchTerm.attrib = Ci.nsMsgSearchAttrib.OtherHeader + 1;
+ searchTerm.op = Ci.nsMsgSearchOp.Contains;
+ let value = searchTerm.value;
+ value.attrib = Ci.nsMsgSearchAttrib.OtherHeader;
+ value.str = "gnupg-users.gnupg.org";
+ searchTerm.value = value;
+ searchTerm.booleanAnd = false;
+ searchTerm.arbitraryHeader = "List-Id";
+ filter.appendTerm(searchTerm);
+ filter.enabled = true;
+
+ // create a mark read action
+ let action = filter.createAction();
+ action.type = Ci.nsMsgFilterAction.MarkRead;
+ filter.appendAction(action);
+ filterList.insertFilterAt(0, filter);
+ Services.prefs.setBoolPref(
+ "mail.server.default.autosync_offline_stores",
+ false
+ );
+ let file = do_get_file("../../../data/bugmail19");
+ let msgfileuri = Services.io.newFileURI(file).QueryInterface(Ci.nsIFileURL);
+
+ IMAPPump.mailbox.addMessage(
+ new ImapMessage(msgfileuri.spec, IMAPPump.mailbox.uidnext++, [])
+ );
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.updateFolderWithListener(null, listener);
+ await listener.promise;
+});
+
+add_task(function checkFilterResults() {
+ let msgHdr = mailTestUtils.firstMsgHdr(IMAPPump.inbox);
+ Assert.ok(msgHdr.isRead);
+ IMAPPump.server.performTest("UID STORE");
+ teardownIMAPPump();
+});
diff --git a/comm/mailnews/imap/test/unit/test_filterNeedsBody.js b/comm/mailnews/imap/test/unit/test_filterNeedsBody.js
new file mode 100644
index 0000000000..e7720b3222
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_filterNeedsBody.js
@@ -0,0 +1,113 @@
+/*
+ * This file tests the needsBody attribute added to a
+ * custom filter action in bug 555051.
+ *
+ * Original author: Kent James <kent@caspia.com>
+ * adapted from test_imapFilterActions.js
+ */
+
+/* import-globals-from ../../../test/resources/logHelper.js */
+load("../../../resources/logHelper.js");
+
+// Globals
+var gFilter; // a message filter with a subject search
+var gAction; // current message action (reused)
+var gMessage = "draft1"; // message file used as the test message
+
+// Definition of tests
+var tests = [
+ setupIMAPPump,
+ setup,
+ function NeedsBodyTrue() {
+ gAction.type = Ci.nsMsgFilterAction.Custom;
+ gAction.customId = "mailnews@mozilla.org#testOffline";
+ actionTestOffline.needsBody = true;
+ gAction.strValue = "true";
+ },
+ runFilterAction,
+ function NeedsBodyFalse() {
+ gAction.type = Ci.nsMsgFilterAction.Custom;
+ gAction.customId = "mailnews@mozilla.org#testOffline";
+ actionTestOffline.needsBody = false;
+ gAction.strValue = "false";
+ },
+ runFilterAction,
+ teardownIMAPPump,
+];
+
+function setup() {
+ // Create a test filter.
+ let filterList = IMAPPump.incomingServer.getFilterList(null);
+ gFilter = filterList.createFilter("test offline");
+ let searchTerm = gFilter.createTerm();
+ searchTerm.matchAll = true;
+
+ gFilter.appendTerm(searchTerm);
+ gFilter.enabled = true;
+
+ // an action that can be modified by tests
+ gAction = gFilter.createAction();
+
+ // add the custom actions
+ MailServices.filters.addCustomAction(actionTestOffline);
+}
+
+// basic preparation done for each test
+async function runFilterAction() {
+ let filterList = IMAPPump.incomingServer.getFilterList(null);
+ while (filterList.filterCount) {
+ filterList.removeFilterAt(0);
+ }
+ if (gFilter) {
+ gFilter.clearActionList();
+ if (gAction) {
+ gFilter.appendAction(gAction);
+ filterList.insertFilterAt(0, gFilter);
+ }
+ }
+ IMAPPump.mailbox.addMessage(
+ new ImapMessage(specForFileName(gMessage), IMAPPump.mailbox.uidnext++, [])
+ );
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.updateFolderWithListener(null, listener);
+ await listener.promise;
+}
+
+function run_test() {
+ tests.forEach(x => add_task(x));
+ run_next_test();
+}
+
+// custom action to test offline status
+var actionTestOffline = {
+ id: "mailnews@mozilla.org#testOffline",
+ name: "test if offline",
+ applyAction(aMsgHdrs, aActionValue, aListener, aType, aMsgWindow) {
+ for (let msgHdr of aMsgHdrs) {
+ let isOffline = msgHdr.flags & Ci.nsMsgMessageFlags.Offline;
+ Assert.equal(!!isOffline, aActionValue == "true");
+ }
+ },
+ isValidForType(type, scope) {
+ return true;
+ },
+
+ validateActionValue(value, folder, type) {
+ return null;
+ },
+
+ allowDuplicates: false,
+
+ needsBody: false, // set during test setup
+};
+
+/*
+ * helper functions
+ */
+
+// given a test file, return the file uri spec
+function specForFileName(aFileName) {
+ let file = do_get_file(gDEPTH + "mailnews/data/" + aFileName);
+ let msgfileuri = Services.io.newFileURI(file).QueryInterface(Ci.nsIFileURL);
+ return msgfileuri.spec;
+}
diff --git a/comm/mailnews/imap/test/unit/test_folderOfflineFlags.js b/comm/mailnews/imap/test/unit/test_folderOfflineFlags.js
new file mode 100644
index 0000000000..b1c26069c6
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_folderOfflineFlags.js
@@ -0,0 +1,108 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Test that the folders that should get flagged for offline use do, and that
+ * those that shouldn't don't.
+ */
+
+// make SOLO_FILE="test_folderOfflineFlags.js" -C mailnews/imap/test check-one
+
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+/**
+ * Setup the mailboxes that will be used for this test.
+ */
+add_setup(async function () {
+ setupIMAPPump("GMail");
+
+ IMAPPump.mailbox.subscribed = true;
+ IMAPPump.mailbox.specialUseFlag = "\\Inbox";
+ IMAPPump.daemon.createMailbox("[Gmail]", {
+ flags: ["\\Noselect"],
+ subscribed: true,
+ });
+ IMAPPump.daemon.createMailbox("[Gmail]/All Mail", {
+ specialUseFlag: "\\AllMail",
+ subscribed: true,
+ });
+ IMAPPump.daemon.createMailbox("[Gmail]/Drafts", {
+ specialUseFlag: "\\Drafts",
+ subscribed: true,
+ });
+ IMAPPump.daemon.createMailbox("[Gmail]/Sent", {
+ specialUseFlag: "\\Sent",
+ subscribed: true,
+ });
+ IMAPPump.daemon.createMailbox("[Gmail]/Spam", {
+ specialUseFlag: "\\Spam",
+ subscribed: true,
+ });
+ IMAPPump.daemon.createMailbox("[Gmail]/Starred", {
+ specialUseFlag: "\\Starred",
+ subscribed: true,
+ });
+ IMAPPump.daemon.createMailbox("[Gmail]/Trash", {
+ specialUseFlag: "\\Trash",
+ subscribed: true,
+ });
+ IMAPPump.daemon.createMailbox("folder1", { subscribed: true });
+ IMAPPump.daemon.createMailbox("folder2", { subscribed: true });
+
+ // select the inbox to force folder discovery, etc.
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.updateFolderWithListener(null, listener);
+ await listener.promise;
+});
+
+/**
+ * Test that folders generally are marked for offline use by default.
+ */
+add_task(function testGeneralFoldersOffline() {
+ Assert.ok(IMAPPump.inbox.getFlag(Ci.nsMsgFolderFlags.Offline));
+
+ let gmail = IMAPPump.incomingServer.rootFolder.getChildNamed("[Gmail]");
+
+ let allmail = gmail.getFolderWithFlags(Ci.nsMsgFolderFlags.Archive);
+ Assert.ok(allmail.getFlag(Ci.nsMsgFolderFlags.Offline));
+
+ let drafts = gmail.getFolderWithFlags(Ci.nsMsgFolderFlags.Drafts);
+ Assert.ok(drafts.getFlag(Ci.nsMsgFolderFlags.Offline));
+
+ let sent = gmail.getFolderWithFlags(Ci.nsMsgFolderFlags.SentMail);
+ Assert.ok(sent.getFlag(Ci.nsMsgFolderFlags.Offline));
+
+ let rootFolder = IMAPPump.incomingServer.rootFolder;
+
+ let folder1 = rootFolder.getChildNamed("folder1");
+ Assert.ok(folder1.getFlag(Ci.nsMsgFolderFlags.Offline));
+
+ let folder2 = rootFolder.getChildNamed("folder2");
+ Assert.ok(folder2.getFlag(Ci.nsMsgFolderFlags.Offline));
+});
+
+/**
+ * Test that Trash isn't flagged for offline use by default.
+ */
+add_task(function testTrashNotOffline() {
+ let gmail = IMAPPump.incomingServer.rootFolder.getChildNamed("[Gmail]");
+ let trash = gmail.getFolderWithFlags(Ci.nsMsgFolderFlags.Trash);
+ Assert.ok(!trash.getFlag(Ci.nsMsgFolderFlags.Offline));
+});
+
+/**
+ * Test that Junk isn't flagged for offline use by default.
+ */
+add_task(function testJunkNotOffline() {
+ let gmail = IMAPPump.incomingServer.rootFolder.getChildNamed("[Gmail]");
+ let spam = gmail.getFolderWithFlags(Ci.nsMsgFolderFlags.Junk);
+ Assert.ok(!spam.getFlag(Ci.nsMsgFolderFlags.Offline));
+});
+
+/** Cleanup at the end. */
+add_task(function endTest() {
+ teardownIMAPPump();
+});
diff --git a/comm/mailnews/imap/test/unit/test_gmailAttributes.js b/comm/mailnews/imap/test/unit/test_gmailAttributes.js
new file mode 100644
index 0000000000..5b424462c5
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_gmailAttributes.js
@@ -0,0 +1,91 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Test to ensure that, in case of GMail server, fetching of custom GMail
+ * attributes works properly.
+ *
+ * Bug 721316
+ *
+ * See https://bugzilla.mozilla.org/show_bug.cgi?id=721316
+ * for more info.
+ *
+ * Original Author: Atul Jangra<atuljangra66@gmail.com>
+ */
+
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+// Messages to load must have CRLF line endings, that is Windows style
+var gMessage = "bugmail10"; // message file used as the test message
+
+var gXGmMsgid = "1278455344230334865";
+var gXGmThrid = "1266894439832287888";
+var gXGmLabels = '(\\Inbox \\Sent Important "Muy Importante" foo)';
+
+add_setup(async function () {
+ Services.prefs.setBoolPref(
+ "mail.server.server1.autosync_offline_stores",
+ false
+ );
+ setupIMAPPump("GMail");
+ IMAPPump.mailbox.specialUseFlag = "\\Inbox";
+ IMAPPump.mailbox.subscribed = true;
+
+ // need all mail folder to identify this as gmail server.
+ IMAPPump.daemon.createMailbox("[Gmail]", { flags: ["\\NoSelect"] });
+ IMAPPump.daemon.createMailbox("[Gmail]/All Mail", {
+ subscribed: true,
+ specialUseFlag: "\\AllMail",
+ });
+ // Load and update a message in the imap fake server.
+ let message = new ImapMessage(
+ specForFileName(gMessage),
+ IMAPPump.mailbox.uidnext++,
+ []
+ );
+ message.xGmMsgid = gXGmMsgid;
+ message.xGmThrid = gXGmThrid;
+ message.xGmLabels = gXGmLabels;
+ IMAPPump.mailbox.addMessage(message);
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.updateFolderWithListener(null, listener);
+ await listener.promise;
+});
+
+add_task(function testFetchXGmMsgid() {
+ let msgHdr = mailTestUtils.firstMsgHdr(IMAPPump.inbox);
+ let val = msgHdr.getStringProperty("X-GM-MSGID");
+ Assert.equal(val, gXGmMsgid);
+});
+
+add_task(function testFetchXGmThrid() {
+ let msgHdr = mailTestUtils.firstMsgHdr(IMAPPump.inbox);
+ let val = msgHdr.getStringProperty("X-GM-THRID");
+ Assert.equal(val, gXGmThrid);
+});
+
+add_task(function testFetchXGmLabels() {
+ let msgHdr = mailTestUtils.firstMsgHdr(IMAPPump.inbox);
+ let val = msgHdr.getStringProperty("X-GM-LABELS");
+ // We need to remove the starting "(" and ending ")" from gXGmLabels while comparing
+ Assert.equal(val, gXGmLabels.substring(1, gXGmLabels.length - 1));
+});
+
+// Cleanup at end
+add_task(function endTest() {
+ teardownIMAPPump();
+});
+
+/*
+ * helper functions
+ */
+
+// given a test file, return the file uri spec
+function specForFileName(aFileName) {
+ let file = do_get_file("../../../data/" + aFileName);
+ let msgfileuri = Services.io.newFileURI(file).QueryInterface(Ci.nsIFileURL);
+ return msgfileuri.spec;
+}
diff --git a/comm/mailnews/imap/test/unit/test_gmailOfflineMsgStore.js b/comm/mailnews/imap/test/unit/test_gmailOfflineMsgStore.js
new file mode 100644
index 0000000000..5ab93d1ad9
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_gmailOfflineMsgStore.js
@@ -0,0 +1,229 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Test to ensure that, in case of GMail server, fetching of a message, which is
+ * already present in offline store of some folder, from a folder doesn't make
+ * us add it to the offline store twice(in this case, in general it can be any
+ * number of times).
+ *
+ * Bug 721316
+ *
+ * See https://bugzilla.mozilla.org/show_bug.cgi?id=721316
+ * for more info.
+ *
+ * Original Author: Atul Jangra<atuljangra66@gmail.com>
+ */
+
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+// Messages to load must have CRLF line endings, that is Windows style.
+
+var gMessage1 = "bugmail10"; // message file used as the test message for Inbox and fooFolder.
+var gXGmMsgid1 = "1278455344230334865";
+var gXGmThrid1 = "1266894439832287888";
+// We need to have different X-GM-LABELS for different folders. I am doing it here manually, but this issue will be tackled in Bug 781443.
+var gXGmLabels11 = '( "\\\\Sent" foo bar)'; // for message in Inbox.
+var gXGmLabels12 = '("\\\\Inbox" "\\\\Sent" bar)'; // for message in fooFolder.
+var gMsgId1 = "200806061706.m56H6RWT004933@mrapp54.mozilla.org";
+
+var gMessage2 = "bugmail11"; // message file used as the test message for fooFolder.
+var gMsgId2 = "200804111417.m3BEHTk4030129@mrapp51.mozilla.org";
+var gXGmMsgid2 = "1278455345230334555";
+var gXGmThrid2 = "1266894639832287111";
+var gXGmLabels2 = '("\\\\Sent")';
+
+var fooBox;
+var fooFolder;
+
+var gImapInboxOfflineStoreSizeInitial;
+var gImapInboxOfflineStoreSizeFinal;
+
+var gFooOfflineStoreSizeInitial;
+var gFooOfflineStoreSizeFinal;
+
+add_setup(async function () {
+ // We aren't interested in downloading messages automatically.
+ Services.prefs.setBoolPref(
+ "mail.server.server1.autosync_offline_stores",
+ false
+ );
+ Services.prefs.setBoolPref("mail.server.server1.offline_download", true);
+ Services.prefs.setBoolPref("mail.biff.alert.show_preview", false);
+
+ setupIMAPPump("GMail");
+
+ IMAPPump.mailbox.specialUseFlag = "\\Inbox";
+ IMAPPump.mailbox.subscribed = true;
+
+ // need all mail folder to identify this as gmail server.
+ IMAPPump.daemon.createMailbox("[Gmail]", { flags: ["\\NoSelect"] });
+ IMAPPump.daemon.createMailbox("[Gmail]/All Mail", {
+ subscribed: true,
+ specialUseFlag: "\\AllMail",
+ });
+
+ // Creating the mailbox "foo"
+ IMAPPump.daemon.createMailbox("foo", { subscribed: true });
+ fooBox = IMAPPump.daemon.getMailbox("foo");
+
+ // Add message1 to inbox.
+ let message = new ImapMessage(
+ specForFileName(gMessage1),
+ IMAPPump.mailbox.uidnext++,
+ []
+ );
+ message.messageId = gMsgId1;
+ message.xGmMsgid = gXGmMsgid1;
+ message.xGmThrid = gXGmThrid1;
+ message.xGmLabels = gXGmLabels11; // With labels excluding "//INBOX".
+ IMAPPump.mailbox.addMessage(message);
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.updateFolderWithListener(null, listener);
+ await listener.promise;
+});
+
+add_task(async function selectInboxMsg() {
+ // Select mesasage1 from inbox which makes message1 available in offline store.
+ let imapService = Cc[
+ "@mozilla.org/messenger/messageservice;1?type=imap"
+ ].getService(Ci.nsIMsgMessageService);
+ let db = IMAPPump.inbox.msgDatabase;
+ let msg1 = db.getMsgHdrForMessageID(gMsgId1);
+ let streamListener = new PromiseTestUtils.PromiseStreamListener();
+ let urlListener = new PromiseTestUtils.PromiseUrlListener();
+ imapService.loadMessage(
+ IMAPPump.inbox.getUriForMsg(msg1),
+ streamListener,
+ null,
+ urlListener,
+ false
+ );
+ await urlListener.promise;
+});
+
+add_task(async function StreamMessageInbox() {
+ // Stream message1 from inbox.
+ let newMsgHdr = IMAPPump.inbox.msgDatabase.getMsgHdrForMessageID(gMsgId1);
+ let msgURI = newMsgHdr.folder.getUriForMsg(newMsgHdr);
+ let msgServ = MailServices.messageServiceFromURI(msgURI);
+ let streamLister = new PromiseTestUtils.PromiseStreamListener();
+ msgServ.streamMessage(msgURI, streamLister, null, null, false, "", false);
+ gImapInboxOfflineStoreSizeInitial = IMAPPump.inbox.filePath.fileSize; // Initial Size of Inbox.
+ await streamLister.promise;
+});
+
+add_task(async function createAndUpdate() {
+ let rootFolder = IMAPPump.incomingServer.rootFolder;
+ fooFolder = rootFolder
+ .getChildNamed("foo")
+ .QueryInterface(Ci.nsIMsgImapMailFolder); // We have created the mailbox earlier.
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ fooFolder.updateFolderWithListener(null, listener);
+ await listener.promise;
+});
+
+add_task(function addToFoo() {
+ // Adding our test message.
+ let message = new ImapMessage(
+ specForFileName(gMessage1),
+ fooBox.uidnext++,
+ []
+ );
+ message.messageId = gMsgId1;
+ message.xGmMsgid = gXGmMsgid1;
+ message.xGmThrid = gXGmThrid1;
+ message.xGmLabels = gXGmLabels12; // With labels excluding "foo".
+ fooBox.addMessage(message);
+ // Adding another message so that fooFolder behaves as LocalFolder while calculating it's size.
+ let message1 = new ImapMessage(
+ specForFileName(gMessage2),
+ fooBox.uidnext++,
+ []
+ );
+ message1.messageId = gMsgId2;
+ message1.xGmMsgid = gXGmMsgid2;
+ message1.xGmThrid = gXGmThrid2;
+ message1.xGmLabels = gXGmLabels2;
+ fooBox.addMessage(message1);
+});
+
+add_task(async function updateFoo() {
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ fooFolder.updateFolderWithListener(null, listener);
+ await listener.promise;
+});
+
+add_task(async function selectFooMsg() {
+ // Select message2 from fooFolder, which makes fooFolder a local folder.
+ let imapService = Cc[
+ "@mozilla.org/messenger/messageservice;1?type=imap"
+ ].getService(Ci.nsIMsgMessageService);
+ let msg1 = fooFolder.msgDatabase.getMsgHdrForMessageID(gMsgId2);
+ let streamListener = new PromiseTestUtils.PromiseStreamListener();
+ let urlListener = new PromiseTestUtils.PromiseUrlListener();
+ imapService.loadMessage(
+ fooFolder.getUriForMsg(msg1),
+ streamListener,
+ null,
+ urlListener,
+ false
+ );
+ await urlListener.promise;
+});
+
+add_task(async function StreamMessageFoo() {
+ // Stream message2 from fooFolder.
+ let newMsgHdr = fooFolder.msgDatabase.getMsgHdrForMessageID(gMsgId2);
+ let msgURI = newMsgHdr.folder.getUriForMsg(newMsgHdr);
+ let msgServ = MailServices.messageServiceFromURI(msgURI);
+ let streamListener = new PromiseTestUtils.PromiseStreamListener();
+ msgServ.streamMessage(msgURI, streamListener, null, null, false, "", false);
+ gFooOfflineStoreSizeInitial = fooFolder.filePath.fileSize;
+ await streamListener.promise;
+});
+
+add_task(async function crossStreaming() {
+ /**
+ * Streaming message1 from fooFolder. message1 is present in
+ * offline store of inbox. We now test that streaming the message1
+ * from fooFolder does not make us add message1 to offline store of
+ * fooFolder. We check this by comparing the sizes of inbox and fooFolder
+ * before and after streaming.
+ */
+ let msg2 = fooFolder.msgDatabase.getMsgHdrForMessageID(gMsgId1);
+ Assert.ok(msg2 !== null);
+ let msgURI = fooFolder.getUriForMsg(msg2);
+ let msgServ = MailServices.messageServiceFromURI(msgURI);
+ // pass true for aLocalOnly since message should be in offline store of Inbox.
+ let streamListener = new PromiseTestUtils.PromiseStreamListener();
+ msgServ.streamMessage(msgURI, streamListener, null, null, false, "", true);
+ await streamListener.promise;
+ gFooOfflineStoreSizeFinal = fooFolder.filePath.fileSize;
+ gImapInboxOfflineStoreSizeFinal = IMAPPump.inbox.filePath.fileSize;
+ Assert.equal(gFooOfflineStoreSizeFinal, gFooOfflineStoreSizeInitial);
+ Assert.equal(
+ gImapInboxOfflineStoreSizeFinal,
+ gImapInboxOfflineStoreSizeInitial
+ );
+});
+
+add_task(function endTest() {
+ teardownIMAPPump();
+});
+
+/*
+ * helper functions
+ */
+
+/**
+ * Given a test file, return the file uri spec.
+ */
+function specForFileName(aFileName) {
+ let file = do_get_file("../../../data/" + aFileName);
+ let msgfileuri = Services.io.newFileURI(file).QueryInterface(Ci.nsIFileURL);
+ return msgfileuri.spec;
+}
diff --git a/comm/mailnews/imap/test/unit/test_imapAttachmentSaves.js b/comm/mailnews/imap/test/unit/test_imapAttachmentSaves.js
new file mode 100644
index 0000000000..d3e3951492
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_imapAttachmentSaves.js
@@ -0,0 +1,199 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Tests imap save and detach attachments.
+ */
+
+var { MessageGenerator } = ChromeUtils.import(
+ "resource://testing-common/mailnews/MessageGenerator.jsm"
+);
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+// javascript mime emitter functions
+var { MsgHdrToMimeMessage } = ChromeUtils.import(
+ "resource:///modules/gloda/MimeMessage.jsm"
+);
+
+var kAttachFileName = "bob.txt";
+function SaveAttachmentCallback() {
+ this.attachments = null;
+ this._promise = new Promise((resolve, reject) => {
+ this._resolve = resolve;
+ this._reject = reject;
+ });
+}
+
+SaveAttachmentCallback.prototype = {
+ callback: function saveAttachmentCallback_callback(aMsgHdr, aMimeMessage) {
+ this.attachments = aMimeMessage.allAttachments;
+ this._resolve();
+ },
+ get promise() {
+ return this._promise;
+ },
+};
+var gCallbackObject = new SaveAttachmentCallback();
+
+// Dummy message window so we can say the inbox is open in a window.
+var dummyMsgWindow = Cc["@mozilla.org/messenger/msgwindow;1"].createInstance(
+ Ci.nsIMsgWindow
+);
+
+function MsgsDeletedListener() {
+ this._promise = new Promise(resolve => (this._resolve = resolve));
+}
+MsgsDeletedListener.prototype = {
+ msgsDeleted(aMsgArray) {
+ this._resolve();
+ },
+ get promise() {
+ return this._promise;
+ },
+};
+var trackDeletionMessageListener = new MsgsDeletedListener();
+
+add_setup(function () {
+ setupIMAPPump();
+
+ // Add folder listeners that will capture async events
+ MailServices.mfn.addListener(
+ trackDeletionMessageListener,
+ Ci.nsIMsgFolderNotificationService.msgsDeleted
+ );
+
+ // We need to register the dummyMsgWindow so that when we've finished running
+ // the append url, in nsImapMailFolder::OnStopRunningUrl, we'll think the
+ // Inbox is open in a folder and update it, which the detach code relies
+ // on to finish the detach.
+
+ dummyMsgWindow.openFolder = IMAPPump.inbox;
+ MailServices.mailSession.AddMsgWindow(dummyMsgWindow);
+});
+
+// load and update a message in the imap fake server
+add_task(async function loadImapMessage() {
+ let gMessageGenerator = new MessageGenerator();
+ // create a synthetic message with attachment
+ let smsg = gMessageGenerator.makeMessage({
+ attachments: [{ filename: kAttachFileName, body: "I like cheese!" }],
+ });
+
+ let msgURI = Services.io.newURI(
+ "data:text/plain;base64," + btoa(smsg.toMessageString())
+ );
+ let imapInbox = IMAPPump.daemon.getMailbox("INBOX");
+ let message = new ImapMessage(msgURI.spec, imapInbox.uidnext++, []);
+ IMAPPump.mailbox.addMessage(message);
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.updateFolderWithListener(null, listener);
+ await listener.promise;
+ Assert.equal(1, IMAPPump.inbox.getTotalMessages(false));
+ let msgHdr = mailTestUtils.firstMsgHdr(IMAPPump.inbox);
+ Assert.ok(msgHdr instanceof Ci.nsIMsgDBHdr);
+});
+
+// process the message through mime
+add_task(async function startMime() {
+ let msgHdr = mailTestUtils.firstMsgHdr(IMAPPump.inbox);
+
+ MsgHdrToMimeMessage(
+ msgHdr,
+ gCallbackObject,
+ gCallbackObject.callback,
+ true // allowDownload
+ );
+ await gCallbackObject.promise;
+});
+
+// detach any found attachments
+add_task(async function startDetach() {
+ let msgHdr = mailTestUtils.firstMsgHdr(IMAPPump.inbox);
+ let msgURI = msgHdr.folder.generateMessageURI(msgHdr.messageKey);
+
+ let messenger = Cc["@mozilla.org/messenger;1"].createInstance(
+ Ci.nsIMessenger
+ );
+ let attachment = gCallbackObject.attachments[0];
+
+ messenger.detachAttachmentsWOPrompts(
+ do_get_profile(),
+ [attachment.contentType],
+ [attachment.url],
+ [attachment.name],
+ [msgURI],
+ null
+ );
+ // deletion of original message should kick async_driver.
+ await trackDeletionMessageListener.promise;
+});
+
+// test that the detachment was successful
+add_task(async function testDetach() {
+ // Check that the file attached to the message now exists in the profile
+ // directory.
+ let checkFile = do_get_profile().clone();
+ checkFile.append(kAttachFileName);
+ Assert.ok(checkFile.exists());
+
+ // The message should now have a detached attachment. Read the message,
+ // and search for "AttachmentDetached" which is added on detachment.
+
+ // Get the message header - detached copy has UID 2.
+ let msgHdr = IMAPPump.inbox.GetMessageHeader(2);
+ Assert.ok(msgHdr !== null);
+ let messageContent = await getContentFromMessage(msgHdr);
+ Assert.ok(messageContent.includes("AttachmentDetached"));
+});
+
+// Cleanup
+add_task(function endTest() {
+ teardownIMAPPump();
+});
+
+/**
+ * Get the full message content.
+ *
+ * @param aMsgHdr - nsIMsgDBHdr object whose text body will be read.
+ * @returns {Promise<string>} full message contents.
+ */
+function getContentFromMessage(aMsgHdr) {
+ let msgFolder = aMsgHdr.folder;
+ let msgUri = msgFolder.getUriForMsg(aMsgHdr);
+
+ return new Promise((resolve, reject) => {
+ let streamListener = {
+ QueryInterface: ChromeUtils.generateQI(["nsIStreamListener"]),
+ sis: Cc["@mozilla.org/scriptableinputstream;1"].createInstance(
+ Ci.nsIScriptableInputStream
+ ),
+ content: "",
+ onDataAvailable(request, inputStream, offset, count) {
+ this.sis.init(inputStream);
+ this.content += this.sis.read(count);
+ },
+ onStartRequest(request) {},
+ onStopRequest(request, statusCode) {
+ this.sis.close();
+ if (Components.isSuccessCode(statusCode)) {
+ resolve(this.content);
+ } else {
+ reject(new Error(statusCode));
+ }
+ },
+ };
+ // Pass true for aLocalOnly since message should be in offline store.
+ MailServices.messageServiceFromURI(msgUri).streamMessage(
+ msgUri,
+ streamListener,
+ null,
+ null,
+ false,
+ "",
+ true
+ );
+ });
+}
diff --git a/comm/mailnews/imap/test/unit/test_imapAuthMethods.js b/comm/mailnews/imap/test/unit/test_imapAuthMethods.js
new file mode 100644
index 0000000000..18e5d54396
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_imapAuthMethods.js
@@ -0,0 +1,165 @@
+/**
+ * Login tests for IMAP
+ *
+ * Test code <copied from="test_mailboxes.js">
+ * and <copied from="test_pop3AuthMethods.js">
+ *
+ * BUGS:
+ * - cleanup after each test doesn't seem to work correctly. Effects:
+ * - one more "lsub" per test, e.g. "capability", "auth...", "lsub", "lsub", "lsub", "list" in the 3. test.,
+ * - root folder check succeeds although login failed
+ * - removeIncomingServer(..., true); (cleanup files) fails.
+ */
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+/* import-globals-from ../../../test/resources/alertTestUtils.js */
+load("../../../resources/alertTestUtils.js");
+
+// const kUsername = "fred";
+// const kPassword = "wilma";
+
+var thisTest;
+
+var tests = [
+ {
+ title: "Cleartext password, with server only supporting old-style login",
+ clientAuthMethod: Ci.nsMsgAuthMethod.passwordCleartext,
+ serverAuthMethods: [],
+ expectSuccess: true,
+ transaction: ["CAPABILITY", "LOGIN", "CAPABILITY", "LIST", "LSUB"],
+ },
+ {
+ // Just to make sure we clean up properly - in the test and in TB, e.g. don't cache stuff
+ title:
+ "Second time Cleartext password, with server only supporting old-style login",
+ clientAuthMethod: Ci.nsMsgAuthMethod.passwordCleartext,
+ serverAuthMethods: [],
+ expectSuccess: true,
+ transaction: ["CAPABILITY", "LOGIN", "CAPABILITY", "LIST", "LSUB"],
+ },
+ {
+ title:
+ "Cleartext password, with server supporting AUTH PLAIN, LOGIN and CRAM",
+ clientAuthMethod: Ci.nsMsgAuthMethod.passwordCleartext,
+ serverAuthMethods: ["PLAIN", "LOGIN", "CRAM-MD5"],
+ expectSuccess: true,
+ transaction: [
+ "CAPABILITY",
+ "AUTHENTICATE PLAIN",
+ "CAPABILITY",
+ "LIST",
+ "LSUB",
+ ],
+ },
+ {
+ title: "Cleartext password, with server supporting only AUTH LOGIN",
+ clientAuthMethod: Ci.nsMsgAuthMethod.passwordCleartext,
+ serverAuthMethods: ["LOGIN"],
+ expectSuccess: true,
+ transaction: [
+ "CAPABILITY",
+ "AUTHENTICATE LOGIN",
+ "CAPABILITY",
+ "LIST",
+ "LSUB",
+ ],
+ },
+ {
+ title: "Encrypted password, with server supporting PLAIN and CRAM",
+ clientAuthMethod: Ci.nsMsgAuthMethod.passwordEncrypted,
+ serverAuthMethods: ["PLAIN", "LOGIN", "CRAM-MD5"],
+ expectSuccess: true,
+ transaction: [
+ "CAPABILITY",
+ "AUTHENTICATE CRAM-MD5",
+ "CAPABILITY",
+ "LIST",
+ "LSUB",
+ ],
+ },
+ {
+ title:
+ "Encrypted password, with server only supporting AUTH PLAIN and LOGIN (must fail)",
+ clientAuthMethod: Ci.nsMsgAuthMethod.passwordEncrypted,
+ serverAuthMethods: ["PLAIN", "LOGIN"],
+ expectSuccess: false,
+ transaction: ["CAPABILITY"],
+ },
+];
+
+function nextTest() {
+ try {
+ thisTest = tests.shift();
+ if (!thisTest) {
+ endTest();
+ return;
+ }
+
+ dump("NEXT test: " + thisTest.title + "\n");
+
+ // (re)create fake server
+ var daemon = new ImapDaemon();
+ var server = makeServer(daemon, "", {
+ kAuthSchemes: thisTest.serverAuthMethods,
+ });
+ server.setDebugLevel(fsDebugAll);
+
+ // If Mailnews ever caches server capabilities, delete and re-create the incomingServer here
+ var incomingServer = createLocalIMAPServer(server.port);
+
+ let msgServer = incomingServer;
+ msgServer.QueryInterface(Ci.nsIMsgIncomingServer);
+ msgServer.authMethod = thisTest.clientAuthMethod;
+
+ // connect
+ incomingServer.performExpand(null);
+ server.performTest("LSUB");
+
+ dump("should " + (thisTest.expectSuccess ? "" : "not ") + "be logged in\n");
+ Assert.equal(true, incomingServer instanceof Ci.nsIImapServerSink);
+ do_check_transaction(server.playTransaction(), thisTest.transaction, false);
+
+ do {
+ incomingServer.closeCachedConnections();
+ } while (incomingServer.serverBusy);
+ incomingServer.shutdown();
+ deleteIMAPServer(incomingServer);
+ incomingServer = null;
+ MailServices.accounts.closeCachedConnections();
+ MailServices.accounts.shutdownServers();
+ MailServices.accounts.unloadAccounts();
+ server.stop();
+ } catch (e) {
+ // server.stop();
+ // endTest();
+ do_throw(e);
+ }
+
+ nextTest();
+}
+
+function deleteIMAPServer(incomingServer) {
+ if (!incomingServer) {
+ return;
+ }
+ MailServices.accounts.removeIncomingServer(incomingServer, true);
+}
+
+function run_test() {
+ do_test_pending();
+
+ registerAlertTestUtils();
+
+ nextTest();
+}
+
+function endTest() {
+ var thread = gThreadManager.currentThread;
+ while (thread.hasPendingEvents()) {
+ thread.processNextEvent(true);
+ }
+
+ do_test_finished();
+}
diff --git a/comm/mailnews/imap/test/unit/test_imapAutoSync.js b/comm/mailnews/imap/test/unit/test_imapAutoSync.js
new file mode 100644
index 0000000000..1f49a408cc
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_imapAutoSync.js
@@ -0,0 +1,239 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 tests various features of imap autosync
+// N.B. We need to beware of MessageInjection, since it turns off
+// imap autosync.
+
+// Our general approach is to attach an nsIAutoSyncMgrListener to the
+// autoSyncManager, and listen for the expected events. We simulate idle
+// by directly poking the nsIAutoSyncManager QI'd to nsIObserver with app
+// idle events. If we really go idle, duplicate idle events are ignored.
+
+// We test that checking non-inbox folders for new messages isn't
+// interfering with autoSync's detection of new messages.
+
+// We also test that folders that have messages added to them via move/copy
+// get put in the front of the queue.
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+var { MessageGenerator } = ChromeUtils.import(
+ "resource://testing-common/mailnews/MessageGenerator.jsm"
+);
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+var msgFlagOffline = Ci.nsMsgMessageFlags.Offline;
+var nsIAutoSyncMgrListener = Ci.nsIAutoSyncMgrListener;
+
+var gAutoSyncManager = Cc["@mozilla.org/imap/autosyncmgr;1"].getService(
+ Ci.nsIAutoSyncManager
+);
+var gTargetFolder;
+
+add_setup(function () {
+ setupIMAPPump();
+ addMessageToFolder(IMAPPump.inbox);
+});
+
+add_task(async function test_createTargetFolder() {
+ gAutoSyncManager.addListener(gAutoSyncListener);
+
+ IMAPPump.incomingServer.rootFolder.createSubfolder("targetFolder", null);
+ await PromiseTestUtils.promiseFolderAdded("targetFolder");
+ gTargetFolder =
+ IMAPPump.incomingServer.rootFolder.getChildNamed("targetFolder");
+ Assert.ok(gTargetFolder instanceof Ci.nsIMsgImapMailFolder);
+ // set folder to be checked for new messages when inbox is checked.
+ gTargetFolder.setFlag(Ci.nsMsgFolderFlags.CheckNew);
+});
+
+add_task(function test_checkForNewMessages() {
+ addMessageToFolder(gTargetFolder);
+ // This will update the INBOX and STATUS targetFolder. We only care about
+ // the latter.
+ IMAPPump.inbox.getNewMessages(null, null);
+ IMAPPump.server.performTest("STATUS");
+ // Now we'd like to make autosync update folders it knows about, to
+ // get the initial autosync out of the way.
+});
+
+add_task(function test_triggerAutoSyncIdle() {
+ // wait for both folders to get updated.
+ gAutoSyncListener._waitingForDiscoveryList.push(IMAPPump.inbox);
+ gAutoSyncListener._waitingForDiscoveryList.push(gTargetFolder);
+ gAutoSyncListener._waitingForDiscovery = true;
+ let observer = gAutoSyncManager.QueryInterface(Ci.nsIObserver);
+ observer.observe(null, "mail-startup-done", "");
+ observer.observe(null, "mail:appIdle", "idle");
+});
+
+// move the message to a diffent folder
+add_task(async function test_moveMessageToTargetFolder() {
+ let observer = gAutoSyncManager.QueryInterface(Ci.nsIObserver);
+ observer.observe(null, "mail:appIdle", "back");
+ let msgHdr = mailTestUtils.firstMsgHdr(IMAPPump.inbox);
+ Assert.ok(msgHdr !== null);
+
+ let listener = new PromiseTestUtils.PromiseCopyListener();
+ // Now move this message to the target folder.
+ MailServices.copy.copyMessages(
+ IMAPPump.inbox,
+ [msgHdr],
+ gTargetFolder,
+ true,
+ listener,
+ null,
+ false
+ );
+ await listener.promise;
+});
+
+add_task(async function test_waitForTargetUpdate() {
+ // After the copy, now we expect to get notified of the gTargetFolder
+ // getting updated, after we simulate going idle.
+ gAutoSyncListener._waitingForUpdate = true;
+ gAutoSyncListener._waitingForUpdateList.push(gTargetFolder);
+ gAutoSyncManager
+ .QueryInterface(Ci.nsIObserver)
+ .observe(null, "mail:appIdle", "idle");
+ await gAutoSyncListener.promiseOnDownloadCompleted;
+ await gAutoSyncListener.promiseOnDiscoveryQProcessed;
+});
+
+// Cleanup
+add_task(function endTest() {
+ let numMsgs = 0;
+ for (let header of gTargetFolder.messages) {
+ numMsgs++;
+ Assert.notEqual(header.flags & Ci.nsMsgMessageFlags.Offline, 0);
+ }
+ Assert.equal(2, numMsgs);
+ Assert.equal(gAutoSyncListener._waitingForUpdateList.length, 0);
+ Assert.ok(!gAutoSyncListener._waitingForDiscovery);
+ Assert.ok(!gAutoSyncListener._waitingForUpdate);
+ teardownIMAPPump();
+});
+
+function autoSyncListenerPromise() {
+ this._inQFolderList = [];
+ this._runnning = false;
+ this._lastMessage = {};
+ this._waitingForUpdateList = [];
+ this._waitingForUpdate = false;
+ this._waitingForDiscoveryList = [];
+ this._waitingForDiscovery = false;
+
+ this._promiseOnDownloadCompleted = new Promise(resolve => {
+ this._resolveOnDownloadCompleted = resolve;
+ });
+ this._promiseOnDiscoveryQProcessed = new Promise(resolve => {
+ this._resolveOnDiscoveryQProcessed = resolve;
+ });
+}
+autoSyncListenerPromise.prototype = {
+ onStateChanged(running) {
+ this._runnning = running;
+ },
+
+ onFolderAddedIntoQ(queue, folder) {
+ dump("Folder added into Q " + this.qName(queue) + " " + folder.URI + "\n");
+ },
+ onFolderRemovedFromQ(queue, folder) {
+ dump(
+ "Folder removed from Q " + this.qName(queue) + " " + folder.URI + "\n"
+ );
+ },
+ onDownloadStarted(folder, numOfMessages, totalPending) {
+ dump("Folder download started" + folder.URI + "\n");
+ },
+
+ onDownloadCompleted(folder) {
+ dump("Folder download completed" + folder.URI + "\n");
+ if (folder instanceof Ci.nsIMsgFolder) {
+ let index = mailTestUtils.non_strict_index_of(
+ this._waitingForUpdateList,
+ folder
+ );
+ if (index != -1) {
+ this._waitingForUpdateList.splice(index, 1);
+ }
+ if (this._waitingForUpdate && this._waitingForUpdateList.length == 0) {
+ dump("Got last folder update looking for.\n");
+ this._waitingForUpdate = false;
+ this._resolveOnDownloadCompleted();
+ }
+ }
+ },
+
+ onDownloadError(folder) {
+ if (folder instanceof Ci.nsIMsgFolder) {
+ dump("OnDownloadError: " + folder.prettyName + "\n");
+ }
+ },
+
+ onDiscoveryQProcessed(folder, numOfHdrsProcessed, leftToProcess) {
+ dump("onDiscoveryQProcessed: " + folder.prettyName + "\n");
+ let index = mailTestUtils.non_strict_index_of(
+ this._waitingForDiscoveryList,
+ folder
+ );
+ if (index != -1) {
+ this._waitingForDiscoveryList.splice(index, 1);
+ }
+ if (
+ this._waitingForDiscovery &&
+ this._waitingForDiscoveryList.length == 0
+ ) {
+ dump("Got last folder discovery looking for\n");
+ this._waitingForDiscovery = false;
+ this._resolveOnDiscoveryQProcessed();
+ }
+ },
+
+ onAutoSyncInitiated(folder) {},
+ qName(queueType) {
+ if (queueType == Ci.nsIAutoSyncMgrListener.PriorityQueue) {
+ return "priorityQ";
+ }
+ if (queueType == Ci.nsIAutoSyncMgrListener.UpdateQueue) {
+ return "updateQ";
+ }
+ if (queueType == Ci.nsIAutoSyncMgrListener.DiscoveryQueue) {
+ return "discoveryQ";
+ }
+ return "";
+ },
+ get promiseOnDownloadCompleted() {
+ return this._promiseOnDownloadCompleted;
+ },
+ get promiseOnDiscoveryQProcessed() {
+ return this._promiseOnDiscoveryQProcessed;
+ },
+};
+var gAutoSyncListener = new autoSyncListenerPromise();
+
+/*
+ * helper functions
+ */
+
+// load and update a message in the imap fake server
+function addMessageToFolder(folder) {
+ let messages = [];
+ let gMessageGenerator = new MessageGenerator();
+ messages = messages.concat(gMessageGenerator.makeMessage());
+
+ let msgURI = Services.io.newURI(
+ "data:text/plain;base64," + btoa(messages[0].toMessageString())
+ );
+ let ImapMailbox = IMAPPump.daemon.getMailbox(folder.name);
+ // We add messages with \Seen flag set so that we won't accidentally
+ // trigger the code that updates imap folders that have unread messages moved
+ // into them.
+ let message = new ImapMessage(msgURI.spec, ImapMailbox.uidnext++, ["\\Seen"]);
+ ImapMailbox.addMessage(message);
+}
diff --git a/comm/mailnews/imap/test/unit/test_imapChunks.js b/comm/mailnews/imap/test/unit/test_imapChunks.js
new file mode 100644
index 0000000000..4de0d6c2b3
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_imapChunks.js
@@ -0,0 +1,114 @@
+/**
+ * Test bug 92111 - imap download-by-chunks doesn't download complete file if the
+ * server lies about rfc822.size (known to happen for Exchange and gmail)
+ */
+
+var gIMAPDaemon, gServer, gIMAPIncomingServer, gSavedMsgFile;
+
+var gIMAPService = Cc[
+ "@mozilla.org/messenger/messageservice;1?type=imap"
+].getService(Ci.nsIMsgMessageService);
+
+var gFileName = "bug92111";
+var gMsgFile = do_get_file("../../../data/" + gFileName);
+
+add_task(async function run_the_test() {
+ /*
+ * Set up an IMAP server. The bug is only triggered when nsMsgSaveAsListener
+ * is used (i.e., for IMAP and NNTP).
+ */
+ gIMAPDaemon = new ImapDaemon();
+ gServer = makeServer(gIMAPDaemon, "");
+ gIMAPIncomingServer = createLocalIMAPServer(gServer.port);
+
+ // pref tuning: one connection only, turn off notifications
+ Services.prefs.setBoolPref(
+ "mail.server.server1.autosync_offline_stores",
+ false
+ );
+ Services.prefs.setIntPref("mail.server.server1.max_cached_connections", 1);
+ Services.prefs.setBoolPref("mail.biff.play_sound", false);
+ Services.prefs.setBoolPref("mail.biff.show_alert", false);
+ Services.prefs.setBoolPref("mail.biff.show_tray_icon", false);
+ Services.prefs.setBoolPref("mail.biff.animate_dock_icon", false);
+
+ // Crank down the message chunk size to make test cases easier
+ Services.prefs.setBoolPref("mail.server.default.fetch_by_chunks", true);
+ Services.prefs.setIntPref("mail.imap.chunk_size", 1000);
+ Services.prefs.setIntPref("mail.imap.min_chunk_size_threshold", 1500);
+ Services.prefs.setIntPref("mail.imap.chunk_add", 0);
+
+ var inbox = gIMAPDaemon.getMailbox("INBOX");
+
+ /*
+ * Ok, prelude done. Read the original message from disk
+ * (through a file URI), and add it to the Inbox.
+ */
+ var msgfileuri = Services.io
+ .newFileURI(gMsgFile)
+ .QueryInterface(Ci.nsIFileURL);
+
+ let message = new ImapMessage(msgfileuri.spec, inbox.uidnext++, []);
+ // report an artificially low size, like gmail and Exchange do
+ message.setSize(gMsgFile.fileSize - 100);
+ inbox.addMessage(message);
+
+ // Save the message to a local file. IMapMD corresponds to
+ // <profile_dir>/mailtest/ImapMail (where fakeserver puts the IMAP mailbox
+ // files). If we pass the test, we'll remove the file afterwards
+ // (cf. UrlListener), otherwise it's kept in IMapMD.
+ gSavedMsgFile = Services.dirsvc.get("IMapMD", Ci.nsIFile);
+ gSavedMsgFile.append(gFileName + ".eml");
+
+ do_test_pending();
+ do_timeout(10000, function () {
+ do_throw(
+ "SaveMessageToDisk did not complete within 10 seconds" +
+ "(incorrect messageURI?). ABORTING."
+ );
+ });
+
+ // Enforcing canonicalLineEnding (i.e., CRLF) makes sure that the
+ // test also runs successfully on platforms not using CRLF by default.
+ let promiseUrlListener = new PromiseTestUtils.PromiseUrlListener();
+ gIMAPService.SaveMessageToDisk(
+ "imap-message://user@localhost/INBOX#" + (inbox.uidnext - 1),
+ gSavedMsgFile,
+ false,
+ promiseUrlListener,
+ {},
+ true,
+ null
+ );
+ await promiseUrlListener.promise;
+
+ let msgFileContent = await IOUtils.readUTF8(gMsgFile.path);
+ let savedMsgFileContent = await IOUtils.readUTF8(gSavedMsgFile.path);
+ // File contents should not have been modified.
+ Assert.equal(msgFileContent, savedMsgFileContent);
+
+ // The file doesn't get closed straight away, but does after a little bit.
+ // So wait, and then remove it. We need to test this to ensure we don't
+ // indefinitely lock the file.
+ do_timeout(1000, endTest);
+});
+
+function endTest() {
+ gIMAPIncomingServer.closeCachedConnections();
+ gServer.stop();
+ var thread = gThreadManager.currentThread;
+ while (thread.hasPendingEvents()) {
+ thread.processNextEvent(true);
+ }
+
+ try {
+ gSavedMsgFile.remove(false);
+ } catch (ex) {
+ dump(ex);
+ do_throw(ex);
+ }
+ do_test_finished();
+}
+
+// XXX IRVING we need a separate check somehow to make sure we store the correct
+// content size for chunked messages where the server lied
diff --git a/comm/mailnews/imap/test/unit/test_imapClientid.js b/comm/mailnews/imap/test/unit/test_imapClientid.js
new file mode 100644
index 0000000000..bceb7c05bf
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_imapClientid.js
@@ -0,0 +1,64 @@
+/* Any copyright is dedicated to the Public Domain.
+http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+var incomingServer, server;
+
+const kUserName = "user";
+const kValidPassword = "password";
+
+var gTests = [
+ {
+ title: "Cleartext password, with server only supporting old-style login",
+ clientAuthMethod: Ci.nsMsgAuthMethod.passwordCleartext,
+ serverAuthMethods: [],
+ expectSuccess: true,
+ transaction: [
+ "capability",
+ "CLIENTID",
+ "authenticate PLAIN",
+ "capability",
+ "list",
+ "lsub",
+ ],
+ },
+];
+
+add_task(async function () {
+ let daemon = new ImapDaemon();
+ server = makeServer(daemon, "", {
+ // Make username of server match the singons.txt file
+ // (pw there is intentionally invalid)
+ kUsername: kUserName,
+ kPassword: kValidPassword,
+ });
+ server.setDebugLevel(fsDebugAll);
+ incomingServer = createLocalIMAPServer(server.port);
+
+ // Turn on CLIENTID and populate the clientid with a uuid.
+ incomingServer.clientidEnabled = true;
+ incomingServer.clientid = "4d8776ca-0251-11ea-8d71-362b9e155667";
+
+ // Connect.
+ incomingServer.performExpand(null);
+ server.performTest("LSUB");
+
+ do_check_transaction(server.playTransaction(), gTests[0].transaction, false);
+
+ server.resetTest();
+});
+
+registerCleanupFunction(function () {
+ incomingServer.closeCachedConnections();
+ server.stop();
+
+ var thread = gThreadManager.currentThread;
+ while (thread.hasPendingEvents()) {
+ thread.processNextEvent(true);
+ }
+});
diff --git a/comm/mailnews/imap/test/unit/test_imapContentLength.js b/comm/mailnews/imap/test/unit/test_imapContentLength.js
new file mode 100644
index 0000000000..acb5001242
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_imapContentLength.js
@@ -0,0 +1,98 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Test content length for the IMAP protocol. This focuses on necko URLs
+ * that are run externally.
+ */
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+var gMsgHdr = null;
+
+// Take a multipart message as we're testing attachment URLs as well
+var gFile = do_get_file("../../../data/multipart-complex2");
+
+add_setup(function () {
+ setupIMAPPump();
+
+ // Set up nsIMsgFolderListener to get the header when it's received
+ MailServices.mfn.addListener(msgAddedListener, MailServices.mfn.msgAdded);
+
+ IMAPPump.inbox.clearFlag(Ci.nsMsgFolderFlags.Offline);
+});
+
+// Adds some messages directly to a mailbox (eg new mail)
+add_task(async function addMessageToServer() {
+ let URI = Services.io.newFileURI(gFile).QueryInterface(Ci.nsIFileURL);
+ IMAPPump.mailbox.addMessage(
+ new ImapMessage(URI.spec, IMAPPump.mailbox.uidnext++, [])
+ );
+
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.updateFolderWithListener(null, listener);
+ await listener.promise;
+});
+
+function MsgAddedListener() {
+ this._promise = new Promise(resolve => {
+ this._resolve = resolve;
+ });
+}
+MsgAddedListener.prototype = {
+ msgAdded(aMsgHdr) {
+ gMsgHdr = aMsgHdr;
+ this._resolve();
+ },
+ get promise() {
+ return this._promise;
+ },
+};
+var msgAddedListener = new MsgAddedListener();
+
+add_task(async function verifyContentLength() {
+ await msgAddedListener.promise;
+ let messageUri = IMAPPump.inbox.getUriForMsg(gMsgHdr);
+ // Convert this to a URI that necko can run
+ let messageService = MailServices.messageServiceFromURI(messageUri);
+ let neckoURL = messageService.getUrlForUri(messageUri);
+ // Don't use the necko URL directly. Instead, get the spec and create a new
+ // URL using the IO service
+ let urlToRun = Services.io.newURI(neckoURL.spec);
+
+ // Get a channel from this URI, and check its content length
+ let channel = Services.io.newChannelFromURI(
+ urlToRun,
+ null,
+ Services.scriptSecurityManager.getSystemPrincipal(),
+ null,
+ Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ Ci.nsIContentPolicy.TYPE_OTHER
+ );
+ Assert.equal(channel.contentLength, gFile.fileSize);
+
+ // Now try an attachment. &part=1.2
+ let attachmentURL = Services.io.newURI(neckoURL.spec + "&part=1.2");
+ let attachmentChannel = Services.io.newChannelFromURI(
+ attachmentURL,
+ null,
+ Services.scriptSecurityManager.getSystemPrincipal(),
+ null,
+ Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ Ci.nsIContentPolicy.TYPE_OTHER
+ );
+ // Currently attachments have their content length set to the length of the
+ // entire message
+ Assert.equal(attachmentChannel.contentLength, gFile.fileSize);
+});
+
+add_task(function endTest() {
+ MailServices.mfn.removeListener(msgAddedListener);
+ teardownIMAPPump();
+});
diff --git a/comm/mailnews/imap/test/unit/test_imapCopyTimeout.js b/comm/mailnews/imap/test/unit/test_imapCopyTimeout.js
new file mode 100644
index 0000000000..75d13159f1
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_imapCopyTimeout.js
@@ -0,0 +1,120 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 tests our handling of server timeouts during online move of
+// an imap message. The move is done as an offline operation and then
+// played back, to copy what the apps do.
+
+Services.prefs.setIntPref("mailnews.tcptimeout", 2);
+
+/* import-globals-from ../../../test/resources/alertTestUtils.js */
+load("../../../resources/alertTestUtils.js");
+
+var { MessageGenerator } = ChromeUtils.import(
+ "resource://testing-common/mailnews/MessageGenerator.jsm"
+);
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+var gTargetFolder;
+var alertResolve;
+var alertPromise = new Promise(resolve => {
+ alertResolve = resolve;
+});
+
+function alertPS(parent, aDialogTitle, aText) {
+ alertResolve(aText);
+}
+
+add_setup(function () {
+ registerAlertTestUtils();
+ setupIMAPPump();
+ Services.prefs.setBoolPref(
+ "mail.server.default.autosync_offline_stores",
+ false
+ );
+});
+
+add_task(async function createTargetFolder() {
+ IMAPPump.daemon.copySleep = 5000;
+ IMAPPump.incomingServer.rootFolder.createSubfolder("targetFolder", null);
+ await PromiseTestUtils.promiseFolderAdded("targetFolder");
+ gTargetFolder =
+ IMAPPump.incomingServer.rootFolder.getChildNamed("targetFolder");
+ Assert.ok(gTargetFolder instanceof Ci.nsIMsgImapMailFolder);
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ gTargetFolder.updateFolderWithListener(null, listener);
+ await listener.promise;
+});
+
+// load and update a message in the imap fake server
+add_task(async function loadImapMessage() {
+ let messages = [];
+ let gMessageGenerator = new MessageGenerator();
+ messages = messages.concat(gMessageGenerator.makeMessage());
+
+ let msgURI = Services.io.newURI(
+ "data:text/plain;base64," + btoa(messages[0].toMessageString())
+ );
+ let imapInbox = IMAPPump.daemon.getMailbox("INBOX");
+ var gMessage = new ImapMessage(msgURI.spec, imapInbox.uidnext++, []);
+ IMAPPump.mailbox.addMessage(gMessage);
+
+ let promiseUrlListener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.updateFolderWithListener(null, promiseUrlListener);
+ await promiseUrlListener.promise;
+ Assert.equal(1, IMAPPump.inbox.getTotalMessages(false));
+ let msgHdr = mailTestUtils.firstMsgHdr(IMAPPump.inbox);
+ Assert.ok(msgHdr instanceof Ci.nsIMsgDBHdr);
+});
+
+// move the message to a diffent folder
+add_task(async function moveMessageToTargetFolder() {
+ let msgHdr = mailTestUtils.firstMsgHdr(IMAPPump.inbox);
+ // This should cause the move to be done as an offline imap operation
+ // that's played back immediately.
+ let copyListener = new PromiseTestUtils.PromiseCopyListener();
+ MailServices.copy.copyMessages(
+ IMAPPump.inbox,
+ [msgHdr],
+ gTargetFolder,
+ true,
+ copyListener,
+ gDummyMsgWindow,
+ true
+ );
+ await copyListener.promise;
+});
+
+add_task(async function waitForOfflinePlayback() {
+ // Just wait for the alert about timed out connection.
+ let alertText = await alertPromise;
+ Assert.ok(alertText.startsWith("Connection to server localhost timed out."));
+});
+
+add_task(async function updateTargetFolderAndInbox() {
+ let urlListenerTargetFolder = new PromiseTestUtils.PromiseUrlListener();
+ gTargetFolder.updateFolderWithListener(null, urlListenerTargetFolder);
+ await urlListenerTargetFolder.promise;
+ let urlListenerInbox = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.updateFolderWithListener(null, urlListenerInbox);
+ await urlListenerInbox.promise;
+});
+
+// Cleanup
+add_task(async function endTest() {
+ // Make sure neither source nor target folder have offline events.
+ Assert.ok(!IMAPPump.inbox.getFlag(Ci.nsMsgFolderFlags.OfflineEvents));
+ Assert.ok(!gTargetFolder.getFlag(Ci.nsMsgFolderFlags.OfflineEvents));
+
+ // fake server does the copy, but then times out, so make sure the target
+ // folder has only 1 message, not the multiple ones it would have if we
+ // retried.
+ Assert.equal(gTargetFolder.getTotalMessages(false), 1);
+ teardownIMAPPump();
+});
diff --git a/comm/mailnews/imap/test/unit/test_imapFilterActions.js b/comm/mailnews/imap/test/unit/test_imapFilterActions.js
new file mode 100644
index 0000000000..21cd2d01aa
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_imapFilterActions.js
@@ -0,0 +1,597 @@
+/*
+ * This file tests imap filter actions, particularly as affected by the
+ * addition of body searches in bug 127250. Actions that involves sending
+ * mail are not tested. The tests check various counts, and the effects
+ * on the message database of the filters. Effects on IMAP server
+ * flags, if any, are not tested.
+ *
+ * Original author: Kent James <kent@caspia.com>
+ * adapted from test_localToImapFilter.js
+ */
+
+var Is = Ci.nsMsgSearchOp.Is;
+var Contains = Ci.nsMsgSearchOp.Contains;
+var Subject = Ci.nsMsgSearchAttrib.Subject;
+var Body = Ci.nsMsgSearchAttrib.Body;
+
+// Globals
+var gSubfolder; // a local message folder used as a target for moves and copies
+var gFilter; // a message filter with a subject search
+var gAction; // current message action (reused)
+var gBodyFilter; // a message filter with a body search
+var gInboxListener; // database listener object
+var gHeader; // the current message db header
+var gInboxCount; // the previous number of messages in the Inbox
+var gSubfolderCount; // the previous number of messages in the subfolder
+var gMessage = "image-attach-test"; // message file used as the test message
+
+// subject of the test message
+var gMessageSubject = "image attach test";
+
+// a string in the body of the test message
+var gMessageInBody = "01234567890test";
+
+// various object references
+var gDbService = Cc["@mozilla.org/msgDatabase/msgDBService;1"].getService(
+ Ci.nsIMsgDBService
+);
+
+var gSmtpServerD = setupSmtpServerDaemon();
+
+function setupSmtpServer() {
+ gSmtpServerD.start();
+ var gSmtpServer = localAccountUtils.create_outgoing_server(
+ gSmtpServerD.port,
+ "user",
+ "password",
+ "localhost"
+ );
+ MailServices.accounts.defaultAccount.defaultIdentity.email =
+ "from@tinderbox.invalid";
+ MailServices.accounts.defaultAccount.defaultIdentity.smtpServerKey =
+ gSmtpServer.key;
+
+ registerCleanupFunction(() => {
+ gSmtpServerD.stop();
+ Services.prefs.clearUserPref("mail.forward_message_mode");
+ });
+}
+
+// Definition of tests. The test function name is the filter action
+// being tested, with "Body" appended to tests that use delayed
+// application of filters due to a body search
+var gTestArray = [
+ setupIMAPPump,
+ // optionally set server parameters, here enabling debug messages
+ // function serverParms() {
+ // IMAPPump.server.setDebugLevel(fsDebugAll);
+ // },
+ setupSmtpServer,
+ setupFilters,
+ // The initial tests do not result in new messages added.
+ async function MoveToFolder() {
+ gAction.type = Ci.nsMsgFilterAction.MoveToFolder;
+ gAction.targetFolderUri = gSubfolder.URI;
+ gInboxCount = folderCount(IMAPPump.inbox);
+ gSubfolderCount = folderCount(gSubfolder);
+ await setupTest(gFilter, gAction);
+
+ testCounts(false, 0, 0, 0);
+ Assert.equal(gInboxCount, folderCount(IMAPPump.inbox));
+ Assert.equal(gSubfolderCount + 1, folderCount(gSubfolder));
+ },
+ // do it again, sometimes that causes multiple downloads
+ async function MoveToFolder2() {
+ gAction.type = Ci.nsMsgFilterAction.MoveToFolder;
+ gAction.targetFolderUri = gSubfolder.URI;
+ gInboxCount = folderCount(IMAPPump.inbox);
+ gSubfolderCount = folderCount(gSubfolder);
+ await setupTest(gFilter, gAction);
+
+ testCounts(false, 0, 0, 0);
+ Assert.equal(gInboxCount, folderCount(IMAPPump.inbox));
+ Assert.equal(gSubfolderCount + 1, folderCount(gSubfolder));
+ },
+ /**/
+ async function MoveToFolderBody() {
+ gAction.type = Ci.nsMsgFilterAction.MoveToFolder;
+ gAction.targetFolderUri = gSubfolder.URI;
+ gInboxCount = folderCount(IMAPPump.inbox);
+ gSubfolderCount = folderCount(gSubfolder);
+ await setupTest(gBodyFilter, gAction);
+
+ testCounts(false, 0, 0, 0);
+ Assert.equal(gSubfolderCount + 1, folderCount(gSubfolder));
+ // no net messages were added to the inbox
+ Assert.equal(gInboxCount, folderCount(IMAPPump.inbox));
+ },
+ async function MoveToFolderBody2() {
+ gAction.type = Ci.nsMsgFilterAction.MoveToFolder;
+ gAction.targetFolderUri = gSubfolder.URI;
+ gInboxCount = folderCount(IMAPPump.inbox);
+ gSubfolderCount = folderCount(gSubfolder);
+ await setupTest(gBodyFilter, gAction);
+
+ testCounts(false, 0, 0, 0);
+ Assert.equal(gSubfolderCount + 1, folderCount(gSubfolder));
+ // no net messages were added to the inbox
+ Assert.equal(gInboxCount, folderCount(IMAPPump.inbox));
+ },
+ async function MarkRead() {
+ gAction.type = Ci.nsMsgFilterAction.MarkRead;
+ gInboxCount = folderCount(IMAPPump.inbox);
+ await setupTest(gFilter, gAction);
+ testCounts(false, 0, 0, 0);
+ Assert.ok(gHeader.isRead);
+ Assert.equal(gInboxCount + 1, folderCount(IMAPPump.inbox));
+ },
+ async function MarkReadBody() {
+ gAction.type = Ci.nsMsgFilterAction.MarkRead;
+ gInboxCount = folderCount(IMAPPump.inbox);
+ await setupTest(gBodyFilter, gAction);
+
+ testCounts(false, 0, 0, 0);
+ Assert.ok(gHeader.isRead);
+ Assert.equal(gInboxCount + 1, folderCount(IMAPPump.inbox));
+ },
+ async function KillThread() {
+ gAction.type = Ci.nsMsgFilterAction.KillThread;
+ await setupTest(gFilter, gAction);
+
+ testCounts(false, 0, 0, 0);
+ let thread = db().getThreadContainingMsgHdr(gHeader);
+ Assert.notEqual(0, thread.flags & Ci.nsMsgMessageFlags.Ignored);
+ },
+ async function KillThreadBody() {
+ gAction.type = Ci.nsMsgFilterAction.KillThread;
+ await setupTest(gBodyFilter, gAction);
+
+ testCounts(false, 0, 0, 0);
+ let thread = db().getThreadContainingMsgHdr(gHeader);
+ Assert.notEqual(0, thread.flags & Ci.nsMsgMessageFlags.Ignored);
+ },
+ async function KillSubthread() {
+ gAction.type = Ci.nsMsgFilterAction.KillSubthread;
+ await setupTest(gFilter, gAction);
+
+ testCounts(false, 0, 0, 0);
+ Assert.notEqual(0, gHeader.flags & Ci.nsMsgMessageFlags.Ignored);
+ },
+ async function KillSubthreadBody() {
+ gAction.type = Ci.nsMsgFilterAction.KillSubthread;
+ await setupTest(gBodyFilter, gAction);
+
+ testCounts(false, 0, 0, 0);
+ Assert.notEqual(0, gHeader.flags & Ci.nsMsgMessageFlags.Ignored);
+ },
+ async function DoNothing() {
+ gAction.type = Ci.nsMsgFilterAction.StopExecution;
+ await setupTest(gFilter, gAction);
+
+ testCounts(true, 1, 1, 1);
+ },
+ async function DoNothingBody() {
+ gAction.type = Ci.nsMsgFilterAction.StopExecution;
+ await setupTest(gFilter, gAction);
+
+ testCounts(true, 1, 1, 1);
+ },
+ // this tests for marking message as junk
+ async function JunkScore() {
+ gAction.type = Ci.nsMsgFilterAction.JunkScore;
+ gAction.junkScore = 100;
+ await setupTest(gFilter, gAction);
+
+ // marking as junk resets new but not unread
+ testCounts(false, 1, 0, 0);
+ Assert.equal(gHeader.getStringProperty("junkscore"), "100");
+ Assert.equal(gHeader.getStringProperty("junkscoreorigin"), "filter");
+ },
+ // this tests for marking message as junk
+ async function JunkScoreBody() {
+ gAction.type = Ci.nsMsgFilterAction.JunkScore;
+ gAction.junkScore = 100;
+ await setupTest(gBodyFilter, gAction);
+
+ // marking as junk resets new but not unread
+ testCounts(false, 1, 0, 0);
+ Assert.equal(gHeader.getStringProperty("junkscore"), "100");
+ Assert.equal(gHeader.getStringProperty("junkscoreorigin"), "filter");
+ },
+ // The remaining tests add new messages
+ async function MarkUnread() {
+ gAction.type = Ci.nsMsgFilterAction.MarkUnread;
+ await setupTest(gFilter, gAction);
+
+ testCounts(true, 1, 1, 1);
+ Assert.ok(!gHeader.isRead);
+ },
+ async function MarkUnreadBody() {
+ gAction.type = Ci.nsMsgFilterAction.MarkUnread;
+ await setupTest(gBodyFilter, gAction);
+
+ testCounts(true, 1, 1, 1);
+ Assert.ok(!gHeader.isRead);
+ },
+ async function WatchThread() {
+ gAction.type = Ci.nsMsgFilterAction.WatchThread;
+ await setupTest(gFilter, gAction);
+
+ testCounts(true, 1, 1, 1);
+ let thread = db().getThreadContainingMsgHdr(gHeader);
+ Assert.notEqual(0, thread.flags & Ci.nsMsgMessageFlags.Watched);
+ },
+ async function WatchThreadBody() {
+ gAction.type = Ci.nsMsgFilterAction.WatchThread;
+ await setupTest(gBodyFilter, gAction);
+
+ testCounts(true, 1, 1, 1);
+ let thread = db().getThreadContainingMsgHdr(gHeader);
+ Assert.notEqual(0, thread.flags & Ci.nsMsgMessageFlags.Watched);
+ },
+ async function MarkFlagged() {
+ gAction.type = Ci.nsMsgFilterAction.MarkFlagged;
+ await setupTest(gFilter, gAction);
+
+ testCounts(true, 1, 1, 1);
+ Assert.ok(gHeader.isFlagged);
+ },
+ async function MarkFlaggedBody() {
+ gAction.type = Ci.nsMsgFilterAction.MarkFlagged;
+ await setupTest(gBodyFilter, gAction);
+
+ testCounts(true, 1, 1, 1);
+ Assert.ok(gHeader.isFlagged);
+ },
+ async function ChangePriority() {
+ gAction.type = Ci.nsMsgFilterAction.ChangePriority;
+ gAction.priority = Ci.nsMsgPriority.highest;
+ await setupTest(gFilter, gAction);
+
+ testCounts(true, 1, 1, 1);
+ Assert.equal(Ci.nsMsgPriority.highest, gHeader.priority);
+ },
+ async function ChangePriorityBody() {
+ gAction.type = Ci.nsMsgFilterAction.ChangePriority;
+ gAction.priority = Ci.nsMsgPriority.highest;
+ await setupTest(gBodyFilter, gAction);
+
+ testCounts(true, 1, 1, 1);
+ Assert.equal(Ci.nsMsgPriority.highest, gHeader.priority);
+ },
+ async function AddTag() {
+ gAction.type = Ci.nsMsgFilterAction.AddTag;
+ gAction.strValue = "TheTag";
+ await setupTest(gFilter, gAction);
+
+ testCounts(true, 1, 1, 1);
+ Assert.equal(gHeader.getStringProperty("keywords"), "thetag");
+ },
+ async function AddTagBody() {
+ gAction.type = Ci.nsMsgFilterAction.AddTag;
+ gAction.strValue = "TheTag2";
+ await setupTest(gBodyFilter, gAction);
+
+ testCounts(true, 1, 1, 1);
+ Assert.equal(gHeader.getStringProperty("keywords"), "thetag2");
+ },
+ // this tests for marking message as good
+ async function JunkScoreAsGood() {
+ gAction.type = Ci.nsMsgFilterAction.JunkScore;
+ gAction.junkScore = 0;
+ await setupTest(gFilter, gAction);
+
+ testCounts(true, 1, 1, 1);
+ Assert.equal(gHeader.getStringProperty("junkscore"), "0");
+ Assert.equal(gHeader.getStringProperty("junkscoreorigin"), "filter");
+ },
+ // this tests for marking message as good
+ async function JunkScoreAsGoodBody() {
+ gAction.type = Ci.nsMsgFilterAction.JunkScore;
+ gAction.junkScore = 0;
+ await setupTest(gBodyFilter, gAction);
+
+ testCounts(true, 1, 1, 1);
+ Assert.equal(gHeader.getStringProperty("junkscore"), "0");
+ Assert.equal(gHeader.getStringProperty("junkscoreorigin"), "filter");
+ },
+ async function CopyToFolder() {
+ gAction.type = Ci.nsMsgFilterAction.CopyToFolder;
+ gAction.targetFolderUri = gSubfolder.URI;
+ gInboxCount = folderCount(IMAPPump.inbox);
+ gSubfolderCount = folderCount(gSubfolder);
+ await setupTest(gFilter, gAction);
+
+ testCounts(true, 1, 1, 1);
+ Assert.equal(gInboxCount + 1, folderCount(IMAPPump.inbox));
+ Assert.equal(gSubfolderCount + 1, folderCount(gSubfolder));
+ },
+ async function CopyToFolderBody() {
+ gAction.type = Ci.nsMsgFilterAction.CopyToFolder;
+ gAction.targetFolderUri = gSubfolder.URI;
+ gInboxCount = folderCount(IMAPPump.inbox);
+ gSubfolderCount = folderCount(gSubfolder);
+ await setupTest(gBodyFilter, gAction);
+
+ testCounts(true, 1, 1, 1);
+ Assert.equal(gInboxCount + 1, folderCount(IMAPPump.inbox));
+ Assert.equal(gSubfolderCount + 1, folderCount(gSubfolder));
+ },
+ async function ForwardInline() {
+ return testForward(2);
+ },
+ async function ForwardAsAttachment() {
+ return testForward(0);
+ },
+ /**/
+ endTest,
+];
+
+function run_test() {
+ // Services.prefs.setBoolPref("mail.server.default.autosync_offline_stores", false);
+ gTestArray.forEach(x => add_task(x));
+ run_next_test();
+}
+
+function setupFilters() {
+ // Create a non-body filter.
+ let filterList = IMAPPump.incomingServer.getFilterList(null);
+ gFilter = filterList.createFilter("subject");
+ let searchTerm = gFilter.createTerm();
+ searchTerm.attrib = Subject;
+ searchTerm.op = Is;
+ var value = searchTerm.value;
+ value.attrib = Subject;
+ value.str = gMessageSubject;
+ searchTerm.value = value;
+ searchTerm.booleanAnd = false;
+ gFilter.appendTerm(searchTerm);
+ gFilter.enabled = true;
+
+ // Create a filter with a body term that that forces delayed application of
+ // filters until after body download.
+ gBodyFilter = filterList.createFilter("body");
+ searchTerm = gBodyFilter.createTerm();
+ searchTerm.attrib = Body;
+ searchTerm.op = Contains;
+ value = searchTerm.value;
+ value.attrib = Body;
+ value.str = gMessageInBody;
+ searchTerm.value = value;
+ searchTerm.booleanAnd = false;
+ gBodyFilter.appendTerm(searchTerm);
+ gBodyFilter.enabled = true;
+
+ // an action that can be modified by tests
+ gAction = gFilter.createAction();
+
+ gSubfolder = localAccountUtils.rootFolder.createLocalSubfolder("Subfolder");
+
+ MailServices.mailSession.AddFolderListener(
+ FolderListener,
+ Ci.nsIFolderListener.event
+ );
+ gPreviousUnread = 0;
+
+ // When a message body is not downloaded, and then later a filter is
+ // applied that requires a download of message bodies, then the previous
+ // bodies are downloaded - and the message filters are applied twice!
+ // See bug 1116228, but for now workaround by always downloading bodies.
+ IMAPPump.incomingServer.downloadBodiesOnGetNewMail = true;
+}
+
+/*
+ * functions used to support test setup and execution
+ */
+
+// basic preparation done for each test
+async function setupTest(aFilter, aAction) {
+ let filterList = IMAPPump.incomingServer.getFilterList(null);
+ while (filterList.filterCount) {
+ filterList.removeFilterAt(0);
+ }
+ if (aFilter) {
+ aFilter.clearActionList();
+ if (aAction) {
+ aFilter.appendAction(aAction);
+ filterList.insertFilterAt(0, aFilter);
+ }
+ }
+ if (gInboxListener) {
+ gDbService.unregisterPendingListener(gInboxListener);
+ }
+
+ IMAPPump.inbox.clearNewMessages();
+
+ gInboxListener = new DBListener();
+ gDbService.registerPendingListener(IMAPPump.inbox, gInboxListener);
+ IMAPPump.mailbox.addMessage(
+ new ImapMessage(specForFileName(gMessage), IMAPPump.mailbox.uidnext++, [])
+ );
+ let promiseUrlListener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.updateFolderWithListener(null, promiseUrlListener);
+ await promiseUrlListener.promise;
+ await PromiseTestUtils.promiseDelay(200);
+}
+
+// Cleanup, null out everything, close all cached connections and stop the
+// server
+function endTest() {
+ if (gInboxListener) {
+ gDbService.unregisterPendingListener(gInboxListener);
+ }
+ gInboxListener = null;
+ MailServices.mailSession.RemoveFolderListener(FolderListener);
+ teardownIMAPPump();
+}
+
+/*
+ * listener objects
+ */
+
+// nsIFolderListener implementation
+var FolderListener = {
+ onFolderEvent(aEventFolder, aEvent) {
+ dump(
+ "received folder event " + aEvent + " folder " + aEventFolder.name + "\n"
+ );
+ },
+};
+
+// nsIDBChangeListener implementation. Counts of calls are kept, but not
+// currently used in the tests. Current role is to provide a reference
+// to the new message header (plus give some examples of using db listeners
+// in javascript).
+function DBListener() {
+ this.counts = {};
+ let counts = this.counts;
+ counts.onHdrFlagsChanged = 0;
+ counts.onHdrDeleted = 0;
+ counts.onHdrAdded = 0;
+ counts.onParentChanged = 0;
+ counts.onAnnouncerGoingAway = 0;
+ counts.onReadChanged = 0;
+ counts.onJunkScoreChanged = 0;
+ counts.onHdrPropertyChanged = 0;
+ counts.onEvent = 0;
+}
+
+DBListener.prototype = {
+ onHdrFlagsChanged(aHdrChanged, aOldFlags, aNewFlags, aInstigator) {
+ this.counts.onHdrFlagsChanged++;
+ },
+
+ onHdrDeleted(aHdrChanged, aParentKey, Flags, aInstigator) {
+ this.counts.onHdrDeleted++;
+ },
+
+ onHdrAdded(aHdrChanged, aParentKey, aFlags, aInstigator) {
+ this.counts.onHdrAdded++;
+ gHeader = aHdrChanged;
+ },
+
+ onParentChanged(aKeyChanged, oldParent, newParent, aInstigator) {
+ this.counts.onParentChanged++;
+ },
+
+ onAnnouncerGoingAway(instigator) {
+ if (gInboxListener) {
+ try {
+ IMAPPump.inbox.msgDatabase.removeListener(gInboxListener);
+ } catch (e) {
+ dump(" listener not found\n");
+ }
+ }
+ this.counts.onAnnouncerGoingAway++;
+ },
+
+ onReadChanged(aInstigator) {
+ this.counts.onReadChanged++;
+ },
+
+ onJunkScoreChanged(aInstigator) {
+ this.counts.onJunkScoreChanged++;
+ },
+
+ onHdrPropertyChanged(aHdrToChange, aPreChange, aStatus, aInstigator) {
+ this.counts.onHdrPropertyChanged++;
+ },
+
+ onEvent(aDB, aEvent) {
+ this.counts.onEvent++;
+ },
+};
+
+/*
+ * helper functions
+ */
+
+// return the number of messages in a folder (and check that the
+// folder counts match the database counts)
+function folderCount(folder) {
+ // count using the database
+ let dbCount = [...folder.msgDatabase.enumerateMessages()].length;
+
+ // count using the folder
+ let count = folder.getTotalMessages(false);
+
+ // compare the two
+ Assert.equal(dbCount, count);
+ return dbCount;
+}
+
+// given a test file, return the file uri spec
+function specForFileName(aFileName) {
+ let file = do_get_file("../../../data/" + aFileName);
+ let msgfileuri = Services.io.newFileURI(file).QueryInterface(Ci.nsIFileURL);
+ return msgfileuri.spec;
+}
+
+// shorthand for the inbox message summary database
+function db() {
+ return IMAPPump.inbox.msgDatabase;
+}
+
+// static variables used in testCounts
+var gPreviousUnread = 0;
+
+// Test various counts.
+//
+// aHasNew: folder hasNew flag
+// aUnreadDelta: change in unread count for the folder
+// aFolderNewDelta: change in new count for the folder
+// aDbNewDelta: change in new count for the database
+//
+function testCounts(aHasNew, aUnreadDelta, aFolderNewDelta, aDbNewDelta) {
+ try {
+ let folderNew = IMAPPump.inbox.getNumNewMessages(false);
+ let hasNew = IMAPPump.inbox.hasNewMessages;
+ let unread = IMAPPump.inbox.getNumUnread(false);
+ let arrayOut = db().getNewList();
+ let dbNew = arrayOut.length;
+ dump(
+ " hasNew: " +
+ hasNew +
+ " unread: " +
+ unread +
+ " folderNew: " +
+ folderNew +
+ " dbNew: " +
+ dbNew +
+ " prevUnread " +
+ gPreviousUnread +
+ "\n"
+ );
+ Assert.equal(aHasNew, hasNew);
+ Assert.equal(aUnreadDelta, unread - gPreviousUnread);
+ gPreviousUnread = unread;
+ // This seems to be reset for each folder update.
+ //
+ // This check seems to be failing in SeaMonkey builds, yet I can see no ill
+ // effects of this in the actual program. Fixing this is complex because of
+ // the messiness of new count management (see bug 507638 for a
+ // refactoring proposal, and attachment 398899 on bug 514801 for one possible
+ // fix to this particular test). So I am disabling this.
+ // Assert.equal(aFolderNewDelta, folderNew);
+ Assert.equal(aDbNewDelta, dbNew);
+ } catch (e) {
+ dump(e);
+ }
+}
+
+/**
+ * Test that Ci.nsMsgFilterAction.Forward works.
+ *
+ * @param {number} mode - 0 means forward as attachment, 2 means forward inline.
+ */
+async function testForward(mode) {
+ Services.prefs.setIntPref("mail.forward_message_mode", mode);
+
+ gSmtpServerD.resetTest();
+ gAction.type = Ci.nsMsgFilterAction.Forward;
+ gAction.strValue = "to@local";
+ await setupTest(gFilter, gAction);
+ let msgData = gSmtpServerD._daemon.post;
+ Assert.ok(msgData.includes(`Subject: Fwd: ${gMessageSubject}`));
+ Assert.ok(msgData.includes(`${gMessageInBody}`));
+}
diff --git a/comm/mailnews/imap/test/unit/test_imapFilterActionsPostplugin.js b/comm/mailnews/imap/test/unit/test_imapFilterActionsPostplugin.js
new file mode 100644
index 0000000000..2d7e00efcc
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_imapFilterActionsPostplugin.js
@@ -0,0 +1,428 @@
+/*
+ * This file tests imap filter actions post-plugin, which uses nsMsgFilterAfterTheFact
+ *
+ * Original author: Kent James <kent@caspia.com>
+ * adapted from test_imapFilterActions.js
+ */
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+var Is = Ci.nsMsgSearchOp.Is;
+var Subject = Ci.nsMsgSearchAttrib.Subject;
+
+// Globals
+var gSubfolder; // a local message folder used as a target for moves and copies
+var gFilter; // a message filter with a subject search
+var gAction; // current message action (reused)
+var gInboxListener; // database listener object
+var gHeader; // the current message db header
+var gInboxCount; // the previous number of messages in the Inbox
+var gSubfolderCount; // the previous number of messages in the subfolder
+var gMessage = "draft1"; // message file used as the test message
+
+// subject of the test message
+var gMessageSubject = "Hello, did you receive my bugmail?";
+
+// various object references
+var gDbService = Cc["@mozilla.org/msgDatabase/msgDBService;1"].getService(
+ Ci.nsIMsgDBService
+);
+
+// Definition of tests. The test function name is the filter action
+// being tested, with "Body" appended to tests that use delayed
+// application of filters due to a body search
+var gTestArray = [
+ setupIMAPPump,
+ setupFilters,
+ async function DoNothing() {
+ gAction.type = Ci.nsMsgFilterAction.StopExecution;
+ gInboxCount = folderCount(IMAPPump.inbox);
+ await setupTest(gFilter, gAction);
+ testCounts(false, 1, 0, 0);
+ Assert.equal(gInboxCount + 1, folderCount(IMAPPump.inbox));
+ },
+ async function Delete() {
+ gAction.type = Ci.nsMsgFilterAction.Delete;
+ gInboxCount = folderCount(IMAPPump.inbox);
+ await setupTest(gFilter, gAction);
+ testCounts(false, 0, 0, 0);
+ Assert.equal(gInboxCount, folderCount(IMAPPump.inbox));
+ },
+ async function MoveToFolder() {
+ gAction.type = Ci.nsMsgFilterAction.MoveToFolder;
+ gAction.targetFolderUri = gSubfolder.URI;
+ gInboxCount = folderCount(IMAPPump.inbox);
+ gSubfolderCount = folderCount(gSubfolder);
+ await setupTest(gFilter, gAction);
+
+ testCounts(false, 0, 0, 0);
+ Assert.equal(gSubfolderCount + 1, folderCount(gSubfolder));
+ // no net messages were added to the inbox
+ Assert.equal(gInboxCount, folderCount(IMAPPump.inbox));
+ },
+ async function MarkRead() {
+ gAction.type = Ci.nsMsgFilterAction.MarkRead;
+ await setupTest(gFilter, gAction);
+ testCounts(false, 0, 0, 0);
+ Assert.ok(gHeader.isRead);
+ },
+ async function KillThread() {
+ gAction.type = Ci.nsMsgFilterAction.KillThread;
+ await setupTest(gFilter, gAction);
+ // In non-postplugin, count here is 0 and not 1. Need to investigate.
+ testCounts(false, 1, 0, 0);
+ let thread = db().getThreadContainingMsgHdr(gHeader);
+ Assert.notEqual(0, thread.flags & Ci.nsMsgMessageFlags.Ignored);
+ },
+ async function WatchThread() {
+ gAction.type = Ci.nsMsgFilterAction.WatchThread;
+ await setupTest(gFilter, gAction);
+ // In non-postplugin, count here is 0 and not 1. Need to investigate.
+ testCounts(false, 1, 0, 0);
+ let thread = db().getThreadContainingMsgHdr(gHeader);
+ Assert.notEqual(0, thread.flags & Ci.nsMsgMessageFlags.Watched);
+ },
+ async function KillSubthread() {
+ gAction.type = Ci.nsMsgFilterAction.KillSubthread;
+ await setupTest(gFilter, gAction);
+ // In non-postplugin, count here is 0 and not 1. Need to investigate.
+ testCounts(false, 1, 0, 0);
+ Assert.notEqual(0, gHeader.flags & Ci.nsMsgMessageFlags.Ignored);
+ },
+ // this tests for marking message as junk
+ async function JunkScore() {
+ gAction.type = Ci.nsMsgFilterAction.JunkScore;
+ gAction.junkScore = 100;
+ await setupTest(gFilter, gAction);
+ // marking as junk resets new but not unread
+ testCounts(false, 1, 0, 0);
+ Assert.equal(gHeader.getStringProperty("junkscore"), "100");
+ Assert.equal(gHeader.getStringProperty("junkscoreorigin"), "filter");
+ },
+ async function MarkUnread() {
+ gAction.type = Ci.nsMsgFilterAction.MarkUnread;
+ await setupTest(gFilter, gAction);
+ testCounts(true, 1, 1, 1);
+ Assert.ok(!gHeader.isRead);
+ },
+ async function MarkFlagged() {
+ gAction.type = Ci.nsMsgFilterAction.MarkFlagged;
+ await setupTest(gFilter, gAction);
+ testCounts(true, 1, 1, 1);
+ Assert.ok(gHeader.isFlagged);
+ },
+ async function ChangePriority() {
+ gAction.type = Ci.nsMsgFilterAction.ChangePriority;
+ gAction.priority = Ci.nsMsgPriority.highest;
+ await setupTest(gFilter, gAction);
+ testCounts(true, 1, 1, 1);
+ Assert.equal(Ci.nsMsgPriority.highest, gHeader.priority);
+ },
+ async function AddTag() {
+ gAction.type = Ci.nsMsgFilterAction.AddTag;
+ gAction.strValue = "TheTag";
+ await setupTest(gFilter, gAction);
+ testCounts(true, 1, 1, 1);
+ Assert.equal(gHeader.getStringProperty("keywords"), "thetag");
+ },
+ // this tests for marking message as good
+ async function JunkScoreAsGood() {
+ gAction.type = Ci.nsMsgFilterAction.JunkScore;
+ gAction.junkScore = 0;
+ await setupTest(gFilter, gAction);
+ testCounts(true, 1, 1, 1);
+ Assert.equal(gHeader.getStringProperty("junkscore"), "0");
+ Assert.equal(gHeader.getStringProperty("junkscoreorigin"), "filter");
+ },
+ async function CopyToFolder() {
+ gAction.type = Ci.nsMsgFilterAction.CopyToFolder;
+ gAction.targetFolderUri = gSubfolder.URI;
+ gInboxCount = folderCount(IMAPPump.inbox);
+ gSubfolderCount = folderCount(gSubfolder);
+ await setupTest(gFilter, gAction);
+ testCounts(true, 1, 1, 1);
+ Assert.equal(gInboxCount + 1, folderCount(IMAPPump.inbox));
+ Assert.equal(gSubfolderCount + 1, folderCount(gSubfolder));
+ },
+ async function Custom() {
+ gAction.type = Ci.nsMsgFilterAction.Custom;
+ gAction.customId = "mailnews@mozilla.org#testOffline";
+ gAction.strValue = "true";
+ actionTestOffline.needsBody = true;
+ await setupTest(gFilter, gAction);
+ testCounts(true, 1, 1, 1);
+ },
+ /**/
+ endTest,
+];
+
+function run_test() {
+ Services.prefs.setBoolPref(
+ "mail.server.default.autosync_offline_stores",
+ false
+ );
+ gTestArray.forEach(x => add_task(x));
+ run_next_test();
+}
+
+function setupFilters() {
+ // Create a non-body filter.
+ let filterList = IMAPPump.incomingServer.getFilterList(null);
+ gFilter = filterList.createFilter("subject");
+ let searchTerm = gFilter.createTerm();
+ searchTerm.attrib = Subject;
+ searchTerm.op = Is;
+ var value = searchTerm.value;
+ value.attrib = Subject;
+ value.str = gMessageSubject;
+ searchTerm.value = value;
+ searchTerm.booleanAnd = false;
+ gFilter.appendTerm(searchTerm);
+ gFilter.filterType = Ci.nsMsgFilterType.PostPlugin;
+ gFilter.enabled = true;
+
+ // an action that can be modified by tests
+ gAction = gFilter.createAction();
+
+ MailServices.filters.addCustomAction(actionTestOffline);
+ MailServices.mailSession.AddFolderListener(
+ FolderListener,
+ Ci.nsIFolderListener.event
+ );
+ gSubfolder = localAccountUtils.rootFolder.createLocalSubfolder("Subfolder");
+ gPreviousUnread = 0;
+}
+
+/*
+ * functions used to support test setup and execution
+ */
+
+// basic preparation done for each test
+async function setupTest(aFilter, aAction) {
+ let filterList = IMAPPump.incomingServer.getFilterList(null);
+ while (filterList.filterCount) {
+ filterList.removeFilterAt(0);
+ }
+ if (aFilter) {
+ aFilter.clearActionList();
+ if (aAction) {
+ aFilter.appendAction(aAction);
+ filterList.insertFilterAt(0, aFilter);
+ }
+ }
+ if (gInboxListener) {
+ gDbService.unregisterPendingListener(gInboxListener);
+ }
+
+ gInboxListener = new DBListener();
+ gDbService.registerPendingListener(IMAPPump.inbox, gInboxListener);
+ IMAPPump.mailbox.addMessage(
+ new ImapMessage(specForFileName(gMessage), IMAPPump.mailbox.uidnext++, [])
+ );
+ let promiseUrlListener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.updateFolderWithListener(null, promiseUrlListener);
+ await promiseUrlListener.promise;
+ await PromiseTestUtils.promiseDelay(200);
+}
+
+// Cleanup, null out everything, close all cached connections and stop the
+// server
+function endTest() {
+ if (gInboxListener) {
+ gDbService.unregisterPendingListener(gInboxListener);
+ }
+ gInboxListener = null;
+ MailServices.mailSession.RemoveFolderListener(FolderListener);
+ teardownIMAPPump();
+}
+
+/*
+ * listener objects
+ */
+
+// nsIFolderListener implementation
+var FolderListener = {
+ onFolderEvent(aEventFolder, aEvent) {
+ dump(
+ "received folder event " + aEvent + " folder " + aEventFolder.name + "\n"
+ );
+ },
+};
+
+// nsIDBChangeListener implementation. Counts of calls are kept, but not
+// currently used in the tests. Current role is to provide a reference
+// to the new message header (plus give some examples of using db listeners
+// in javascript).
+function DBListener() {
+ this.counts = {};
+ let counts = this.counts;
+ counts.onHdrFlagsChanged = 0;
+ counts.onHdrDeleted = 0;
+ counts.onHdrAdded = 0;
+ counts.onParentChanged = 0;
+ counts.onAnnouncerGoingAway = 0;
+ counts.onReadChanged = 0;
+ counts.onJunkScoreChanged = 0;
+ counts.onHdrPropertyChanged = 0;
+ counts.onEvent = 0;
+}
+
+DBListener.prototype = {
+ onHdrFlagsChanged(aHdrChanged, aOldFlags, aNewFlags, aInstigator) {
+ this.counts.onHdrFlagsChanged++;
+ },
+
+ onHdrDeleted(aHdrChanged, aParentKey, Flags, aInstigator) {
+ this.counts.onHdrDeleted++;
+ },
+
+ onHdrAdded(aHdrChanged, aParentKey, aFlags, aInstigator) {
+ this.counts.onHdrAdded++;
+ gHeader = aHdrChanged;
+ },
+
+ onParentChanged(aKeyChanged, oldParent, newParent, aInstigator) {
+ this.counts.onParentChanged++;
+ },
+
+ onAnnouncerGoingAway(instigator) {
+ if (gInboxListener) {
+ try {
+ IMAPPump.inbox.msgDatabase.removeListener(gInboxListener);
+ } catch (e) {
+ dump(" listener not found\n");
+ }
+ }
+ this.counts.onAnnouncerGoingAway++;
+ },
+
+ onReadChanged(aInstigator) {
+ this.counts.onReadChanged++;
+ },
+
+ onJunkScoreChanged(aInstigator) {
+ this.counts.onJunkScoreChanged++;
+ },
+
+ onHdrPropertyChanged(aHdrToChange, aPreChange, aStatus, aInstigator) {
+ this.counts.onHdrPropertyChanged++;
+ },
+
+ onEvent(aDB, aEvent) {
+ this.counts.onEvent++;
+ },
+};
+
+/*
+ * helper functions
+ */
+
+// return the number of messages in a folder (and check that the
+// folder counts match the database counts)
+function folderCount(folder) {
+ // count using the database
+ let dbCount = [...folder.msgDatabase.enumerateMessages()].length;
+
+ // count using the folder
+ let count = folder.getTotalMessages(false);
+
+ // compare the two
+ Assert.equal(dbCount, count);
+ return dbCount;
+}
+
+// given a test file, return the file uri spec
+function specForFileName(aFileName) {
+ let file = do_get_file("../../../data/" + aFileName);
+ let msgfileuri = Services.io.newFileURI(file).QueryInterface(Ci.nsIFileURL);
+ return msgfileuri.spec;
+}
+
+// shorthand for the inbox message summary database
+function db() {
+ return IMAPPump.inbox.msgDatabase;
+}
+
+// static variables used in testCounts
+var gPreviousUnread;
+
+// Test various counts.
+//
+// aHasNew: folder hasNew flag
+// aUnreadDelta: change in unread count for the folder
+// aFolderNewDelta: change in new count for the folder
+// aDbNewDelta: change in new count for the database
+//
+function testCounts(aHasNew, aUnreadDelta, aFolderNewDelta, aDbNewDelta) {
+ try {
+ let folderNew = IMAPPump.inbox.getNumNewMessages(false);
+ let hasNew = IMAPPump.inbox.hasNewMessages;
+ let unread = IMAPPump.inbox.getNumUnread(false);
+ let arrayOut = db().getNewList();
+ let dbNew = arrayOut.length;
+ dump(
+ " hasNew: " +
+ hasNew +
+ " unread: " +
+ unread +
+ " folderNew: " +
+ folderNew +
+ " dbNew: " +
+ dbNew +
+ " prevUnread " +
+ gPreviousUnread +
+ "\n"
+ );
+ // Assert.equal(aHasNew, hasNew);
+ Assert.equal(aUnreadDelta, unread - gPreviousUnread);
+ gPreviousUnread = unread;
+ // This seems to be reset for each folder update.
+ //
+ // This check seems to be failing in SeaMonkey builds, yet I can see no ill
+ // effects of this in the actual program. Fixing this is complex because of
+ // the messiness of new count management (see bug 507638 for a
+ // refactoring proposal, and attachment 398899 on bug 514801 for one possible
+ // fix to this particular test). So I am disabling this.
+ // Assert.equal(aFolderNewDelta, folderNew);
+ // Assert.equal(aDbNewDelta, dbNew - gPreviousDbNew);
+ // gPreviousDbNew = dbNew;
+ } catch (e) {
+ dump(e);
+ }
+}
+
+// custom action to test offline status
+var actionTestOffline = {
+ id: "mailnews@mozilla.org#testOffline",
+ name: "test if offline",
+ applyAction(aMsgHdrs, aActionValue, aListener, aType, aMsgWindow) {
+ for (let msgHdr of aMsgHdrs) {
+ let isOffline = !!(msgHdr.flags & Ci.nsMsgMessageFlags.Offline);
+ dump(
+ "in actionTestOffline, flags are " +
+ msgHdr.flags +
+ " subject is " +
+ msgHdr.subject +
+ " isOffline is " +
+ isOffline +
+ "\n"
+ );
+ // XXX TODO: the offline flag is not set here when it should be in postplugin filters
+ // Assert.equal(isOffline, aActionValue == 'true');
+ Assert.equal(msgHdr.subject, gMessageSubject);
+ }
+ },
+ isValidForType(type, scope) {
+ return true;
+ },
+
+ validateActionValue(value, folder, type) {
+ return null;
+ },
+
+ allowDuplicates: false,
+
+ needsBody: true, // set during test setup
+};
diff --git a/comm/mailnews/imap/test/unit/test_imapFlagChange.js b/comm/mailnews/imap/test/unit/test_imapFlagChange.js
new file mode 100644
index 0000000000..332e434527
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_imapFlagChange.js
@@ -0,0 +1,216 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Test to ensure that imap flag changes made from a different profile/machine
+ * are stored in db.
+ */
+
+var { MessageGenerator } = ChromeUtils.import(
+ "resource://testing-common/mailnews/MessageGenerator.jsm"
+);
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+var gMessage;
+var gSecondFolder;
+var gSynthMessage;
+
+add_setup(async function () {
+ Services.prefs.setBoolPref(
+ "mail.server.default.autosync_offline_stores",
+ false
+ );
+
+ setupIMAPPump();
+
+ IMAPPump.daemon.createMailbox("secondFolder", { subscribed: true });
+
+ // build up a diverse list of messages
+ let messages = [];
+ let gMessageGenerator = new MessageGenerator();
+ messages = messages.concat(gMessageGenerator.makeMessage());
+ gSynthMessage = messages[0];
+
+ let msgURI = Services.io.newURI(
+ "data:text/plain;base64," + btoa(gSynthMessage.toMessageString())
+ );
+ gMessage = new ImapMessage(msgURI.spec, IMAPPump.mailbox.uidnext++, []);
+ IMAPPump.mailbox.addMessage(gMessage);
+
+ // update folder to download header.
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.updateFolderWithListener(null, listener);
+ await listener.promise;
+});
+
+add_task(async function switchAwayFromInbox() {
+ let rootFolder = IMAPPump.incomingServer.rootFolder;
+ gSecondFolder = rootFolder
+ .getChildNamed("secondFolder")
+ .QueryInterface(Ci.nsIMsgImapMailFolder);
+
+ // Selecting the second folder will close the cached connection
+ // on the inbox because fake server only supports one connection at a time.
+ // Then, we can poke at the message on the imap server directly, which
+ // simulates the user changing the message from a different machine,
+ // and Thunderbird discovering the change when it does a flag sync
+ // upon reselecting the Inbox.
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ gSecondFolder.updateFolderWithListener(null, listener);
+ await listener.promise;
+});
+
+add_task(async function simulateForwardFlagSet() {
+ gMessage.setFlag("$Forwarded");
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.updateFolderWithListener(null, listener);
+ await listener.promise;
+});
+
+add_task(async function checkForwardedFlagSet() {
+ let msgHdr = IMAPPump.inbox.msgDatabase.getMsgHdrForMessageID(
+ gSynthMessage.messageId
+ );
+ Assert.equal(
+ msgHdr.flags & Ci.nsMsgMessageFlags.Forwarded,
+ Ci.nsMsgMessageFlags.Forwarded
+ );
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ gSecondFolder.updateFolderWithListener(null, listener);
+ await listener.promise;
+});
+
+add_task(async function clearForwardedFlag() {
+ gMessage.clearFlag("$Forwarded");
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.updateFolderWithListener(null, listener);
+ await listener.promise;
+});
+
+add_task(async function checkForwardedFlagCleared() {
+ let msgHdr = IMAPPump.inbox.msgDatabase.getMsgHdrForMessageID(
+ gSynthMessage.messageId
+ );
+ Assert.equal(msgHdr.flags & Ci.nsMsgMessageFlags.Forwarded, 0);
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ gSecondFolder.updateFolderWithListener(null, listener);
+ await listener.promise;
+});
+
+add_task(async function setSeenFlag() {
+ gMessage.setFlag("\\Seen");
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.updateFolderWithListener(null, listener);
+ await listener.promise;
+});
+
+add_task(async function checkSeenFlagSet() {
+ let msgHdr = IMAPPump.inbox.msgDatabase.getMsgHdrForMessageID(
+ gSynthMessage.messageId
+ );
+ Assert.equal(
+ msgHdr.flags & Ci.nsMsgMessageFlags.Read,
+ Ci.nsMsgMessageFlags.Read
+ );
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ gSecondFolder.updateFolderWithListener(null, listener);
+ await listener.promise;
+});
+
+add_task(async function simulateRepliedFlagSet() {
+ gMessage.setFlag("\\Answered");
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.updateFolderWithListener(null, listener);
+ await listener.promise;
+});
+
+add_task(async function checkRepliedFlagSet() {
+ let msgHdr = IMAPPump.inbox.msgDatabase.getMsgHdrForMessageID(
+ gSynthMessage.messageId
+ );
+ Assert.equal(
+ msgHdr.flags & Ci.nsMsgMessageFlags.Replied,
+ Ci.nsMsgMessageFlags.Replied
+ );
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ gSecondFolder.updateFolderWithListener(null, listener);
+ await listener.promise;
+});
+
+add_task(async function simulateTagAdded() {
+ gMessage.setFlag("randomtag");
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.updateFolderWithListener(null, listener);
+ await listener.promise;
+});
+
+add_task(async function checkTagSet() {
+ let msgHdr = IMAPPump.inbox.msgDatabase.getMsgHdrForMessageID(
+ gSynthMessage.messageId
+ );
+ let keywords = msgHdr.getStringProperty("keywords");
+ Assert.ok(keywords.includes("randomtag"));
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ gSecondFolder.updateFolderWithListener(null, listener);
+ await listener.promise;
+});
+
+/** Test that the NonJunk tag from the server is noticed. */
+add_task(async function checkNonJunkTagSet() {
+ gMessage.clearFlag("NotJunk");
+ gMessage.setFlag("NonJunk");
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.updateFolderWithListener(null, listener);
+ await listener.promise;
+
+ let msgHdr = IMAPPump.inbox.msgDatabase.getMsgHdrForMessageID(
+ gSynthMessage.messageId
+ );
+ let junkScore = msgHdr.getStringProperty("junkscore");
+ Assert.equal(
+ junkScore,
+ Ci.nsIJunkMailPlugin.IS_HAM_SCORE,
+ "NonJunk flag on server should mark as ham"
+ );
+});
+
+/** Test that the NotJunk tag from the server is noticed. */
+add_task(async function checkNotJunkTagSet() {
+ gMessage.clearFlag("NonJunk");
+ gMessage.setFlag("NotJunk");
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.updateFolderWithListener(null, listener);
+ await listener.promise;
+
+ let msgHdr = IMAPPump.inbox.msgDatabase.getMsgHdrForMessageID(
+ gSynthMessage.messageId
+ );
+ let junkScore = msgHdr.getStringProperty("junkscore");
+ Assert.equal(
+ junkScore,
+ Ci.nsIJunkMailPlugin.IS_HAM_SCORE,
+ "NotJunk flag on server should mark as ham"
+ );
+});
+
+add_task(async function clearTag() {
+ gMessage.clearFlag("randomtag");
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.updateFolderWithListener(null, listener);
+ await listener.promise;
+});
+
+add_task(function checkTagCleared() {
+ let msgHdr = IMAPPump.inbox.msgDatabase.getMsgHdrForMessageID(
+ gSynthMessage.messageId
+ );
+ let keywords = msgHdr.getStringProperty("keywords");
+ Assert.ok(!keywords.includes("randomtag"));
+});
+
+add_task(function endTest() {
+ teardownIMAPPump();
+});
diff --git a/comm/mailnews/imap/test/unit/test_imapFolderCopy.js b/comm/mailnews/imap/test/unit/test_imapFolderCopy.js
new file mode 100644
index 0000000000..4b7e1bdb18
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_imapFolderCopy.js
@@ -0,0 +1,137 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 tests the folder copying with IMAP. In particular, we're
+// going to test copying local folders to imap servers, but other tests
+// could be added.
+
+var { MessageGenerator } = ChromeUtils.import(
+ "resource://testing-common/mailnews/MessageGenerator.jsm"
+);
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+var gEmptyLocal1, gEmptyLocal2, gEmptyLocal3, gNotEmptyLocal4;
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+add_setup(function () {
+ setupIMAPPump();
+
+ gEmptyLocal1 = localAccountUtils.rootFolder.createLocalSubfolder("empty 1");
+ gEmptyLocal2 = localAccountUtils.rootFolder.createLocalSubfolder("empty 2");
+ gEmptyLocal3 = localAccountUtils.rootFolder.createLocalSubfolder("empty 3");
+ gNotEmptyLocal4 =
+ localAccountUtils.rootFolder.createLocalSubfolder("not empty 4");
+
+ let messageGenerator = new MessageGenerator();
+ let message = messageGenerator.makeMessage();
+ gNotEmptyLocal4.QueryInterface(Ci.nsIMsgLocalMailFolder);
+ gNotEmptyLocal4.addMessage(message.toMboxString());
+
+ // these hacks are required because we've created the inbox before
+ // running initial folder discovery, and adding the folder bails
+ // out before we set it as verified online, so we bail out, and
+ // then remove the INBOX folder since it's not verified.
+ IMAPPump.inbox.hierarchyDelimiter = "/";
+ IMAPPump.inbox.verifiedAsOnlineFolder = true;
+});
+
+add_task(async function copyFolder1() {
+ let copyListener = new PromiseTestUtils.PromiseCopyListener();
+ MailServices.copy.copyFolder(
+ gEmptyLocal1,
+ IMAPPump.inbox,
+ false,
+ copyListener,
+ null
+ );
+ await copyListener.promise;
+});
+
+add_task(async function copyFolder2() {
+ let copyListener = new PromiseTestUtils.PromiseCopyListener();
+ MailServices.copy.copyFolder(
+ gEmptyLocal2,
+ IMAPPump.inbox,
+ false,
+ copyListener,
+ null
+ );
+ await copyListener.promise;
+});
+
+add_task(async function copyFolder3() {
+ let copyListener = new PromiseTestUtils.PromiseCopyListener();
+ MailServices.copy.copyFolder(
+ gEmptyLocal3,
+ IMAPPump.inbox,
+ false,
+ copyListener,
+ null
+ );
+ await copyListener.promise;
+});
+
+add_task(function verifyFolders() {
+ let folder1 = IMAPPump.inbox.getChildNamed("empty 1");
+ let folder2 = IMAPPump.inbox.getChildNamed("empty 2");
+ let folder3 = IMAPPump.inbox.getChildNamed("empty 3");
+ Assert.ok(folder1 !== null);
+ Assert.ok(folder2 !== null);
+ Assert.ok(folder3 !== null);
+});
+
+add_task(async function moveImapFolder1() {
+ let folder1 = IMAPPump.inbox.getChildNamed("empty 1");
+ let folder2 = IMAPPump.inbox.getChildNamed("empty 2");
+ let copyListener = new PromiseTestUtils.PromiseCopyListener();
+ MailServices.copy.copyFolder(folder2, folder1, true, copyListener, null);
+ await copyListener.promise;
+});
+
+add_task(async function moveImapFolder2() {
+ let folder1 = IMAPPump.inbox.getChildNamed("empty 1");
+ let folder3 = IMAPPump.inbox.getChildNamed("empty 3");
+ let copyListener = new PromiseTestUtils.PromiseCopyListener();
+ MailServices.copy.copyFolder(folder3, folder1, true, copyListener, null);
+ await copyListener.promise;
+});
+
+add_task(function verifyImapFolders() {
+ let folder1 = IMAPPump.inbox.getChildNamed("empty 1");
+ let folder2 = folder1.getChildNamed("empty 2");
+ let folder3 = folder1.getChildNamed("empty 3");
+ Assert.ok(folder1 !== null);
+ Assert.ok(folder2 !== null);
+ Assert.ok(folder3 !== null);
+});
+
+add_task(async function testImapFolderCopyFailure() {
+ IMAPPump.daemon.commandToFail = "APPEND";
+ // we expect NS_MSG_ERROR_IMAP_COMMAND_FAILED;
+ const NS_MSG_ERROR_IMAP_COMMAND_FAILED = 0x80550021;
+ let copyListener = new PromiseTestUtils.PromiseCopyListener();
+ MailServices.copy.copyFolder(
+ gNotEmptyLocal4,
+ IMAPPump.inbox,
+ false,
+ copyListener,
+ null
+ );
+ await Assert.rejects(
+ copyListener.promise,
+ e => {
+ return e === NS_MSG_ERROR_IMAP_COMMAND_FAILED;
+ },
+ "NS_MSG_ERROR_IMAP_COMMAND_FAILED should be the cause of the error"
+ );
+});
+
+add_task(function teardown() {
+ teardownIMAPPump();
+});
diff --git a/comm/mailnews/imap/test/unit/test_imapHdrChunking.js b/comm/mailnews/imap/test/unit/test_imapHdrChunking.js
new file mode 100644
index 0000000000..8fc23e8502
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_imapHdrChunking.js
@@ -0,0 +1,168 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Tests imap msg header download chunking
+ */
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+var { MessageGenerator, MessageScenarioFactory } = ChromeUtils.import(
+ "resource://testing-common/mailnews/MessageGenerator.jsm"
+);
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+var { TestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/TestUtils.sys.mjs"
+);
+
+/**
+ * Keep it so that OVERALL_MESSAGES % CHUNKING_SIZE !== 0.
+ * With a modulo operator for CHUNKING_SIZE and a prime number for
+ * OVERALL_MESSAGES this should prove that there have been a
+ * chunking process without being depended on the first chunk.
+ */
+const CHUNKING_SIZE = 3;
+const OVERALL_MESSAGES = 137;
+
+// Dummy message window so we can say the inbox is open in a window.
+var dummyMsgWindow = Cc["@mozilla.org/messenger/msgwindow;1"].createInstance(
+ Ci.nsIMsgWindow
+);
+
+function FolderIntPropertyChangedListener() {
+ this._promise = new Promise(resolve => {
+ this._resolve = resolve;
+ });
+ this._gotNewMailBiff = false;
+}
+
+FolderIntPropertyChangedListener.prototype = {
+ onFolderIntPropertyChanged(aItem, aProperty, aOldValue, aNewValue) {
+ if (
+ aProperty == "BiffState" &&
+ aNewValue == Ci.nsIMsgFolder.nsMsgBiffState_NewMail
+ ) {
+ this._gotNewMailBiff = true;
+ this._resolve();
+ }
+ },
+ get promise() {
+ return this._promise;
+ },
+ get gotNewMailBiff() {
+ return this._gotNewMailBiff;
+ },
+};
+
+var gFolderListener = new FolderIntPropertyChangedListener();
+/** Used to store a listener between tasks for inspecting chunking behaviour. */
+var gListener = new PromiseTestUtils.PromiseUrlListener();
+
+add_setup(async function () {
+ Assert.equal(
+ OVERALL_MESSAGES % CHUNKING_SIZE !== 0,
+ true,
+ "const sanity check"
+ );
+ setupIMAPPump();
+ // We need to register the dummyMsgWindow so that we'll think the
+ // Inbox is open in a folder and fetch headers in chunks.
+ dummyMsgWindow.openFolder = IMAPPump.inbox;
+ MailServices.mailSession.AddMsgWindow(dummyMsgWindow);
+ MailServices.mailSession.AddFolderListener(
+ gFolderListener,
+ Ci.nsIFolderListener.intPropertyChanged
+ );
+
+ // Set chunk size to CHUNKING_SIZE, so we'll have to chain several requests to get
+ // OVERALL_MESSAGES headers.
+ Services.prefs.setIntPref("mail.imap.hdr_chunk_size", CHUNKING_SIZE);
+ // Turn off offline sync to avoid complications in verifying that we can
+ // run a url after the first header chunk.
+ Services.prefs.setBoolPref(
+ "mail.server.server1.autosync_offline_stores",
+ false
+ );
+});
+
+// Upload messages to the imap fake server Inbox.
+add_task(async function uploadImapMessages() {
+ // make OVERALL_MESSAGES messages
+ let messageGenerator = new MessageGenerator();
+ let scenarioFactory = new MessageScenarioFactory(messageGenerator);
+
+ // build up a list of messages
+ let messages = [];
+ messages = messages.concat(scenarioFactory.directReply(OVERALL_MESSAGES));
+
+ // Add OVERALL_MESSAGES messages with uids 1,2,3...,OVERALL_MESSAGES.
+ let imapInbox = IMAPPump.daemon.getMailbox("INBOX");
+ // Create the ImapMessages and store them on the mailbox.
+ messages.forEach(function (message) {
+ let dataUri = Services.io.newURI(
+ "data:text/plain;base64," + btoa(message.toMessageString())
+ );
+ imapInbox.addMessage(
+ new ImapMessage(dataUri.spec, imapInbox.uidnext++, [])
+ );
+ });
+ // Do not wait for the listener to finish.
+ // We want to observe the message batches in the update process.
+ // updateFolderWithListener with null for nsIMsgWindow makes biff notify.
+ IMAPPump.inbox.updateFolderWithListener(null, gListener);
+});
+
+add_task(async function testMessageFetched() {
+ // If we're really chunking, then the message fetch should have started before
+ // we finished the updateFolder URL.
+ await TestUtils.waitForCondition(() => {
+ return gFolderListener.gotNewMailBiff === true;
+ });
+ Assert.ok(gFolderListener.gotNewMailBiff);
+
+ // We do not check for the first chunk as this is unreliable without explicit
+ // listeners/events.
+ // Instead we are checking if there's no rest of the division with
+ // CHUNKING_SIZE while the chunking process is ongoing.
+ // It's important that the chunking is intact and as well not failing
+ // randomly in the test infrastructure.
+ // See at the CHUNKING_SIZE and OVERALL_MESSAGES declarations.
+ //
+ // HINT:
+ // If this causes future problems because stuff getting faster,
+ // try to increase the overall message count.
+ await TestUtils.waitForCondition(() => {
+ let messagesDBFolder = IMAPPump.inbox.msgDatabase.dBFolderInfo.numMessages;
+ if (messagesDBFolder !== 0) {
+ Assert.equal(
+ messagesDBFolder % CHUNKING_SIZE,
+ 0,
+ `${messagesDBFolder} messages in folder should be of chunk size ${CHUNKING_SIZE}`
+ ); // This is the primary test.
+ return true;
+ } else if (messagesDBFolder === OVERALL_MESSAGES) {
+ throw new Error(
+ `Batching failed in sizes of ${CHUNKING_SIZE} found instead ${OVERALL_MESSAGES} immediately`
+ );
+ }
+ return false; // Rerun waitForCondition.
+ }, 50);
+});
+
+add_task(async function testHdrsDownloaded() {
+ await gListener.promise; // Now we wait for the finished update of the Folder.
+ // Make sure that we got all OVERALL_MESSAGES headers.
+ Assert.equal(
+ IMAPPump.inbox.msgDatabase.dBFolderInfo.numMessages,
+ OVERALL_MESSAGES
+ );
+});
+
+// Cleanup
+add_task(async function endTest() {
+ teardownIMAPPump();
+});
diff --git a/comm/mailnews/imap/test/unit/test_imapHdrStreaming.js b/comm/mailnews/imap/test/unit/test_imapHdrStreaming.js
new file mode 100644
index 0000000000..ca148dace6
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_imapHdrStreaming.js
@@ -0,0 +1,74 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 test checks if the imap message service code streams headers correctly.
+ * It checks that streaming headers for messages stored for offline use works.
+ * It doesn't test streaming messages that haven't been stored for offline use
+ * because that's not implemented yet, and it's unclear if anyone will want it.
+ */
+
+var { MessageGenerator } = ChromeUtils.import(
+ "resource://testing-common/mailnews/MessageGenerator.jsm"
+);
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+setupIMAPPump();
+
+var gMsgFile1 = do_get_file("../../../data/bugmail10");
+var gMsgId1 = "200806061706.m56H6RWT004933@mrapp54.mozilla.org";
+
+// Adds some messages directly to a mailbox (e.g. new mail).
+function addMessagesToServer(messages, mailbox) {
+ // For every message we have, we need to convert it to a file:/// URI.
+ messages.forEach(function (message) {
+ let URI = Services.io
+ .newFileURI(message.file)
+ .QueryInterface(Ci.nsIFileURL);
+ // Create the ImapMessage and store it on the mailbox.
+ mailbox.addMessage(new ImapMessage(URI.spec, mailbox.uidnext++, []));
+ });
+}
+
+add_setup(async function () {
+ // Add a couple of messages to the INBOX
+ // this is synchronous, afaik.
+ addMessagesToServer(
+ [{ file: gMsgFile1, messageId: gMsgId1 }],
+ IMAPPump.daemon.getMailbox("INBOX")
+ );
+ Services.prefs.setBoolPref(
+ "mail.server.server1.autosync_offline_stores",
+ false
+ );
+ // Update IMAP Folder.
+ let listenerUpdate = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.updateFolderWithListener(null, listenerUpdate);
+ await listenerUpdate.promise;
+ // Download all for offline.
+ let listenerDownload = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.downloadAllForOffline(listenerDownload, null);
+ await listenerDownload.promise;
+});
+
+add_task(async function test_streamHeaders() {
+ let newMsgHdr = IMAPPump.inbox.GetMessageHeader(1);
+ let msgURI = newMsgHdr.folder.getUriForMsg(newMsgHdr);
+ let msgServ = MailServices.messageServiceFromURI(msgURI);
+ // We use this as a display consumer
+ let streamListener = new PromiseTestUtils.PromiseStreamListener();
+ msgServ.streamHeaders(msgURI, streamListener, null, true);
+ let data = await streamListener.promise;
+ Assert.ok(data.includes("Content-Type"));
+});
+
+add_task(async function endTest() {
+ teardownIMAPPump();
+});
diff --git a/comm/mailnews/imap/test/unit/test_imapHighWater.js b/comm/mailnews/imap/test/unit/test_imapHighWater.js
new file mode 100644
index 0000000000..3fcd4bcc23
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_imapHighWater.js
@@ -0,0 +1,194 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* import-globals-from ../../../test/resources/alertTestUtils.js */
+load("../../../resources/alertTestUtils.js");
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+var { MessageGenerator, MessageScenarioFactory } = ChromeUtils.import(
+ "resource://testing-common/mailnews/MessageGenerator.jsm"
+);
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+var gIMAPDaemon, gServer, gIMAPIncomingServer;
+
+var gIMAPInbox;
+var gFolder1, gRootFolder;
+
+// Adds some messages directly to a mailbox (eg new mail)
+function addMessagesToServer(messages, mailbox) {
+ // Create the ImapMessages and store them on the mailbox
+ messages.forEach(function (message) {
+ let dataUri = Services.io.newURI(
+ "data:text/plain;base64," + btoa(message.toMessageString())
+ );
+ mailbox.addMessage(new ImapMessage(dataUri.spec, mailbox.uidnext++, []));
+ });
+}
+
+add_setup(function () {
+ localAccountUtils.loadLocalMailAccount();
+
+ /*
+ * Set up an IMAP server.
+ */
+ gIMAPDaemon = new ImapDaemon();
+ gServer = makeServer(gIMAPDaemon, "");
+ gIMAPDaemon.createMailbox("folder 1", { subscribed: true });
+ gIMAPIncomingServer = createLocalIMAPServer(gServer.port);
+ gIMAPIncomingServer.maximumConnectionsNumber = 1;
+
+ // We need an identity so that updateFolder doesn't fail
+ let localAccount = MailServices.accounts.createAccount();
+ let identity = MailServices.accounts.createIdentity();
+ localAccount.addIdentity(identity);
+ localAccount.defaultIdentity = identity;
+ localAccount.incomingServer = localAccountUtils.incomingServer;
+
+ // Let's also have another account, using the same identity
+ let imapAccount = MailServices.accounts.createAccount();
+ imapAccount.addIdentity(identity);
+ imapAccount.defaultIdentity = identity;
+ imapAccount.incomingServer = gIMAPIncomingServer;
+ MailServices.accounts.defaultAccount = imapAccount;
+
+ // pref tuning: one connection only, turn off notifications
+ Services.prefs.setBoolPref("mail.biff.play_sound", false);
+ Services.prefs.setBoolPref("mail.biff.show_alert", false);
+ Services.prefs.setBoolPref("mail.biff.show_tray_icon", false);
+ Services.prefs.setBoolPref("mail.biff.animate_dock_icon", false);
+ Services.prefs.setBoolPref(
+ "mail.server.default.autosync_offline_stores",
+ false
+ );
+ // Don't prompt about offline download when going offline
+ Services.prefs.setIntPref("offline.download.download_messages", 2);
+});
+
+add_setup(function () {
+ // make 10 messages
+ let messageGenerator = new MessageGenerator();
+ let scenarioFactory = new MessageScenarioFactory(messageGenerator);
+
+ // build up a list of messages
+ let messages = [];
+ messages = messages.concat(scenarioFactory.directReply(10));
+
+ // Add 10 messages with uids 1-10.
+ let imapInbox = gIMAPDaemon.getMailbox("INBOX");
+ addMessagesToServer(messages, imapInbox);
+ messages = [];
+ messages = messages.concat(messageGenerator.makeMessage());
+ // Add a single message to move target folder.
+ addMessagesToServer(messages, gIMAPDaemon.getMailbox("folder 1"));
+
+ // Get the IMAP inbox...
+ gRootFolder = gIMAPIncomingServer.rootFolder;
+ gIMAPInbox = gRootFolder
+ .getFolderWithFlags(Ci.nsMsgFolderFlags.Inbox)
+ .QueryInterface(Ci.nsIMsgImapMailFolder);
+});
+
+add_task(async function doMoves() {
+ // update folders to download headers.
+ let urlListenerInbox = new PromiseTestUtils.PromiseUrlListener();
+ gIMAPInbox.updateFolderWithListener(null, urlListenerInbox);
+ await urlListenerInbox.promise;
+ gFolder1 = gRootFolder
+ .getChildNamed("folder 1")
+ .QueryInterface(Ci.nsIMsgImapMailFolder);
+ let urlListenerFolder1 = new PromiseTestUtils.PromiseUrlListener();
+ gFolder1.updateFolderWithListener(null, urlListenerFolder1);
+ await urlListenerFolder1.promise;
+ // get five messages to move from Inbox to folder 1.
+ let headers1 = [];
+ let count = 0;
+ for (let header of gIMAPInbox.msgDatabase.enumerateMessages()) {
+ if (count >= 5) {
+ break;
+ }
+ if (header instanceof Ci.nsIMsgDBHdr) {
+ headers1.push(header);
+ }
+ count++;
+ }
+ // this will add dummy headers with keys > 0xffffff80
+ let copyListenerDummyHeaders = new PromiseTestUtils.PromiseCopyListener();
+ MailServices.copy.copyMessages(
+ gIMAPInbox,
+ headers1,
+ gFolder1,
+ true,
+ copyListenerDummyHeaders,
+ gDummyMsgWindow,
+ true
+ );
+ await copyListenerDummyHeaders.promise;
+
+ let urlListenerInboxAfterDummy = new PromiseTestUtils.PromiseUrlListener();
+ gIMAPInbox.updateFolderWithListener(null, urlListenerInboxAfterDummy);
+ await urlListenerInboxAfterDummy.promise;
+
+ let urlListenerFolder1AfterDummy = new PromiseTestUtils.PromiseUrlListener();
+ gFolder1.updateFolderWithListener(
+ gDummyMsgWindow,
+ urlListenerFolder1AfterDummy
+ );
+ await urlListenerFolder1AfterDummy.promise;
+
+ // Check that playing back offline events gets rid of dummy
+ // headers, and thus highWater is recalculated.
+ Assert.equal(gFolder1.msgDatabase.dBFolderInfo.highWater, 6);
+ headers1 = [];
+ count = 0;
+ for (let header of gIMAPInbox.msgDatabase.enumerateMessages()) {
+ if (count >= 5) {
+ break;
+ }
+ if (header instanceof Ci.nsIMsgDBHdr) {
+ headers1.push(header);
+ }
+ count++;
+ }
+ // Check that copyMessages will handle having a high highwater mark.
+ // It will thrown an exception if it can't.
+ let msgHdr = gFolder1.msgDatabase.createNewHdr(0xfffffffd);
+ gFolder1.msgDatabase.addNewHdrToDB(msgHdr, false);
+ let copyListenerHighWater = new PromiseTestUtils.PromiseCopyListener();
+ MailServices.copy.copyMessages(
+ gIMAPInbox,
+ headers1,
+ gFolder1,
+ true,
+ copyListenerHighWater,
+ gDummyMsgWindow,
+ true
+ );
+ await copyListenerHighWater.promise;
+ gServer.performTest("UID COPY");
+
+ gFolder1.msgDatabase.deleteHeader(msgHdr, null, true, false);
+ let urlListenerInboxAfterDelete = new PromiseTestUtils.PromiseUrlListener();
+ gIMAPInbox.updateFolderWithListener(null, urlListenerInboxAfterDelete);
+ await urlListenerInboxAfterDelete.promise;
+ // this should clear the dummy headers.
+ let urlListenerFolder1AfterDelete = new PromiseTestUtils.PromiseUrlListener();
+ gFolder1.updateFolderWithListener(
+ gDummyMsgWindow,
+ urlListenerFolder1AfterDelete
+ );
+ await urlListenerFolder1AfterDelete.promise;
+ Assert.equal(gFolder1.msgDatabase.dBFolderInfo.highWater, 11);
+});
+
+add_task(function endTest() {
+ Services.io.offline = true;
+ gServer.performTest("LOGOUT");
+ gIMAPIncomingServer.closeCachedConnections();
+ gServer.stop();
+});
diff --git a/comm/mailnews/imap/test/unit/test_imapID.js b/comm/mailnews/imap/test/unit/test_imapID.js
new file mode 100644
index 0000000000..fe1f70fbdf
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_imapID.js
@@ -0,0 +1,40 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Test to ensure that we handle the RFC2197 ID command.
+ */
+
+/* import-globals-from ../../../test/resources/logHelper.js */
+load("../../../resources/logHelper.js");
+
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+var kIDResponse =
+ '("name" "GImap" "vendor" "Google, Inc." "support-url" "http://mail.google.com/support")';
+
+add_setup(async function () {
+ setupIMAPPump("GMail");
+ IMAPPump.daemon.idResponse = kIDResponse;
+
+ // update folder to kick start tests.
+ let promiseUrlListener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.updateFolderWithListener(null, promiseUrlListener);
+ await promiseUrlListener.promise;
+});
+
+add_task(async function updateInbox() {
+ let promiseUrlListener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.updateFolderWithListener(null, promiseUrlListener);
+ await promiseUrlListener.promise;
+});
+
+add_task(function checkIDHandling() {
+ Assert.equal(IMAPPump.daemon.clientID, '("name" "xpcshell" "version" "1")');
+ Assert.equal(IMAPPump.incomingServer.serverIDPref, kIDResponse);
+});
+
+add_task(teardownIMAPPump);
diff --git a/comm/mailnews/imap/test/unit/test_imapMove.js b/comm/mailnews/imap/test/unit/test_imapMove.js
new file mode 100644
index 0000000000..e19896ef90
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_imapMove.js
@@ -0,0 +1,88 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 tests that we use IMAP move if the IMAP server supports it.
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+/* import-globals-from ../../../test/resources/logHelper.js */
+/* import-globals-from ../../../test/resources/MessageGenerator.jsm */
+load("../../../resources/logHelper.js");
+load("../../../resources/MessageGenerator.jsm");
+
+var gFolder1;
+
+var tests = [setupCUSTOM1, startTest, doMove, testMove, teardownIMAPPump];
+
+function setupCUSTOM1() {
+ setupIMAPPump("CUSTOM1");
+ Services.prefs.setBoolPref(
+ "mail.server.default.autosync_offline_stores",
+ false
+ );
+}
+
+async function startTest() {
+ IMAPPump.incomingServer.rootFolder.createSubfolder("folder 1", null);
+ await PromiseTestUtils.promiseFolderAdded("folder 1");
+
+ addImapMessage();
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.updateFolderWithListener(null, listener);
+ await listener.promise;
+
+ // ...and download for offline use.
+ let promiseUrlListener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.downloadAllForOffline(promiseUrlListener, null);
+ await promiseUrlListener.promise;
+}
+
+async function doMove() {
+ let rootFolder = IMAPPump.incomingServer.rootFolder;
+ gFolder1 = rootFolder
+ .getChildNamed("folder 1")
+ .QueryInterface(Ci.nsIMsgImapMailFolder);
+ let msg = IMAPPump.inbox.msgDatabase.getMsgHdrForKey(
+ IMAPPump.mailbox.uidnext - 1
+ );
+ IMAPPump.server._test = true;
+ let listener = new PromiseTestUtils.PromiseCopyListener();
+ MailServices.copy.copyMessages(
+ IMAPPump.inbox,
+ [msg],
+ gFolder1,
+ true,
+ listener,
+ null,
+ false
+ );
+ IMAPPump.server.performTest("UID MOVE");
+ await listener.promise;
+}
+
+async function testMove() {
+ Assert.equal(IMAPPump.inbox.getTotalMessages(false), 0);
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ gFolder1.updateFolderWithListener(null, listener);
+ await listener.promise;
+ Assert.equal(gFolder1.getTotalMessages(false), 1);
+
+ // maildir should also delete the files.
+ if (IMAPPump.inbox.msgStore.storeType == "maildir") {
+ let curDir = IMAPPump.inbox.filePath.clone();
+ curDir.append("cur");
+ Assert.ok(curDir.exists());
+ Assert.ok(curDir.isDirectory());
+ let curEnum = curDir.directoryEntries;
+ // the directory should be empty, fails from bug 771643
+ Assert.ok(!curEnum.hasMoreElements());
+ }
+}
+
+function run_test() {
+ tests.forEach(x => add_task(x));
+ run_next_test();
+}
diff --git a/comm/mailnews/imap/test/unit/test_imapPasswordFailure.js b/comm/mailnews/imap/test/unit/test_imapPasswordFailure.js
new file mode 100644
index 0000000000..b28c646907
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_imapPasswordFailure.js
@@ -0,0 +1,179 @@
+/**
+ * This test checks to see if the imap password failure is handled correctly.
+ * The steps are:
+ * - Have an invalid password in the password database.
+ * - Check we get a prompt asking what to do.
+ * - Check retry does what it should do.
+ * - Check cancel does what it should do.
+ * - Re-initiate connection, this time select enter new password, check that
+ * we get a new password prompt and can enter the password.
+ */
+
+/* import-globals-from ../../../test/resources/alertTestUtils.js */
+/* import-globals-from ../../../test/resources/passwordStorage.js */
+load("../../../resources/alertTestUtils.js");
+load("../../../resources/passwordStorage.js");
+
+var kUserName = "user";
+var kInvalidPassword = "imaptest";
+var kValidPassword = "password";
+
+var incomingServer, server;
+var attempt = 0;
+
+function confirmExPS(
+ aDialogTitle,
+ aText,
+ aButtonFlags,
+ aButton0Title,
+ aButton1Title,
+ aButton2Title,
+ aCheckMsg,
+ aCheckState
+) {
+ switch (++attempt) {
+ // First attempt, retry.
+ case 1:
+ dump("\nAttempting retry\n");
+ return 0;
+ // Second attempt, cancel.
+ case 2:
+ dump("\nCancelling login attempt\n");
+ return 1;
+ // Third attempt, retry.
+ case 3:
+ dump("\nAttempting Retry\n");
+ return 0;
+ // Fourth attempt, enter a new password.
+ case 4:
+ dump("\nEnter new password\n");
+ return 2;
+ default:
+ do_throw("unexpected attempt number " + attempt);
+ return 1;
+ }
+}
+
+function promptPasswordPS(
+ aParent,
+ aDialogTitle,
+ aText,
+ aPassword,
+ aCheckMsg,
+ aCheckState
+) {
+ if (attempt == 4) {
+ aPassword.value = kValidPassword;
+ aCheckState.value = true;
+ return true;
+ }
+ return false;
+}
+
+add_task(async function () {
+ do_test_pending();
+
+ // Prepare files for passwords (generated by a script in bug 1018624).
+ await setupForPassword("signons-mailnews1.8-imap.json");
+
+ registerAlertTestUtils();
+
+ let daemon = new ImapDaemon();
+ daemon.createMailbox("Subscribed", { subscribed: true });
+ server = makeServer(daemon, "", {
+ // Make username of server match the singons.txt file
+ // (pw there is intentionally invalid)
+ kUsername: kUserName,
+ kPassword: kValidPassword,
+ });
+ server.setDebugLevel(fsDebugAll);
+
+ incomingServer = createLocalIMAPServer(server.port);
+
+ // PerformExpand expects us to already have a password loaded into the
+ // incomingServer when we call it, so force a get password call to get it
+ // out of the signons file (first removing the value that
+ // createLocalIMAPServer puts in there).
+ incomingServer.password = "";
+ let password = incomingServer.getPasswordWithUI(
+ "Prompt Message",
+ "Prompt Title"
+ );
+
+ // The fake server expects one password, but we're feeding it an invalid one
+ // initially so that we can check what happens when password is denied.
+ Assert.equal(password, kInvalidPassword);
+
+ // First step, try and perform a subscribe where we won't be able to log in.
+ // This covers attempts 1 and 2 in confirmEx.
+ dump("\nperformExpand 1\n\n");
+
+ incomingServer.performExpand(gDummyMsgWindow);
+ server.performTest("SUBSCRIBE");
+
+ dump("\nfinished subscribe 1\n\n");
+
+ Assert.equal(attempt, 2);
+
+ let rootFolder = incomingServer.rootFolder;
+ Assert.ok(rootFolder.containsChildNamed("Inbox"));
+ Assert.ok(!rootFolder.containsChildNamed("Subscribed"));
+
+ // Check that we haven't forgotten the login even though we've retried and cancelled.
+ let logins = Services.logins.findLogins(
+ "imap://localhost",
+ null,
+ "imap://localhost"
+ );
+
+ Assert.equal(logins.length, 1);
+ Assert.equal(logins[0].username, kUserName);
+ Assert.equal(logins[0].password, kInvalidPassword);
+
+ server.resetTest();
+
+ dump("\nperformExpand 2\n\n");
+
+ incomingServer.performExpand(gDummyMsgWindow);
+ server.performTest("SUBSCRIBE");
+
+ dump("\nfinished subscribe 2\n");
+
+ Assert.ok(rootFolder.containsChildNamed("Inbox"));
+ Assert.ok(rootFolder.containsChildNamed("Subscribed"));
+
+ // Now check the new one has been saved.
+ logins = Services.logins.findLogins(
+ "imap://localhost",
+ null,
+ "imap://localhost"
+ );
+
+ Assert.equal(logins.length, 1);
+ Assert.equal(logins[0].username, kUserName);
+ Assert.equal(logins[0].password, kValidPassword);
+
+ // Remove the login via the incoming server.
+ incomingServer.forgetPassword();
+ logins = Services.logins.findLogins(
+ "imap://localhost",
+ null,
+ "imap://localhost"
+ );
+
+ Assert.equal(logins.length, 0);
+
+ do_timeout(500, endTest);
+});
+
+function endTest() {
+ incomingServer.closeCachedConnections();
+ server.stop();
+
+ var thread = gThreadManager.currentThread;
+ while (thread.hasPendingEvents()) {
+ thread.processNextEvent(true);
+ }
+
+ do_test_finished();
+}
diff --git a/comm/mailnews/imap/test/unit/test_imapProtocols.js b/comm/mailnews/imap/test/unit/test_imapProtocols.js
new file mode 100644
index 0000000000..2043c44567
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_imapProtocols.js
@@ -0,0 +1,59 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ * Test suite for IMAP nsIProtocolHandler implementations.
+ */
+
+var defaultProtocolFlags =
+ Ci.nsIProtocolHandler.URI_NORELATIVE |
+ Ci.nsIProtocolHandler.URI_FORBIDS_AUTOMATIC_DOCUMENT_REPLACEMENT |
+ Ci.nsIProtocolHandler.URI_DANGEROUS_TO_LOAD |
+ Ci.nsIProtocolHandler.ALLOWS_PROXY |
+ Ci.nsIProtocolHandler.URI_FORBIDS_COOKIE_ACCESS |
+ Ci.nsIProtocolHandler.ORIGIN_IS_FULL_SPEC;
+
+var protocols = [
+ {
+ protocol: "imap",
+ urlSpec: "imap://user@localhost/",
+ defaultPort: Ci.nsIImapUrl.DEFAULT_IMAP_PORT,
+ },
+ // XXX Imaps protocol not available via nsIProtocolHandler yet.
+ // {
+ // protocol: "imaps",
+ // urlSpec: "iamps://user@localhost/",
+ // defaultPort: Ci.nsIImapUrl.DEFAULT_IMAPS_PORT,
+ // },
+];
+
+function run_test() {
+ // We need a server to match the urlSpecs above.
+ createLocalIMAPServer();
+
+ for (var part = 0; part < protocols.length; ++part) {
+ print("protocol: " + protocols[part].protocol);
+
+ var pH = Cc[
+ "@mozilla.org/network/protocol;1?name=" + protocols[part].protocol
+ ].createInstance(Ci.nsIProtocolHandler);
+
+ Assert.equal(pH.scheme, protocols[part].protocol);
+ Assert.equal(
+ Services.io.getDefaultPort(pH.scheme),
+ protocols[part].defaultPort
+ );
+ Assert.equal(Services.io.getProtocolFlags(pH.scheme), defaultProtocolFlags);
+
+ // Whip through some of the ports to check we get the right results.
+ // IMAP allows connecting to any port.
+ for (let i = 0; i < 1024; ++i) {
+ Assert.ok(pH.allowPort(i, ""));
+ }
+
+ // Check we get a URI when we ask for one
+ var uri = Services.io.newURI(protocols[part].urlSpec);
+
+ uri.QueryInterface(Ci.nsIImapUrl);
+
+ Assert.equal(uri.spec, protocols[part].urlSpec);
+ }
+}
diff --git a/comm/mailnews/imap/test/unit/test_imapProxy.js b/comm/mailnews/imap/test/unit/test_imapProxy.js
new file mode 100644
index 0000000000..aa935fff68
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_imapProxy.js
@@ -0,0 +1,68 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+// Test that IMAP over a SOCKS proxy works.
+
+var { NetworkTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/NetworkTestUtils.jsm"
+);
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+/* import-globals-from ../../../test/resources/MessageGenerator.jsm */
+load("../../../resources/MessageGenerator.jsm");
+
+var server, daemon, incomingServer;
+
+const PORT = 143;
+
+add_setup(async function () {
+ // Disable new mail notifications
+ Services.prefs.setBoolPref("mail.biff.play_sound", false);
+ Services.prefs.setBoolPref("mail.biff.show_alert", false);
+ Services.prefs.setBoolPref("mail.biff.show_tray_icon", false);
+ Services.prefs.setBoolPref("mail.biff.animate_dock_icon", false);
+
+ daemon = new ImapDaemon();
+ server = makeServer(daemon, "");
+
+ let messages = [];
+ let messageGenerator = new MessageGenerator();
+ messages = messages.concat(messageGenerator.makeMessage());
+ let dataUri = Services.io.newURI(
+ "data:text/plain;base64," + btoa(messages[0].toMessageString())
+ );
+ let imapMsg = new ImapMessage(dataUri.spec, daemon.inbox.uidnext++, []);
+ daemon.inbox.addMessage(imapMsg);
+
+ NetworkTestUtils.configureProxy("imap.tinderbox.invalid", PORT, server.port);
+
+ // Set up the basic accounts and folders
+ incomingServer = createLocalIMAPServer(PORT, "imap.tinderbox.invalid");
+ let identity = MailServices.accounts.createIdentity();
+ let imapAccount = MailServices.accounts.createAccount();
+ imapAccount.addIdentity(identity);
+ imapAccount.defaultIdentity = identity;
+ imapAccount.incomingServer = incomingServer;
+});
+
+add_task(async function downloadEmail() {
+ let inboxFolder = incomingServer.rootFolder.getChildNamed("INBOX");
+
+ // Check that we haven't got any messages in the folder, if we have its a test
+ // setup issue.
+ Assert.equal(inboxFolder.getTotalMessages(false), 0);
+
+ // Now get the mail
+ let asyncUrlListener = new PromiseTestUtils.PromiseUrlListener();
+ inboxFolder.getNewMessages(null, asyncUrlListener);
+ await asyncUrlListener.promise;
+
+ // We downloaded a message, so it works!
+ Assert.equal(inboxFolder.getTotalMessages(false), 1);
+});
+
+add_task(async function cleanUp() {
+ NetworkTestUtils.shutdownServers();
+ incomingServer.closeCachedConnections();
+ server.stop();
+});
diff --git a/comm/mailnews/imap/test/unit/test_imapRename.js b/comm/mailnews/imap/test/unit/test_imapRename.js
new file mode 100644
index 0000000000..f346729a92
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_imapRename.js
@@ -0,0 +1,43 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// This tests that renaming non-ASCII name folder works.
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+add_setup(async function () {
+ setupIMAPPump();
+ Services.prefs.setBoolPref(
+ "mail.server.default.autosync_offline_stores",
+ false
+ );
+ IMAPPump.incomingServer.rootFolder.createSubfolder("folder 1", null);
+ await PromiseTestUtils.promiseFolderAdded("folder 1");
+
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.updateFolderWithListener(null, listener);
+ await listener.promise;
+});
+
+add_task(async function test_rename() {
+ let rootFolder = IMAPPump.incomingServer.rootFolder;
+ let targetFolder = rootFolder.getChildNamed("folder 1");
+
+ targetFolder.rename("folder \u00e1", null);
+
+ IMAPPump.server.performTest("RENAME");
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.updateFolderWithListener(null, listener);
+ await listener.promise;
+
+ let folder = rootFolder.getChildNamed("folder \u00e1");
+ Assert.ok(folder.msgDatabase.summaryValid);
+ Assert.equal("folder &AOE-", folder.filePath.leafName);
+ Assert.equal("folder \u00e1", folder.prettyName);
+});
diff --git a/comm/mailnews/imap/test/unit/test_imapSearch.js b/comm/mailnews/imap/test/unit/test_imapSearch.js
new file mode 100644
index 0000000000..975d1f2dae
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_imapSearch.js
@@ -0,0 +1,348 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* Tests traditional (non-gloda) search on IMAP folders.
+ * Derived from a combination of test_imapPump.js and test_search.js
+ * Original author: Kent James <kent@caspia.com>
+ */
+
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+// headers we will store in db
+// set value of headers we want parsed into the db
+Services.prefs.setCharPref(
+ "mailnews.customDBHeaders",
+ "x-spam-status oneliner twoliner threeliner nospace withspace"
+);
+Assert.equal(
+ Services.prefs.getCharPref("mailnews.customDBHeaders"),
+ "x-spam-status oneliner twoliner threeliner nospace withspace"
+);
+
+// set customHeaders, which post-bug 363238 should get added to the db. Note that all headers but the last
+// seem to end in colon.
+Services.prefs.setCharPref(
+ "mailnews.customHeaders",
+ "x-uidl: x-bugzilla-watch-reason: x-bugzilla-component: received: x-spam-checker-version"
+);
+
+// Messages to load must have CRLF line endings, that is Windows style
+var gMessage = "bugmail12"; // message file used as the test message
+
+/*
+/*
+ * Testing of general mail search features.
+ *
+ * This tests some search attributes not tested by other specific tests,
+ * e.g., test_searchTag.js or test_searchJunk.js
+ */
+/* import-globals-from ../../../test/resources/searchTestUtils.js */
+load("../../../resources/searchTestUtils.js");
+
+var Isnt = Ci.nsMsgSearchOp.Isnt;
+var Is = Ci.nsMsgSearchOp.Is;
+var Contains = Ci.nsMsgSearchOp.Contains;
+var BeginsWith = Ci.nsMsgSearchOp.BeginsWith;
+var EndsWith = Ci.nsMsgSearchOp.EndsWith;
+
+var OtherHeader = Ci.nsMsgSearchAttrib.OtherHeader;
+var From = Ci.nsMsgSearchAttrib.Sender;
+var Subject = Ci.nsMsgSearchAttrib.Subject;
+
+var searchTests = [
+ // test the To: header
+ {
+ testString: "PrimaryEmail1@test.invalid",
+ testAttribute: From,
+ op: Is,
+ count: 1,
+ },
+ {
+ testString: "PrimaryEmail1@test.invalid",
+ testAttribute: From,
+ op: Isnt,
+ count: 0,
+ },
+ {
+ testString: "PrimaryEmail",
+ testAttribute: From,
+ op: BeginsWith,
+ count: 1,
+ },
+ {
+ testString: "invalid",
+ testAttribute: From,
+ op: BeginsWith,
+ count: 0,
+ },
+ {
+ testString: "invalid",
+ testAttribute: From,
+ op: EndsWith,
+ count: 1,
+ },
+ {
+ testString: "Primary",
+ testAttribute: From,
+ op: EndsWith,
+ count: 0,
+ },
+ {
+ testString: "QAContact",
+ testAttribute: OtherHeader,
+ op: BeginsWith,
+ count: 1,
+ },
+ {
+ testString: "filters",
+ testAttribute: OtherHeader,
+ op: BeginsWith,
+ count: 0,
+ },
+ {
+ testString: "mail.bugs",
+ testAttribute: OtherHeader,
+ op: EndsWith,
+ count: 1,
+ },
+ {
+ testString: "QAContact",
+ testAttribute: OtherHeader,
+ op: EndsWith,
+ count: 0,
+ },
+ {
+ testString: "QAcontact filters@mail.bugs",
+ testAttribute: OtherHeader,
+ op: Is,
+ count: 1,
+ },
+ {
+ testString: "filters@mail.bugs",
+ testAttribute: OtherHeader,
+ op: Is,
+ count: 0,
+ },
+ {
+ testString: "QAcontact filters@mail.bugs",
+ testAttribute: OtherHeader,
+ op: Isnt,
+ count: 0,
+ },
+ {
+ testString: "QAcontact",
+ testAttribute: OtherHeader,
+ op: Isnt,
+ count: 1,
+ },
+ {
+ testString: "filters",
+ testAttribute: OtherHeader,
+ op: Contains,
+ count: 1,
+ },
+ {
+ testString: "foobar",
+ testAttribute: OtherHeader,
+ op: Contains,
+ count: 0,
+ },
+ // test accumulation of received header
+ {
+ // only in first received
+ testString: "caspiaco",
+ testAttribute: OtherHeader,
+ op: Contains,
+ customHeader: "Received",
+ count: 1,
+ },
+ {
+ // only in second
+ testString: "webapp01.sj.mozilla.com",
+ testAttribute: OtherHeader,
+ op: Contains,
+ customHeader: "received",
+ count: 1,
+ },
+ {
+ // in neither
+ testString: "not there",
+ testAttribute: OtherHeader,
+ op: Contains,
+ customHeader: "received",
+ count: 0,
+ },
+ // test multiple line arbitrary headers
+ {
+ // in the first line
+ testString: "SpamAssassin 3.2.3",
+ testAttribute: OtherHeader,
+ op: Contains,
+ customHeader: "X-Spam-Checker-Version",
+ count: 1,
+ },
+ {
+ // in the second line
+ testString: "host29.example.com",
+ testAttribute: OtherHeader,
+ op: Contains,
+ customHeader: "X-Spam-Checker-Version",
+ count: 1,
+ },
+ {
+ // spans two lines with space
+ testString: "on host29.example.com",
+ testAttribute: OtherHeader,
+ op: Contains,
+ customHeader: "X-Spam-Checker-Version",
+ count: 1,
+ },
+ // subject spanning several lines
+ {
+ // on the first line
+ testString: "A filter will",
+ testAttribute: Subject,
+ op: Contains,
+ count: 1,
+ },
+ {
+ testString: "I do not exist",
+ testAttribute: Subject,
+ op: Contains,
+ count: 0,
+ },
+ {
+ // on the second line
+ testString: "this message",
+ testAttribute: Subject,
+ op: Contains,
+ count: 1,
+ },
+ {
+ // spanning second and third line
+ testString: "over many",
+ testAttribute: Subject,
+ op: Contains,
+ count: 1,
+ },
+ // tests of custom headers db values
+ {
+ testString: "a one line header",
+ dbHeader: "oneliner",
+ },
+ {
+ testString: "a two line header",
+ dbHeader: "twoliner",
+ },
+ {
+ testString: "a three line header with lotsa space and tabs",
+ dbHeader: "threeliner",
+ },
+ {
+ testString: "I have no space",
+ dbHeader: "nospace",
+ },
+ {
+ testString: "too much space",
+ dbHeader: "withspace",
+ },
+ // tests of custom db headers in a search
+ {
+ testString: "one line",
+ testAttribute: OtherHeader,
+ op: Contains,
+ customHeader: "oneliner",
+ count: 1,
+ },
+ {
+ testString: "two line header",
+ testAttribute: OtherHeader,
+ op: Contains,
+ customHeader: "twoliner",
+ count: 1,
+ },
+ {
+ testString: "three line header with lotsa",
+ testAttribute: OtherHeader,
+ op: Contains,
+ customHeader: "threeliner",
+ count: 1,
+ },
+ {
+ testString: "I have no space",
+ testAttribute: OtherHeader,
+ op: Contains,
+ customHeader: "nospace",
+ count: 1,
+ },
+ {
+ testString: "too much space",
+ testAttribute: OtherHeader,
+ op: Contains,
+ customHeader: "withspace",
+ count: 1,
+ },
+];
+
+add_setup(async function () {
+ setupIMAPPump();
+
+ // don't use offline store
+ IMAPPump.inbox.clearFlag(Ci.nsMsgFolderFlags.Offline);
+
+ // Load imap message.
+ IMAPPump.mailbox.addMessage(
+ new ImapMessage(specForFileName(gMessage), IMAPPump.mailbox.uidnext++, [])
+ );
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.updateFolderWithListener(null, listener);
+ await listener.promise;
+
+ Assert.equal(1, IMAPPump.inbox.getTotalMessages(false));
+});
+
+// process each test from queue, calls itself upon completion of each search
+add_task(async function testSearch() {
+ while (searchTests.length) {
+ let test = searchTests.shift();
+ if (test.dbHeader) {
+ // Test of a custom db header.
+ let customValue = mailTestUtils
+ .firstMsgHdr(IMAPPump.inbox)
+ .getStringProperty(test.dbHeader);
+ Assert.equal(customValue, test.testString);
+ } else {
+ await new Promise(resolve => {
+ new TestSearch(
+ IMAPPump.inbox,
+ test.testString,
+ test.testAttribute,
+ test.op,
+ test.count,
+ resolve,
+ null,
+ test.customHeader ? test.customHeader : "X-Bugzilla-Watch-Reason"
+ );
+ });
+ }
+ }
+});
+
+// Cleanup at end
+add_task(function endTest() {
+ teardownIMAPPump();
+});
+
+/*
+ * helper function
+ */
+
+// given a test file, return the file uri spec
+function specForFileName(aFileName) {
+ let file = do_get_file("../../../data/" + aFileName);
+ let msgfileuri = Services.io.newFileURI(file).QueryInterface(Ci.nsIFileURL);
+ return msgfileuri.spec;
+}
diff --git a/comm/mailnews/imap/test/unit/test_imapStatusCloseDBs.js b/comm/mailnews/imap/test/unit/test_imapStatusCloseDBs.js
new file mode 100644
index 0000000000..e81e5a120d
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_imapStatusCloseDBs.js
@@ -0,0 +1,49 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 tests that checking folders for new mail with STATUS
+// doesn't leave db's open.
+
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+var gFolder1, gFolder2;
+
+add_setup(function () {
+ Services.prefs.setBoolPref("mail.check_all_imap_folders_for_new", true);
+ Services.prefs.setIntPref("mail.server.server1.max_cached_connections", 2);
+
+ setupIMAPPump();
+
+ IMAPPump.daemon.createMailbox("folder 1", { subscribed: true });
+ IMAPPump.daemon.createMailbox("folder 2", { subscribed: true });
+
+ IMAPPump.server.performTest("SUBSCRIBE");
+
+ let rootFolder = IMAPPump.incomingServer.rootFolder;
+ gFolder1 = rootFolder.getChildNamed("folder 1");
+ gFolder2 = rootFolder.getChildNamed("folder 2");
+
+ IMAPPump.inbox.getNewMessages(null, null);
+ IMAPPump.server.performTest("STATUS");
+ Assert.ok(IMAPPump.server.isTestFinished());
+ // don't know if this will work, but we'll try. Wait for
+ // second status response
+ IMAPPump.server.performTest("STATUS");
+ Assert.ok(IMAPPump.server.isTestFinished());
+});
+
+add_task(function check() {
+ const gDbService = Cc["@mozilla.org/msgDatabase/msgDBService;1"].getService(
+ Ci.nsIMsgDBService
+ );
+ Assert.ok(gDbService.cachedDBForFolder(IMAPPump.inbox) !== null);
+ Assert.ok(gDbService.cachedDBForFolder(gFolder1) === null);
+ Assert.ok(gDbService.cachedDBForFolder(gFolder2) === null);
+});
+
+add_task(function endTest() {
+ teardownIMAPPump();
+});
diff --git a/comm/mailnews/imap/test/unit/test_imapStoreMsgOffline.js b/comm/mailnews/imap/test/unit/test_imapStoreMsgOffline.js
new file mode 100644
index 0000000000..4601d4b509
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_imapStoreMsgOffline.js
@@ -0,0 +1,221 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 test checks if the imap protocol code saves message to
+ * offline stores correctly, when we fetch the message for display.
+ * It checks:
+ * - Normal messages, no attachments.
+ * - Message with inline attachment (e.g., image)
+ * - Message with non-inline attachment (e.g., .doc file)
+ * - Message with mix of attachment types.
+ */
+
+var { MessageGenerator } = ChromeUtils.import(
+ "resource://testing-common/mailnews/MessageGenerator.jsm"
+);
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+var gMessageGenerator = new MessageGenerator();
+
+var gMsgFile1 = do_get_file("../../../data/bugmail10");
+var gMsgId1 = "200806061706.m56H6RWT004933@mrapp54.mozilla.org";
+var gMsgFile2 = do_get_file("../../../data/image-attach-test");
+var gMsgId2 = "4A947F73.5030709@example.com";
+var gMsgFile3 = do_get_file("../../../data/external-attach-test");
+var gMsgId3 = "876TY.5030709@example.com";
+
+var gFirstNewMsg;
+var gFirstMsgSize;
+var gImapInboxOfflineStoreSize;
+
+// Adds some messages directly to a mailbox (e.g. new mail).
+function addMessagesToServer(messages, mailbox) {
+ // For every message we have, we need to convert it to a file:/// URI.
+ messages.forEach(function (message) {
+ let URI = Services.io
+ .newFileURI(message.file)
+ .QueryInterface(Ci.nsIFileURL);
+ // Create the ImapMessage and store it on the mailbox.
+ mailbox.addMessage(new ImapMessage(URI.spec, mailbox.uidnext++, []));
+ });
+}
+
+add_setup(async function () {
+ // We aren't interested in downloading messages automatically.
+ Services.prefs.setBoolPref(
+ "mail.server.server1.autosync_offline_stores",
+ false
+ );
+ Services.prefs.setBoolPref("mail.server.server1.offline_download", true);
+
+ setupIMAPPump();
+
+ // These hacks are required because we've created the inbox before
+ // running initial folder discovery, and adding the folder bails
+ // out before we set it as verified online, so we bail out, and
+ // then remove the INBOX folder since it's not verified.
+ IMAPPump.inbox.hierarchyDelimiter = "/";
+ IMAPPump.inbox.verifiedAsOnlineFolder = true;
+
+ addMessagesToServer(
+ [
+ { file: gMsgFile1, messageId: gMsgId1 },
+ { file: gMsgFile2, messageId: gMsgId2 },
+ { file: gMsgFile3, messageId: gMsgId3 },
+ ],
+ IMAPPump.daemon.getMailbox("INBOX")
+ );
+
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.updateFolderWithListener(null, listener);
+ await listener.promise;
+});
+
+var gIMAPService;
+
+add_task(async function selectFirstMsg() {
+ // We postpone creating the imap service until after we've set the prefs
+ // that it reads on its startup.
+ gIMAPService = Cc[
+ "@mozilla.org/messenger/messageservice;1?type=imap"
+ ].getService(Ci.nsIMsgMessageService);
+
+ let db = IMAPPump.inbox.msgDatabase;
+ let msg1 = db.getMsgHdrForMessageID(gMsgId1);
+ let listener = new PromiseTestUtils.PromiseUrlListener({
+ OnStopRunningUrl: (aUrl, aExitCode) => {
+ Assert.equal(aExitCode, 0);
+ },
+ });
+ // We use the streamListener as a display consumer.
+ let streamListener = new PromiseTestUtils.PromiseStreamListener();
+ gIMAPService.loadMessage(
+ IMAPPump.inbox.getUriForMsg(msg1),
+ streamListener,
+ null,
+ listener,
+ false
+ );
+ await listener.promise;
+});
+
+add_task(async function select2ndMsg() {
+ let msg1 = IMAPPump.inbox.msgDatabase.getMsgHdrForMessageID(gMsgId1);
+ Assert.notEqual(msg1.flags & Ci.nsMsgMessageFlags.Offline, 0);
+ let db = IMAPPump.inbox.msgDatabase;
+ let msg2 = db.getMsgHdrForMessageID(gMsgId2);
+ let listener = new PromiseTestUtils.PromiseUrlListener({
+ OnStopRunningUrl: (aUrl, aExitCode) => {
+ Assert.equal(aExitCode, 0);
+ },
+ });
+ // We use the streamListener as a display consumer.
+ let streamListener = new PromiseTestUtils.PromiseStreamListener();
+ gIMAPService.loadMessage(
+ IMAPPump.inbox.getUriForMsg(msg2),
+ streamListener,
+ null,
+ listener,
+ false
+ );
+ await listener.promise;
+});
+
+add_task(async function select3rdMsg() {
+ let msg2 = IMAPPump.inbox.msgDatabase.getMsgHdrForMessageID(gMsgId2);
+ Assert.notEqual(msg2.flags & Ci.nsMsgMessageFlags.Offline, 0);
+ let db = IMAPPump.inbox.msgDatabase;
+ let msg3 = db.getMsgHdrForMessageID(gMsgId3);
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ // We use the streamListener as a display consumer.
+ let streamListener = new PromiseTestUtils.PromiseStreamListener();
+ gIMAPService.loadMessage(
+ IMAPPump.inbox.getUriForMsg(msg3),
+ streamListener,
+ null,
+ listener,
+ false
+ );
+ await listener.promise;
+});
+
+add_task(
+ {
+ // Can't turn this on because our fake server doesn't support body structure.
+ skip_if: () => true,
+ },
+ function verify3rdMsg() {
+ let db = IMAPPump.inbox.msgDatabase;
+ let msg3 = db.getMsgHdrForMessageID(gMsgId3);
+ Assert.equal(msg3.flags & Ci.nsMsgMessageFlags.Offline, 0);
+ }
+);
+
+add_task(async function addNewMsgs() {
+ let mbox = IMAPPump.daemon.getMailbox("INBOX");
+ // Make a couple of messages.
+ let messages = [];
+ let bodyString = "";
+ for (let i = 0; i < 100; i++) {
+ bodyString +=
+ "1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890\r\n";
+ }
+
+ gMessageGenerator = new MessageGenerator();
+ messages = messages.concat(
+ gMessageGenerator.makeMessage({
+ body: { body: bodyString, contentType: "text/plain" },
+ })
+ );
+
+ gFirstNewMsg = mbox.uidnext;
+ // Need to account for x-mozilla-status, status2, and envelope.
+ gFirstMsgSize = messages[0].toMessageString().length + 102;
+
+ messages.forEach(function (message) {
+ let dataUri = Services.io.newURI(
+ "data:text/plain;base64," + btoa(message.toMessageString())
+ );
+ mbox.addMessage(new ImapMessage(dataUri.spec, mbox.uidnext++, []));
+ });
+
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.updateFolderWithListener(null, listener);
+ await listener.promise;
+});
+add_task(async function test_queuedOfflineDownload() {
+ // Make sure that streaming the same message and then trying to download
+ // it for offline use doesn't end up in it getting added to the offline
+ // store twice.
+ gImapInboxOfflineStoreSize = IMAPPump.inbox.filePath.fileSize + gFirstMsgSize;
+ let newMsgHdr = IMAPPump.inbox.GetMessageHeader(gFirstNewMsg);
+ let msgURI = newMsgHdr.folder.getUriForMsg(newMsgHdr);
+ let msgServ = MailServices.messageServiceFromURI(msgURI);
+ let listener = new PromiseTestUtils.PromiseStreamListener();
+ msgServ.streamMessage(msgURI, listener, null, null, false, "", false);
+ await listener.promise;
+});
+add_task(async function firstStreamFinished() {
+ // nsIMsgFolder.downloadMessagesForOffline does not take a listener, so
+ // we invoke nsIImapService.downloadMessagesForOffline directly
+ // with a listener.
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ MailServices.imap.downloadMessagesForOffline(
+ gFirstNewMsg,
+ IMAPPump.inbox,
+ listener,
+ null
+ );
+ await listener.promise;
+});
+add_task(function checkOfflineStoreSize() {
+ Assert.ok(IMAPPump.inbox.filePath.fileSize <= gImapInboxOfflineStoreSize);
+});
+
+add_task(function endTest() {
+ teardownIMAPPump();
+});
diff --git a/comm/mailnews/imap/test/unit/test_imapUndo.js b/comm/mailnews/imap/test/unit/test_imapUndo.js
new file mode 100644
index 0000000000..8db0ad6903
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_imapUndo.js
@@ -0,0 +1,160 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 tests undoing of an imap delete to the trash.
+// There are three main cases:
+// 1. Normal undo
+// 2. Undo after the source folder has been compacted.
+// 2.1 Same, but the server doesn't support COPYUID (GMail case)
+//
+// Original Author: David Bienvenu <bienvenu@nventure.com>
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+var gRootFolder;
+var gMessages = [];
+var gMsgWindow;
+
+var gMsgFile1 = do_get_file("../../../data/bugmail10");
+var gMsgFile2 = do_get_file("../../../data/bugmail11");
+// var gMsgFile3 = do_get_file("../../../data/draft1");
+var gMsgFile4 = do_get_file("../../../data/bugmail7");
+var gMsgFile5 = do_get_file("../../../data/bugmail6");
+
+// Copied straight from the example files
+var gMsgId1 = "200806061706.m56H6RWT004933@mrapp54.mozilla.org";
+var gMsgId2 = "200804111417.m3BEHTk4030129@mrapp51.mozilla.org";
+// var gMsgId3 = "4849BF7B.2030800@example.com";
+var gMsgId4 = "bugmail7.m47LtAEf007542@mrapp51.mozilla.org";
+var gMsgId5 = "bugmail6.m47LtAEf007542@mrapp51.mozilla.org";
+
+// Adds some messages directly to a mailbox (eg new mail)
+function addMessagesToServer(messages, mailbox) {
+ // For every message we have, we need to convert it to a file:/// URI
+ messages.forEach(function (message) {
+ let URI = Services.io
+ .newFileURI(message.file)
+ .QueryInterface(Ci.nsIFileURL);
+ // Create the ImapMessage and store it on the mailbox.
+ mailbox.addMessage(new ImapMessage(URI.spec, mailbox.uidnext++, []));
+ });
+}
+
+function alertListener() {}
+
+alertListener.prototype = {
+ reset() {},
+
+ onAlert(aMessage, aMsgWindow) {
+ throw new Error("got alert - TEST FAILED " + aMessage);
+ },
+};
+
+add_setup(function () {
+ setupIMAPPump();
+
+ var listener1 = new alertListener();
+
+ MailServices.mailSession.addUserFeedbackListener(listener1);
+
+ Services.prefs.setBoolPref(
+ "mail.server.server1.autosync_offline_stores",
+ false
+ );
+ Services.prefs.setBoolPref("mail.server.server1.offline_download", false);
+
+ gMsgWindow = Cc["@mozilla.org/messenger/msgwindow;1"].createInstance(
+ Ci.nsIMsgWindow
+ );
+
+ gRootFolder = IMAPPump.incomingServer.rootFolder;
+ // these hacks are required because we've created the inbox before
+ // running initial folder discovery, and adding the folder bails
+ // out before we set it as verified online, so we bail out, and
+ // then remove the INBOX folder since it's not verified.
+ IMAPPump.inbox.hierarchyDelimiter = "/";
+ IMAPPump.inbox.verifiedAsOnlineFolder = true;
+
+ // Add a couple of messages to the INBOX
+ // this is synchronous, afaik
+ addMessagesToServer(
+ [
+ { file: gMsgFile1, messageId: gMsgId1 },
+ { file: gMsgFile4, messageId: gMsgId4 },
+ { file: gMsgFile5, messageId: gMsgId5 },
+ { file: gMsgFile2, messageId: gMsgId2 },
+ ],
+ IMAPPump.mailbox
+ );
+});
+
+add_task(async function updateFolder() {
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.updateFolderWithListener(null, listener);
+ await listener.promise;
+});
+
+add_task(async function deleteMessage() {
+ let msgToDelete = IMAPPump.inbox.msgDatabase.getMsgHdrForMessageID(gMsgId1);
+ let copyListener = new PromiseTestUtils.PromiseCopyListener();
+ gMessages.push(msgToDelete);
+ IMAPPump.inbox.deleteMessages(
+ gMessages,
+ gMsgWindow,
+ false,
+ true,
+ copyListener,
+ true
+ );
+ await copyListener.promise;
+});
+
+add_task(async function expunge() {
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.expunge(listener, gMsgWindow);
+ await listener.promise;
+
+ // Ensure that the message has been surely deleted.
+ Assert.equal(IMAPPump.inbox.msgDatabase.dBFolderInfo.numMessages, 3);
+});
+
+add_task(async function undoDelete() {
+ gMsgWindow.transactionManager.undoTransaction();
+ // after undo, we select the trash and then the inbox, so that we sync
+ // up with the server, and clear out the effects of having done the
+ // delete offline.
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ let trash = gRootFolder.getChildNamed("Trash");
+ trash
+ .QueryInterface(Ci.nsIMsgImapMailFolder)
+ .updateFolderWithListener(null, listener);
+ await listener.promise;
+});
+
+add_task(async function goBackToInbox() {
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.updateFolderWithListener(gMsgWindow, listener);
+ await listener.promise;
+});
+
+add_task(function verifyFolders() {
+ let msgRestored = IMAPPump.inbox.msgDatabase.getMsgHdrForMessageID(gMsgId1);
+ Assert.ok(msgRestored !== null);
+ Assert.equal(IMAPPump.inbox.msgDatabase.dBFolderInfo.numMessages, 4);
+});
+
+add_task(function endTest() {
+ // Cleanup, null out everything, close all cached connections and stop the
+ // server
+ gMessages = [];
+ gMsgWindow.closeWindow();
+ gMsgWindow = null;
+ gRootFolder = null;
+ teardownIMAPPump();
+});
diff --git a/comm/mailnews/imap/test/unit/test_imapUrls.js b/comm/mailnews/imap/test/unit/test_imapUrls.js
new file mode 100644
index 0000000000..e310705169
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_imapUrls.js
@@ -0,0 +1,31 @@
+/* -*- Mode: JavaScript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ *
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/licenses/publicdomain/
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+// IMAP pump
+
+setupIMAPPump();
+
+/*
+ * Test parsing of imap uri's with very large UID's.
+ */
+
+function run_test() {
+ let imapS = Cc[
+ "@mozilla.org/messenger/messageservice;1?type=imap"
+ ].getService(Ci.nsIMsgMessageService);
+ let uri = imapS.getUrlForUri(
+ "imap-message://user@localhost/INBOX#4294967168"
+ );
+ Assert.equal(
+ uri.spec,
+ "imap://user@localhost:" +
+ IMAPPump.server.port +
+ "/fetch%3EUID%3E%5EINBOX%3E4294967168"
+ );
+ teardownIMAPPump();
+}
diff --git a/comm/mailnews/imap/test/unit/test_largeOfflineStore.js b/comm/mailnews/imap/test/unit/test_largeOfflineStore.js
new file mode 100644
index 0000000000..6e0d2e9dd1
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_largeOfflineStore.js
@@ -0,0 +1,141 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Test to ensure that downloadAllForOffline works correctly for large imap
+ * stores, i.e., over 4 GiB.
+ */
+
+var { MessageGenerator, MessageScenarioFactory } = ChromeUtils.import(
+ "resource://testing-common/mailnews/MessageGenerator.jsm"
+);
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+Services.prefs.setCharPref(
+ "mail.serverDefaultStoreContractID",
+ "@mozilla.org/msgstore/berkeleystore;1"
+);
+
+var gOfflineStoreSize;
+
+add_setup(async function () {
+ setupIMAPPump();
+
+ // Figure out the name of the IMAP inbox
+ let inboxFile = IMAPPump.incomingServer.rootMsgFolder.filePath;
+ inboxFile.append("INBOX");
+ if (!inboxFile.exists()) {
+ inboxFile.create(Ci.nsIFile.NORMAL_FILE_TYPE, parseInt("0644", 8));
+ }
+
+ let neededFreeSpace = 0x200000000;
+ // On Windows, check whether the drive is NTFS. If it is, mark the file as
+ // sparse. If it isn't, then bail out now, because in all probability it is
+ // FAT32, which doesn't support file sizes greater than 4 GB.
+ if (
+ "@mozilla.org/windows-registry-key;1" in Cc &&
+ mailTestUtils.get_file_system(inboxFile) != "NTFS"
+ ) {
+ throw new Error("On Windows, this test only works on NTFS volumes.\n");
+ }
+
+ let isFileSparse = mailTestUtils.mark_file_region_sparse(
+ inboxFile,
+ 0,
+ 0x10000000f
+ );
+ let freeDiskSpace = inboxFile.diskSpaceAvailable;
+ Assert.ok(
+ isFileSparse && freeDiskSpace > neededFreeSpace,
+ "This test needs " +
+ mailTestUtils.toMiBString(neededFreeSpace) +
+ " free space to run."
+ );
+});
+
+add_task(async function addOfflineMessages() {
+ // Create a couple test messages on the IMAP server.
+ let messages = [];
+ let messageGenerator = new MessageGenerator();
+ let scenarioFactory = new MessageScenarioFactory(messageGenerator);
+
+ messages = messages.concat(scenarioFactory.directReply(2));
+ let dataUri = Services.io.newURI(
+ "data:text/plain;base64," + btoa(messages[0].toMessageString())
+ );
+ let imapMsg = new ImapMessage(dataUri.spec, IMAPPump.mailbox.uidnext++, []);
+ IMAPPump.mailbox.addMessage(imapMsg);
+
+ dataUri = Services.io.newURI(
+ "data:text/plain;base64," + btoa(messages[1].toMessageString())
+ );
+ imapMsg = new ImapMessage(dataUri.spec, IMAPPump.mailbox.uidnext++, []);
+ IMAPPump.mailbox.addMessage(imapMsg);
+
+ // Extend local IMAP inbox to over 4 GiB.
+ let outputStream = Cc["@mozilla.org/network/file-output-stream;1"]
+ .createInstance(Ci.nsIFileOutputStream)
+ .QueryInterface(Ci.nsISeekableStream);
+ // Open in write-only mode, no truncate.
+ outputStream.init(IMAPPump.inbox.filePath, 0x02, -1, 0);
+ // seek to 15 bytes past 4GB.
+ outputStream.seek(0, 0x10000000f);
+ // Write an empty "from" line.
+ outputStream.write("from\r\n", 6);
+ outputStream.close();
+
+ // Save initial file size.
+ gOfflineStoreSize = IMAPPump.inbox.filePath.fileSize;
+ dump(
+ "Offline store size (before 1st downloadAllForOffline()) = " +
+ gOfflineStoreSize +
+ "\n"
+ );
+
+ // Download for offline use, to append created messages to local IMAP inbox.
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.downloadAllForOffline(listener, null);
+ await listener.promise;
+});
+
+add_task(async function check_result() {
+ // Call downloadAllForOffline() a second time.
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.downloadAllForOffline(listener, null);
+ await listener.promise;
+
+ // Make sure offline store grew (i.e., we were not writing over data).
+ let offlineStoreSize = IMAPPump.inbox.filePath.fileSize;
+ dump(
+ "Offline store size (after 2nd downloadAllForOffline()): " +
+ offlineStoreSize +
+ " Msg hdr offsets should be close to it.\n"
+ );
+ Assert.ok(offlineStoreSize > gOfflineStoreSize);
+
+ // Verify that the message headers have the offline flag set.
+ for (let header of IMAPPump.inbox.msgDatabase.enumerateMessages()) {
+ // Verify that each message has been downloaded and looks OK.
+ Assert.ok(
+ header instanceof Ci.nsIMsgDBHdr &&
+ header.flags & Ci.nsMsgMessageFlags.Offline,
+ "Message downloaded for offline use"
+ );
+
+ // Make sure we don't fall over if we ask to read the message.
+ IMAPPump.inbox.getLocalMsgStream(header).close();
+ }
+});
+
+add_task(function teardown() {
+ // Free up disk space - if you want to look at the file after running
+ // this test, comment out this line.
+ if (IMAPPump.inbox) {
+ IMAPPump.inbox.filePath.remove(false);
+ }
+
+ teardownIMAPPump();
+});
diff --git a/comm/mailnews/imap/test/unit/test_listClosesDB.js b/comm/mailnews/imap/test/unit/test_listClosesDB.js
new file mode 100644
index 0000000000..63ab5b70d4
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_listClosesDB.js
@@ -0,0 +1,58 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 tests that listing folders on startup because we're not using
+// subscription doesn't leave db's open.
+
+var { MessageGenerator } = ChromeUtils.import(
+ "resource://testing-common/mailnews/MessageGenerator.jsm"
+);
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+var gSub3;
+
+add_setup(async function () {
+ setupIMAPPump();
+
+ IMAPPump.daemon.createMailbox("folder1", { subscribed: true });
+ IMAPPump.daemon.createMailbox("folder1/sub1", "", { subscribed: true });
+ IMAPPump.daemon.createMailbox("folder1/sub1/sub2", "", { subscribed: true });
+ IMAPPump.daemon.createMailbox("folder1/sub1/sub2/sub3", "", {
+ subscribed: true,
+ });
+
+ IMAPPump.incomingServer.usingSubscription = false;
+
+ let rootFolder = IMAPPump.incomingServer.rootFolder.QueryInterface(
+ Ci.nsIMsgImapMailFolder
+ );
+ rootFolder.hierarchyDelimiter = "/";
+ IMAPPump.inbox.hierarchyDelimiter = "/";
+ let folder1 = rootFolder.addSubfolder("folder1");
+ let sub1 = folder1.addSubfolder("sub1");
+ let sub2 = sub1.addSubfolder("sub2");
+ gSub3 = sub2.addSubfolder("sub3");
+ IMAPPump.server.performTest("LIST");
+
+ await PromiseTestUtils.promiseDelay(1000);
+});
+
+add_task(async function updateInbox() {
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.updateFolderWithListener(null, listener);
+ await listener.promise;
+});
+
+add_task(function checkCachedDBForFolder() {
+ const gDbService = Cc["@mozilla.org/msgDatabase/msgDBService;1"].getService(
+ Ci.nsIMsgDBService
+ );
+ Assert.equal(gDbService.cachedDBForFolder(gSub3), null);
+});
+
+add_task(function teardown() {
+ teardownIMAPPump();
+});
diff --git a/comm/mailnews/imap/test/unit/test_listSubscribed.js b/comm/mailnews/imap/test/unit/test_listSubscribed.js
new file mode 100644
index 0000000000..0d536dd45c
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_listSubscribed.js
@@ -0,0 +1,123 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/** Test that listing subscribed mailboxes uses LIST (SUBSCRIBED) instead of LSUB
+ * for servers that have LIST-EXTENDED capability
+ */
+/* References:
+ * RFC 5258 - http://tools.ietf.org/html/rfc5258
+ * Bug 495318
+ * Bug 816028
+ * http://bugzilla.zimbra.com/show_bug.cgi?id=78794
+ */
+
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+add_setup(async function () {
+ // Zimbra is one of the servers that supports LIST-EXTENDED
+ // it also has a bug that causes a server crash in certain setups
+ setupIMAPPump("Zimbra");
+
+ Services.prefs.setBoolPref(
+ "mail.server.server1.autosync_offline_stores",
+ false
+ );
+
+ // Setup the mailboxes that will be used for this test.
+ IMAPPump.mailbox.subscribed = true;
+ IMAPPump.daemon.createMailbox("folder1", {
+ subscribed: true,
+ flags: ["\\Noselect"],
+ });
+ IMAPPump.daemon.createMailbox("folder1/folder11", {
+ subscribed: true,
+ flags: ["\\Noinferiors"],
+ });
+ IMAPPump.daemon.createMailbox("folder2", {
+ subscribed: true,
+ nonExistent: true,
+ });
+ IMAPPump.daemon.createMailbox("folder3", {});
+
+ // select the inbox to force folder discovery, etc.
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.updateFolderWithListener(null, listener);
+ await listener.promise;
+});
+
+// tests that LIST (SUBSCRIBED) returns the proper response
+add_task(async function testListSubscribed() {
+ // check that we have \Noselect and \Noinferiors flags - these would not have
+ // been returned if we had used LSUB instead of LIST(SUBSCRIBED)
+ let rootFolder = IMAPPump.incomingServer.rootFolder;
+ let folder1 = rootFolder.getChildNamed("folder1");
+ Assert.ok(folder1.getFlag(Ci.nsMsgFolderFlags.ImapNoselect));
+ Assert.ok(!folder1.getFlag(Ci.nsMsgFolderFlags.ImapNoinferiors));
+
+ // make sure the above test was not a fluke
+ let folder11 = folder1.getChildNamed("folder11");
+ Assert.ok(!folder11.getFlag(Ci.nsMsgFolderFlags.ImapNoselect));
+ Assert.ok(folder11.getFlag(Ci.nsMsgFolderFlags.ImapNoinferiors));
+
+ // test that \NonExistent implies \Noselect
+ rootFolder.getChildNamed("folder2");
+ Assert.ok(folder1.getFlag(Ci.nsMsgFolderFlags.ImapNoselect));
+
+ // should not get a folder3 since it is not subscribed
+ let folder3;
+ try {
+ folder3 = rootFolder.getChildNamed("folder3");
+ } catch (ex) {}
+ // do_check_false(folder1.getFlag(Ci.nsMsgFolderFlags.Subscribed));
+ Assert.equal(null, folder3);
+});
+
+add_task(async function testZimbraServerVersions() {
+ if (Services.prefs.getBoolPref("mailnews.imap.jsmodule", false)) {
+ return;
+ }
+
+ // older versions of Zimbra can crash if we send LIST (SUBSCRIBED) so we want
+ // to make sure that we are checking for versions
+
+ let testValues = [
+ { version: "6.3.1_GA_2790", expectedResult: false },
+ { version: "7.2.2_GA_2790", expectedResult: false },
+ { version: "7.2.3_GA_2790", expectedResult: true },
+ { version: "8.0.2_GA_2790", expectedResult: false },
+ { version: "8.0.3_GA_2790", expectedResult: true },
+ { version: "9.0.0_GA_2790", expectedResult: true },
+ ];
+
+ for (let i = 0; i < testValues.length; i++) {
+ IMAPPump.daemon.idResponse =
+ '("NAME" "Zimbra" ' +
+ '"VERSION" "' +
+ testValues[i].version +
+ '" ' +
+ '"RELEASE" "20120815212257" ' +
+ '"USER" "user@domain.com" ' +
+ '"SERVER" "14b63305-d002-4f1b-bcd9-23d402d4ef40")';
+ IMAPPump.incomingServer.closeCachedConnections();
+ IMAPPump.incomingServer.performExpand(null);
+ // select inbox is just to wait on performExpand since performExpand does not have listener
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.updateFolderWithListener(null, listener);
+ await listener.promise;
+ // if we send LSUB instead of LIST(SUBSCRIBED), then we should not have \NoSelect flag
+ let rootFolder = IMAPPump.incomingServer.rootFolder;
+ let folder1 = rootFolder.getChildNamed("folder1");
+ Assert.equal(
+ folder1.getFlag(Ci.nsMsgFolderFlags.ImapNoselect),
+ testValues[i].expectedResult
+ );
+ }
+});
+
+// Cleanup at end
+add_task(function endTest() {
+ teardownIMAPPump();
+});
diff --git a/comm/mailnews/imap/test/unit/test_localToImapFilter.js b/comm/mailnews/imap/test/unit/test_localToImapFilter.js
new file mode 100644
index 0000000000..b0a28aedda
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_localToImapFilter.js
@@ -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 tests copies of multiple messages using filters
+ * from incoming POP3, with filter actions copying and moving
+ * messages to IMAP folders. This test is adapted from
+ * test_imapFolderCopy.js
+ *
+ * Original author: Kent James <kent@caspia.com>
+ */
+
+/**
+ * NOTE:
+ * There's a problem with this test in chaos mode (mach xpcshell-test --verify)
+ * with the filter applying.
+ * It's either a problem with the POP3Pump implementation (testing infrastructure failure)
+ * or a problem with the copy filter.
+ */
+
+/* import-globals-from ../../../test/resources/POP3pump.js */
+load("../../../resources/POP3pump.js");
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+var gEmptyLocal1, gEmptyLocal2;
+var gFiles = ["../../../data/bugmail1", "../../../data/draft1"];
+
+add_setup(async function () {
+ setupIMAPPump();
+ let emptyFolder1Listener = PromiseTestUtils.promiseFolderAdded("empty 1");
+ gEmptyLocal1 = localAccountUtils.rootFolder.createLocalSubfolder("empty 1");
+ await emptyFolder1Listener;
+ let emptyFolder2Listener = PromiseTestUtils.promiseFolderAdded("empty 2");
+ gEmptyLocal2 = localAccountUtils.rootFolder.createLocalSubfolder("empty 2");
+ await emptyFolder2Listener;
+
+ // These hacks are required because we've created the inbox before
+ // running initial folder discovery, and adding the folder bails
+ // out before we set it as verified online, so we bail out, and
+ // then remove the INBOX folder since it's not verified.
+ IMAPPump.inbox.hierarchyDelimiter = "/";
+ IMAPPump.inbox.verifiedAsOnlineFolder = true;
+});
+
+add_task(async function copyFolder1() {
+ let copyListener = new PromiseTestUtils.PromiseCopyListener();
+ MailServices.copy.copyFolder(
+ gEmptyLocal1,
+ IMAPPump.inbox,
+ false,
+ copyListener,
+ null
+ );
+ await copyListener.promise;
+});
+
+add_task(async function updateTrash() {
+ let trashFolder = IMAPPump.incomingServer.rootFolder
+ .getChildNamed("Trash")
+ .QueryInterface(Ci.nsIMsgImapMailFolder);
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ // hack to force uid validity to get initialized for trash.
+ trashFolder.updateFolderWithListener(null, listener);
+ await listener.promise;
+});
+
+add_task(async function copyFolder2() {
+ let copyListener = new PromiseTestUtils.PromiseCopyListener();
+ MailServices.copy.copyFolder(
+ gEmptyLocal2,
+ IMAPPump.inbox,
+ false,
+ copyListener,
+ null
+ );
+ await copyListener.promise;
+});
+
+add_task(async function getLocalMessages() {
+ // setup copy then move mail filters on the inbox
+ let filterList = gPOP3Pump.fakeServer.getFilterList(null);
+ let filter = filterList.createFilter("copyThenMoveAll");
+ let searchTerm = filter.createTerm();
+ searchTerm.matchAll = true;
+ filter.appendTerm(searchTerm);
+ let copyAction = filter.createAction();
+ copyAction.type = Ci.nsMsgFilterAction.CopyToFolder;
+ copyAction.targetFolderUri = IMAPPump.inbox.getChildNamed("empty 1").URI;
+ filter.appendAction(copyAction);
+ let moveAction = filter.createAction();
+ moveAction.type = Ci.nsMsgFilterAction.MoveToFolder;
+ moveAction.targetFolderUri = IMAPPump.inbox.getChildNamed("empty 2").URI;
+ filter.appendAction(moveAction);
+ filter.enabled = true;
+ filterList.insertFilterAt(0, filter);
+ let resolveOnDone;
+ let promiseOnDone = new Promise(resolve => {
+ resolveOnDone = resolve;
+ });
+ gPOP3Pump.files = gFiles;
+ gPOP3Pump.onDone = resolveOnDone;
+ gPOP3Pump.run();
+
+ await promiseOnDone;
+});
+
+add_task(async function test_update1_copyFilter() {
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ let folder1 = IMAPPump.inbox
+ .getChildNamed("empty 1")
+ .QueryInterface(Ci.nsIMsgImapMailFolder);
+ folder1.updateFolderWithListener(null, listener);
+ await listener.promise;
+ Assert.ok(folder1 !== null);
+ Assert.equal(
+ folderCount(folder1),
+ 2,
+ "the two filtered messages should be in empty 1"
+ );
+});
+
+add_task(async function test_update2_moveFilter() {
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ let folder2 = IMAPPump.inbox
+ .getChildNamed("empty 2")
+ .QueryInterface(Ci.nsIMsgImapMailFolder);
+ folder2.updateFolderWithListener(null, listener);
+ await listener.promise;
+ Assert.ok(folder2 !== null);
+ Assert.equal(
+ folderCount(folder2),
+ 2,
+ "the two filtered messages should be in empty 2"
+ );
+});
+
+add_task(async function verifyLocalFolder() {
+ // the local inbox folder should now be empty, since the second
+ // operation was a move
+ Assert.equal(folderCount(localAccountUtils.inboxFolder), 0);
+});
+
+add_task(function endTest() {
+ gEmptyLocal1 = null;
+ gEmptyLocal2 = null;
+ gPOP3Pump = null;
+ teardownIMAPPump();
+});
+
+function folderCount(folder) {
+ return [...folder.msgDatabase.enumerateMessages()].length;
+}
diff --git a/comm/mailnews/imap/test/unit/test_localToImapFilterQuarantine.js b/comm/mailnews/imap/test/unit/test_localToImapFilterQuarantine.js
new file mode 100644
index 0000000000..130a03e403
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_localToImapFilterQuarantine.js
@@ -0,0 +1,121 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 tests copies of multiple messages using filters
+ * from incoming POP3, with filter actions copying and moving
+ * messages to an IMAP folder, when the POP3 message uses
+ * quarantining to help antivirus software. See bug 387361.
+ *
+ */
+
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+Services.prefs.setCharPref(
+ "mail.serverDefaultStoreContractID",
+ "@mozilla.org/msgstore/berkeleystore;1"
+);
+
+/* import-globals-from ../../../test/resources/POP3pump.js */
+load("../../../resources/POP3pump.js");
+
+var gSubfolder;
+
+add_setup(function () {
+ setupIMAPPump();
+ // quarantine messages
+ Services.prefs.setBoolPref("mailnews.downloadToTempFile", true);
+});
+
+add_task(async function createSubfolder() {
+ let folderAddedListener = PromiseTestUtils.promiseFolderAdded("subfolder");
+ IMAPPump.incomingServer.rootFolder.createSubfolder("subfolder", null);
+ await folderAddedListener;
+ gSubfolder = IMAPPump.incomingServer.rootFolder.getChildNamed("subfolder");
+ Assert.ok(gSubfolder instanceof Ci.nsIMsgImapMailFolder);
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ gSubfolder.updateFolderWithListener(null, listener);
+ await listener.promise;
+});
+
+add_task(async function getLocalMessages() {
+ // setup copy then move mail filters on the inbox
+ let filterList = gPOP3Pump.fakeServer.getFilterList(null);
+ let filter = filterList.createFilter("copyThenMoveAll");
+ let searchTerm = filter.createTerm();
+ searchTerm.matchAll = true;
+ filter.appendTerm(searchTerm);
+ let copyAction = filter.createAction();
+ copyAction.type = Ci.nsMsgFilterAction.CopyToFolder;
+ copyAction.targetFolderUri = gSubfolder.URI;
+ filter.appendAction(copyAction);
+ filter.enabled = true;
+ filterList.insertFilterAt(0, filter);
+
+ let resolveDone;
+ let promise = new Promise(resolve => {
+ resolveDone = resolve;
+ });
+ gPOP3Pump.files = ["../../../data/bugmail1"];
+ gPOP3Pump.onDone = resolveDone;
+ gPOP3Pump.run();
+ await promise;
+});
+
+add_task(async function updateSubfolderAndTest() {
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ let folderLoaded = PromiseTestUtils.promiseFolderEvent(
+ gSubfolder,
+ "FolderLoaded"
+ );
+ gSubfolder.updateFolderWithListener(null, listener);
+ await listener.promise;
+ await folderLoaded;
+
+ Assert.equal(folderCount(gSubfolder), 1);
+ Assert.equal(folderCount(localAccountUtils.inboxFolder), 1);
+});
+
+add_task(async function get2Messages() {
+ let resolveDone;
+ let promise = new Promise(resolve => {
+ resolveDone = resolve;
+ });
+ gPOP3Pump.files = ["../../../data/bugmail10", "../../../data/draft1"];
+ gPOP3Pump.onDone = resolveDone;
+ gPOP3Pump.run();
+ await promise;
+});
+
+add_task(async function updateSubfolderAndTest2() {
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ let folderLoaded = PromiseTestUtils.promiseFolderEvent(
+ gSubfolder,
+ "FolderLoaded"
+ );
+ gSubfolder.updateFolderWithListener(null, listener);
+ await listener.promise;
+ await folderLoaded;
+ Assert.equal(folderCount(gSubfolder), 3);
+ Assert.equal(folderCount(localAccountUtils.inboxFolder), 3);
+});
+
+add_task(function endTest() {
+ // Cleanup, null out everything, close all cached connections and stop the
+ // server
+ gPOP3Pump = null;
+ teardownIMAPPump();
+});
+
+// helper functions
+
+// count of messages in a folder, using the database
+function folderCount(folder) {
+ return [...folder.msgDatabase.enumerateMessages()].length;
+}
diff --git a/comm/mailnews/imap/test/unit/test_lsub.js b/comm/mailnews/imap/test/unit/test_lsub.js
new file mode 100644
index 0000000000..1d7f479a48
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_lsub.js
@@ -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/. */
+
+// Test that listing subscribed mailboxes uses LIST (SUBSCRIBED) instead of LSUB
+// for servers that have LIST-EXTENDED capability
+// see: bug 495318
+// see: RFC 5258 - http://tools.ietf.org/html/rfc5258
+
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+add_setup(function () {
+ setupIMAPPump();
+
+ Services.prefs.setBoolPref(
+ "mail.server.server1.autosync_offline_stores",
+ false
+ );
+});
+
+// Setup the mailboxes that will be used for this test.
+add_setup(async function () {
+ IMAPPump.mailbox.subscribed = true;
+ IMAPPump.daemon.createMailbox("folder1", {
+ subscribed: true,
+ flags: ["\\Noselect"],
+ });
+ IMAPPump.daemon.createMailbox("folder1/folder11", {
+ subscribed: true,
+ flags: ["\\Noinferiors"],
+ });
+ IMAPPump.daemon.createMailbox("folder2", {
+ subscribed: true,
+ nonExistent: true,
+ });
+ IMAPPump.daemon.createMailbox("folder3", {});
+
+ // select the inbox to force folder discovery, etc.
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.updateFolderWithListener(null, listener);
+ await listener.promise;
+});
+
+// Tests that LSUB returns the proper response.
+add_task(function testLsub() {
+ // Check that we have \Noselect and \Noinferiors flags - these would not have
+ // been returned if we had used LSUB instead of LIST(SUBSCRIBED).
+ let rootFolder = IMAPPump.incomingServer.rootFolder;
+ let folder1 = rootFolder.getChildNamed("folder1");
+ Assert.ok(folder1.getFlag(Ci.nsMsgFolderFlags.ImapNoselect));
+ Assert.ok(!folder1.getFlag(Ci.nsMsgFolderFlags.ImapNoinferiors));
+
+ // Make sure the above test was not a fluke.
+ let folder11 = folder1.getChildNamed("folder11");
+ Assert.ok(!folder11.getFlag(Ci.nsMsgFolderFlags.ImapNoselect));
+ Assert.ok(folder11.getFlag(Ci.nsMsgFolderFlags.ImapNoinferiors));
+
+ // Test that \NonExistent implies \Noselect.
+ rootFolder.getChildNamed("folder2");
+ Assert.ok(folder1.getFlag(Ci.nsMsgFolderFlags.ImapNoselect));
+
+ // Should not get a folder3 since it is not subscribed.
+ let folder3;
+ try {
+ folder3 = rootFolder.getChildNamed("folder3");
+ } catch (ex) {}
+ Assert.equal(false, folder1.getFlag(Ci.nsMsgFolderFlags.Subscribed));
+ Assert.equal(null, folder3);
+});
+
+// Cleanup at end.
+add_task(function endTest() {
+ teardownIMAPPump();
+});
diff --git a/comm/mailnews/imap/test/unit/test_mailboxes.js b/comm/mailnews/imap/test/unit/test_mailboxes.js
new file mode 100644
index 0000000000..0f13f6b328
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_mailboxes.js
@@ -0,0 +1,80 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Tests basic mailbox handling of IMAP, like discovery, rename and empty folder.
+ */
+
+var { MessageGenerator } = ChromeUtils.import(
+ "resource://testing-common/mailnews/MessageGenerator.jsm"
+);
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+// The following folder names are not pure ASCII and will be MUTF-7 encoded.
+const folderName1 = "I18N box\u00E1"; // I18N boxá
+const folderName2 = "test \u00E4"; // test ä
+
+add_setup(async function () {
+ setupIMAPPump();
+
+ IMAPPump.daemon.createMailbox(folderName1, { subscribed: true });
+ IMAPPump.daemon.createMailbox("Unsubscribed box");
+ // Create an all upper case trash folder name to make sure
+ // we handle special folder names case-insensitively.
+ IMAPPump.daemon.createMailbox("TRASH", { subscribed: true });
+
+ // Get the server list...
+ IMAPPump.server.performTest("LIST");
+
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.updateFolderWithListener(null, listener);
+ await listener.promise;
+});
+
+add_task(async function checkDiscovery() {
+ let rootFolder = IMAPPump.incomingServer.rootFolder;
+ // Check that we've subscribed to the boxes returned by LSUB. We also get
+ // checking of proper i18n in mailboxes for free here.
+ Assert.ok(rootFolder.containsChildNamed("Inbox"));
+ Assert.ok(rootFolder.containsChildNamed("TRASH"));
+ // Make sure we haven't created an extra "Trash" folder.
+ let trashes = rootFolder.getFoldersWithFlags(Ci.nsMsgFolderFlags.Trash);
+ Assert.equal(trashes.length, 1);
+ Assert.equal(rootFolder.numSubFolders, 3);
+ Assert.ok(rootFolder.containsChildNamed(folderName1));
+ // This is not a subscribed box, so we shouldn't be subscribing to it.
+ Assert.ok(!rootFolder.containsChildNamed("Unsubscribed box"));
+
+ let i18nChild = rootFolder.getChildNamed(folderName1);
+
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ MailServices.imap.renameLeaf(i18nChild, folderName2, listener, null);
+ await listener.promise;
+});
+
+add_task(async function checkRename() {
+ let rootFolder = IMAPPump.incomingServer.rootFolder;
+ Assert.ok(rootFolder.containsChildNamed(folderName2));
+ let newChild = rootFolder
+ .getChildNamed(folderName2)
+ .QueryInterface(Ci.nsIMsgImapMailFolder);
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ newChild.updateFolderWithListener(null, listener);
+ await listener.promise;
+});
+
+add_task(function checkEmptyFolder() {
+ try {
+ let serverSink = IMAPPump.server.QueryInterface(Ci.nsIImapServerSink);
+ serverSink.possibleImapMailbox("/", "/", 0);
+ } catch (ex) {
+ // We expect this to fail, but not crash or assert.
+ }
+});
+
+add_task(function endTest() {
+ teardownIMAPPump();
+});
diff --git a/comm/mailnews/imap/test/unit/test_nsIMsgFolderListenerIMAP.js b/comm/mailnews/imap/test/unit/test_nsIMsgFolderListenerIMAP.js
new file mode 100644
index 0000000000..7b733ab9e4
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_nsIMsgFolderListenerIMAP.js
@@ -0,0 +1,363 @@
+/* -*- Mode: Javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ *
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/licenses/publicdomain/
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+/*
+ * Test suite for nsIMsgFolderListener events due to IMAP operations
+ *
+ * Currently tested
+ * - Adding new folders
+ * - Copying messages from files to mailboxes
+ * - Adding new messages directly to mailboxes
+ *
+ * NOTE (See Bug 1632022):
+ * Running this test by itself...
+ *
+ * $ ./mach xpcshell-test comm/mailnews/imap/test/unit/test_nsIMsgFolderListenerIMAP.js
+ * ...will fail.
+ *
+ * This is because all the IMAP tests run twice - once with mbox storage and
+ * once with maildir storage. For this test, the two parallel instances
+ * interact badly.
+ *
+ */
+
+/* import-globals-from ../../../test/resources/msgFolderListenerSetup.js */
+load("../../../resources/msgFolderListenerSetup.js");
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+// Globals
+var gRootFolder;
+var gIMAPInbox, gIMAPFolder2, gIMAPFolder3;
+var gIMAPDaemon, gServer, gIMAPIncomingServer;
+var gMsgFile1 = do_get_file("../../../data/bugmail10");
+var gMsgFile2 = do_get_file("../../../data/bugmail11");
+var gMsgFile3 = do_get_file("../../../data/draft1");
+var gMsgFile4 = do_get_file("../../../data/bugmail7");
+var gMsgFile5 = do_get_file("../../../data/bugmail6");
+
+// Copied straight from the example files
+var gMsgId1 = "200806061706.m56H6RWT004933@mrapp54.mozilla.org";
+var gMsgId2 = "200804111417.m3BEHTk4030129@mrapp51.mozilla.org";
+var gMsgId3 = "4849BF7B.2030800@example.com";
+var gMsgId4 = "bugmail7.m47LtAEf007542@mrapp51.mozilla.org";
+var gMsgId5 = "bugmail6.m47LtAEf007542@mrapp51.mozilla.org";
+var gMsgWindow = Cc["@mozilla.org/messenger/msgwindow;1"].createInstance(
+ Ci.nsIMsgWindow
+);
+
+function addFolder(parent, folderName, storeIn) {
+ gExpectedEvents = [
+ [MailServices.mfn.folderAdded, parent, folderName, storeIn],
+ ];
+ // No copy listener notification for this
+ gCurrStatus |= kStatus.onStopCopyDone;
+ parent.createSubfolder(folderName, null);
+ gCurrStatus |= kStatus.functionCallDone;
+ gServer.performTest("LIST");
+ if (gCurrStatus == kStatus.everythingDone) {
+ resetStatusAndProceed();
+ }
+}
+
+function copyFileMessage(file, messageId, destFolder) {
+ copyListener.mFolderStoredIn = destFolder;
+
+ // This *needs* to be a draft (fourth parameter), as for non-UIDPLUS servers,
+ // nsImapProtocol::UploadMessageFromFile is hardcoded not to send a copy
+ // listener notification. The same function also asks for the message id from
+ // the copy listener, without which it will *not* send the notification.
+
+ // ...but wait, nsImapProtocol.cpp requires SEARCH afterwards to retrieve the
+ // message header, and fakeserver doesn't implement it yet. So get it to fail
+ // earlier by *not* sending the message id.
+ // copyListener.mMessageId = messageId;
+
+ // Instead store the message id in gExpectedEvents, so we can match that up
+ gExpectedEvents = [
+ [MailServices.mfn.msgAdded, { expectedMessageId: messageId }],
+ [MailServices.mfn.msgsClassified, [messageId], false, false],
+ ];
+ destFolder.updateFolder(null);
+ MailServices.copy.copyFileMessage(
+ file,
+ destFolder,
+ null,
+ true,
+ 0,
+ "",
+ copyListener,
+ null
+ );
+ gCurrStatus |= kStatus.functionCallDone;
+ gServer.performTest("APPEND");
+ // Allow some time for the append operation to complete, so update folder
+ // every second
+ gFolderBeingUpdated = destFolder;
+ doUpdateFolder(gTest);
+}
+
+var gFolderBeingUpdated = null;
+function doUpdateFolder(test) {
+ // In case we've moved on to the next test, exit
+ if (gTest > test) {
+ return;
+ }
+
+ gFolderBeingUpdated.updateFolder(null);
+
+ if (gCurrStatus == kStatus.everythingDone) {
+ resetStatusAndProceed();
+ } else {
+ do_timeout(1000, function () {
+ doUpdateFolder(test);
+ });
+ }
+}
+
+// Adds some messages directly to a mailbox (eg new mail)
+function addMessagesToServer(messages, mailbox, localFolder) {
+ // For every message we have, we need to convert it to a file:/// URI
+ messages.forEach(function (message) {
+ let URI = Services.io
+ .newFileURI(message.file)
+ .QueryInterface(Ci.nsIFileURL);
+ // Create the ImapMessage and store it on the mailbox.
+ mailbox.addMessage(new ImapMessage(URI.spec, mailbox.uidnext++, []));
+ // We can't get the headers again, so just pass on the message id
+ gExpectedEvents.push([
+ MailServices.mfn.msgAdded,
+ { expectedMessageId: message.messageId },
+ ]);
+ });
+ gExpectedEvents.push([
+ MailServices.mfn.msgsClassified,
+ messages.map(hdr => hdr.messageId),
+ false,
+ false,
+ ]);
+
+ // No copy listener notification for this
+ gCurrStatus |= kStatus.functionCallDone | kStatus.onStopCopyDone;
+
+ gFolderBeingUpdated = localFolder;
+ doUpdateFolder(gTest);
+}
+
+function copyMessages(messages, isMove, srcFolder, destFolder) {
+ gExpectedEvents = [
+ [
+ MailServices.mfn.msgsMoveCopyCompleted,
+ isMove,
+ messages,
+ destFolder,
+ true,
+ ],
+ ];
+ // We'll also get the msgAdded events when we go and update the destination
+ // folder
+ messages.forEach(function (message) {
+ // We can't use the headers directly, because the notifications we'll
+ // receive are for message headers in the destination folder
+ gExpectedEvents.push([
+ MailServices.mfn.msgKeyChanged,
+ { expectedMessageId: message.messageId },
+ ]);
+ gExpectedEvents.push([
+ MailServices.mfn.msgAdded,
+ { expectedMessageId: message.messageId },
+ ]);
+ });
+ gExpectedEvents.push([
+ MailServices.mfn.msgsClassified,
+ messages.map(hdr => hdr.messageId),
+ false,
+ false,
+ ]);
+ MailServices.copy.copyMessages(
+ srcFolder,
+ messages,
+ destFolder,
+ isMove,
+ copyListener,
+ gMsgWindow,
+ true
+ );
+ gCurrStatus |= kStatus.functionCallDone;
+
+ gServer.performTest("COPY");
+
+ gFolderBeingUpdated = destFolder;
+ doUpdateFolder(gTest);
+ if (gCurrStatus == kStatus.everythingDone) {
+ resetStatusAndProceed();
+ }
+}
+
+var gTestArray = [
+ // Adding folders
+ // Create another folder to move and copy messages around, and force initialization.
+ function testAddFolder1() {
+ addFolder(gRootFolder, "folder2", function (folder) {
+ gIMAPFolder2 = folder;
+ });
+ },
+ function testAddFolder2() {
+ addFolder(gRootFolder, "folder3", function (folder) {
+ gIMAPFolder3 = folder;
+ });
+ },
+
+ // Adding messages to folders
+ function testCopyFileMessage1() {
+ // Make sure the offline flag is not set for any of the folders
+ [gIMAPInbox, gIMAPFolder2, gIMAPFolder3].forEach(function (folder) {
+ folder.clearFlag(Ci.nsMsgFolderFlags.Offline);
+ });
+ copyFileMessage(gMsgFile1, gMsgId1, gIMAPInbox);
+ },
+ function testCopyFileMessage2() {
+ copyFileMessage(gMsgFile2, gMsgId2, gIMAPInbox);
+ },
+
+ // Add message straight to the server, so that we get a message added
+ // notification on the next folder update
+ function testNewMessageArrival1() {
+ addMessagesToServer(
+ [{ file: gMsgFile3, messageId: gMsgId3 }],
+ gIMAPDaemon.getMailbox("INBOX"),
+ gIMAPInbox
+ );
+ },
+
+ // Add another couple of messages, this time to another folder on the server
+ function testNewMessageArrival2() {
+ addMessagesToServer(
+ [
+ { file: gMsgFile4, messageId: gMsgId4 },
+ { file: gMsgFile5, messageId: gMsgId5 },
+ ],
+ gIMAPDaemon.getMailbox("INBOX"),
+ gIMAPInbox
+ );
+ },
+
+ // Moving/copying messages (this doesn't work right now)
+ function testCopyMessages1() {
+ copyMessages(
+ [gMsgHdrs[0].hdr, gMsgHdrs[1].hdr],
+ false,
+ gIMAPInbox,
+ gIMAPFolder3
+ );
+ },
+];
+
+function run_test() {
+ // This is before any of the actual tests, so...
+ gTest = 0;
+
+ gIMAPDaemon = new ImapDaemon();
+ gServer = makeServer(gIMAPDaemon, "");
+
+ gIMAPIncomingServer = createLocalIMAPServer(gServer.port);
+
+ // Also make sure a local folders server is created, as that's what is used
+ // for sent items
+ localAccountUtils.loadLocalMailAccount();
+
+ // We need an identity so that updateFolder doesn't fail
+ let localAccount = MailServices.accounts.createAccount();
+ let identity = MailServices.accounts.createIdentity();
+ localAccount.addIdentity(identity);
+ localAccount.defaultIdentity = identity;
+ localAccount.incomingServer = localAccountUtils.incomingServer;
+
+ // Let's also have another account, using the same identity
+ let imapAccount = MailServices.accounts.createAccount();
+ imapAccount.addIdentity(identity);
+ imapAccount.defaultIdentity = identity;
+ imapAccount.incomingServer = gIMAPIncomingServer;
+ MailServices.accounts.defaultAccount = imapAccount;
+
+ // The server doesn't support more than one connection
+ Services.prefs.setIntPref("mail.server.server1.max_cached_connections", 1);
+ // Make sure no biff notifications happen
+ Services.prefs.setBoolPref("mail.biff.play_sound", false);
+ Services.prefs.setBoolPref("mail.biff.show_alert", false);
+ Services.prefs.setBoolPref("mail.biff.show_tray_icon", false);
+ Services.prefs.setBoolPref("mail.biff.animate_dock_icon", false);
+ // We aren't interested in downloading messages automatically
+ Services.prefs.setBoolPref("mail.server.server1.download_on_biff", false);
+
+ // Add a listener so that we can check all folder events from this point.
+ MailServices.mfn.addListener(gMFListener, allTestedEvents);
+
+ // Get the server list...
+ gIMAPIncomingServer.performExpand(null);
+
+ // We get these notifications on initial discovery
+ gRootFolder = gIMAPIncomingServer.rootFolder;
+ gIMAPInbox = gRootFolder.getChildNamed("Inbox");
+ gExpectedEvents = [
+ [MailServices.mfn.folderAdded, gRootFolder, "Trash", function (folder) {}],
+ ];
+ gCurrStatus |= kStatus.onStopCopyDone | kStatus.functionCallDone;
+
+ gServer.performTest("SUBSCRIBE");
+
+ // "Master" do_test_pending(), paired with a do_test_finished() at the end of
+ // all the operations.
+ do_test_pending();
+}
+
+function doTest(test) {
+ // eslint-disable-line no-unused-vars
+ if (test <= gTestArray.length) {
+ let testFn = gTestArray[test - 1];
+
+ dump(`Doing test ${test} (${testFn.name})\n`);
+
+ // Set a limit of ten seconds; if the notifications haven't arrived by then there's a problem.
+ do_timeout(10000, function () {
+ if (gTest == test) {
+ do_throw(
+ "Notifications not received in 10000 ms for operation " +
+ testFn.name +
+ ", current status is " +
+ gCurrStatus
+ );
+ }
+ });
+ testFn();
+ } else {
+ MailServices.mfn.removeListener(gMFListener);
+ // Cleanup, null out everything, close all cached connections and stop the
+ // server
+ gRootFolder = null;
+ gIMAPInbox.msgDatabase = null;
+ gIMAPInbox = null;
+ gIMAPFolder2 = null;
+ gIMAPFolder3 = null;
+ do_timeout(1000, endTest);
+ }
+}
+
+function endTest() {
+ gIMAPIncomingServer.closeCachedConnections();
+ gServer.performTest();
+ gServer.stop();
+ let thread = gThreadManager.currentThread;
+ while (thread.hasPendingEvents()) {
+ thread.processNextEvent(true);
+ }
+
+ do_test_finished(); // for the one in run_test()
+}
diff --git a/comm/mailnews/imap/test/unit/test_offlineCopy.js b/comm/mailnews/imap/test/unit/test_offlineCopy.js
new file mode 100644
index 0000000000..0bbf80e352
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_offlineCopy.js
@@ -0,0 +1,271 @@
+/* -*- Mode: JavaScript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ *
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/licenses/publicdomain/
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+/**
+ * This test checks pseudo-offline message copies (which is triggered
+ * by allowUndo == true in copyMessages).
+ */
+
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+/* import-globals-from ../../../test/resources/logHelper.js */
+load("../../../resources/logHelper.js");
+
+var gMsgFile1 = do_get_file("../../../data/bugmail10");
+var gMsgId1 = "200806061706.m56H6RWT004933@mrapp54.mozilla.org";
+var gMsgFile2 = do_get_file("../../../data/image-attach-test");
+var gMsgId2 = "4A947F73.5030709@example.com";
+var gMsgFile3 = do_get_file("../../../data/SpamAssassinYes");
+var gMsg3Id = "bugmail7.m47LtAEf007543@mrapp51.mozilla.org";
+var gMsgFile4 = do_get_file("../../../data/bug460636");
+var gMsg4Id = "foo.12345@example";
+
+var gFolder1;
+
+// Adds some messages directly to a mailbox (eg new mail)
+function addMessagesToServer(messages, mailbox) {
+ // For every message we have, we need to convert it to a file:/// URI
+ messages.forEach(function (message) {
+ let URI = Services.io
+ .newFileURI(message.file)
+ .QueryInterface(Ci.nsIFileURL);
+ // Create the ImapMessage and store it on the mailbox.
+ mailbox.addMessage(new ImapMessage(URI.spec, mailbox.uidnext++, []));
+ });
+}
+
+var tests = [
+ async function setup() {
+ // Turn off autosync_offline_stores because
+ // fetching messages is invoked after copying the messages.
+ // (i.e. The fetching process will be invoked after OnStopCopy)
+ // It will cause crash with an assertion
+ // (ASSERTION: tried to add duplicate listener: 'index == -1') on teardown.
+ Services.prefs.setBoolPref(
+ "mail.server.default.autosync_offline_stores",
+ false
+ );
+
+ setupIMAPPump();
+
+ let promiseFolderAdded = PromiseTestUtils.promiseFolderAdded("folder 1");
+ IMAPPump.incomingServer.rootFolder.createSubfolder("folder 1", null);
+ await promiseFolderAdded;
+
+ gFolder1 = IMAPPump.incomingServer.rootFolder.getChildNamed("folder 1");
+ Assert.ok(gFolder1 instanceof Ci.nsIMsgFolder);
+
+ // these hacks are required because we've created the inbox before
+ // running initial folder discovery, and adding the folder bails
+ // out before we set it as verified online, so we bail out, and
+ // then remove the INBOX folder since it's not verified.
+ IMAPPump.inbox.hierarchyDelimiter = "/";
+ IMAPPump.inbox.verifiedAsOnlineFolder = true;
+
+ // Add messages to the INBOX
+ // this is synchronous, afaik
+ addMessagesToServer(
+ [
+ { file: gMsgFile1, messageId: gMsgId1 },
+ { file: gMsgFile2, messageId: gMsgId2 },
+ ],
+ IMAPPump.daemon.getMailbox("INBOX")
+ );
+ },
+ async function updateFolder() {
+ let promiseUrlListener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.updateFolderWithListener(null, promiseUrlListener);
+ await promiseUrlListener.promise;
+ },
+ async function downloadAllForOffline() {
+ let promiseUrlListener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.downloadAllForOffline(promiseUrlListener, null);
+ await promiseUrlListener.promise;
+ },
+ async function copyMessagesToInbox() {
+ let promiseCopyListener = new PromiseTestUtils.PromiseCopyListener();
+ MailServices.copy.copyFileMessage(
+ gMsgFile3,
+ IMAPPump.inbox,
+ null,
+ false,
+ 0,
+ "",
+ promiseCopyListener,
+ null
+ );
+ await promiseCopyListener.promise;
+
+ promiseCopyListener = new PromiseTestUtils.PromiseCopyListener();
+ MailServices.copy.copyFileMessage(
+ gMsgFile4,
+ IMAPPump.inbox,
+ null,
+ false,
+ 0,
+ "",
+ promiseCopyListener,
+ null
+ );
+ await promiseCopyListener.promise;
+
+ let promiseUrlListener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.updateFolderWithListener(null, promiseUrlListener);
+ await promiseUrlListener.promise;
+
+ let db = IMAPPump.inbox.msgDatabase;
+
+ // test the headers in the inbox
+ let count = 0;
+ for (let message of db.enumerateMessages()) {
+ count++;
+ message instanceof Ci.nsIMsgDBHdr;
+ dump(
+ "message <" +
+ message.subject +
+ "> storeToken: <" +
+ message.getStringProperty("storeToken") +
+ "> offset: <" +
+ message.messageOffset +
+ "> id: <" +
+ message.messageId +
+ ">\n"
+ );
+ // This fails for file copies in bug 790912. Without this, messages that
+ // are copied are not visible in pre-pluggableStores versions of TB (pre TB 12)
+ if (IMAPPump.inbox.msgStore.storeType == "mbox") {
+ Assert.equal(
+ message.messageOffset,
+ parseInt(message.getStringProperty("storeToken"))
+ );
+ }
+ }
+ Assert.equal(count, 4);
+ },
+ function copyMessagesToSubfolder() {
+ // a message created from IMAP download
+ let db = IMAPPump.inbox.msgDatabase;
+ let msg1 = db.getMsgHdrForMessageID(gMsgId1);
+ // this is sync, I believe?
+ MailServices.copy.copyMessages(
+ IMAPPump.inbox,
+ [msg1],
+ gFolder1,
+ false,
+ null,
+ null,
+ true
+ );
+
+ // two messages originally created from file copies (like in Send)
+ let msg3 = db.getMsgHdrForMessageID(gMsg3Id);
+ Assert.ok(msg3 instanceof Ci.nsIMsgDBHdr);
+ MailServices.copy.copyMessages(
+ IMAPPump.inbox,
+ [msg3],
+ gFolder1,
+ false,
+ null,
+ null,
+ true
+ );
+
+ let msg4 = db.getMsgHdrForMessageID(gMsg4Id);
+ Assert.ok(msg4 instanceof Ci.nsIMsgDBHdr);
+
+ // because bug 790912 created messages with correct storeToken but messageOffset=0,
+ // these messages may not copy correctly. Make sure that they do, as fixed in bug 790912
+ msg4.messageOffset = 0;
+ MailServices.copy.copyMessages(
+ IMAPPump.inbox,
+ [msg4],
+ gFolder1,
+ false,
+ null,
+ null,
+ true
+ );
+
+ // test the db headers in folder1
+ db = gFolder1.msgDatabase;
+ let count = 0;
+ for (let message of db.enumerateMessages()) {
+ count++;
+ message instanceof Ci.nsIMsgDBHdr;
+ dump(
+ "message <" +
+ message.subject +
+ "> storeToken: <" +
+ message.getStringProperty("storeToken") +
+ "> offset: <" +
+ message.messageOffset +
+ "> id: <" +
+ message.messageId +
+ ">\n"
+ );
+ if (gFolder1.msgStore.storeType == "mbox") {
+ Assert.equal(
+ message.messageOffset,
+ parseInt(message.getStringProperty("storeToken"))
+ );
+ }
+ }
+ Assert.equal(count, 3);
+ },
+ async function test_headers() {
+ let msgIds = [gMsgId1, gMsg3Id, gMsg4Id];
+ for (let msgId of msgIds) {
+ let newMsgHdr = gFolder1.msgDatabase.getMsgHdrForMessageID(msgId);
+ Assert.ok(newMsgHdr.flags & Ci.nsMsgMessageFlags.Offline);
+ let msgURI = newMsgHdr.folder.getUriForMsg(newMsgHdr);
+ let msgServ = MailServices.messageServiceFromURI(msgURI);
+ let promiseStreamListener = new PromiseTestUtils.PromiseStreamListener();
+ msgServ.streamHeaders(msgURI, promiseStreamListener, null, true);
+ let data = await promiseStreamListener.promise;
+ dump("\nheaders for messageId " + msgId + "\n" + data + "\n\n");
+ Assert.ok(data.includes(msgId));
+ }
+ },
+ function moveMessagesToSubfolder() {
+ let db = IMAPPump.inbox.msgDatabase;
+ let messages = [...db.enumerateMessages()];
+ Assert.ok(messages.length > 0);
+ // this is sync, I believe?
+ MailServices.copy.copyMessages(
+ IMAPPump.inbox,
+ messages,
+ gFolder1,
+ true,
+ null,
+ null,
+ true
+ );
+
+ // the inbox should now be empty
+ Assert.ok([...db.enumerateMessages()].length == 0);
+
+ // maildir should also delete the files.
+ if (IMAPPump.inbox.msgStore.storeType == "maildir") {
+ let curDir = IMAPPump.inbox.filePath.clone();
+ curDir.append("cur");
+ Assert.ok(curDir.exists());
+ Assert.ok(curDir.isDirectory());
+ let curEnum = curDir.directoryEntries;
+ // the directory should be empty, fails from bug 771643
+ Assert.ok(!curEnum.hasMoreElements());
+ }
+ },
+ teardownIMAPPump,
+];
+
+function run_test() {
+ tests.forEach(x => add_task(x));
+ run_next_test();
+}
diff --git a/comm/mailnews/imap/test/unit/test_offlineDraftDataloss.js b/comm/mailnews/imap/test/unit/test_offlineDraftDataloss.js
new file mode 100644
index 0000000000..d13e07a367
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_offlineDraftDataloss.js
@@ -0,0 +1,152 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 tests that a message saved as draft in an IMAP folder in offline
+ * mode is not lost when going back online
+ * See Bug 805626
+ */
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+var { TestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/TestUtils.sys.mjs"
+);
+
+var gDraftsFolder;
+
+add_setup(function () {
+ setupIMAPPump();
+ Services.prefs.setBoolPref(
+ "mail.server.default.autosync_offline_stores",
+ false
+ );
+});
+
+add_task(async function createDraftsFolder() {
+ IMAPPump.incomingServer.rootFolder.createSubfolder("Drafts", null);
+ await PromiseTestUtils.promiseFolderAdded("Drafts");
+ gDraftsFolder = IMAPPump.incomingServer.rootFolder.getChildNamed("Drafts");
+ Assert.ok(gDraftsFolder instanceof Ci.nsIMsgImapMailFolder);
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ gDraftsFolder.updateFolderWithListener(null, listener);
+ await listener.promise;
+});
+
+add_task(async function goOffline() {
+ // Don't prompt about offline download when going offline.
+ Services.prefs.setIntPref("offline.download.download_messages", 2);
+
+ IMAPPump.incomingServer.closeCachedConnections();
+ let thread = gThreadManager.currentThread;
+ while (thread.hasPendingEvents()) {
+ thread.processNextEvent(true);
+ }
+ IMAPPump.server.stop();
+ Services.io.offline = true;
+});
+
+add_task(async function saveDraft() {
+ let msgCompose = Cc["@mozilla.org/messengercompose/compose;1"].createInstance(
+ Ci.nsIMsgCompose
+ );
+ let fields = Cc[
+ "@mozilla.org/messengercompose/composefields;1"
+ ].createInstance(Ci.nsIMsgCompFields);
+ fields.from = "Nobody <nobody@tinderbox.test>";
+
+ let params = Cc[
+ "@mozilla.org/messengercompose/composeparams;1"
+ ].createInstance(Ci.nsIMsgComposeParams);
+ params.composeFields = fields;
+ msgCompose.initialize(params);
+
+ // Set up the identity.
+ let identity = MailServices.accounts.createIdentity();
+ identity.draftFolder = gDraftsFolder.URI;
+
+ let progress = Cc["@mozilla.org/messenger/progress;1"].createInstance(
+ Ci.nsIMsgProgress
+ );
+ let progressListener = new WebProgressListener();
+ progress.registerListener(progressListener);
+ msgCompose.sendMsg(
+ Ci.nsIMsgSend.nsMsgSaveAsDraft,
+ identity,
+ "",
+ null,
+ progress
+ );
+ await progressListener.promise;
+ // Verify that message is not on the server yet.
+ Assert.equal(IMAPPump.daemon.getMailbox("Drafts")._messages.length, 0);
+});
+
+add_task(async function goOnline() {
+ let offlineManager = Cc[
+ "@mozilla.org/messenger/offline-manager;1"
+ ].getService(Ci.nsIMsgOfflineManager);
+ IMAPPump.daemon.closing = false;
+ Services.io.offline = false;
+
+ IMAPPump.server.start();
+ offlineManager.inProgress = true;
+ offlineManager.goOnline(false, true, null);
+ // There seem to be some untraceable postprocessing with 100ms.
+ // (Found through xpcshell-test --verify)
+ await PromiseTestUtils.promiseDelay(100);
+ await TestUtils.waitForCondition(
+ () => !offlineManager.inProgress,
+ "wait for offlineManager not in progress"
+ );
+ // Verify that message is now on the server.
+ Assert.equal(IMAPPump.daemon.getMailbox("Drafts")._messages.length, 1);
+});
+
+add_task(function endTest() {
+ teardownIMAPPump();
+});
+
+/*
+ * helper functions
+ */
+
+function WebProgressListener() {
+ this._promise = new Promise(resolve => {
+ this._resolve = resolve;
+ });
+}
+WebProgressListener.prototype = {
+ onStateChange(aWebProgress, aRequest, aStateFlags, aStatus) {
+ if (aStateFlags & Ci.nsIWebProgressListener.STATE_STOP) {
+ this._resolve();
+ }
+ },
+
+ onProgressChange(
+ aWebProgress,
+ aRequest,
+ aCurSelfProgress,
+ aMaxSelfProgress,
+ aCurTotalProgress,
+ aMaxTotalProgress
+ ) {},
+ onLocationChange(aWebProgress, aRequest, aLocation, aFlags) {},
+ onStatusChange(aWebProgress, aRequest, aStatus, aMessage) {},
+ onSecurityChange(aWebProgress, aRequest, state) {},
+ onContentBlockingEvent(aWebProgress, aRequest, aEvent) {},
+
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIWebProgressListener",
+ "nsISupportsWeakReference",
+ ]),
+
+ get promise() {
+ return this._promise;
+ },
+};
diff --git a/comm/mailnews/imap/test/unit/test_offlineMoveLocalToIMAP.js b/comm/mailnews/imap/test/unit/test_offlineMoveLocalToIMAP.js
new file mode 100644
index 0000000000..c98a26470a
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_offlineMoveLocalToIMAP.js
@@ -0,0 +1,125 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Test to check that offline IMAP operation for a local->IMAP message
+ * move completes correctly once we go back online.
+ */
+
+// NOTE: PromiseTestUtils and MailServices already imported
+
+const { MessageGenerator } = ChromeUtils.import(
+ "resource://testing-common/mailnews/MessageGenerator.jsm"
+);
+
+function setupTest() {
+ // Turn off autosync_offline_stores because
+ // fetching messages is invoked after copying the messages.
+ // (i.e. The fetching process will be invoked after OnStopCopy)
+ // It will cause crash with an assertion
+ // (ASSERTION: tried to add duplicate listener: 'index == -1') on teardown.
+
+ Services.prefs.setBoolPref(
+ "mail.server.default.autosync_offline_stores",
+ false
+ );
+
+ setupIMAPPump();
+
+ // These hacks are required because we've created the inbox before
+ // running initial folder discovery, and adding the folder bails
+ // out before we set it as verified online, so we bail out, and
+ // then remove the INBOX folder since it's not verified.
+ IMAPPump.inbox.hierarchyDelimiter = "/";
+ IMAPPump.inbox.verifiedAsOnlineFolder = true;
+}
+
+function teardownTest() {
+ teardownIMAPPump();
+}
+
+function goOffline() {
+ IMAPPump.incomingServer.closeCachedConnections();
+ IMAPPump.server.stop();
+ Services.io.offline = true;
+}
+
+// Go back into online mode, and wait until offline IMAP operations are completed.
+async function goOnline() {
+ IMAPPump.daemon.closing = false;
+ Services.io.offline = false;
+ IMAPPump.server.start();
+
+ let offlineManager = Cc[
+ "@mozilla.org/messenger/offline-manager;1"
+ ].getService(Ci.nsIMsgOfflineManager);
+ offlineManager.goOnline(
+ false, // sendUnsentMessages
+ true, // playbackOfflineImapOperations
+ null // msgWindow
+ );
+ // No way to signal when offline IMAP operations are complete... so we
+ // just blindly wait and cross our fingers :-(
+ await PromiseTestUtils.promiseDelay(2000);
+}
+
+async function loadTestMessage(folder) {
+ let copyListener = new PromiseTestUtils.PromiseCopyListener();
+ let file = do_get_file("../../../data/bugmail1");
+ MailServices.copy.copyFileMessage(
+ file,
+ folder,
+ null,
+ false,
+ 0,
+ "",
+ copyListener,
+ null
+ );
+ await copyListener.promise;
+}
+
+add_task(async function testOfflineMoveLocalToIMAP() {
+ setupTest();
+
+ // Install a test message in the local folder.
+ await loadTestMessage(localAccountUtils.inboxFolder);
+
+ goOffline();
+
+ // Move messages in local folder to the IMAP inbox.
+ // We're offline so this should result in a queued-up offline IMAP
+ // operation, which will execute when we go back online.
+ let copyListener = new PromiseTestUtils.PromiseCopyListener();
+ let msgs = [...localAccountUtils.inboxFolder.msgDatabase.enumerateMessages()];
+ MailServices.copy.copyMessages(
+ localAccountUtils.inboxFolder,
+ msgs,
+ IMAPPump.inbox, // dest folder
+ true, // move
+ copyListener,
+ null,
+ false // undo?
+ );
+ await copyListener.promise;
+
+ // Now, go back online and see if the operation has been performed
+
+ await goOnline();
+
+ let imapINBOX = IMAPPump.inbox.QueryInterface(Ci.nsIMsgImapMailFolder);
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ imapINBOX.updateFolderWithListener(null, listener);
+ await listener.promise;
+
+ // Local folder should be empty, contents now in IMAP inbox.
+ let localCount = [
+ ...localAccountUtils.inboxFolder.msgDatabase.enumerateMessages(),
+ ].length;
+ let imapCount = [...IMAPPump.inbox.msgDatabase.enumerateMessages()].length;
+ Assert.equal(imapCount, msgs.length);
+ Assert.equal(localCount, 0);
+
+ teardownTest();
+});
diff --git a/comm/mailnews/imap/test/unit/test_offlinePlayback.js b/comm/mailnews/imap/test/unit/test_offlinePlayback.js
new file mode 100644
index 0000000000..ae4361e16b
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_offlinePlayback.js
@@ -0,0 +1,187 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Test to ensure that changes made while offline are played back when we
+ * go back online.
+ */
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+/* import-globals-from ../../../test/resources/MessageGenerator.jsm */
+load("../../../resources/MessageGenerator.jsm");
+
+var gSecondFolder, gThirdFolder;
+var gSynthMessage1, gSynthMessage2;
+// the message id of bugmail10
+var gMsgId1 = "200806061706.m56H6RWT004933@mrapp54.mozilla.org";
+var gOfflineManager;
+
+var tests = [
+ setupIMAPPump,
+ function serverParms() {
+ var { fsDebugAll } = ChromeUtils.import(
+ "resource://testing-common/mailnews/Maild.jsm"
+ );
+ IMAPPump.server.setDebugLevel(fsDebugAll);
+ },
+ setup,
+
+ function prepareToGoOffline() {
+ let rootFolder = IMAPPump.incomingServer.rootFolder;
+ gSecondFolder = rootFolder
+ .getChildNamed("secondFolder")
+ .QueryInterface(Ci.nsIMsgImapMailFolder);
+ gThirdFolder = rootFolder
+ .getChildNamed("thirdFolder")
+ .QueryInterface(Ci.nsIMsgImapMailFolder);
+ IMAPPump.incomingServer.closeCachedConnections();
+ },
+ async function doOfflineOps() {
+ IMAPPump.server.stop();
+ Services.io.offline = true;
+
+ // Flag the two messages, and then copy them to different folders. Since
+ // we're offline, these operations are synchronous.
+ let msgHdr1 = IMAPPump.inbox.msgDatabase.getMsgHdrForMessageID(
+ gSynthMessage1.messageId
+ );
+ let msgHdr2 = IMAPPump.inbox.msgDatabase.getMsgHdrForMessageID(
+ gSynthMessage2.messageId
+ );
+ let headers1 = [msgHdr1];
+ let headers2 = [msgHdr2];
+ msgHdr1.folder.markMessagesFlagged(headers1, true);
+ msgHdr2.folder.markMessagesFlagged(headers2, true);
+ let promiseCopyListener1 = new PromiseTestUtils.PromiseCopyListener();
+ MailServices.copy.copyMessages(
+ IMAPPump.inbox,
+ headers1,
+ gSecondFolder,
+ true,
+ promiseCopyListener1,
+ null,
+ true
+ );
+ let promiseCopyListener2 = new PromiseTestUtils.PromiseCopyListener();
+ MailServices.copy.copyMessages(
+ IMAPPump.inbox,
+ headers2,
+ gThirdFolder,
+ true,
+ promiseCopyListener2,
+ null,
+ true
+ );
+ var file = do_get_file("../../../data/bugmail10");
+ let promiseCopyListener3 = new PromiseTestUtils.PromiseCopyListener();
+ MailServices.copy.copyFileMessage(
+ file,
+ IMAPPump.inbox,
+ null,
+ false,
+ 0,
+ "",
+ promiseCopyListener3,
+ null
+ );
+ await Promise.all([
+ promiseCopyListener1.promise,
+ promiseCopyListener2.promise,
+ promiseCopyListener3.promise,
+ ]);
+ },
+ async function goOnline() {
+ gOfflineManager = Cc["@mozilla.org/messenger/offline-manager;1"].getService(
+ Ci.nsIMsgOfflineManager
+ );
+ IMAPPump.daemon.closing = false;
+ Services.io.offline = false;
+
+ IMAPPump.server.start();
+ gOfflineManager.goOnline(false, true, null);
+ await PromiseTestUtils.promiseDelay(2000);
+ },
+ async function updateSecondFolder() {
+ let promiseUrlListener = new PromiseTestUtils.PromiseUrlListener();
+ gSecondFolder.updateFolderWithListener(null, promiseUrlListener);
+ await promiseUrlListener.promise;
+ },
+ async function updateThirdFolder() {
+ let promiseUrlListener = new PromiseTestUtils.PromiseUrlListener();
+ gThirdFolder.updateFolderWithListener(null, promiseUrlListener);
+ await promiseUrlListener.promise;
+ },
+ async function updateInbox() {
+ let promiseUrlListener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.updateFolderWithListener(null, promiseUrlListener);
+ await promiseUrlListener.promise;
+ },
+ function checkDone() {
+ let msgHdr1 = gSecondFolder.msgDatabase.getMsgHdrForMessageID(
+ gSynthMessage1.messageId
+ );
+ let msgHdr2 = gThirdFolder.msgDatabase.getMsgHdrForMessageID(
+ gSynthMessage2.messageId
+ );
+ let msgHdr3 = IMAPPump.inbox.msgDatabase.getMsgHdrForMessageID(gMsgId1);
+ Assert.notEqual(msgHdr1, null);
+ Assert.notEqual(msgHdr2, null);
+ Assert.notEqual(msgHdr3, null);
+ },
+ teardownIMAPPump,
+];
+
+async function setup() {
+ /*
+ * Set up an IMAP server.
+ */
+ IMAPPump.daemon.createMailbox("secondFolder", { subscribed: true });
+ IMAPPump.daemon.createMailbox("thirdFolder", { subscribed: true });
+ Services.prefs.setBoolPref(
+ "mail.server.default.autosync_offline_stores",
+ false
+ );
+ // Don't prompt about offline download when going offline
+ Services.prefs.setIntPref("offline.download.download_messages", 2);
+
+ // make a couple of messages
+ let messages = [];
+ let gMessageGenerator = new MessageGenerator();
+ messages = messages.concat(gMessageGenerator.makeMessage());
+ messages = messages.concat(gMessageGenerator.makeMessage());
+ gSynthMessage1 = messages[0];
+ gSynthMessage2 = messages[1];
+
+ let msgURI = Services.io.newURI(
+ "data:text/plain;base64," + btoa(messages[0].toMessageString())
+ );
+ let imapInbox = IMAPPump.daemon.getMailbox("INBOX");
+ let message = new ImapMessage(msgURI.spec, imapInbox.uidnext++, ["\\Seen"]);
+ imapInbox.addMessage(message);
+ msgURI = Services.io.newURI(
+ "data:text/plain;base64," + btoa(messages[1].toMessageString())
+ );
+ message = new ImapMessage(msgURI.spec, imapInbox.uidnext++, ["\\Seen"]);
+ imapInbox.addMessage(message);
+
+ // update folder to download header.
+ let promiseUrlListener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.updateFolderWithListener(null, promiseUrlListener);
+ await promiseUrlListener.promise;
+}
+
+function run_test() {
+ Services.prefs.setBoolPref(
+ "mail.server.default.autosync_offline_stores",
+ false
+ );
+ tests.forEach(x => add_task(x));
+ run_next_test();
+}
diff --git a/comm/mailnews/imap/test/unit/test_offlineStoreLocking.js b/comm/mailnews/imap/test/unit/test_offlineStoreLocking.js
new file mode 100644
index 0000000000..e3a5da62b2
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_offlineStoreLocking.js
@@ -0,0 +1,258 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Test to ensure that code that writes to the imap offline store deals
+ * with offline store locking correctly.
+ */
+
+/* import-globals-from ../../../test/resources/alertTestUtils.js */
+load("../../../resources/alertTestUtils.js");
+
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+var { MessageGenerator } = ChromeUtils.import(
+ "resource://testing-common/mailnews/MessageGenerator.jsm"
+);
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+// Globals
+var gIMAPTrashFolder, gMsgImapInboxFolder;
+var gMovedMsgId;
+
+var gAlertResolve;
+var gGotAlert = new Promise(resolve => {
+ gAlertResolve = resolve;
+});
+
+/* exported alert to alertTestUtils.js */
+function alertPS(parent, aDialogTitle, aText) {
+ gAlertResolve(aText);
+}
+
+function addGeneratedMessagesToServer(messages, mailbox) {
+ // Create the ImapMessages and store them on the mailbox
+ messages.forEach(function (message) {
+ let dataUri = Services.io.newURI(
+ "data:text/plain;base64," + btoa(message.toMessageString())
+ );
+ mailbox.addMessage(new ImapMessage(dataUri.spec, mailbox.uidnext++, []));
+ });
+}
+
+var gStreamedHdr = null;
+
+add_setup(async function () {
+ registerAlertTestUtils();
+ Services.prefs.setBoolPref(
+ "mail.server.default.autosync_offline_stores",
+ false
+ );
+
+ setupIMAPPump();
+
+ gMsgImapInboxFolder = IMAPPump.inbox.QueryInterface(Ci.nsIMsgImapMailFolder);
+ // these hacks are required because we've created the inbox before
+ // running initial folder discovery, and adding the folder bails
+ // out before we set it as verified online, so we bail out, and
+ // then remove the INBOX folder since it's not verified.
+ gMsgImapInboxFolder.hierarchyDelimiter = "/";
+ gMsgImapInboxFolder.verifiedAsOnlineFolder = true;
+
+ let messageGenerator = new MessageGenerator();
+ let messages = [];
+ let bodyString = "";
+ for (let i = 0; i < 100; i++) {
+ bodyString +=
+ "1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890\r\n";
+ }
+
+ for (let i = 0; i < 50; i++) {
+ messages = messages.concat(
+ messageGenerator.makeMessage({
+ body: { body: bodyString, contentType: "text/plain" },
+ })
+ );
+ }
+
+ addGeneratedMessagesToServer(messages, IMAPPump.daemon.getMailbox("INBOX"));
+ // ...and download for offline use.
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.updateFolderWithListener(null, listener);
+ await listener.promise;
+});
+
+add_task(async function downloadForOffline() {
+ // ...and download for offline use.
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.downloadAllForOffline(listener, null);
+ await listener.promise;
+});
+
+add_task(async function deleteOneMsg() {
+ let enumerator = IMAPPump.inbox.msgDatabase.enumerateMessages();
+ let msgHdr = enumerator.getNext().QueryInterface(Ci.nsIMsgDBHdr);
+ let copyListener = new PromiseTestUtils.PromiseCopyListener();
+ IMAPPump.inbox.deleteMessages(
+ [msgHdr],
+ null,
+ false,
+ true,
+ copyListener,
+ false
+ );
+ await copyListener.promise;
+});
+
+add_task(async function compactOneFolder() {
+ let enumerator = IMAPPump.inbox.msgDatabase.enumerateMessages();
+ let msgHdr = enumerator.getNext().QueryInterface(Ci.nsIMsgDBHdr);
+ gStreamedHdr = msgHdr;
+ // Mark the message as not being offline, and then we'll make sure that
+ // streaming the message while we're compacting doesn't result in the
+ // message being marked for offline use.
+ // Luckily, compaction compacts the offline store first, so it should
+ // lock the offline store.
+ IMAPPump.inbox.msgDatabase.markOffline(msgHdr.messageKey, false, null);
+ let msgURI = msgHdr.folder.getUriForMsg(msgHdr);
+ let msgServ = MailServices.messageServiceFromURI(msgURI);
+ // UrlListener will get called when both expunge and offline store
+ // compaction are finished. dummyMsgWindow is required to make the backend
+ // compact the offline store.
+ let compactUrlListener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.compact(compactUrlListener, gDummyMsgWindow);
+ // Stream the message w/o a stream listener in an attempt to get the url
+ // started more quickly, while the compact is still going on.
+ let urlListener = new PromiseTestUtils.PromiseUrlListener({});
+ await PromiseTestUtils.promiseDelay(100); // But don't be too fast.
+ msgServ.streamMessage(
+ msgURI,
+ new PromiseTestUtils.PromiseStreamListener(),
+ null,
+ urlListener,
+ false,
+ "",
+ false
+ );
+ await compactUrlListener.promise;
+
+ // Because we're streaming the message while compaction is going on,
+ // we should not have stored it for offline use.
+ Assert.equal(false, gStreamedHdr.flags & Ci.nsMsgMessageFlags.Offline);
+
+ await urlListener.promise;
+});
+
+add_task(async function deleteAnOtherMsg() {
+ let enumerator = IMAPPump.inbox.msgDatabase.enumerateMessages();
+ let msgHdr = enumerator.getNext().QueryInterface(Ci.nsIMsgDBHdr);
+ let copyListener = new PromiseTestUtils.PromiseCopyListener();
+ IMAPPump.inbox.deleteMessages(
+ [msgHdr],
+ null,
+ false,
+ true,
+ copyListener,
+ false
+ );
+ await copyListener.promise;
+});
+
+add_task(async function updateTrash() {
+ gIMAPTrashFolder = IMAPPump.incomingServer.rootFolder
+ .getChildNamed("Trash")
+ .QueryInterface(Ci.nsIMsgImapMailFolder);
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ // hack to force uid validity to get initialized for trash.
+ gIMAPTrashFolder.updateFolderWithListener(null, listener);
+ await listener.promise;
+});
+
+add_task(async function downloadTrashForOffline() {
+ // ...and download for offline use.
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ gIMAPTrashFolder.downloadAllForOffline(listener, null);
+ await listener.promise;
+});
+
+add_task(async function testOfflineBodyCopy() {
+ // In order to check that offline copy of messages doesn't try to copy
+ // the body if the offline store is locked, we're going to go offline.
+ // Thunderbird itself does move/copies pseudo-offline, but that's too
+ // hard to test because of the half-second delay.
+ IMAPPump.server.stop();
+ Services.io.offline = true;
+ let enumerator = gIMAPTrashFolder.msgDatabase.enumerateMessages();
+ let msgHdr = enumerator.getNext().QueryInterface(Ci.nsIMsgDBHdr);
+ gMovedMsgId = msgHdr.messageId;
+ let compactionListener = new PromiseTestUtils.PromiseUrlListener();
+ // NOTE: calling compact() even if msgStore doesn't support compaction.
+ // It should be a safe no-op, and we're just testing that the listener is
+ // still invoked.
+ IMAPPump.inbox.compact(compactionListener, gDummyMsgWindow);
+ let copyListener = new PromiseTestUtils.PromiseCopyListener();
+ MailServices.copy.copyMessages(
+ gIMAPTrashFolder,
+ [msgHdr],
+ IMAPPump.inbox,
+ true,
+ copyListener,
+ null,
+ true
+ );
+
+ // Verify that the moved Msg is not offline.
+ try {
+ let movedMsg =
+ IMAPPump.inbox.msgDatabase.getMsgHdrForMessageID(gMovedMsgId);
+ Assert.equal(0, movedMsg.flags & Ci.nsMsgMessageFlags.Offline);
+ } catch (ex) {
+ throw new Error(ex);
+ }
+ await compactionListener.promise;
+ await copyListener.promise;
+});
+
+add_task(async function test_checkAlert() {
+ // Check if testing maildir which doesn't produce an the alert like mbox.
+ // If so, don't wait for an alert.
+ let storageCID = Services.prefs.getCharPref(
+ "mail.serverDefaultStoreContractID"
+ );
+ if (storageCID == "@mozilla.org/msgstore/maildirstore;1") {
+ return;
+ }
+
+ let alertText = await gGotAlert;
+ Assert.ok(
+ alertText.startsWith(
+ "The folder 'Inbox on Mail for ' cannot be compacted because another operation is in progress. Please try again later."
+ )
+ );
+});
+
+add_task(function teardown() {
+ gMsgImapInboxFolder = null;
+ gIMAPTrashFolder = null;
+
+ // IMAPPump.server has already stopped, we do not need to IMAPPump.server.stop().
+ IMAPPump.inbox = null;
+ try {
+ IMAPPump.incomingServer.closeCachedConnections();
+ let serverSink = IMAPPump.incomingServer.QueryInterface(
+ Ci.nsIImapServerSink
+ );
+ serverSink.abortQueuedUrls();
+ } catch (ex) {
+ throw new Error(ex);
+ }
+ let thread = gThreadManager.currentThread;
+ while (thread.hasPendingEvents()) {
+ thread.processNextEvent(true);
+ }
+});
diff --git a/comm/mailnews/imap/test/unit/test_preserveDataOnMove.js b/comm/mailnews/imap/test/unit/test_preserveDataOnMove.js
new file mode 100644
index 0000000000..a1f08cc8f0
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_preserveDataOnMove.js
@@ -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/. */
+
+// This tests that arbitrary message header properties are preserved
+// during online move of an imap message.
+
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+var gMessage = "bugmail10"; // message file used as the test message
+var gSubfolder;
+
+add_setup(function () {
+ setupIMAPPump();
+ Services.prefs.setBoolPref(
+ "mail.server.default.autosync_offline_stores",
+ false
+ );
+});
+
+add_task(async function createSubfolder() {
+ IMAPPump.incomingServer.rootFolder.createSubfolder("Subfolder", null);
+ await PromiseTestUtils.promiseFolderAdded("Subfolder");
+ gSubfolder = IMAPPump.incomingServer.rootFolder.getChildNamed("Subfolder");
+ Assert.ok(gSubfolder instanceof Ci.nsIMsgImapMailFolder);
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ gSubfolder.updateFolderWithListener(null, listener);
+ await listener.promise;
+});
+
+// load and update a message in the imap fake server
+add_task(async function loadImapMessage() {
+ IMAPPump.mailbox.addMessage(
+ new ImapMessage(specForFileName(gMessage), IMAPPump.mailbox.uidnext++, [])
+ );
+ IMAPPump.inbox.updateFolder(null);
+ await PromiseTestUtils.promiseFolderNotification(IMAPPump.inbox, "msgAdded");
+ Assert.equal(1, IMAPPump.inbox.getTotalMessages(false));
+ let msgHdr = mailTestUtils.firstMsgHdr(IMAPPump.inbox);
+ Assert.ok(msgHdr instanceof Ci.nsIMsgDBHdr);
+
+ // set an arbitrary property
+ msgHdr.setStringProperty("testprop", "somevalue");
+});
+
+// move the message to a subfolder
+add_task(async function moveMessageToSubfolder() {
+ let msgHdr = mailTestUtils.firstMsgHdr(IMAPPump.inbox);
+ let copyListener = new PromiseTestUtils.PromiseCopyListener();
+ MailServices.copy.copyMessages(
+ IMAPPump.inbox, // srcFolder
+ [msgHdr], // messages
+ gSubfolder, // dstFolder
+ true, // isMove
+ copyListener, // listener
+ null, // msgWindow
+ false // allowUndo
+ );
+ await copyListener.promise;
+});
+
+add_task(async function testPropertyOnMove() {
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ gSubfolder.updateFolderWithListener(null, listener);
+ await PromiseTestUtils.promiseFolderNotification(gSubfolder, "msgAdded");
+ await listener.promise;
+ let msgHdr = mailTestUtils.firstMsgHdr(gSubfolder);
+ Assert.equal(msgHdr.getStringProperty("testprop"), "somevalue");
+});
+
+// Cleanup
+add_task(function endTest() {
+ teardownIMAPPump();
+});
+
+/*
+ * helper functions
+ */
+
+// given a test file, return the file uri spec
+function specForFileName(aFileName) {
+ let file = do_get_file("../../../data/" + aFileName);
+ let msgfileuri = Services.io.newFileURI(file).QueryInterface(Ci.nsIFileURL);
+ return msgfileuri.spec;
+}
diff --git a/comm/mailnews/imap/test/unit/test_saveImapDraft.js b/comm/mailnews/imap/test/unit/test_saveImapDraft.js
new file mode 100644
index 0000000000..ec2b96b3de
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_saveImapDraft.js
@@ -0,0 +1,119 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 tests that a message saved as draft in an IMAP folder is correctly
+ * marked as unread.
+ */
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+var gDraftsFolder;
+
+add_setup(function () {
+ setupIMAPPump();
+ Services.prefs.setBoolPref(
+ "mail.server.default.autosync_offline_stores",
+ false
+ );
+});
+
+add_task(async function createDraftsFolder() {
+ IMAPPump.incomingServer.rootFolder.createSubfolder("Drafts", null);
+ await PromiseTestUtils.promiseFolderAdded("Drafts");
+ gDraftsFolder = IMAPPump.incomingServer.rootFolder.getChildNamed("Drafts");
+ Assert.ok(gDraftsFolder instanceof Ci.nsIMsgImapMailFolder);
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ gDraftsFolder.updateFolderWithListener(null, listener);
+ await listener.promise;
+});
+
+add_task(async function saveDraft() {
+ let msgCompose = Cc["@mozilla.org/messengercompose/compose;1"].createInstance(
+ Ci.nsIMsgCompose
+ );
+ let fields = Cc[
+ "@mozilla.org/messengercompose/composefields;1"
+ ].createInstance(Ci.nsIMsgCompFields);
+ fields.from = "Nobody <nobody@tinderbox.test>";
+
+ let params = Cc[
+ "@mozilla.org/messengercompose/composeparams;1"
+ ].createInstance(Ci.nsIMsgComposeParams);
+ params.composeFields = fields;
+ msgCompose.initialize(params);
+
+ // Set up the identity
+ let identity = MailServices.accounts.createIdentity();
+ identity.draftFolder = gDraftsFolder.URI;
+
+ let progress = Cc["@mozilla.org/messenger/progress;1"].createInstance(
+ Ci.nsIMsgProgress
+ );
+ let progressListener = new ProgressListener();
+ progress.registerListener(progressListener);
+ msgCompose.sendMsg(
+ Ci.nsIMsgSend.nsMsgSaveAsDraft,
+ identity,
+ "",
+ null,
+ progress
+ );
+ await progressListener.promise;
+});
+
+add_task(async function updateDrafts() {
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ gDraftsFolder.updateFolderWithListener(null, listener);
+ await listener.promise;
+});
+
+add_task(function checkResult() {
+ Assert.equal(gDraftsFolder.getTotalMessages(false), 1);
+ Assert.equal(gDraftsFolder.getNumUnread(false), 1);
+});
+
+add_task(function endTest() {
+ teardownIMAPPump();
+});
+
+function ProgressListener() {
+ this._promise = new Promise(resolve => {
+ this._resolve = resolve;
+ });
+}
+
+ProgressListener.prototype = {
+ onStateChange(aWebProgress, aRequest, aStateFlags, aStatus) {
+ if (aStateFlags & Ci.nsIWebProgressListener.STATE_STOP) {
+ this._resolve();
+ }
+ },
+
+ onProgressChange(
+ aWebProgress,
+ aRequest,
+ aCurSelfProgress,
+ aMaxSelfProgress,
+ aCurTotalProgress,
+ aMaxTotalProgress
+ ) {},
+ onLocationChange(aWebProgress, aRequest, aLocation, aFlags) {},
+ onStatusChange(aWebProgress, aRequest, aStatus, aMessage) {},
+ onSecurityChange(aWebProgress, aRequest, state) {},
+ onContentBlockingEvent(aWebProgress, aRequest, aEvent) {},
+
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIWebProgressListener",
+ "nsISupportsWeakReference",
+ ]),
+ get promise() {
+ return this._promise;
+ },
+};
diff --git a/comm/mailnews/imap/test/unit/test_saveTemplate.js b/comm/mailnews/imap/test/unit/test_saveTemplate.js
new file mode 100644
index 0000000000..12560411ee
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_saveTemplate.js
@@ -0,0 +1,96 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Tests imap save of message as a template, and test initial save right after
+ * creation of folder.
+ */
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+var { MailUtils } = ChromeUtils.import("resource:///modules/MailUtils.jsm");
+var { MessageGenerator } = ChromeUtils.import(
+ "resource://testing-common/mailnews/MessageGenerator.jsm"
+);
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+add_setup(function () {
+ setupIMAPPump();
+ Services.prefs.setBoolPref(
+ "mail.server.default.autosync_offline_stores",
+ false
+ );
+});
+
+// load and update a message in the imap fake server
+add_task(async function loadImapMessage() {
+ let gMessageGenerator = new MessageGenerator();
+ // create a synthetic message with attachment
+ let smsg = gMessageGenerator.makeMessage();
+
+ let msgURI = Services.io.newURI(
+ "data:text/plain;base64," + btoa(smsg.toMessageString())
+ );
+ let imapInbox = IMAPPump.daemon.getMailbox("INBOX");
+ let message = new ImapMessage(msgURI.spec, imapInbox.uidnext++, []);
+ IMAPPump.mailbox.addMessage(message);
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.updateFolderWithListener(null, listener);
+ await listener.promise;
+});
+
+// This is similar to the method in mailCommands.js, to test the way that
+// it creates a new templates folder before saving the message as a template.
+add_task(async function saveAsTemplate() {
+ // Prepare msgAddedListener for this test.
+ let msgAddedListener = new MsgAddedListener();
+ MailServices.mfn.addListener(msgAddedListener, MailServices.mfn.msgAdded);
+
+ let hdr = mailTestUtils.firstMsgHdr(IMAPPump.inbox);
+ let uri = IMAPPump.inbox.getUriForMsg(hdr);
+ let identity = MailServices.accounts.getFirstIdentityForServer(
+ IMAPPump.incomingServer
+ );
+ identity.stationeryFolder =
+ IMAPPump.incomingServer.rootFolder.URI + "/Templates";
+ let templates = MailUtils.getOrCreateFolder(identity.stationeryFolder);
+ // Verify that Templates folder doesn't exist, and then create it.
+ Assert.equal(templates.parent, null);
+ templates.setFlag(Ci.nsMsgFolderFlags.Templates);
+ let listener = new PromiseTestUtils.PromiseUrlListener({
+ OnStopRunningUrl() {
+ let messenger = Cc["@mozilla.org/messenger;1"].createInstance(
+ Ci.nsIMessenger
+ );
+ messenger.saveAs(uri, false, identity, null);
+ },
+ });
+ templates.createStorageIfMissing(listener);
+ await listener.promise;
+
+ await msgAddedListener.promise;
+});
+
+// Cleanup
+add_task(function endTest() {
+ teardownIMAPPump();
+});
+
+// listener for saveAsTemplate adding a message to the templates folder.
+function MsgAddedListener() {
+ this._promise = new Promise(resolve => {
+ this._resolve = resolve;
+ });
+}
+
+MsgAddedListener.prototype = {
+ msgAdded(aMsg) {
+ // Check this is the templates folder.
+ Assert.equal(aMsg.folder.prettyName, "Templates");
+ this._resolve();
+ },
+};
diff --git a/comm/mailnews/imap/test/unit/test_starttlsFailure.js b/comm/mailnews/imap/test/unit/test_starttlsFailure.js
new file mode 100644
index 0000000000..59c7f2e70f
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_starttlsFailure.js
@@ -0,0 +1,79 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 test checks that we handle the server dropping the connection
+ * on starttls. Since fakeserver doesn't support STARTTLS, I've made
+ * it drop the connection when it's attempted.
+ */
+
+/* import-globals-from ../../../test/resources/alertTestUtils.js */
+load("../../../resources/alertTestUtils.js");
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+var gAlertResolve;
+var gGotAlert = new Promise(resolve => {
+ gAlertResolve = resolve;
+});
+
+/* exported alert to alertTestUtils.js */
+function alertPS(parent, aDialogTitle, aText) {
+ gAlertResolve(aText);
+}
+
+add_setup(async function () {
+ // Set up IMAP fakeserver and incoming server.
+ IMAPPump.daemon = new ImapDaemon();
+ IMAPPump.server = makeServer(IMAPPump.daemon, "", { dropOnStartTLS: true });
+ IMAPPump.incomingServer = createLocalIMAPServer(IMAPPump.server.port);
+ IMAPPump.incomingServer.socketType = Ci.nsMsgSocketType.alwaysSTARTTLS;
+
+ // We need a local account for the IMAP server to have its sent messages in.
+ localAccountUtils.loadLocalMailAccount();
+
+ // We need an identity so that updateFolder doesn't fail.
+ let imapAccount = MailServices.accounts.createAccount();
+ let identity = MailServices.accounts.createIdentity();
+ imapAccount.addIdentity(identity);
+ imapAccount.defaultIdentity = identity;
+ imapAccount.incomingServer = IMAPPump.incomingServer;
+ MailServices.accounts.defaultAccount = imapAccount;
+
+ // The server doesn't support more than one connection.
+ Services.prefs.setIntPref("mail.server.server1.max_cached_connections", 1);
+ // We aren't interested in downloading messages automatically.
+ Services.prefs.setBoolPref("mail.server.server1.download_on_biff", false);
+
+ IMAPPump.inbox = IMAPPump.incomingServer.rootFolder
+ .getChildNamed("Inbox")
+ .QueryInterface(Ci.nsIMsgImapMailFolder);
+
+ registerAlertTestUtils();
+
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.updateFolderWithListener(gDummyMsgWindow, listener);
+ await listener.promise
+ .then(res => {
+ throw new Error("updateFolderWithListener has to fail");
+ })
+ .catch(exitCode => {
+ Assert.ok(!Components.isSuccessCode(exitCode));
+ });
+});
+
+add_task(async function check_alert() {
+ let alertText = await gGotAlert;
+ Assert.ok(alertText.startsWith("Server localhost has disconnected"));
+});
+
+add_task(function teardown() {
+ IMAPPump.incomingServer.closeCachedConnections();
+ IMAPPump.server.stop();
+});
diff --git a/comm/mailnews/imap/test/unit/test_stopMovingToLocalFolder.js b/comm/mailnews/imap/test/unit/test_stopMovingToLocalFolder.js
new file mode 100644
index 0000000000..9a7718332f
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_stopMovingToLocalFolder.js
@@ -0,0 +1,95 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* Test that the message failed to move to a local folder remains on IMAP
+ * server. */
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+var { MessageGenerator } = ChromeUtils.import(
+ "resource://testing-common/mailnews/MessageGenerator.jsm"
+);
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+function stop_server() {
+ IMAPPump.incomingServer.closeCachedConnections();
+ IMAPPump.server.stop();
+ let thread = gThreadManager.currentThread;
+ while (thread.hasPendingEvents()) {
+ thread.processNextEvent(true);
+ }
+}
+
+add_setup(function () {
+ setupIMAPPump();
+ Services.prefs.setBoolPref(
+ "mail.server.default.autosync_offline_stores",
+ false
+ );
+});
+
+add_setup(async function () {
+ let messageGenerator = new MessageGenerator();
+ let messageString = messageGenerator.makeMessage().toMessageString();
+ let dataUri = Services.io.newURI(
+ "data:text/plain;base64," + btoa(messageString)
+ );
+ let imapMsg = new ImapMessage(dataUri.spec, IMAPPump.mailbox.uidnext++, []);
+ IMAPPump.mailbox.addMessage(imapMsg);
+
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.updateFolderWithListener(null, listener);
+ await listener.promise;
+});
+
+add_task(async function move_messages() {
+ let msg = IMAPPump.inbox.msgDatabase.getMsgHdrForKey(
+ IMAPPump.mailbox.uidnext - 1
+ );
+ let copyListener = new PromiseTestUtils.PromiseCopyListener({
+ OnProgress(aProgress, aProgressMax) {
+ stop_server();
+ },
+ });
+ MailServices.copy.copyMessages(
+ IMAPPump.inbox,
+ [msg],
+ localAccountUtils.inboxFolder,
+ true,
+ copyListener,
+ null,
+ false
+ );
+ await copyListener.promise;
+});
+
+add_task(function check_messages() {
+ Assert.equal(IMAPPump.inbox.getTotalMessages(false), 1);
+ Assert.equal(localAccountUtils.inboxFolder.getTotalMessages(false), 0);
+});
+
+add_task(function endTest() {
+ // IMAPPump.server.performTest() brings this test to a halt,
+ // so we need teardownIMAPPump() without IMAPPump.server.performTest().
+ IMAPPump.inbox = null;
+ IMAPPump.server.resetTest();
+ try {
+ IMAPPump.incomingServer.closeCachedConnections();
+ let serverSink = IMAPPump.incomingServer.QueryInterface(
+ Ci.nsIImapServerSink
+ );
+ serverSink.abortQueuedUrls();
+ } catch (ex) {
+ dump(ex);
+ }
+ IMAPPump.server.stop();
+ let thread = gThreadManager.currentThread;
+ while (thread.hasPendingEvents()) {
+ thread.processNextEvent(true);
+ }
+});
diff --git a/comm/mailnews/imap/test/unit/test_subfolderLocation.js b/comm/mailnews/imap/test/unit/test_subfolderLocation.js
new file mode 100644
index 0000000000..7cbf1b7bb8
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_subfolderLocation.js
@@ -0,0 +1,83 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test proper location of new imap offline subfolders for maildir.
+
+// async support
+/* import-globals-from ../../../test/resources/logHelper.js */
+/* import-globals-from ../../../test/resources/alertTestUtils.js */
+load("../../../resources/logHelper.js");
+load("../../../resources/alertTestUtils.js");
+
+// Globals
+
+// Messages to load must have CRLF line endings, that is Windows style
+var gMessage = "bugmail10"; // message file used as the test message
+
+add_task(function () {
+ Services.prefs.setBoolPref(
+ "mail.server.server1.autosync_offline_stores",
+ false
+ );
+ setupIMAPPump();
+});
+
+// load and update a message in the imap fake server
+add_task(async function loadImapMessage() {
+ IMAPPump.mailbox.addMessage(
+ new ImapMessage(specForFileName(gMessage), IMAPPump.mailbox.uidnext++, [])
+ );
+ let promiseUrlListener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.updateFolderWithListener(gDummyMsgWindow, promiseUrlListener);
+ await promiseUrlListener.promise;
+
+ Assert.equal(1, IMAPPump.inbox.getTotalMessages(false));
+ let msgHdr = mailTestUtils.firstMsgHdr(IMAPPump.inbox);
+ Assert.ok(msgHdr instanceof Ci.nsIMsgDBHdr);
+});
+
+add_task(async function downloadOffline() {
+ // ...and download for offline use.
+ let promiseUrlListener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.downloadAllForOffline(promiseUrlListener, null);
+ await promiseUrlListener.promise;
+});
+
+var folderName1 = "sub1";
+var folderName2 = "sub2";
+
+// use a folder method to add a subfolder
+add_task(async function addSubfolder() {
+ let promiseFolder1 = PromiseTestUtils.promiseFolderAdded(folderName1);
+ IMAPPump.inbox.createSubfolder(folderName1, null);
+ await promiseFolder1;
+});
+
+// use a store method to add a subfolder
+add_task(function storeAddSubfolder() {
+ IMAPPump.incomingServer.msgStore.createFolder(IMAPPump.inbox, folderName2);
+});
+
+// test that folders created with store and folder have the same parent
+add_task(function testSubfolder() {
+ let subfolder1 = IMAPPump.inbox.getChildNamed(folderName1);
+ let subfolder2 = IMAPPump.inbox.getChildNamed(folderName2);
+ Assert.equal(
+ subfolder1.filePath.parent.path,
+ subfolder2.filePath.parent.path
+ );
+});
+
+// Cleanup at end
+add_task(teardownIMAPPump);
+
+/*
+ * helper functions
+ */
+
+// given a test file, return the file uri spec
+function specForFileName(aFileName) {
+ let file = do_get_file("../../../data/" + aFileName);
+ let msgfileuri = Services.io.newFileURI(file).QueryInterface(Ci.nsIFileURL);
+ return msgfileuri.spec;
+}
diff --git a/comm/mailnews/imap/test/unit/test_syncChanges.js b/comm/mailnews/imap/test/unit/test_syncChanges.js
new file mode 100644
index 0000000000..e4b46f35a8
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_syncChanges.js
@@ -0,0 +1,80 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Test to ensure that changes made from a different profile/machine
+ * are synced correctly. In particular, we're checking that emptying out
+ * an imap folder on the server makes us delete all the headers from our db.
+ */
+
+var { MessageGenerator } = ChromeUtils.import(
+ "resource://testing-common/mailnews/MessageGenerator.jsm"
+);
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+var gMessage;
+var gSecondFolder;
+var gSynthMessage;
+
+add_setup(async function () {
+ /*
+ * Set up an IMAP server.
+ */
+ setupIMAPPump();
+
+ IMAPPump.daemon.createMailbox("secondFolder", { subscribed: true });
+
+ let messages = [];
+ let gMessageGenerator = new MessageGenerator();
+ messages = messages.concat(gMessageGenerator.makeMessage());
+ gSynthMessage = messages[0];
+
+ let msgURI = Services.io.newURI(
+ "data:text/plain;base64," + btoa(gSynthMessage.toMessageString())
+ );
+ gMessage = new ImapMessage(msgURI.spec, IMAPPump.mailbox.uidnext++, []);
+ IMAPPump.mailbox.addMessage(gMessage);
+
+ // update folder to download header.
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.updateFolderWithListener(null, listener);
+ await listener.promise;
+});
+
+add_task(async function switchAwayFromInbox() {
+ let rootFolder = IMAPPump.incomingServer.rootFolder;
+ gSecondFolder = rootFolder
+ .getChildNamed("secondFolder")
+ .QueryInterface(Ci.nsIMsgImapMailFolder);
+
+ // Selecting the second folder will close the cached connection
+ // on the inbox because fake server only supports one connection at a time.
+ // Then, we can poke at the message on the imap server directly, which
+ // simulates the user changing the message from a different machine,
+ // and Thunderbird discovering the change when it does a flag sync
+ // upon reselecting the Inbox.
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ gSecondFolder.updateFolderWithListener(null, listener);
+ await listener.promise;
+});
+
+add_task(async function simulateMailboxEmptied() {
+ gMessage.setFlag("\\Deleted");
+ let expungeListener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.expunge(expungeListener, null);
+ await expungeListener.promise;
+ let updateListener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.updateFolderWithListener(null, updateListener);
+ await updateListener.promise;
+});
+
+add_task(function checkMailboxEmpty() {
+ Assert.equal(IMAPPump.inbox.getTotalMessages(false), 0);
+});
+
+add_task(function endTest() {
+ teardownIMAPPump();
+});
diff --git a/comm/mailnews/imap/test/unit/test_trustSpamAssassin.js b/comm/mailnews/imap/test/unit/test_trustSpamAssassin.js
new file mode 100644
index 0000000000..89c223b206
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/test_trustSpamAssassin.js
@@ -0,0 +1,146 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 tests recognizing a message as junk due to
+ * SpamAssassin headers, and marking that as good
+ * without having the message return to the junk folder,
+ * as discussed in bug 540385.
+ *
+ * adapted from test_filterNeedsBody.js
+ */
+
+var { PromiseTestUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/PromiseTestUtils.jsm"
+);
+
+// Globals
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+var gMessage = "SpamAssassinYes"; // message file used as the test message
+var gJunkFolder;
+
+add_setup(function () {
+ setupIMAPPump();
+ let server = IMAPPump.incomingServer;
+ let spamSettings = server.spamSettings;
+ server.setBoolValue("useServerFilter", true);
+ server.setCharValue("serverFilterName", "SpamAssassin");
+ server.setIntValue(
+ "serverFilterTrustFlags",
+ Ci.nsISpamSettings.TRUST_POSITIVES
+ );
+ server.setBoolValue("moveOnSpam", true);
+ server.setIntValue(
+ "moveTargetMode",
+ Ci.nsISpamSettings.MOVE_TARGET_MODE_ACCOUNT
+ );
+ server.setCharValue("spamActionTargetAccount", server.serverURI);
+
+ spamSettings.initialize(server);
+});
+
+add_task(async function createJunkFolder() {
+ IMAPPump.incomingServer.rootFolder.createSubfolder("Junk", null);
+ await PromiseTestUtils.promiseFolderAdded("Junk");
+ gJunkFolder = IMAPPump.incomingServer.rootFolder.getChildNamed("Junk");
+ Assert.ok(gJunkFolder instanceof Ci.nsIMsgImapMailFolder);
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ gJunkFolder.updateFolderWithListener(null, listener);
+ await listener.promise;
+});
+
+/*
+ * Load and update a message in the imap fake server, should move
+ * SpamAssassin-marked junk message to junk folder
+ */
+add_task(async function loadImapMessage() {
+ IMAPPump.mailbox.addMessage(
+ new ImapMessage(specForFileName(gMessage), IMAPPump.mailbox.uidnext++, [])
+ );
+ /*
+ * The message matched the SpamAssassin header, so it moved
+ * to the junk folder
+ */
+ IMAPPump.inbox.updateFolder(null);
+ await PromiseTestUtils.promiseFolderNotification(
+ gJunkFolder,
+ "msgsMoveCopyCompleted"
+ );
+ let listener = new PromiseTestUtils.PromiseUrlListener();
+ gJunkFolder.updateFolderWithListener(null, listener);
+ await listener.promise;
+});
+
+add_task(function testMessageInJunk() {
+ Assert.equal(0, IMAPPump.inbox.getTotalMessages(false));
+ Assert.equal(1, gJunkFolder.getTotalMessages(false));
+});
+
+add_task(async function markMessageAsGood() {
+ /*
+ * This is done in the application in nsMsgDBView, which is difficult
+ * to test in xpcshell tests. We aren't really trying to test that here
+ * though, since the point of this test is working with the server-based
+ * filters. So I will simply simulate the operations that would typically
+ * be done by a manual marking of the messages.
+ */
+ let msgHdr = mailTestUtils.firstMsgHdr(gJunkFolder);
+ msgHdr.setStringProperty("junkscoreorigin", "user");
+ msgHdr.setStringProperty("junkpercent", "0"); // good percent
+ msgHdr.setStringProperty("junkscore", "0"); // good score
+
+ /*
+ * Now move this message to the inbox. In bug 540385, the message just
+ * gets moved back to the junk folder again. We'll test that we
+ * are now preventing that.
+ */
+ MailServices.copy.copyMessages(
+ gJunkFolder, // srcFolder
+ [msgHdr], // messages
+ IMAPPump.inbox, // dstFolder
+ true, // isMove
+ null, // listener
+ null, // msgWindow
+ false // allowUndo
+ );
+ await PromiseTestUtils.promiseFolderNotification(
+ IMAPPump.inbox,
+ "msgsMoveCopyCompleted"
+ );
+});
+
+add_task(async function updateFoldersAndCheck() {
+ let inboxUrlListener = new PromiseTestUtils.PromiseUrlListener();
+ IMAPPump.inbox.updateFolderWithListener(null, inboxUrlListener);
+ await inboxUrlListener.promise;
+ let junkUrlListener = new PromiseTestUtils.PromiseUrlListener();
+ gJunkFolder.updateFolderWithListener(null, junkUrlListener);
+ await junkUrlListener.promise;
+ // bug 540385 causes this test to fail
+ Assert.equal(1, IMAPPump.inbox.getTotalMessages(false));
+ Assert.equal(0, gJunkFolder.getTotalMessages(false));
+});
+
+add_task(function endTest() {
+ teardownIMAPPump();
+});
+
+/*
+ * helper functions
+ */
+
+// given a test file, return the file uri spec
+function specForFileName(aFileName) {
+ let file = do_get_file("../../../data/" + aFileName);
+ let msgfileuri = Services.io.newFileURI(file).QueryInterface(Ci.nsIFileURL);
+ return msgfileuri.spec;
+}
+
+// quick shorthand for output of a line of text.
+function dl(text) {
+ dump(text + "\n");
+}
diff --git a/comm/mailnews/imap/test/unit/xpcshell-cpp.ini b/comm/mailnews/imap/test/unit/xpcshell-cpp.ini
new file mode 100644
index 0000000000..394ca5fb1d
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/xpcshell-cpp.ini
@@ -0,0 +1,14 @@
+[DEFAULT]
+head = head_server.js
+tags = mbox cpp
+dupe-manifest =
+run-sequentially =
+prefs =
+ mailnews.imap.jsmodule=false
+
+[test_chunkLastLF.js]
+[test_customCommandReturnsFetchResponse.js]
+[test_imapChunks.js]
+[test_imapHdrChunking.js]
+
+[include:xpcshell-shared.ini]
diff --git a/comm/mailnews/imap/test/unit/xpcshell-shared.ini b/comm/mailnews/imap/test/unit/xpcshell-shared.ini
new file mode 100644
index 0000000000..b169aee8d1
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/xpcshell-shared.ini
@@ -0,0 +1,58 @@
+[test_autosync_date_constraints.js]
+[test_bccProperty.js]
+[test_bug460636.js]
+[test_compactOfflineStore.js]
+[test_converterImap.js]
+[test_copyThenMove.js]
+[test_dontStatNoSelect.js]
+[test_downloadOffline.js]
+[test_fetchCustomAttribute.js]
+[test_filterCustomHeaders.js]
+[test_filterNeedsBody.js]
+[test_folderOfflineFlags.js]
+[test_gmailAttributes.js]
+[test_gmailOfflineMsgStore.js]
+[test_imapAttachmentSaves.js]
+[test_imapAutoSync.js]
+[test_imapClientid.js]
+[test_imapContentLength.js]
+[test_imapCopyTimeout.js]
+[test_imapFilterActions.js]
+[test_imapFilterActionsPostplugin.js]
+[test_imapFlagChange.js]
+[test_imapFolderCopy.js]
+[test_imapHdrStreaming.js]
+[test_imapHighWater.js]
+[test_imapID.js]
+[test_imapMove.js]
+[test_imapPasswordFailure.js]
+[test_imapProtocols.js]
+[test_imapProxy.js]
+[test_imapRename.js]
+[test_imapSearch.js]
+[test_imapStatusCloseDBs.js]
+[test_imapStoreMsgOffline.js]
+[test_imapUndo.js]
+[test_imapUrls.js]
+[test_largeOfflineStore.js]
+skip-if = os == 'mac'
+[test_listClosesDB.js]
+[test_listSubscribed.js]
+[test_localToImapFilter.js]
+[test_localToImapFilterQuarantine.js]
+[test_lsub.js]
+[test_mailboxes.js]
+[test_nsIMsgFolderListenerIMAP.js]
+[test_offlineCopy.js]
+[test_offlineDraftDataloss.js]
+[test_offlineMoveLocalToIMAP.js]
+[test_offlinePlayback.js]
+[test_offlineStoreLocking.js]
+[test_preserveDataOnMove.js]
+[test_saveImapDraft.js]
+[test_saveTemplate.js]
+[test_starttlsFailure.js]
+[test_stopMovingToLocalFolder.js]
+[test_subfolderLocation.js]
+[test_syncChanges.js]
+[test_trustSpamAssassin.js]
diff --git a/comm/mailnews/imap/test/unit/xpcshell.ini b/comm/mailnews/imap/test/unit/xpcshell.ini
new file mode 100644
index 0000000000..b0be23ad01
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/xpcshell.ini
@@ -0,0 +1,12 @@
+[DEFAULT]
+head = head_server.js
+tags = mbox jsm
+dupe-manifest =
+run-sequentially =
+prefs =
+ mailnews.imap.jsmodule=true
+
+[test_ImapResponse.js]
+[test_imapAuthMethods.js]
+
+[include:xpcshell-shared.ini]
diff --git a/comm/mailnews/imap/test/unit/xpcshell_maildir-cpp.ini b/comm/mailnews/imap/test/unit/xpcshell_maildir-cpp.ini
new file mode 100644
index 0000000000..4c345d69e3
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/xpcshell_maildir-cpp.ini
@@ -0,0 +1,14 @@
+[DEFAULT]
+head = head_imap_maildir.js
+tags = maildir cpp
+dupe-manifest =
+run-sequentially =
+prefs =
+ mailnews.imap.jsmodule=false
+
+[test_chunkLastLF.js]
+[test_customCommandReturnsFetchResponse.js]
+[test_imapChunks.js]
+[test_imapHdrChunking.js]
+
+[include:xpcshell-shared.ini]
diff --git a/comm/mailnews/imap/test/unit/xpcshell_maildir.ini b/comm/mailnews/imap/test/unit/xpcshell_maildir.ini
new file mode 100644
index 0000000000..6bb40162a8
--- /dev/null
+++ b/comm/mailnews/imap/test/unit/xpcshell_maildir.ini
@@ -0,0 +1,9 @@
+[DEFAULT]
+head = head_imap_maildir.js
+tags = maildir jsm
+dupe-manifest =
+run-sequentially =
+prefs =
+ mailnews.imap.jsmodule=true
+
+[include:xpcshell-shared.ini]