summaryrefslogtreecommitdiffstats
path: root/mailnews/imap/src
diff options
context:
space:
mode:
Diffstat (limited to 'mailnews/imap/src')
-rw-r--r--mailnews/imap/src/moz.build33
-rw-r--r--mailnews/imap/src/nsAutoSyncManager.cpp1412
-rw-r--r--mailnews/imap/src/nsAutoSyncManager.h265
-rw-r--r--mailnews/imap/src/nsAutoSyncState.cpp765
-rw-r--r--mailnews/imap/src/nsAutoSyncState.h107
-rw-r--r--mailnews/imap/src/nsIMAPBodyShell.cpp1333
-rw-r--r--mailnews/imap/src/nsIMAPBodyShell.h361
-rw-r--r--mailnews/imap/src/nsIMAPGenericParser.cpp484
-rw-r--r--mailnews/imap/src/nsIMAPGenericParser.h76
-rw-r--r--mailnews/imap/src/nsIMAPHostSessionList.cpp701
-rw-r--r--mailnews/imap/src/nsIMAPHostSessionList.h135
-rw-r--r--mailnews/imap/src/nsIMAPNamespace.cpp651
-rw-r--r--mailnews/imap/src/nsIMAPNamespace.h87
-rw-r--r--mailnews/imap/src/nsImapCore.h190
-rw-r--r--mailnews/imap/src/nsImapFlagAndUidState.cpp321
-rw-r--r--mailnews/imap/src/nsImapFlagAndUidState.h55
-rw-r--r--mailnews/imap/src/nsImapIncomingServer.cpp3382
-rw-r--r--mailnews/imap/src/nsImapIncomingServer.h137
-rw-r--r--mailnews/imap/src/nsImapMailFolder.cpp9837
-rw-r--r--mailnews/imap/src/nsImapMailFolder.h550
-rw-r--r--mailnews/imap/src/nsImapOfflineSync.cpp1290
-rw-r--r--mailnews/imap/src/nsImapOfflineSync.h92
-rw-r--r--mailnews/imap/src/nsImapProtocol.cpp10093
-rw-r--r--mailnews/imap/src/nsImapProtocol.h772
-rw-r--r--mailnews/imap/src/nsImapSearchResults.cpp92
-rw-r--r--mailnews/imap/src/nsImapSearchResults.h42
-rw-r--r--mailnews/imap/src/nsImapServerResponseParser.cpp3483
-rw-r--r--mailnews/imap/src/nsImapServerResponseParser.h272
-rw-r--r--mailnews/imap/src/nsImapService.cpp3400
-rw-r--r--mailnews/imap/src/nsImapService.h123
-rw-r--r--mailnews/imap/src/nsImapStringBundle.cpp42
-rw-r--r--mailnews/imap/src/nsImapStringBundle.h17
-rw-r--r--mailnews/imap/src/nsImapUndoTxn.cpp751
-rw-r--r--mailnews/imap/src/nsImapUndoTxn.h92
-rw-r--r--mailnews/imap/src/nsImapUrl.cpp1561
-rw-r--r--mailnews/imap/src/nsImapUrl.h133
-rw-r--r--mailnews/imap/src/nsImapUtils.cpp373
-rw-r--r--mailnews/imap/src/nsImapUtils.h77
-rw-r--r--mailnews/imap/src/nsSyncRunnableHelpers.cpp601
-rw-r--r--mailnews/imap/src/nsSyncRunnableHelpers.h151
40 files changed, 44339 insertions, 0 deletions
diff --git a/mailnews/imap/src/moz.build b/mailnews/imap/src/moz.build
new file mode 100644
index 000000000..e62ec5fa8
--- /dev/null
+++ b/mailnews/imap/src/moz.build
@@ -0,0 +1,33 @@
+# 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',
+ 'nsIMAPBodyShell.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'
+
diff --git a/mailnews/imap/src/nsAutoSyncManager.cpp b/mailnews/imap/src/nsAutoSyncManager.cpp
new file mode 100644
index 000000000..9778919c7
--- /dev/null
+++ b/mailnews/imap/src/nsAutoSyncManager.cpp
@@ -0,0 +1,1412 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "nsIIdleService.h"
+#include "nsImapMailFolder.h"
+#include "nsMsgImapCID.h"
+#include "nsIObserverService.h"
+#include "nsIMsgMailNewsUrl.h"
+#include "nsIMsgAccountManager.h"
+#include "nsIMsgIncomingServer.h"
+#include "nsIMsgMailSession.h"
+#include "nsMsgFolderFlags.h"
+#include "nsImapIncomingServer.h"
+#include "nsMsgUtils.h"
+#include "nsIIOService.h"
+#include "nsComponentManagerUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "mozilla/Services.h"
+#include "nsArrayUtils.h"
+#include "mozilla/Logging.h"
+
+using namespace mozilla;
+
+NS_IMPL_ISUPPORTS(nsDefaultAutoSyncMsgStrategy, nsIAutoSyncMsgStrategy)
+
+const char* kAppIdleNotification = "mail:appIdle";
+const char* kStartupDoneNotification = "mail-startup-done";
+PRLogModuleInfo *gAutoSyncLog;
+
+// 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(NS_MSGMAILSESSION_CONTRACTID, &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;
+ mUpdateState = completed;
+ mPaused = false;
+
+ nsresult rv;
+ mIdleService = do_GetService("@mozilla.org/widget/idleservice;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);
+ gAutoSyncLog = PR_NewLogModule("ImapAutoSync");
+}
+
+nsAutoSyncManager::~nsAutoSyncManager()
+{
+}
+
+void nsAutoSyncManager::InitTimer()
+{
+ if (!mTimer)
+ {
+ nsresult rv;
+ mTimer = do_CreateInstance(NS_TIMER_CONTRACTID, &rv);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "failed to create timer in nsAutoSyncManager");
+
+ mTimer->InitWithFuncCallback(TimerCallback, (void *) this,
+ kTimerIntervalInMs, nsITimer::TYPE_REPEATING_SLACK);
+ }
+}
+
+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 folders within 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 (autoSyncMgr->mUpdateQ.Count() > 0)
+ {
+ if (autoSyncMgr->mUpdateState == completed)
+ {
+ 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->mUpdateState = initiated;
+ NOTIFY_LISTENERS_STATIC(autoSyncMgr, OnAutoSyncInitiated, (folder));
+ }
+ }
+ }
+ }
+ }
+ // 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->mUpdateState != initiated)
+ {
+ nsCOMPtr<nsIMsgFolder> folder;
+ autoSyncMgr->mUpdateQ[0]->GetOwnerFolder(getter_AddRefs(folder));
+
+ autoSyncMgr->mUpdateQ.RemoveObjectAt(0);
+
+ if (folder)
+ NOTIFY_LISTENERS_STATIC(autoSyncMgr, OnFolderRemovedFromQ, (nsIAutoSyncMgrListener::UpdateQueue, folder));
+ }
+
+ }//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;
+ else
+ 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;
+}
+
+
+NS_IMETHODIMP nsAutoSyncManager::OnStopRunningUrl(nsIURI* aUrl, nsresult aExitCode)
+{
+ mUpdateState = completed;
+ if (mUpdateQ.Count() > 0)
+ mUpdateQ.RemoveObjectAt(0);
+
+ return aExitCode;
+}
+
+NS_IMETHODIMP nsAutoSyncManager::Pause()
+{
+ StopTimer();
+ mPaused = true;
+ MOZ_LOG(gAutoSyncLog, LogLevel::Debug, ("autosync paused\n"));
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAutoSyncManager::Resume()
+{
+ mPaused = false;
+ StartTimerIfNeeded();
+ MOZ_LOG(gAutoSyncLog, LogLevel::Debug, ("autosync resumed\n"));
+ 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;
+ }
+ else 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;
+
+ return StartIdleProcessing();
+ }
+ // we're back from appIdle - if already notIdle, just return;
+ else if (GetIdleState() == notIdle)
+ return NS_OK;
+
+ 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));
+ }
+ 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;
+ return StartIdleProcessing();
+ }
+ return NS_OK;
+}
+
+nsresult nsAutoSyncManager::StartIdleProcessing()
+{
+ if (mPaused)
+ return NS_OK;
+
+ 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));
+
+ 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
+
+ nsCOMPtr<nsIMsgAccountManager> accountManager = do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr<nsIArray> accounts;
+ rv = accountManager->GetAccounts(getter_AddRefs(accounts));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ uint32_t accountCount;
+ accounts->GetLength(&accountCount);
+
+ for (uint32_t i = 0; i < accountCount; ++i)
+ {
+ nsCOMPtr<nsIMsgAccount> account(do_QueryElementAt(accounts, i, &rv));
+ 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, then skip this server.
+ bool passwordRequired;
+ incomingServer->GetServerRequiresPasswordForBiff(&passwordRequired);
+ if (passwordRequired)
+ continue;
+
+ nsCOMPtr<nsIMsgFolder> rootFolder;
+ nsCOMPtr<nsIArray> allDescendants;
+
+ rv = incomingServer->GetRootFolder(getter_AddRefs(rootFolder));
+ if (rootFolder)
+ {
+ if (NS_FAILED(rv))
+ continue;
+
+ rv = rootFolder->GetDescendants(getter_AddRefs(allDescendants));
+ if (!allDescendants)
+ continue;
+
+ uint32_t cnt = 0;
+ rv = allDescendants->GetLength(&cnt);
+ if (NS_FAILED(rv))
+ continue;
+
+ for (uint32_t i = 0; i < cnt; i++)
+ {
+ nsCOMPtr<nsIMsgFolder> folder(do_QueryElementAt(allDescendants, i, &rv));
+ if (NS_FAILED(rv))
+ continue;
+
+ 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);
+
+ if (NS_SUCCEEDED(rv) && nsAutoSyncState::stCompletedIdle == state)
+ {
+ // ensure that we wait for at least nsMsgIncomingServer::BiffMinutes between
+ // each update of the same folder
+ PRTime lastUpdateTime;
+ rv = autoSyncState->GetLastUpdateTime(&lastUpdateTime);
+ PRTime span = GetUpdateIntervalFor(autoSyncState) * (PR_USEC_PER_SEC * 60UL);
+ if ( NS_SUCCEEDED(rv) && ((lastUpdateTime + span) < PR_Now()) )
+ {
+ if (mUpdateQ.IndexOf(autoSyncState) == -1)
+ {
+ mUpdateQ.AppendObject(autoSyncState);
+ if (folder)
+ NOTIFY_LISTENERS(OnFolderAddedIntoQ, (nsIAutoSyncMgrListener::UpdateQueue, folder));
+ }
+ }
+ }
+
+ // check last sync time
+ 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);
+ 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;
+ }//endwhile
+ }
+ }//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;
+
+ nsCOMPtr<nsIMutableArray> messagesToDownload;
+ uint32_t totalSize = 0;
+ rv = aAutoSyncStateObj->GetNextGroupOfMessages(mGroupSize, &totalSize, getter_AddRefs(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;
+
+ uint32_t length;
+ rv = messagesToDownload->GetLength(&length);
+ if (NS_SUCCEEDED(rv) && length > 0)
+ {
+ 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, length, totalCount));
+ }
+
+ return rv;
+}
+
+/**
+ * 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.
+ */
+nsresult nsAutoSyncManager::HandleDownloadErrorFor(nsIAutoSyncState *aAutoSyncStateObj,
+ const nsresult error)
+{
+ if (!aAutoSyncStateObj)
+ return NS_ERROR_INVALID_ARG;
+
+ // ensure that an error occured
+ 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;
+ else 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;
+ else
+ autoSyncStateObj->TryCurrentGroupAgain(kGroupRetryCount);
+ }
+ }
+
+ return NS_OK;
+}
+
+uint32_t nsAutoSyncManager::GetUpdateIntervalFor(nsIAutoSyncState *aAutoSyncStateObj)
+{
+ nsCOMPtr<nsIMsgFolder> folder;
+ nsresult rv = aAutoSyncStateObj->GetOwnerFolder(getter_AddRefs(folder));
+ if (NS_FAILED(rv))
+ return kDefaultUpdateInterval;
+
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = folder->GetServer(getter_AddRefs(server));
+ if (NS_FAILED(rv))
+ return kDefaultUpdateInterval;
+
+ if (server)
+ {
+ int32_t interval;
+ rv = server->GetBiffMinutes(&interval);
+
+ if (NS_SUCCEEDED(rv))
+ return (uint32_t)interval;
+ }
+
+ return kDefaultUpdateInterval;
+}
+
+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/mailnews/imap/src/nsAutoSyncManager.h b/mailnews/imap/src/nsAutoSyncManager.h
new file mode 100644
index 000000000..2b98b7b82
--- /dev/null
+++ b/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 "nsAutoPtr.h"
+#include "nsStringGlue.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 "prtime.h"
+
+class nsImapMailFolder;
+class nsIMsgDBHdr;
+class nsIIdleService;
+class nsIMsgFolder;
+
+/* 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.
+ */
+
+/**
+ * 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 };
+ enum UpdateState { initiated, completed };
+
+ 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();
+
+ /// pref helpers
+ uint32_t GetUpdateIntervalFor(nsIAutoSyncState *aAutoSyncStateObj);
+
+ 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 the update state for the current folder.
+ UpdateState mUpdateState;
+
+ // 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<nsIIdleService> 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.
+
+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/mailnews/imap/src/nsAutoSyncState.cpp b/mailnews/imap/src/nsAutoSyncState.cpp
new file mode 100644
index 000000000..7b1aeabbe
--- /dev/null
+++ b/mailnews/imap/src/nsAutoSyncState.cpp
@@ -0,0 +1,765 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "nsMsgImapCID.h"
+#include "nsIMsgMailNewsUrl.h"
+#include "nsMsgKeyArray.h"
+#include "nsIMsgWindow.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 PRLogModuleInfo *gAutoSyncLog;
+
+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;
+
+ nsCOMPtr<nsIMsgFolder> folder = do_QueryInterface(mFolder);
+ if (mStrategy)
+ rv = mStrategy->Sort(folder, 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;
+
+ nsCOMPtr<nsIMsgFolder> folder = do_QueryInterface(mFolder);
+ if (mStrategy)
+ rv = mStrategy->Sort(folder, 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));
+}
+
+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,
+ nsIMutableArray **aMessagesList)
+{
+ NS_ENSURE_ARG_POINTER(aMessagesList);
+ NS_ENSURE_ARG_POINTER(aActualGroupSize);
+
+ *aActualGroupSize = 0;
+
+ nsresult rv;
+ nsCOMPtr <nsIMsgFolder> folder = do_QueryReferent(mOwnerFolder, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgDatabase> database;
+ folder->GetMsgDatabase(getter_AddRefs(database));
+
+ nsCOMPtr<nsIMutableArray> group = do_CreateInstance(NS_ARRAY_CONTRACTID);
+ 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;
+ group->AppendElement(qhdr, false);
+ idx++;
+ break;
+ }
+ else if ((*aActualGroupSize) + msgSize > aSuggestedGroupSizeLimit)
+ break;
+ else
+ {
+ group->AppendElement(qhdr, false);
+ *aActualGroupSize += msgSize;
+ }
+ }// endfor
+
+ mLastOffset = mOffset;
+ mOffset = idx;
+ }
+
+ LogOwnerFolderName("Next group of messages to be downloaded.");
+ LogQWithSize(group.get(), 0);
+ } //endif
+
+ // return it to the caller
+ NS_IF_ADDREF(*aMessagesList = group);
+
+ return NS_OK;
+}
+
+/**
+ * Usually called by nsAutoSyncManager when the last sync time is expired.
+ */
+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())
+ {
+ RefPtr<nsMsgKeyArray> keys = new nsMsgKeyArray;
+ rv = database->ListAllKeys(keys);
+ NS_ENSURE_SUCCESS(rv, rv);
+ keys->Sort();
+ mExistingHeadersQ.AppendElements(keys->m_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,
+ ("%d messages will be added into the download q of folder %s\n",
+ 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);
+}
+
+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);
+}
+
+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);
+ if (serverNextUID != mLastNextUID || serverTotal != mLastServerTotal ||
+ serverUnseen != mLastServerUnseen || serverRecent != mLastServerRecent)
+ {
+ nsCString folderName;
+ ownerFolder->GetURI(folderName);
+ MOZ_LOG(gAutoSyncLog, LogLevel::Debug,
+ ("folder %s status changed serverNextUID = %lx lastNextUID = %lx\n", folderName.get(),
+ serverNextUID, mLastNextUID));
+ MOZ_LOG(gAutoSyncLog, LogLevel::Debug,
+ ("serverTotal = %lx lastServerTotal = %lx serverRecent = %lx lastServerRecent = %lx\n",
+ serverTotal, mLastServerTotal, serverRecent, mLastServerRecent));
+ SetServerCounts(serverTotal, serverRecent, serverUnseen, serverNextUID);
+ SetState(nsAutoSyncState::stUpdateIssued);
+ return imapFolder->UpdateFolderWithListener(nullptr, autoSyncMgrListener);
+ }
+ else
+ {
+ ownerFolder->SetMsgDatabase(nullptr);
+ // nothing more to do.
+ SetState(nsAutoSyncState::stCompletedIdle);
+ // autoSyncMgr needs this notification, so manufacture it.
+ return autoSyncMgrListener->OnStopRunningUrl(nullptr, NS_OK);
+ }
+ }
+ //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);
+
+ return autoSyncMgr->OnDownloadCompleted(this, aExitCode);
+}
+
+NS_IMETHODIMP nsAutoSyncState::GetState(int32_t *aState)
+{
+ NS_ENSURE_ARG_POINTER(aState);
+ *aState = mSyncState;
+ return NS_OK;
+}
+
+const char *stateStrings[] = {"idle", "status issued", "update needed",
+ "update issued", "downloading",
+ "ready to download"};
+
+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(NS_MSGMAILSESSION_CONTRACTID, &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.Append(" 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);
+
+ NS_IF_ADDREF(*aFolder = ownerFolder);
+ 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;
+}
+
+
+NS_IMETHODIMP nsAutoSyncState::DownloadMessagesForOffline(nsIArray *aMessagesList)
+{
+ NS_ENSURE_ARG_POINTER(aMessagesList);
+
+ uint32_t count;
+ nsresult rv = aMessagesList->GetLength(&count);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr<nsIImapService> imapService = do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsAutoCString messageIds;
+ nsTArray<nsMsgKey> msgKeys;
+
+ rv = nsImapMailFolder::BuildIdsAndKeyArray(aMessagesList, 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);
+
+ nsCString folderName;
+ folder->GetURI(folderName);
+ MOZ_LOG(gAutoSyncLog, LogLevel::Debug, ("downloading %s for %s", 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(nsIMutableArray *q, uint32_t toOffset)
+{
+ nsCOMPtr <nsIMsgFolder> ownerFolder = do_QueryReferent(mOwnerFolder);
+ if (ownerFolder)
+ {
+ nsCOMPtr<nsIMsgDatabase> database;
+ ownerFolder->GetMsgDatabase(getter_AddRefs(database));
+
+ uint32_t x;
+ q->GetLength(&x);
+ while (x > toOffset && database)
+ {
+ x--;
+ nsCOMPtr<nsIMsgDBHdr> h;
+ q->QueryElementAt(x, NS_GET_IID(nsIMsgDBHdr),
+ 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, ("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/mailnews/imap/src/nsAutoSyncState.h b/mailnews/imap/src/nsAutoSyncState.h
new file mode 100644
index 000000000..0b10c8c94
--- /dev/null
+++ b/mailnews/imap/src/nsAutoSyncState.h
@@ -0,0 +1,107 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "nsIAutoSyncManager.h"
+#include "nsIUrlListener.h"
+#include "nsWeakPtr.h"
+#include "nsTHashtable.h"
+#include "nsHashKeys.h"
+#include "nsTArray.h"
+#include "prlog.h"
+#include "nsIWeakReferenceUtils.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(nsIMutableArray *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;
+};
+
+#endif
diff --git a/mailnews/imap/src/nsIMAPBodyShell.cpp b/mailnews/imap/src/nsIMAPBodyShell.cpp
new file mode 100644
index 000000000..087fe2033
--- /dev/null
+++ b/mailnews/imap/src/nsIMAPBodyShell.cpp
@@ -0,0 +1,1333 @@
+/* -*- 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/. */
+
+#include "msgCore.h"
+#include "nsIMAPHostSessionList.h"
+#include "nsIMAPBodyShell.h"
+#include "nsImapProtocol.h"
+#include "nsImapStringBundle.h"
+
+#include "nsMimeTypes.h"
+#include "nsIPrefBranch.h"
+#include "nsIPrefService.h"
+#include "nsITransport.h"
+#include "nsServiceManagerUtils.h"
+
+// need to talk to Rich about this...
+#define IMAP_EXTERNAL_CONTENT_HEADER "X-Mozilla-IMAP-Part"
+
+// 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(nsImapProtocol *protocolConnection,
+ nsIMAPBodypartMessage *message, uint32_t UID,
+ const char *folderName)
+{
+ m_isValid = false;
+ m_isBeingGenerated = false;
+ m_cached = false;
+ m_gotAttachmentPref = false;
+ m_generatingWholeMessage = false;
+ m_generatingPart = NULL;
+ m_protocolConnection = protocolConnection;
+ m_message = message;
+ NS_ASSERTION(m_protocolConnection, "non null connection");
+ if (!m_protocolConnection)
+ return;
+ m_prefetchQueue = new nsIMAPMessagePartIDArray();
+ if (!m_prefetchQueue)
+ return;
+ m_UID = "";
+ m_UID.AppendInt(UID);
+#ifdef DEBUG_chrisf
+ NS_ASSERTION(folderName);
+#endif
+ if (!folderName)
+ return;
+ m_folderName = NS_strdup(folderName);
+ if (!m_folderName)
+ return;
+
+ SetContentModified(GetShowAttachmentsInline() ? IMAP_CONTENT_MODIFIED_VIEW_INLINE : IMAP_CONTENT_MODIFIED_VIEW_AS_LINKS);
+
+ SetIsValid(m_message != nullptr);
+}
+
+nsIMAPBodyShell::~nsIMAPBodyShell()
+{
+ delete m_message;
+ delete m_prefetchQueue;
+ PR_Free(m_folderName);
+}
+
+void nsIMAPBodyShell::SetIsValid(bool valid)
+{
+ m_isValid = valid;
+}
+
+bool nsIMAPBodyShell::GetShowAttachmentsInline()
+{
+ if (!m_gotAttachmentPref)
+ {
+ m_showAttachmentsInline = !m_protocolConnection || m_protocolConnection->GetShowAttachmentsInline();
+ m_gotAttachmentPref = true;
+ }
+
+ return m_showAttachmentsInline;
+}
+
+// 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 = new nsIMAPMessagePartID(fields, partNumber);
+ if (newPart)
+ {
+ m_prefetchQueue->AppendElement(newPart);
+ }
+ else
+ {
+ // HandleMemoryFailure();
+ }
+}
+
+// Flushes all of the prefetches that have been queued up in the prefetch queue,
+// freeing them as we go
+void nsIMAPBodyShell::FlushPrefetchQueue()
+{
+ m_protocolConnection->PipelinedFetchMessageParts(GetUID(), m_prefetchQueue);
+ m_prefetchQueue->RemoveAndFreeAll();
+}
+
+// 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(char *partNum)
+{
+ 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 = m_protocolConnection->GetMessageSize(GetUID().get(), true);
+ m_protocolConnection->SetContentModified(IMAP_CONTENT_NOT_MODIFIED); // So that when we cache it, we know we have the whole message
+ if (!DeathSignalReceived())
+ m_protocolConnection->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 (!GetPseudoInterrupted())
+ m_message->Generate(this, false, true); // This queues up everything we need to prefetch
+ // Now, run a single pipelined prefetch (neato!)
+ FlushPrefetchQueue();
+
+ ////// 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 (!GetPseudoInterrupted())
+ contentLength = m_message->Generate(this, false, false);
+
+ // Setup the stream
+ if (!GetPseudoInterrupted() && !DeathSignalReceived())
+ {
+ nsresult rv =
+ m_protocolConnection->BeginMessageDownLoad(contentLength, MESSAGE_RFC822);
+ if (NS_FAILED(rv))
+ {
+ m_generatingPart = nullptr;
+ m_protocolConnection->AbortMessageDownLoad();
+ return 0;
+ }
+ else
+ {
+ streamCreated = true;
+ }
+ }
+
+ ////// PASS 3 : GENERATE ///////
+ // Generate the message
+ if (!GetPseudoInterrupted() && !DeathSignalReceived())
+ m_message->Generate(this, true, false);
+
+ // Close the stream here - normal. If pseudointerrupted, the connection will abort the download stream
+ if (!GetPseudoInterrupted() && !DeathSignalReceived())
+ m_protocolConnection->NormalMessageEndDownload();
+ else if (streamCreated)
+ m_protocolConnection->AbortMessageDownLoad();
+
+ m_generatingPart = NULL;
+
+ }
+
+ m_isBeingGenerated = false;
+ return contentLength;
+}
+
+bool nsIMAPBodyShell::GetPseudoInterrupted()
+{
+ bool rv = m_protocolConnection->GetPseudoInterrupted();
+ return rv;
+}
+
+bool nsIMAPBodyShell::DeathSignalReceived()
+{
+ bool rv = m_protocolConnection->DeathSignalReceived();
+ return rv;
+}
+
+
+///////////// 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::PrefetchMIMEHeader()
+{
+if (!m_headerData && !m_shell->DeathSignalReceived())
+{
+m_shell->GetConnection()->FetchMessage(m_shell->GetUID(), kMIMEHeader, true, 0, 0, m_partNumberString);
+// m_headerLength will be filled in when it is adopted from the parser
+}
+if (!m_headerData)
+{
+SetIsValid(false);
+}
+}
+*/
+
+void nsIMAPBodypart::QueuePrefetchMIMEHeader(nsIMAPBodyShell *aShell)
+{
+ aShell->AddPrefetchToQueue(kMIMEHeader, m_partNumberString);
+}
+
+int32_t nsIMAPBodypart::GenerateMIMEHeader(nsIMAPBodyShell *aShell, bool stream, bool prefetch)
+{
+ if (prefetch && !m_headerData)
+ {
+ QueuePrefetchMIMEHeader(aShell);
+ return 0;
+ }
+ else 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)
+ {
+ aShell->GetConnection()->Log("SHELL","GENERATE-XHeader",m_partNumberString);
+ aShell->GetConnection()->HandleMessageDownLoadLine(xPartHeader, false);
+ }
+ mimeHeaderLength += PL_strlen(xPartHeader);
+ PR_Free(xPartHeader);
+ }
+ }
+
+ mimeHeaderLength += PL_strlen(m_headerData);
+ if (stream)
+ {
+ aShell->GetConnection()->Log("SHELL","GENERATE-MIMEHeader",m_partNumberString);
+ aShell->GetConnection()->HandleMessageDownLoadLine(m_headerData, false); // all one line? Can we do that?
+ }
+
+ return mimeHeaderLength;
+ }
+ else
+ {
+ SetIsValid(false); // prefetch didn't adopt a MIME header
+ return 0;
+ }
+}
+
+int32_t nsIMAPBodypart::GeneratePart(nsIMAPBodyShell *aShell, 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)
+ {
+ aShell->GetConnection()->Log("SHELL","GENERATE-Part-Prefetched",m_partNumberString);
+ aShell->GetConnection()->HandleMessageDownLoadLine(m_partData, false);
+ }
+ return PL_strlen(m_partData);
+ }
+ else // we are fetching and streaming this part's body as we go
+ {
+ if (stream && !aShell->DeathSignalReceived())
+ {
+ char *generatingPart = aShell->GetGeneratingPart();
+ bool fetchingSpecificPart = (generatingPart && !PL_strcmp(generatingPart, m_partNumberString));
+
+ aShell->GetConnection()->Log("SHELL","GENERATE-Part-Inline",m_partNumberString);
+ aShell->GetConnection()->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, bool stream, bool prefetch, bool lastBoundary)
+{
+ if (prefetch)
+ return 0; // don't need to prefetch anything
+
+ if (m_boundaryData)
+ {
+ if (!lastBoundary)
+ {
+ if (stream)
+ {
+ aShell->GetConnection()->Log("SHELL","GENERATE-Boundary",m_partNumberString);
+ aShell->GetConnection()->HandleMessageDownLoadLine(m_boundaryData, false);
+ }
+ return PL_strlen(m_boundaryData);
+ }
+ else // the last boundary
+ {
+ char *lastBoundaryData = PR_smprintf("%s--", m_boundaryData);
+ if (lastBoundaryData)
+ {
+ if (stream)
+ {
+ aShell->GetConnection()->Log("SHELL","GENERATE-Boundary-Last",m_partNumberString);
+ aShell->GetConnection()->HandleMessageDownLoadLine(lastBoundaryData, false);
+ }
+ int32_t rv = PL_strlen(lastBoundaryData);
+ PR_Free(lastBoundaryData);
+ return rv;
+ }
+ else
+ {
+ //HandleMemoryFailure();
+ return 0;
+ }
+ }
+ }
+ else
+ return 0;
+}
+
+int32_t nsIMAPBodypart::GenerateEmptyFilling(nsIMAPBodyShell *aShell, bool stream, bool prefetch)
+{
+ if (prefetch)
+ return 0; // don't need to prefetch anything
+
+ const nsString &emptyString = aShell->GetConnection()->
+ GetEmptyMimePartString();
+ if (!emptyString.IsEmpty())
+ {
+ if (stream)
+ {
+ nsImapProtocol *conn = aShell->GetConnection();
+ conn->Log("SHELL", "GENERATE-Filling", m_partNumberString);
+ conn->HandleMessageDownLoadLine(NS_ConvertUTF16toUTF8(emptyString).get(),
+ false);
+ }
+ return emptyString.Length();
+ }
+ else
+ 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, bool stream, bool prefetch)
+{
+ int32_t len = 0;
+
+ if (GetIsValid())
+ {
+
+ if (stream && !prefetch)
+ aShell->GetConnection()->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)
+ && !aShell->GetPseudoInterrupted())
+ len += GenerateMIMEHeader(aShell, stream, prefetch);
+
+ if (!aShell->GetPseudoInterrupted())
+ {
+ if (ShouldFetchInline(aShell))
+ {
+ // Fetch and stream the content of this part
+ len += GeneratePart(aShell, stream, prefetch);
+ }
+ else
+ {
+ // fill in the filling within the empty part
+ len += GenerateEmptyFilling(aShell, 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;
+ }
+ else
+ {
+ // 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 (m_parentPart &&
+ !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;
+ }
+ }
+ else
+ {
+ // 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);
+ }
+ else // it is applesingle
+ {
+ return false; // we can leave it on the server
+ }
+ }
+#endif // XP_MACOSX
+
+ // Leave out parts with type application/*
+ if (!PL_strcasecmp(m_bodyType, "APPLICATION") && // If it is of type "application"
+ PL_strncasecmp(m_bodySubType, "x-pkcs7", 7) // and it's not a signature (signatures are inline)
+ )
+ 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->ElementAt(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, bool stream, bool prefetch)
+{
+ if (!GetIsValid())
+ return 0;
+
+ m_contentLength = 0;
+
+ if (stream && !prefetch)
+ aShell->GetConnection()->Log("SHELL","GENERATE-MessageRFC822",m_partNumberString);
+
+ if (!m_topLevelMessage && !aShell->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, stream, prefetch);
+ }
+
+ if (!aShell->GetPseudoInterrupted())
+ m_contentLength += m_headers->Generate(aShell, stream, prefetch);
+ if (!aShell->GetPseudoInterrupted())
+ m_contentLength += m_body->Generate(aShell, 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;
+ }
+ else
+ {
+ // 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_strdup(m_parentPart->GetPartNumberString());
+ }
+ m_partList = new nsTArray<nsIMAPBodypart*>();
+ m_bodyType = NS_strdup("multipart");
+ if (m_partList && 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->ElementAt(i);
+ }
+ delete m_partList;
+}
+
+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, bool stream, bool prefetch)
+{
+ int32_t len = 0;
+
+ if (GetIsValid())
+ {
+ if (stream && !prefetch)
+ aShell->GetConnection()->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 && !aShell->GetPseudoInterrupted()) // not a message body's type
+ {
+ len += GenerateMIMEHeader(aShell, stream, prefetch);
+ }
+
+ if (ShouldFetchInline(aShell))
+ {
+ for (size_t i = 0; i < m_partList->Length(); i++)
+ {
+ if (!aShell->GetPseudoInterrupted())
+ len += GenerateBoundary(aShell, stream, prefetch, false);
+ if (!aShell->GetPseudoInterrupted())
+ len += m_partList->ElementAt(i)->Generate(aShell, stream, prefetch);
+ }
+ if (!aShell->GetPseudoInterrupted())
+ len += GenerateBoundary(aShell, stream, prefetch, true);
+ }
+ else
+ {
+ // fill in the filling within the empty part
+ if (!aShell->GetPseudoInterrupted())
+ len += GenerateEmptyFilling(aShell, 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;
+ }
+ else
+ {
+ // 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->ElementAt(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->ElementAt(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_strdup(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, bool stream, bool prefetch)
+{
+ // prefetch the header
+ if (prefetch && !m_partData && !aShell->DeathSignalReceived())
+ {
+ QueuePrefetchMessageHeaders(aShell);
+ }
+
+ if (stream && !prefetch)
+ aShell->GetConnection()->Log("SHELL","GENERATE-MessageHeaders",m_partNumberString);
+
+ // stream out the part data
+ if (ShouldFetchInline(aShell))
+ {
+ if (!aShell->GetPseudoInterrupted())
+ m_contentLength = GeneratePart(aShell, 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 ////////////////////////////////////
+
+#if 0 // mscott - commenting out because it does not appear to be used
+static int
+imap_shell_cache_strcmp (const void *a, const void *b)
+{
+ return PL_strcmp ((const char *) a, (const char *) b);
+}
+#endif
+
+nsIMAPBodyShellCache::nsIMAPBodyShellCache()
+: m_shellHash(20)
+{
+ m_shellList = new nsTArray<nsIMAPBodyShell*>();
+}
+
+/* static */ nsIMAPBodyShellCache *nsIMAPBodyShellCache::Create()
+{
+ nsIMAPBodyShellCache *cache = new nsIMAPBodyShellCache();
+ if (!cache || !cache->m_shellList)
+ return NULL;
+
+ return cache;
+}
+
+nsIMAPBodyShellCache::~nsIMAPBodyShellCache()
+{
+ while (EjectEntry()) ;
+ delete m_shellList;
+}
+
+// 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.
+bool nsIMAPBodyShellCache::EjectEntry()
+{
+ if (m_shellList->Length() < 1)
+ return false;
+
+ nsIMAPBodyShell *removedShell = m_shellList->ElementAt(0);
+
+ m_shellList->RemoveElementAt(0);
+ m_shellHash.Remove(removedShell->GetUID());
+
+ return true;
+}
+
+void nsIMAPBodyShellCache::Clear()
+{
+ while (EjectEntry()) ;
+}
+
+bool 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(), shell->GetFolderName(), shell->GetContentModified()))
+ return true;
+
+ // 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(), getter_AddRefs(foundShell));
+ if (foundShell)
+ {
+ m_shellHash.Remove(foundShell->GetUID());
+ m_shellList->RemoveElement(foundShell);
+ }
+
+ // Add the new one to the cache
+ m_shellList->AppendElement(shell);
+
+ m_shellHash.Put(shell->GetUID(), shell);
+ shell->SetIsCached(true);
+
+ // while we're not over our size limit, eject entries
+ bool rv = true;
+ while (GetSize() > GetMaxSize())
+ rv = EjectEntry();
+
+ return rv;
+
+}
+
+nsIMAPBodyShell *nsIMAPBodyShellCache::FindShellForUID(nsCString &UID, const char *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 (PL_strcmp(mailboxName, 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)
+{
+}
+
+nsIMAPMessagePartIDArray::nsIMAPMessagePartIDArray()
+{
+}
+
+nsIMAPMessagePartIDArray::~nsIMAPMessagePartIDArray()
+{
+ RemoveAndFreeAll();
+}
+
+void nsIMAPMessagePartIDArray::RemoveAndFreeAll()
+{
+ uint32_t n = Length();
+ for (uint32_t i = 0; i < n; i++)
+ {
+ nsIMAPMessagePartID *part = GetPart(i);
+ delete part;
+ }
+ Clear();
+}
diff --git a/mailnews/imap/src/nsIMAPBodyShell.h b/mailnews/imap/src/nsIMAPBodyShell.h
new file mode 100644
index 000000000..1c8d58ae5
--- /dev/null
+++ b/mailnews/imap/src/nsIMAPBodyShell.h
@@ -0,0 +1,361 @@
+/* -*- 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 "nsStringGlue.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, 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, bool stream, bool prefetch);
+ // the MIME headers of the part
+ virtual int32_t GenerateMIMEHeader(nsIMAPBodyShell *aShell, bool stream, bool prefetch);
+ // Generates the MIME boundary wrapper for this part.
+ virtual int32_t GenerateBoundary(nsIMAPBodyShell *aShell, 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, 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, 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, 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:
+ nsTArray<nsIMAPBodypart*> *m_partList; // An ordered list of top-level body parts for this shell
+};
+
+
+// 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, 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, 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
+
+};
+
+
+class nsIMAPMessagePartIDArray;
+
+// 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(nsImapProtocol *protocolConnection,
+ nsIMAPBodypartMessage *message, uint32_t UID,
+ const char *folderName);
+ // To be used after a shell is uncached
+ void SetConnection(nsImapProtocol *con) { m_protocolConnection = con; }
+ virtual bool GetIsValid() { return m_isValid; }
+ virtual 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);
+ // Runs a single pipelined command which fetches all of the
+ // elements in the prefetch queue
+ void FlushPrefetchQueue();
+ // 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.
+ virtual int32_t Generate(char *partNum);
+
+ // Returns TRUE if the user has the pref "Show Attachments Inline" set.
+ // Returns FALSE if the setting is "Show Attachments as Links"
+ virtual bool GetShowAttachmentsInline();
+ // Returns true if all parts are inline, false otherwise. Does not generate anything.
+ bool PreflightCheckAllInline();
+
+ // Helpers
+ nsImapProtocol *GetConnection() { return m_protocolConnection; }
+ bool GetPseudoInterrupted();
+ bool DeathSignalReceived();
+ nsCString &GetUID() { return m_UID; }
+ const char *GetFolderName() { 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; }
+ void SetContentModified(IMAP_ContentModifiedType modType) { m_contentModified = modType; }
+protected:
+ virtual ~nsIMAPBodyShell();
+
+ nsIMAPBodypartMessage *m_message;
+
+ nsIMAPMessagePartIDArray *m_prefetchQueue; // array of pipelined part prefetches. Ok, so it's not really a queue.
+
+ bool m_isValid;
+ nsImapProtocol *m_protocolConnection; // Connection, for filling in parts
+ nsCString m_UID; // UID of this message
+ char *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_gotAttachmentPref; // Whether or not m_showAttachmentsInline has been initialized
+ bool m_showAttachmentsInline; // Whether or not we should display attachment inline
+ 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.
+// It should pay attention to UIDVALIDITY.
+
+class nsIMAPBodyShellCache
+{
+public:
+ static nsIMAPBodyShellCache *Create();
+ virtual ~nsIMAPBodyShellCache();
+
+ // Adds shell to cache, possibly ejecting
+ // another entry based on scheme in EjectEntry().
+ bool AddShellToCache(nsIMAPBodyShell *shell);
+ // Looks up a shell in the cache given the message's UID.
+ nsIMAPBodyShell *FindShellForUID(nsCString &UID, const char *mailboxName,
+ IMAP_ContentModifiedType modType);
+ void Clear();
+
+protected:
+ nsIMAPBodyShellCache();
+ // Chooses an entry to eject; deletes that entry; and ejects it from the
+ // cache, clearing up a new space. Returns true if it found an entry
+ // to eject, false otherwise.
+ bool EjectEntry();
+ uint32_t GetSize() { return m_shellList->Length(); }
+ uint32_t GetMaxSize() { return 20; }
+ nsTArray<nsIMAPBodyShell*> *m_shellList; // For maintenance
+ // For quick lookup based on UID
+ nsRefPtrHashtable <nsCStringHashKey, nsIMAPBodyShell> m_shellHash;
+};
+
+// MessagePartID and MessagePartIDArray 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;
+};
+
+
+class nsIMAPMessagePartIDArray : public nsTArray<nsIMAPMessagePartID*> {
+public:
+ nsIMAPMessagePartIDArray();
+ ~nsIMAPMessagePartIDArray();
+
+ void RemoveAndFreeAll();
+ uint32_t GetNumParts() { return Length(); }
+ nsIMAPMessagePartID *GetPart(uint32_t i)
+ {
+ NS_ASSERTION(i < Length(), "invalid message part #");
+ return ElementAt(i);
+ }
+};
+
+
+#endif // IMAPBODY_H
diff --git a/mailnews/imap/src/nsIMAPGenericParser.cpp b/mailnews/imap/src/nsIMAPGenericParser.cpp
new file mode 100644
index 000000000..3053ab540
--- /dev/null
+++ b/mailnews/imap/src/nsIMAPGenericParser.cpp
@@ -0,0 +1,484 @@
+/* -*- 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 "nsImapProtocol.h"
+#include "nsIMAPGenericParser.h"
+#include "nsStringGlue.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_PRECONDITION(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
+ else if (*fNextToken == '"')
+ return CreateQuoted(); // quoted
+ else
+ 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;
+ }
+ else
+ 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);
+ }
+ else if (*fNextToken == '"')
+ {
+ char *rv = CreateQuoted(); // quoted
+ return (rv);
+ }
+ else
+ {
+ 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;
+ }
+ else 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/mailnews/imap/src/nsIMAPGenericParser.h b/mailnews/imap/src/nsIMAPGenericParser.h
new file mode 100644
index 000000000..34f66e4ff
--- /dev/null
+++ b/mailnews/imap/src/nsIMAPGenericParser.h
@@ -0,0 +1,76 @@
+/* -*- 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
+
+#include "nsImapCore.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/mailnews/imap/src/nsIMAPHostSessionList.cpp b/mailnews/imap/src/nsIMAPHostSessionList.cpp
new file mode 100644
index 000000000..650faf9d4
--- /dev/null
+++ b/mailnews/imap/src/nsIMAPHostSessionList.cpp
@@ -0,0 +1,701 @@
+/* -*- 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/. */
+
+#include "msgCore.h"
+#include "nsIMAPHostSessionList.h"
+#include "nsIMAPBodyShell.h"
+#include "nsIMAPNamespace.h"
+#include "nsISupportsUtils.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...\n");
+ server->GetServerDirectory(fOnlineDir);
+ fNextHost = NULL;
+ fCachedPassword = 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
+ fCanonicalOnlineSubDir = NULL;
+ fNamespaceList = nsIMAPNamespaceList::CreatensIMAPNamespaceList();
+ fUsingSubscription = true;
+ server->GetUsingSubscription(&fUsingSubscription);
+ fOnlineTrashFolderExists = false;
+ fShouldAlwaysListInbox = true;
+ fShellCache = nsIMAPBodyShellCache::Create();
+ fPasswordVerifiedOnline = false;
+ fDeleteIsMoveToTrash = true;
+ fShowDeletedMessages = false;
+ fGotNamespaces = false;
+ fHaveAdminURL = false;
+ fNamespacesOverridable = true;
+ server->GetOverrideNamespaces(&fNamespacesOverridable);
+ fTempNamespaceList = nsIMAPNamespaceList::CreatensIMAPNamespaceList();
+}
+
+nsIMAPHostInfo::~nsIMAPHostInfo()
+{
+ PR_Free(fCachedPassword);
+ PR_Free(fHierarchyDelimiters);
+ delete fNamespaceList;
+ delete fTempNamespaceList;
+ delete fShellCache;
+}
+
+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)
+ CopyASCIItoUTF16(nsDependentCString(host->fCachedPassword), result);
+ PR_ExitMonitor(gCachedHostInfoMonitor);
+ return (host == NULL) ? NS_ERROR_ILLEGAL_VALUE : NS_OK;
+}
+
+NS_IMETHODIMP nsIMAPHostSessionList::SetPasswordForHost(const char *serverKey, const char *password)
+{
+ PR_EnterMonitor(gCachedHostInfoMonitor);
+ nsIMAPHostInfo *host = FindHost(serverKey);
+ if (host)
+ {
+ PR_FREEIF(host->fCachedPassword);
+ if (password)
+ host->fCachedPassword = NS_strdup(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::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 ;
+}
+
+NS_IMETHODIMP nsIMAPHostSessionList::AddShellToCacheForHost(const char *serverKey, nsIMAPBodyShell *shell)
+{
+ nsresult rv = NS_OK;
+ PR_EnterMonitor(gCachedHostInfoMonitor);
+ nsIMAPHostInfo *host = FindHost(serverKey);
+ if (host)
+ {
+ if (host->fShellCache)
+ {
+ if (!host->fShellCache->AddShellToCache(shell))
+ rv = NS_ERROR_UNEXPECTED;
+ }
+ }
+ else
+ rv = NS_ERROR_ILLEGAL_VALUE;
+
+ PR_ExitMonitor(gCachedHostInfoMonitor);
+ return rv;
+}
+
+NS_IMETHODIMP nsIMAPHostSessionList::FindShellInCacheForHost(const char *serverKey, const char *mailboxName, const char *UID,
+ IMAP_ContentModifiedType modType, nsIMAPBodyShell **shell)
+{
+ nsCString uidString(UID);
+
+ PR_EnterMonitor(gCachedHostInfoMonitor);
+ nsIMAPHostInfo *host = FindHost(serverKey);
+ if (host && host->fShellCache)
+ NS_IF_ADDREF(*shell = host->fShellCache->FindShellForUID(uidString,
+ mailboxName,
+ modType));
+ PR_ExitMonitor(gCachedHostInfoMonitor);
+ return (host == NULL) ? NS_ERROR_ILLEGAL_VALUE : NS_OK;
+}
+
+NS_IMETHODIMP
+nsIMAPHostSessionList::ClearShellCacheForHost(const char *serverKey)
+{
+ PR_EnterMonitor(gCachedHostInfoMonitor);
+ nsIMAPHostInfo *host = FindHost(serverKey);
+ if (host && host->fShellCache)
+ host->fShellCache->Clear();
+ PR_ExitMonitor(gCachedHostInfoMonitor);
+ return (host == NULL) ? NS_ERROR_ILLEGAL_VALUE : NS_OK;
+}
+
diff --git a/mailnews/imap/src/nsIMAPHostSessionList.h b/mailnews/imap/src/nsIMAPHostSessionList.h
new file mode 100644
index 000000000..5f601fe43
--- /dev/null
+++ b/mailnews/imap/src/nsIMAPHostSessionList.h
@@ -0,0 +1,135 @@
+/* -*- 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 "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;
+ char *fCachedPassword;
+ nsCString fOnlineDir;
+ nsIMAPHostInfo *fNextHost;
+ eIMAPCapabilityFlags fCapabilityFlags;
+ char *fHierarchyDelimiters;// string of top-level hierarchy delimiters
+ bool fHaveWeEverDiscoveredFolders;
+ char *fCanonicalOnlineSubDir;
+ nsIMAPNamespaceList *fNamespaceList, *fTempNamespaceList;
+ bool fNamespacesOverridable;
+ bool fUsingSubscription;
+ bool fOnlineTrashFolderExists;
+ bool fShouldAlwaysListInbox;
+ bool fHaveAdminURL;
+ bool fPasswordVerifiedOnline;
+ bool fDeleteIsMoveToTrash;
+ bool fShowDeletedMessages;
+ bool fGotNamespaces;
+ nsIMAPBodyShellCache *fShellCache;
+};
+
+// 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 char *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;
+
+ // 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;
+
+ // Message Body Shells
+ NS_IMETHOD AddShellToCacheForHost(const char *serverKey, nsIMAPBodyShell *shell) override;
+ NS_IMETHOD FindShellInCacheForHost(const char *serverKey, const char *mailboxName, const char *UID, IMAP_ContentModifiedType modType, nsIMAPBodyShell **result) override;
+ NS_IMETHOD ClearShellCacheForHost(const char *serverKey) 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/mailnews/imap/src/nsIMAPNamespace.cpp b/mailnews/imap/src/nsIMAPNamespace.cpp
new file mode 100644
index 000000000..2cba54016
--- /dev/null
+++ b/mailnews/imap/src/nsIMAPNamespace.cpp
@@ -0,0 +1,651 @@
+/* -*- 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 "msgCore.h" // for pre-compiled headers
+
+#include "nsImapCore.h"
+#include "nsIMAPNamespace.h"
+#include "nsImapProtocol.h"
+#include "nsMsgImapCID.h"
+#include "nsImapUrl.h"
+#include "nsStringGlue.h"
+#include "nsServiceManagerUtils.h"
+#include <algorithm>
+
+//////////////////// nsIMAPNamespace /////////////////////////////////////////////////////////////
+
+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.AppendLiteral(",");
+
+ serializedNamespaces.AppendLiteral("\"");
+ serializedNamespaces.Append(prefixes[i]);
+ serializedNamespaces.AppendLiteral("\"");
+ }
+ 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;
+ else
+ {
+ 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;
+ }
+ }
+ else
+ {
+ if ((str[0] != '"') && (len >= 1))
+ {
+ prefixes[0] = PL_strdup(str);
+ return 1;
+ }
+ else
+ {
+ 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;
+ }
+ }
+}
+
+
+
+
+char *nsIMAPNamespaceList::AllocateCanonicalFolderName(const char *onlineFolderName, char delimiter)
+{
+ char *canonicalPath = nullptr;
+ if (delimiter)
+ canonicalPath = nsImapUrl::ReplaceCharsInCopiedString(onlineFolderName, delimiter , '/');
+ else
+ canonicalPath = PL_strdup(onlineFolderName);
+
+ // eat any escape characters for escaped dir separators
+ if (canonicalPath)
+ {
+ char *currentEscapeSequence = strstr(canonicalPath, "\\/");
+ while (currentEscapeSequence)
+ {
+ strcpy(currentEscapeSequence, currentEscapeSequence+1);
+ currentEscapeSequence = strstr(currentEscapeSequence+1, "\\/");
+ }
+ }
+
+ return canonicalPath;
+}
+
+
+
+/*
+ GetFolderNameWithoutNamespace takes as input a folder name
+ in canonical form, and the namespace for the given folder. It returns an allocated
+ string of the folder's path with the namespace string stripped out. For instance,
+ when passed the folder Folders/a/b where the namespace is "Folders/", it will return
+ "a/b". Similarly, if the folder name is "#news/comp/mail/imap" in canonical form,
+ with a real delimiter of "." and a namespace of "#news.", it will return "comp/mail/imap".
+ The return value is always in canonical form.
+*/
+char* nsIMAPNamespaceList::GetFolderNameWithoutNamespace(nsIMAPNamespace *namespaceForFolder, const char *canonicalFolderName)
+{
+ NS_ASSERTION(canonicalFolderName, "null folder name");
+#ifdef DEBUG
+ NS_ASSERTION(namespaceForFolder || !PL_strcasecmp(canonicalFolderName, "INBOX"), "need namespace or INBOX");
+#endif
+
+ char *retFolderName = nullptr;
+
+ if (!PL_strcasecmp(canonicalFolderName, "INBOX"))
+ return PL_strdup(canonicalFolderName);
+
+ // convert the canonical path to the online path
+ char *convertedFolderName = nsIMAPNamespaceList::AllocateServerFolderName(canonicalFolderName, namespaceForFolder->GetDelimiter());
+ if (convertedFolderName)
+ {
+ char *beginFolderPath = nullptr;
+ if (strlen(convertedFolderName) <= strlen(namespaceForFolder->GetPrefix()))
+ beginFolderPath = convertedFolderName;
+ else
+ beginFolderPath = convertedFolderName + strlen(namespaceForFolder->GetPrefix());
+ NS_ASSERTION(beginFolderPath, "empty folder path");
+ retFolderName = nsIMAPNamespaceList::AllocateCanonicalFolderName(beginFolderPath, namespaceForFolder->GetDelimiter());
+ PR_Free(convertedFolderName);
+ }
+
+ NS_ASSERTION(retFolderName, "returning null folder name");
+ return retFolderName;
+}
+
+
+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);
+ else
+ return NS_strdup(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 an allocated copy of that owner's name
+*/
+/* static */
+char *nsIMAPNamespaceList::GetFolderOwnerNameFromPath(nsIMAPNamespace *namespaceForFolder, const char *canonicalFolderName)
+{
+ if (!namespaceForFolder || !canonicalFolderName)
+ {
+ NS_ASSERTION(false,"null namespace or canonical folder name");
+ return nullptr;
+ }
+
+ char *rv = nullptr;
+
+ // convert the canonical path to the online path
+ char *convertedFolderName = AllocateServerFolderName(canonicalFolderName, namespaceForFolder->GetDelimiter());
+ if (convertedFolderName)
+ {
+#ifdef DEBUG
+ NS_ASSERTION(strlen(convertedFolderName) > strlen(namespaceForFolder->GetPrefix()), "server folder name invalid");
+#endif
+ if (strlen(convertedFolderName) > strlen(namespaceForFolder->GetPrefix()))
+ {
+ char *owner = convertedFolderName + strlen(namespaceForFolder->GetPrefix());
+ NS_ASSERTION(owner, "couldn't find folder owner");
+ char *nextDelimiter = strchr(owner, namespaceForFolder->GetDelimiter());
+ // if !nextDelimiter, then the path is of the form Shared/Users/chrisf (no subfolder)
+ if (nextDelimiter)
+ {
+ *nextDelimiter = 0;
+ }
+ rv = PL_strdup(owner);
+ }
+ PR_Free(convertedFolderName);
+ }
+ else
+ {
+ NS_ASSERTION(false, "couldn't allocate server folder name");
+ }
+
+ return rv;
+}
+
+/*
+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 it to online form, allocates a string to contain 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 (allocated) to libmsg.
+ It returns NULL 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.
+*/
+char *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, nullptr);
+ nsIMAPNamespace *ns;
+ char *fullFolderName = nullptr;
+ rv = hostSession->GetDefaultNamespaceOfTypeForHost(hostName, nsType, ns);
+ NS_ENSURE_SUCCESS(rv, nullptr);
+ 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/mailnews/imap/src/nsIMAPNamespace.h b/mailnews/imap/src/nsIMAPNamespace.h
new file mode 100644
index 000000000..327f78416
--- /dev/null
+++ b/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 "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 char* GetFolderNameWithoutNamespace(nsIMAPNamespace *namespaceForFolder, const char *canonicalFolderName);
+ static char *AllocateServerFolderName(const char *canonicalFolderName, char delimiter);
+ static char *GetFolderOwnerNameFromPath(nsIMAPNamespace *namespaceForFolder, const char *canonicalFolderName);
+ static char *AllocateCanonicalFolderName(const char *onlineFolderName, char delimiter);
+ static void SuggestHierarchySeparatorForNamespace(nsIMAPNamespace *namespaceForFolder, char delimiterFromFolder);
+ static char *GenerateFullFolderNameWithDefaultNamespace(const char *hostName,
+ const char *canonicalFolderName,
+ const char *owner,
+ EIMAPNamespaceType nsType,
+ nsIMAPNamespace **nsUsed);
+
+protected:
+ nsIMAPNamespaceList(); // use CreatensIMAPNamespaceList to create one
+
+ nsTArray<nsIMAPNamespace*> m_NamespaceList;
+
+};
+
+
+#endif
diff --git a/mailnews/imap/src/nsImapCore.h b/mailnews/imap/src/nsImapCore.h
new file mode 100644
index 000000000..0eac33663
--- /dev/null
+++ b/mailnews/imap/src/nsImapCore.h
@@ -0,0 +1,190 @@
+/* -*- 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 _nsImapCore_H_
+#define _nsImapCore_H_
+
+#include "MailNewsTypes.h"
+#include "nsStringGlue.h"
+#include "nsIMailboxSpec.h"
+#include "nsIImapFlagAndUidState.h"
+
+class nsIMAPNamespace;
+class nsImapProtocol;
+class nsImapFlagAndUidState;
+
+/* 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 kImapMsgLabelFlags 0x0E00 /* supports 5 labels only supported if the folder supports keywords */
+#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 */
+#ifdef MOZ_MAILNEWS_OAUTH2
+const eIMAPCapabilityFlag kHasXOAuth2Capability = 0x800000000LL; /* AUTH XOAUTH2 extension */
+#endif
+
+
+// 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;
+
+#endif
diff --git a/mailnews/imap/src/nsImapFlagAndUidState.cpp b/mailnews/imap/src/nsImapFlagAndUidState.cpp
new file mode 100644
index 000000000..deae831db
--- /dev/null
+++ b/mailnews/imap/src/nsImapFlagAndUidState.cpp
@@ -0,0 +1,321 @@
+/* -*- 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 "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::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;
+}
+
+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;
+ 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);
+ }
+
+ 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::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 = MsgFind(oldValue, customFlagString, false, existingCustomFlagPos + customFlagLen);
+ }
+ ourCustomFlags.Assign(oldValue);
+ ourCustomFlags.AppendLiteral(" ");
+ ourCustomFlags.Append(customFlag);
+ m_customFlagsHash.Remove(uid);
+ }
+ else
+ {
+ ourCustomFlags.Assign(customFlag);
+ }
+ m_customFlagsHash.Put(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_strdup(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.Put(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, &val);
+ aCustomAttributeValue.Assign(val);
+ return NS_OK;
+}
diff --git a/mailnews/imap/src/nsImapFlagAndUidState.h b/mailnews/imap/src/nsImapFlagAndUidState.h
new file mode 100644
index 000000000..259bc016f
--- /dev/null
+++ b/mailnews/imap/src/nsImapFlagAndUidState.h
@@ -0,0 +1,55 @@
+/* -*- 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 "nsTArray.h"
+#include "nsIImapFlagAndUidState.h"
+#include "mozilla/Mutex.h"
+
+const int32_t kImapFlagAndUidStateSize = 100;
+
+#include "nsBaseHashtable.h"
+#include "nsDataHashtable.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; }
+
+private:
+ virtual ~nsImapFlagAndUidState();
+
+ nsTArray<nsMsgKey> fUids;
+ nsTArray<imapMessageFlagsType> fFlags;
+ // Hash table, mapping uids to extra flags
+ nsDataHashtable<nsUint32HashKey, nsCString> m_customFlagsHash;
+ // Hash table, mapping UID+customAttributeName to customAttributeValue.
+ nsDataHashtable<nsCStringHashKey, nsCString> m_customAttributesHash;
+ uint16_t fSupportedUserFlags;
+ int32_t fNumberDeleted;
+ bool fPartialUIDFetch;
+ mozilla::Mutex mLock;
+};
+
+
+
+
+#endif
diff --git a/mailnews/imap/src/nsImapIncomingServer.cpp b/mailnews/imap/src/nsImapIncomingServer.cpp
new file mode 100644
index 000000000..77a34985b
--- /dev/null
+++ b/mailnews/imap/src/nsImapIncomingServer.cpp
@@ -0,0 +1,3382 @@
+/* -*- 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 "nsMsgImapCID.h"
+
+#include "netCore.h"
+#include "nsIMAPHostSessionList.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 "nsImapUtils.h"
+#include "nsIRDFService.h"
+#include "nsRDFCID.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 "nsITimer.h"
+#include "nsMsgUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "nsCRTGlue.h"
+#include "mozilla/Services.h"
+
+using namespace mozilla;
+
+#define PREF_TRASH_FOLDER_NAME "trash_folder_name"
+#define DEFAULT_TRASH_FOLDER_NAME "Trash"
+
+static NS_DEFINE_CID(kImapProtocolCID, NS_IMAPPROTOCOL_CID);
+static NS_DEFINE_CID(kRDFServiceCID, NS_RDFSERVICE_CID);
+static NS_DEFINE_CID(kSubscribableServerCID, NS_SUBSCRIBABLESERVER_CID);
+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;
+}
+
+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(NS_MSGACCOUNTMANAGER_CONTRACTID, &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 = GetRealUsername(username);
+ NS_ENSURE_SUCCESS(rv,rv);
+ rv = GetRealHostName(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_STR(nsImapIncomingServer, ForceSelect,
+ "force_select")
+
+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_INT(nsImapIncomingServer, EmptyTrashThreshhold,
+ "empty_trash_threshhold")
+
+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_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);
+
+ nsAutoString trashFolderName;
+ nsresult rv = GetTrashFolderName(trashFolderName);
+ if (NS_SUCCEEDED(rv))
+ {
+ nsAutoCString trashFolderNameUtf7;
+ rv = CopyUTF16toMUTF7(trashFolderName, trashFolderNameUtf7);
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr<nsIMsgFolder> trashFolder;
+ // XXX GetFolder only returns folders one level below root.
+ // trashFolderName is a leaf name. So this will not find INBOX.Trash
+ rv = GetFolder(trashFolderNameUtf7, getter_AddRefs(trashFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCString trashURI;
+ trashFolder->GetURI(trashURI);
+ 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, MimePartsOnDemand,
+ "mime_parts_on_demand")
+
+NS_IMPL_SERVERPREF_BOOL(nsImapIncomingServer, SendID,
+ "send_client_info")
+
+NS_IMPL_SERVERPREF_BOOL(nsImapIncomingServer, CapabilityACL,
+ "cacheCapa.acl")
+NS_IMPL_SERVERPREF_BOOL(nsImapIncomingServer, CapabilityQuota,
+ "cacheCapa.quota")
+
+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 unforseen problems let's give it a second chance
+ // to run the url
+ if (NS_FAILED(rv) && rv != NS_ERROR_ILLEGAL_VALUE)
+ {
+ NS_ASSERTION(false, "shouldn't get an error loading url");
+ 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.
+ NS_ASSERTION(NS_SUCCEEDED(rv), "failed running queued url");
+ }
+ }
+ 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]);
+ nsCOMPtr<nsIMsgMailNewsUrl> aMailNewsUrl(do_QueryInterface(aImapUrl, &rv));
+
+ 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);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "failed running queued url");
+ bool isInbox;
+ protocolInstance->IsBusy(&urlRun, &isInbox);
+ if (!urlRun)
+ nsImapProtocol::LogImapUrl("didn't need to run", aImapUrl);
+ removeUrlFromQueue = true;
+ }
+ }
+ 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;
+ nsCOMPtr<nsIRequest> request = do_QueryInterface(mockChannel);
+ if (!request)
+ return NS_ERROR_FAILURE;
+ request->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)
+ {
+ nsCOMPtr<nsIImapProtocol> aProtocol(do_QueryInterface(aConnection,
+ &rv));
+ if (NS_SUCCEEDED(rv) && aProtocol)
+ {
+ RemoveConnection(aConnection);
+ aProtocol->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.
+ bool userCancelled = false;
+
+ // 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)
+ {
+ *aImapConnection = connection;
+ NS_IF_ADDREF(*aImapConnection);
+ }
+ else if (canRunButBusy)
+ {
+ // do nothing; return NS_OK; for queuing
+ }
+ else if (userCancelled)
+ {
+ rv = NS_BINDING_ABORTED; // user cancelled
+ }
+ // 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)
+ {
+ *aImapConnection = freeConnection;
+ NS_IF_ADDREF(*aImapConnection);
+ }
+ else // cannot get anyone to handle the url queue it
+ {
+ 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;
+ }
+ nsIImapProtocol * protocolInstance;
+ rv = CallCreateInstance(kImapProtocolCID, &protocolInstance);
+ if (NS_SUCCEEDED(rv) && protocolInstance)
+ {
+ nsCOMPtr<nsIImapHostSessionList> hostSession =
+ do_GetService(kCImapHostSessionListCID, &rv);
+ if (NS_SUCCEEDED(rv))
+ rv = protocolInstance->Initialize(hostSession, this);
+ }
+
+ // take the protocol instance and add it to the connectionCache
+ if (protocolInstance)
+ m_connectionCache.AppendObject(protocolInstance);
+ *aImapConnection = protocolInstance; // this is already ref counted.
+ 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)
+{
+ nsCString password;
+ nsresult rv;
+ rv = GetPassword(password);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (password.IsEmpty())
+ 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(NS_IMAPSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIThread> thread(do_GetCurrentThread());
+ rv = imapService->DiscoverAllFolders(rootMsgFolder,
+ this, aMsgWindow, nullptr);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::VerifyLogon(nsIUrlListener *aUrlListener,
+ nsIMsgWindow *aMsgWindow, nsIURI **aURL)
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIImapService> imapService = do_GetService(NS_IMAPSERVICE_CONTRACTID, &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 nsCString &serverUri,
+ nsIMsgFolder **rootFolder)
+{
+ nsImapMailFolder *newRootFolder = new nsImapMailFolder;
+ if (!newRootFolder)
+ return NS_ERROR_OUT_OF_MEMORY;
+ newRootFolder->Init(serverUri.get());
+ 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> a_nsIFolder;
+ rv = GetRootFolder(getter_AddRefs(a_nsIFolder));
+
+ 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 (a_nsIFolder && !dupFolderPath.IsEmpty())
+ {
+ nsCOMPtr<nsIMsgFolder> msgFolder;
+ bool isNamespace = false;
+ bool noSelect = false;
+
+ rv = a_nsIFolder->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(a_nsIFolder, &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.Append("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 (MsgLowerCaseEqualsLiteral(folderPath, "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 = MsgLowerCaseEqualsLiteral(dupFolderPath, "inbox");
+ a_nsIFolder->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 = MsgLowerCaseEqualsLiteral(parentName, "inbox");
+ a_nsIFolder->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 = MsgLowerCaseEqualsLiteral(dupFolderPath, "inbox");
+ a_nsIFolder->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.
+ MsgReplaceChar(dupFolderPath, '/', hierarchyDelimiter);
+ if (hierarchyDelimiter != '/')
+ nsImapUrl::UnescapeSlashes(dupFolderPath.BeginWriting());
+
+ // GMail gives us a localized name for the inbox but doesn't let
+ // us select that localized name.
+ if (boxFlags & kImapInbox)
+ imapFolder->SetOnlineName(NS_LITERAL_CSTRING("INBOX"));
+ else if (onlineName.IsEmpty() || !onlineName.Equals(dupFolderPath))
+ imapFolder->SetOnlineName(dupFolderPath);
+
+ if (hierarchyDelimiter != '/')
+ nsImapUrl::UnescapeSlashes(folderName.BeginWriting());
+ if (NS_SUCCEEDED(CopyMUTF7toUTF16(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);
+ nsCOMPtr<nsIRDFService> rdf(do_GetService(kRDFServiceCID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIRDFResource> res;
+ rv = rdf->GetResource(uriString, getter_AddRefs(res));
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr<nsIMsgFolder> folder(do_QueryInterface(res, &rv));
+ if (NS_SUCCEEDED(rv) && folder)
+ folder.swap(*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 imap mod utf7. It needs to be convert to utf8.
+ CopyMUTF7toUTF16(tmpNewName, unicodeNewName);
+ CopyUTF16toUTF8(unicodeNewName, tmpNewName);
+ rv = GetFolder(tmpNewName, getter_AddRefs(newFolder));
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr <nsIAtom> folderRenameAtom;
+ folderRenameAtom = MsgGetAtom("RenameCompleted");
+ newFolder->NotifyFolderEvent(folderRenameAtom);
+ }
+ }
+ }
+ }
+ 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 resouce 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<nsIRDFService> rdf(do_GetService("@mozilla.org/rdf/rdf-service;1",
+ &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgAccountManager> accountMgr = do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &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(rdf, folderUri, nsMsgFolderFlags::SentMail,
+ existingUri))
+ {
+ identity->SetFccFolder(existingUri);
+ identity->SetFccFolderPickerMode(NS_LITERAL_CSTRING("1"));
+ }
+ identity->GetDraftFolder(folderUri);
+ if (CheckSpecialFolder(rdf, folderUri, nsMsgFolderFlags::Drafts,
+ existingUri))
+ {
+ identity->SetDraftFolder(existingUri);
+ identity->SetDraftsFolderPickerMode(NS_LITERAL_CSTRING("1"));
+ }
+ bool archiveEnabled;
+ identity->GetArchiveEnabled(&archiveEnabled);
+ if (archiveEnabled)
+ {
+ identity->GetArchiveFolder(folderUri);
+ if (CheckSpecialFolder(rdf, folderUri, nsMsgFolderFlags::Archive,
+ existingUri))
+ {
+ identity->SetArchiveFolder(existingUri);
+ identity->SetArchivesFolderPickerMode(NS_LITERAL_CSTRING("1"));
+ }
+ }
+ identity->GetStationeryFolder(folderUri);
+ nsCOMPtr<nsIRDFResource> res;
+ if (!folderUri.IsEmpty() && NS_SUCCEEDED(rdf->GetResource(folderUri, getter_AddRefs(res))))
+ {
+ nsCOMPtr<nsIMsgFolder> folder(do_QueryInterface(res, &rv));
+ 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(getter_Copies(spamFolderUri));
+ if (CheckSpecialFolder(rdf, spamFolderUri, nsMsgFolderFlags::Junk,
+ existingUri))
+ {
+ // This only sets the cached values in the spam settings object.
+ spamSettings->SetActionTargetFolder(existingUri.get());
+ spamSettings->SetMoveTargetMode(nsISpamSettings::MOVE_TARGET_MODE_FOLDER);
+ // Set the preferences too so that the values persist.
+ SetCharValue("spamActionTargetFolder", 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.
+ nsCOMPtr<nsIArray> trashFolders;
+ rv = rootMsgFolder->GetFoldersWithFlags(nsMsgFolderFlags::Trash,
+ getter_AddRefs(trashFolders));
+
+ if (NS_SUCCEEDED(rv) && trashFolders)
+ {
+ uint32_t numFolders;
+ trashFolders->GetLength(&numFolders);
+ nsAutoString trashName;
+ if (NS_SUCCEEDED(GetTrashFolderName(trashName)))
+ {
+ for (uint32_t i = 0; i < numFolders; i++)
+ {
+ nsCOMPtr<nsIMsgFolder> trashFolder(do_QueryElementAt(trashFolders, i));
+ if (trashFolder)
+ {
+ // 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.
+ if (isGMailServer)
+ {
+ nsCOMPtr<nsIMsgImapMailFolder> imapFolder(do_QueryInterface(trashFolder));
+ int32_t boxFlags;
+ imapFolder->GetBoxFlags(&boxFlags);
+ if (boxFlags & kImapXListTrash)
+ {
+ continue;
+ }
+ }
+ else
+ {
+ // trashName is the leaf name on the folder URI, which will be
+ // different from the folder GetName if the trash name is
+ // localized.
+ nsAutoCString trashURL;
+ trashFolder->GetFolderURL(trashURL);
+ int32_t leafPos = trashURL.RFindChar('/');
+ nsAutoCString unescapedName;
+ MsgUnescapeString(Substring(trashURL, leafPos + 1),
+ nsINetUtil::ESCAPE_URL_PATH, unescapedName);
+ nsAutoString nameUnicode;
+ if (NS_FAILED(CopyMUTF7toUTF16(unescapedName, nameUnicode)) ||
+ trashName.Equals(nameUnicode))
+ {
+ continue;
+ }
+ if (numFolders == 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_NAME, nameUnicode);
+ continue;
+ }
+ }
+ 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
+ DeleteNonVerifiedFolders(currentFolder);
+ }
+
+ 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(nsIRDFService *rdf,
+ nsCString &folderUri,
+ uint32_t folderFlag,
+ nsCString &existingUri)
+{
+ nsCOMPtr<nsIRDFResource> res;
+ 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(rdf->GetResource(folderUri, getter_AddRefs(res))))
+ {
+ folder = do_QueryInterface(res, &rv);
+ if (NS_SUCCEEDED(rv))
+ {
+ 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;
+}
+
+nsresult nsImapIncomingServer::DeleteNonVerifiedFolders(nsIMsgFolder *curFolder)
+{
+ bool autoUnsubscribeFromNoSelectFolders = true;
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv))
+ prefBranch->GetBoolPref("mail.imap.auto_unsubscribe_from_noselect_folders", &autoUnsubscribeFromNoSelectFolders);
+
+ nsCOMPtr<nsISimpleEnumerator> subFolders;
+
+ rv = curFolder->GetSubFolders(getter_AddRefs(subFolders));
+ if(NS_SUCCEEDED(rv))
+ {
+ bool moreFolders;
+
+ while (NS_SUCCEEDED(subFolders->HasMoreElements(&moreFolders)) && moreFolders)
+ {
+ nsCOMPtr<nsISupports> child;
+ rv = subFolders->GetNext(getter_AddRefs(child));
+ if (NS_SUCCEEDED(rv) && child)
+ {
+ bool childVerified = false;
+ nsCOMPtr <nsIMsgImapMailFolder> childImapFolder = do_QueryInterface(child, &rv);
+ if (NS_SUCCEEDED(rv) && childImapFolder)
+ {
+ uint32_t flags;
+
+ nsCOMPtr <nsIMsgFolder> childFolder = do_QueryInterface(child, &rv);
+ rv = childImapFolder->GetVerifiedAsOnlineFolder(&childVerified);
+
+ rv = childFolder->GetFlags(&flags);
+ bool folderIsNoSelectFolder = NS_SUCCEEDED(rv) && ((flags & nsMsgFolderFlags::ImapNoselect) != 0);
+
+ bool usingSubscription = true;
+ GetUsingSubscription(&usingSubscription);
+ if (usingSubscription)
+ {
+ bool folderIsNameSpace = false;
+ bool noDescendentsAreVerified = NoDescendentsAreVerified(childFolder);
+ bool shouldDieBecauseNoSelect = (folderIsNoSelectFolder ?
+ ((noDescendentsAreVerified || AllDescendentsAreNoSelect(childFolder)) && !folderIsNameSpace)
+ : false);
+ if (!childVerified && (noDescendentsAreVerified || shouldDieBecauseNoSelect))
+ {
+ }
+ }
+ else
+ {
+ }
+ }
+ }
+ }
+ }
+
+ nsCOMPtr<nsIMsgFolder> parent;
+ rv = curFolder->GetParent(getter_AddRefs(parent));
+
+ if (NS_SUCCEEDED(rv) && parent)
+ {
+ nsCOMPtr<nsIMsgImapMailFolder> imapParent = do_QueryInterface(parent);
+ if (imapParent)
+ imapParent->RemoveSubFolder(curFolder);
+ }
+
+ return rv;
+}
+
+bool nsImapIncomingServer::NoDescendentsAreVerified(nsIMsgFolder *parentFolder)
+{
+ bool nobodyIsVerified = true;
+ nsCOMPtr<nsISimpleEnumerator> subFolders;
+ nsresult rv = parentFolder->GetSubFolders(getter_AddRefs(subFolders));
+ if(NS_SUCCEEDED(rv))
+ {
+ bool moreFolders;
+ while (NS_SUCCEEDED(subFolders->HasMoreElements(&moreFolders)) &&
+ moreFolders && nobodyIsVerified)
+ {
+ nsCOMPtr<nsISupports> child;
+ rv = subFolders->GetNext(getter_AddRefs(child));
+ if (NS_SUCCEEDED(rv) && child)
+ {
+ bool childVerified = false;
+ nsCOMPtr <nsIMsgImapMailFolder> childImapFolder = do_QueryInterface(child, &rv);
+ if (NS_SUCCEEDED(rv) && childImapFolder)
+ {
+ nsCOMPtr <nsIMsgFolder> childFolder = do_QueryInterface(child, &rv);
+ rv = childImapFolder->GetVerifiedAsOnlineFolder(&childVerified);
+ nobodyIsVerified = !childVerified && NoDescendentsAreVerified(childFolder);
+ }
+ }
+ }
+ }
+ return nobodyIsVerified;
+}
+
+
+bool nsImapIncomingServer::AllDescendentsAreNoSelect(nsIMsgFolder *parentFolder)
+{
+ bool allDescendentsAreNoSelect = true;
+ nsCOMPtr<nsISimpleEnumerator> subFolders;
+ nsresult rv = parentFolder->GetSubFolders(getter_AddRefs(subFolders));
+ if(NS_SUCCEEDED(rv))
+ {
+ bool moreFolders;
+ while (NS_SUCCEEDED(subFolders->HasMoreElements(&moreFolders)) &&
+ moreFolders && allDescendentsAreNoSelect)
+ {
+ nsCOMPtr<nsISupports> child;
+ rv = subFolders->GetNext(getter_AddRefs(child));
+ if (NS_SUCCEEDED(rv) && child)
+ {
+ bool childIsNoSelect = false;
+ nsCOMPtr <nsIMsgImapMailFolder> childImapFolder = do_QueryInterface(child, &rv);
+ if (NS_SUCCEEDED(rv) && childImapFolder)
+ {
+ uint32_t flags;
+ nsCOMPtr <nsIMsgFolder> childFolder = do_QueryInterface(child, &rv);
+ rv = childFolder->GetFlags(&flags);
+ childIsNoSelect = NS_SUCCEEDED(rv) && (flags & nsMsgFolderFlags::ImapNoselect);
+ allDescendentsAreNoSelect = !childIsNoSelect && AllDescendentsAreNoSelect(childFolder);
+ }
+ }
+ }
+ }
+#if 0
+ int numberOfSubfolders = parentFolder->GetNumSubFolders();
+
+ for (int childIndex=0; allDescendantsAreNoSelect && (childIndex < numberOfSubfolders); childIndex++)
+ {
+ MSG_IMAPFolderInfoMail *currentChild = (MSG_IMAPFolderInfoMail *) parentFolder->GetSubFolder(childIndex);
+ allDescendentsAreNoSelect = (currentChild->GetFolderPrefFlags() & MSG_FOLDER_PREF_IMAPNOSELECT) &&
+ AllDescendentsAreNoSelect(currentChild);
+ }
+#endif // 0
+ return allDescendentsAreNoSelect;
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::PromptLoginFailed(nsIMsgWindow *aMsgWindow,
+ int32_t *aResult)
+{
+ nsAutoCString hostName;
+ GetRealHostName(hostName);
+
+ return MsgPromptLoginFailed(aMsgWindow, hostName, 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);
+ const char16_t *params[] = { hostName.get(), tempString.get() };
+
+ rv = m_stringBundle->FormatStringFromName(
+ u"imapServerAlert",
+ params, 2, getter_Copies(message));
+ if (NS_SUCCEEDED(rv))
+ return AlertUser(message, aUrl);
+ }
+ }
+ return AlertUser(aAlertString, aUrl);
+}
+
+nsresult nsImapIncomingServer::AlertUser(const nsAString& aString,
+ nsIMsgMailNewsUrl *aUrl)
+{
+ nsresult rv;
+ nsCOMPtr <nsIMsgMailSession> mailSession =
+ do_GetService(NS_MSGMAILSESSION_CONTRACTID, &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))
+ {
+ const NS_ConvertUTF8toUTF16 hostName16(hostName);
+ const char16_t *params[] = { hostName16.get() };
+ rv = m_stringBundle->FormatStringFromName(
+ NS_ConvertASCIItoUTF16(aMsgName).get(),
+ params, 1,getter_Copies(message));
+ if (NS_SUCCEEDED(rv))
+ 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");
+ 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);
+
+ const char16_t *formatStrings[] =
+ {
+ hostName.get(),
+ nullptr,
+ nullptr
+ };
+
+ nsString msgName;
+ int32_t numStrings;
+ 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);
+
+ nsCOMPtr<nsIMsgFolder> folder;
+ if (imapState == nsIImapUrl::nsImapSelectedState ||
+ imapAction == nsIImapUrl::nsImapFolderStatus)
+ {
+ aUrl->GetFolder(getter_AddRefs(folder));
+ if (folder)
+ folder->GetPrettyName(folderName);
+ numStrings = 3;
+ msgName.AssignLiteral("imapFolderCommandFailed");
+ formatStrings[1] = folderName.get();
+ }
+ else
+ {
+ msgName.AssignLiteral("imapServerCommandFailed");
+ numStrings = 2;
+ }
+
+ formatStrings[numStrings -1] = unicodeMsg.get();
+
+ nsresult rv = GetStringBundle();
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (m_stringBundle)
+ {
+ rv = m_stringBundle->FormatStringFromName(msgName.get(),
+ formatStrings, numStrings, getter_Copies(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::services::GetStringBundleService();
+ 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(
+ NS_ConvertASCIItoUTF16(msgName).get(),
+ getter_Copies(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);
+ }
+ else
+ {
+ nsCOMPtr<nsISimpleEnumerator> subFolders;
+ nsCOMPtr<nsIMsgImapMailFolder> imapFolder = do_QueryInterface(parentFolder, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = imapFolder->SetVerifiedAsOnlineFolder(false);
+ rv = parentFolder->GetSubFolders(getter_AddRefs(subFolders));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool moreFolders = false;
+ while (NS_SUCCEEDED(subFolders->HasMoreElements(&moreFolders)) && moreFolders)
+ {
+ nsCOMPtr<nsISupports> child;
+ rv = subFolders->GetNext(getter_AddRefs(child));
+ if (NS_SUCCEEDED(rv) && child)
+ {
+ nsCOMPtr<nsIMsgFolder> childFolder = do_QueryInterface(child, &rv);
+ if (NS_SUCCEEDED(rv) && childFolder)
+ {
+ rv = ResetFoldersToUnverified(childFolder);
+ if (NS_FAILED(rv))
+ break;
+ }
+ }
+ }
+ }
+ 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);
+ }
+
+ nsCOMPtr<nsISimpleEnumerator> subFolders;
+ if (NS_SUCCEEDED(parentFolder->GetSubFolders(getter_AddRefs(subFolders))))
+ {
+ bool moreFolders;
+
+ while (NS_SUCCEEDED(subFolders->HasMoreElements(&moreFolders)) && moreFolders)
+ {
+ nsCOMPtr<nsISupports> child;
+ subFolders->GetNext(getter_AddRefs(child));
+ if (child)
+ {
+ nsCOMPtr<nsIMsgFolder> childFolder(do_QueryInterface(child));
+ if (childFolder)
+ GetUnverifiedSubFolders(childFolder, aFoldersArray);
+ }
+ }
+ }
+}
+
+NS_IMETHODIMP nsImapIncomingServer::ForgetSessionPassword()
+{
+ nsresult rv = nsMsgIncomingServer::ForgetSessionPassword();
+ 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,
+ nsACString &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(NS_MSGASYNCPROMPTER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIMsgAsyncPromptListener> promptListener(do_QueryInterface(aProtocol));
+ rv = asyncPrompter->QueueAsyncAuthPrompt(m_serverKey, aNewPasswordRequested,
+ promptListener);
+ // Explict 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;
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::PromptPassword(nsIMsgWindow *aMsgWindow,
+ nsACString &aPassword)
+{
+ nsString passwordTitle;
+ GetImapStringByName("imapEnterPasswordPromptTitle", passwordTitle);
+ NS_ENSURE_STATE(m_stringBundle);
+
+ nsAutoCString userName;
+ GetRealUsername(userName);
+
+ nsAutoCString hostName;
+ GetRealHostName(hostName);
+
+ nsresult rv = GetStringBundle();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ConvertASCIItoUTF16 finalUserName(userName);
+ NS_ConvertASCIItoUTF16 finalHostName(hostName);
+
+ const char16_t *formatStrings[] = { finalUserName.get(), finalHostName.get() };
+
+ nsString passwordText;
+ rv = m_stringBundle->FormatStringFromName(
+ u"imapEnterServerPasswordPrompt",
+ formatStrings, 2, getter_Copies(passwordText));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = GetPasswordWithUI(passwordText, passwordTitle, aMsgWindow, 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);
+ SetCapabilityACL(capability & kACLCapability);
+ SetCapabilityQuota(capability & kQuotaCapability);
+ 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(NS_MSGACCOUNTMANAGER_CONTRACTID, &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 char *uri)
+{
+ NS_ENSURE_ARG_POINTER (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(NS_IMAPSERVICE_CONTRACTID, &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 /)
+*/
+ const char *path = uri + serverUri.Length() + 1;
+ return imapService->GetListOfFoldersWithPath(this, aMsgWindow, nsDependentCString(path));
+}
+
+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(NS_IMAPSERVICE_CONTRACTID, &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(NS_MSGMAILSESSION_CONTRACTID, &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 modified UTF-7
+ // If it's not UTF-8, it cannot be MUTF7, either. We just ignore it.
+ // (otherwise we'll crash. see #63186)
+ if (!MsgIsUTF8(aName))
+ return NS_OK;
+
+ if (!NS_IsAscii(aName.BeginReading(), aName.Length())) {
+ nsAutoCString name;
+ CopyUTF16toMUTF7(NS_ConvertUTF8toUTF16(aName), name);
+ return mInner->AddTo(name, addAsSubscribed, aSubscribable, changeIfExists);
+ }
+ return mInner->AddTo(aName, addAsSubscribed, aSubscribable, changeIfExists);
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::StopPopulating(nsIMsgWindow *aMsgWindow)
+{
+ nsCOMPtr<nsISubscribeListener> listener;
+ (void) GetSubscribeListener(getter_AddRefs(listener));
+
+ if (listener)
+ listener->OnDonePopulating();
+
+ 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(NS_IMAPSERVICE_CONTRACTID, &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 modified UTF-7
+ NS_LossyConvertUTF16toASCII folderCName(aName);
+ nsCOMPtr<nsIMsgFolder> msgFolder;
+ if (rootMsgFolder && !aName.IsEmpty())
+ rv = rootMsgFolder->FindSubFolder(folderCName, getter_AddRefs(msgFolder));
+
+ nsCOMPtr<nsIThread> thread(do_GetCurrentThread());
+
+ nsAutoString unicodeName;
+ rv = CopyMUTF7toUTF16(folderCName, unicodeName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (subscribe)
+ rv = imapService->SubscribeFolder(msgFolder, unicodeName, nullptr, aUri);
+ else
+ rv = imapService->UnsubscribeFolder(msgFolder, unicodeName, 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::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::GetChildren(const nsACString &aPath,
+ nsISimpleEnumerator **aResult)
+{
+ nsresult rv = EnsureInner();
+ NS_ENSURE_SUCCESS(rv,rv);
+ return mInner->GetChildren(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;
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::GetCanEmptyTrashOnExit(bool *canEmptyTrashOnExit)
+{
+ NS_ENSURE_ARG_POINTER(canEmptyTrashOnExit);
+ // Initialize canEmptyTrashOnExit true, a default value for IMAP
+ *canEmptyTrashOnExit = true;
+ GetPrefForServerAttribute("canEmptyTrashOnExit", canEmptyTrashOnExit);
+ 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(NS_IMAPPROTOCOLINFO_CONTRACTID, &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);
+ const char16_t *formatStrings[] = { tmpVal.get() };
+
+ nsString result;
+ rv = m_stringBundle->FormatStringFromName(
+ NS_ConvertASCIItoUTF16(aName).get(),
+ formatStrings, 1, getter_Copies(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::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(NS_MSGMAILSESSION_CONTRACTID);
+ 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.
+ nsCOMPtr<nsISimpleEnumerator> enumerator;
+ rv = aFolder->GetSubFolders(getter_AddRefs(enumerator));
+ if (NS_FAILED(rv))
+ return rv;
+
+ bool hasMore;
+ while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMore)) && hasMore)
+ {
+ nsCOMPtr<nsISupports> item;
+ enumerator->GetNext(getter_AddRefs(item));
+
+ nsCOMPtr<nsIMsgFolder> msgFolder(do_QueryInterface(item));
+ if (!msgFolder)
+ {
+ NS_WARNING("Not an nsIMsgFolder");
+ continue;
+ }
+ 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);
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::OnUserOrHostNameChanged(const nsACString& oldName,
+ const nsACString& newName,
+ bool hostnameChanged)
+{
+ nsresult rv;
+ // 1. Do common things in the base class.
+ rv = nsMsgIncomingServer::OnUserOrHostNameChanged(oldName, newName, hostnameChanged);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // 2. Reset 'HaveWeEverDiscoveredFolders' flag so the new folder list can be
+ // reloaded (ie, DiscoverMailboxList() will be invoked in nsImapProtocol).
+ nsCOMPtr<nsIImapHostSessionList> hostSessionList = do_GetService(kCImapHostSessionListCID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsAutoCString serverKey;
+ rv = GetKey(serverKey);
+ NS_ENSURE_SUCCESS(rv, rv);
+ hostSessionList->SetHaveWeEverDiscoveredFoldersForHost(serverKey.get(), false);
+ // 3. Make all the existing folders 'unverified' so that they can be
+ // removed from the folder pane after users log into the new server.
+ ResetFoldersToUnverified(nullptr);
+ return NS_OK;
+}
+
+// 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;
+ }
+
+ MsgReplaceChar(namespacePrefix, 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 (MsgFind(uri, namespacePrefix, false, index + 1) != index + 1 &&
+ !MsgLowerCaseEqualsLiteral(Substring(uri, index + 1), "inbox"))
+ uri.Insert(namespacePrefix, index + 1); // insert namespace prefix
+ convertedUri = uri;
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapIncomingServer::GetTrashFolderName(nsAString& retval)
+{
+ nsresult rv = GetUnicharValue(PREF_TRASH_FOLDER_NAME, retval);
+ if (NS_FAILED(rv))
+ return rv;
+ if (retval.IsEmpty())
+ retval = NS_LITERAL_STRING(DEFAULT_TRASH_FOLDER_NAME);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapIncomingServer::SetTrashFolderName(const nsAString& chvalue)
+{
+ // clear trash flag from the old pref
+ nsAutoString oldTrashName;
+ nsresult rv = GetTrashFolderName(oldTrashName);
+ if (NS_SUCCEEDED(rv))
+ {
+ nsAutoCString oldTrashNameUtf7;
+ rv = CopyUTF16toMUTF7(oldTrashName, oldTrashNameUtf7);
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr<nsIMsgFolder> oldFolder;
+ rv = GetFolder(oldTrashNameUtf7, getter_AddRefs(oldFolder));
+ if (NS_SUCCEEDED(rv) && oldFolder)
+ oldFolder->ClearFlag(nsMsgFolderFlags::Trash);
+ }
+ }
+ return SetUnicharValue(PREF_TRASH_FOLDER_NAME, chvalue);
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::GetMsgFolderFromURI(nsIMsgFolder *aFolderResource,
+ const nsACString& aURI,
+ nsIMsgFolder **aFolder)
+{
+ nsCOMPtr<nsIMsgFolder> msgFolder;
+ bool namespacePrefixAdded = false;
+ nsCString folderUriWithNamespace;
+
+ // 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));
+
+ if (NS_FAILED(rv) || !msgFolder) {
+ // we didn't find the folder so we will have to create a new one.
+ if (namespacePrefixAdded)
+ {
+ nsCOMPtr <nsIRDFService> rdf = do_GetService("@mozilla.org/rdf/rdf-service;1", &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr<nsIRDFResource> resource;
+ rv = rdf->GetResource(folderUriWithNamespace, getter_AddRefs(resource));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsCOMPtr <nsIMsgFolder> folderResource;
+ folderResource = do_QueryInterface(resource, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+ msgFolder = folderResource;
+ }
+ else
+ msgFolder = aFolderResource;
+ }
+
+ msgFolder.swap(*aFolder);
+ return NS_OK;
+}
+
+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 GetRealUsername(aLoginUsername);
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::GetOriginalUsername(nsACString &aUsername)
+{
+ return GetUsername(aUsername);
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::GetServerKey(nsACString &aServerKey)
+{
+ return GetKey(aServerKey);
+}
+
+NS_IMETHODIMP
+nsImapIncomingServer::GetServerPassword(nsACString &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::SetServerForceSelect(const nsACString &aForceSelect)
+{
+ return SetForceSelect(aForceSelect);
+}
diff --git a/mailnews/imap/src/nsImapIncomingServer.h b/mailnews/imap/src/nsImapIncomingServer.h
new file mode 100644
index 000000000..0948a3178
--- /dev/null
+++ b/mailnews/imap/src/nsImapIncomingServer.h
@@ -0,0 +1,137 @@
+/* -*- 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 "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"
+
+class nsIRDFService;
+
+/* 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 GetCanEmptyTrashOnExit(bool *canEmptyTrashOnExit) 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 OnUserOrHostNameChanged(const nsACString& oldName,
+ const nsACString& newName,
+ bool hostnameChanged) override;
+ NS_IMETHOD GetNumIdleConnections(int32_t *aNumIdleConnections);
+ NS_IMETHOD ForgetSessionPassword() 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 nsCString &serverUri,
+ nsIMsgFolder **rootFolder) override;
+ nsresult ResetFoldersToUnverified(nsIMsgFolder *parentFolder);
+ void GetUnverifiedSubFolders(nsIMsgFolder *parentFolder,
+ nsCOMArray<nsIMsgImapMailFolder> &aFoldersArray);
+ void GetUnverifiedFolders(nsCOMArray<nsIMsgImapMailFolder> &aFolderArray);
+ nsresult DeleteNonVerifiedFolders(nsIMsgFolder *parentFolder);
+ 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(nsIRDFService *rdf, nsCString &folderUri,
+ uint32_t folderFlag, nsCString &existingUri);
+
+ nsCOMArray<nsIImapProtocol> m_connectionCache;
+ nsCOMArray<nsIImapUrl> m_urlQueue;
+ 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
+ nsTArray<nsISupports*> m_urlConsumers;
+ eIMAPCapabilityFlags m_capability;
+ nsCString m_manageMailAccountUrl;
+ bool m_userAuthenticated;
+ bool mDoingSubscribeDialog;
+ bool mDoingLsub;
+ bool m_shuttingDown;
+
+ 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/mailnews/imap/src/nsImapMailFolder.cpp b/mailnews/imap/src/nsImapMailFolder.cpp
new file mode 100644
index 000000000..da1411cd0
--- /dev/null
+++ b/mailnews/imap/src/nsImapMailFolder.cpp
@@ -0,0 +1,9837 @@
+/* -*- 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 "nsMsgImapCID.h"
+#include "nsImapMailFolder.h"
+#include "nsIFile.h"
+#include "nsIFolderListener.h"
+#include "nsCOMPtr.h"
+#include "nsAutoPtr.h"
+#include "nsIRDFService.h"
+#include "nsRDFCID.h"
+#include "nsMsgDBCID.h"
+#include "nsMsgFolderFlags.h"
+#include "nsImapFlagAndUidState.h"
+#include "nsISeekableStream.h"
+#include "nsThreadUtils.h"
+#include "nsIImapUrl.h"
+#include "nsImapUtils.h"
+#include "nsMsgUtils.h"
+#include "nsIMsgMailSession.h"
+#include "nsMsgKeyArray.h"
+#include "nsMsgBaseCID.h"
+#include "nsMsgLocalCID.h"
+#include "nsImapUndoTxn.h"
+#include "nsIIMAPHostSessionList.h"
+#include "nsIMsgCopyService.h"
+#include "nsICopyMsgStreamListener.h"
+#include "nsImapStringBundle.h"
+#include "nsIMsgFolderCacheElement.h"
+#include "nsTextFormatter.h"
+#include "nsIPrefBranch.h"
+#include "nsIPrefService.h"
+#include "nsMsgI18N.h"
+#include "nsICacheSession.h"
+#include "nsIDOMWindow.h"
+#include "nsIMsgFilter.h"
+#include "nsIMsgFilterService.h"
+#include "nsIMsgSearchCustomTerm.h"
+#include "nsIMsgSearchTerm.h"
+#include "nsImapMoveCoalescer.h"
+#include "nsIPrompt.h"
+#include "nsIPromptService.h"
+#include "nsIDocShell.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.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 "nsIMsgOfflineImapOperation.h"
+#include "nsImapOfflineSync.h"
+#include "nsIMsgAccountManager.h"
+#include "nsQuickSort.h"
+#include "nsIImapMockChannel.h"
+#include "nsIWebNavigation.h"
+#include "nsNetUtil.h"
+#include "nsIMAPNamespace.h"
+#include "nsIMsgFolderCompactor.h"
+#include "nsMsgMessageFlags.h"
+#include "nsIMimeHeaders.h"
+#include "nsIMsgMdnGenerator.h"
+#include "nsISpamSettings.h"
+#include <time.h>
+#include "nsIMsgMailNewsUrl.h"
+#include "nsEmbedCID.h"
+#include "nsIMsgComposeService.h"
+#include "nsMsgCompCID.h"
+#include "nsICacheEntry.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsIMsgIdentity.h"
+#include "nsIMsgFolderNotificationService.h"
+#include "nsNativeCharsetUtils.h"
+#include "nsIExternalProtocolService.h"
+#include "nsCExternalHandlerService.h"
+#include "prprf.h"
+#include "nsIMutableArray.h"
+#include "nsArrayUtils.h"
+#include "nsArrayEnumerator.h"
+#include "nsAutoSyncManager.h"
+#include "nsIMsgFilterCustomAction.h"
+#include "nsMsgReadStateTxn.h"
+#include "nsIStringEnumerator.h"
+#include "nsIMsgStatusFeedback.h"
+#include "nsMsgLineBuffer.h"
+#include "mozilla/Logging.h"
+#include "mozilla/Attributes.h"
+#include "nsStringStream.h"
+#include "nsIStreamListener.h"
+
+static NS_DEFINE_CID(kRDFServiceCID, NS_RDFSERVICE_CID);
+static NS_DEFINE_CID(kParseMailMsgStateCID, NS_PARSEMAILMSGSTATE_CID);
+static NS_DEFINE_CID(kCImapHostSessionList, NS_IIMAPHOSTSESSIONLIST_CID);
+
+extern PRLogModuleInfo *gAutoSyncLog;
+extern PRLogModuleInfo* IMAP;
+
+#define MAILNEWS_CUSTOM_HEADERS "mailnews.customHeaders"
+
+/*
+ Copies the contents of srcDir into destDir.
+ destDir will be created if it doesn't exist.
+*/
+
+static
+nsresult RecursiveCopy(nsIFile* srcDir, nsIFile* destDir)
+{
+ nsresult rv;
+ bool isDir;
+
+ rv = srcDir->IsDirectory(&isDir);
+ if (NS_FAILED(rv)) return rv;
+ if (!isDir) return NS_ERROR_INVALID_ARG;
+
+ bool exists;
+ rv = destDir->Exists(&exists);
+ if (NS_SUCCEEDED(rv) && !exists)
+ rv = destDir->Create(nsIFile::DIRECTORY_TYPE, 0775);
+ if (NS_FAILED(rv)) return rv;
+
+ bool hasMore = false;
+ nsCOMPtr<nsISimpleEnumerator> dirIterator;
+ rv = srcDir->GetDirectoryEntries(getter_AddRefs(dirIterator));
+ if (NS_FAILED(rv)) return rv;
+
+ rv = dirIterator->HasMoreElements(&hasMore);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIFile> dirEntry;
+
+ while (hasMore)
+ {
+ nsCOMPtr<nsISupports> supports;
+ rv = dirIterator->GetNext(getter_AddRefs(supports));
+ dirEntry = do_QueryInterface(supports);
+ if (NS_SUCCEEDED(rv) && dirEntry)
+ {
+ rv = dirEntry->IsDirectory(&isDir);
+ if (NS_SUCCEEDED(rv))
+ {
+ if (isDir)
+ {
+ nsCOMPtr<nsIFile> newChild;
+ rv = destDir->Clone(getter_AddRefs(newChild));
+ if (NS_SUCCEEDED(rv))
+ {
+ nsAutoString leafName;
+ dirEntry->GetLeafName(leafName);
+ newChild->AppendRelativePath(leafName);
+ rv = newChild->Exists(&exists);
+ if (NS_SUCCEEDED(rv) && !exists)
+ rv = newChild->Create(nsIFile::DIRECTORY_TYPE, 0775);
+ rv = RecursiveCopy(dirEntry, newChild);
+ }
+ }
+ else
+ rv = dirEntry->CopyTo(destDir, EmptyString());
+ }
+
+ }
+ rv = dirIterator->HasMoreElements(&hasMore);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ return rv;
+}
+
+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_folderQuotaCommandIssued(false),
+ m_folderQuotaDataIsValid(false),
+ m_updatingFolder(false),
+ m_compactingOfflineStore(false),
+ m_expunging(false),
+ m_applyIncomingFilters(false),
+ m_downloadingFolderForOfflineUse(false),
+ m_filterListRequiresBody(false),
+ m_folderQuotaUsedKB(0),
+ m_folderQuotaMaxKB(0)
+{
+ MOZ_COUNT_CTOR(nsImapMailFolder); // double count these for now.
+
+ m_moveCoalescer = nullptr;
+ 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()
+{
+ MOZ_COUNT_DTOR(nsImapMailFolder);
+
+ NS_IF_RELEASE(m_moveCoalescer);
+ 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.Append(NS_LITERAL_STRING(FOLDER_SUFFIX));
+ path->SetLeafName(leafName);
+ }
+
+ return NS_OK;
+}
+
+static bool
+nsShouldIgnoreFile(nsString& name)
+{
+ int32_t len = name.Length();
+ if (len > 4 && name.RFind(SUMMARY_SUFFIX, true) == len - 4)
+ {
+ name.SetLength(len-4); // truncate the string
+ return false;
+ }
+ return true;
+}
+
+nsresult nsImapMailFolder::CreateChildFromURI(const nsCString &uri, nsIMsgFolder **folder)
+{
+ nsImapMailFolder *newFolder = new nsImapMailFolder;
+ if (!newFolder)
+ return NS_ERROR_OUT_OF_MEMORY;
+ newFolder->Init(uri.get());
+ 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;
+ nsCOMPtr<nsIRDFService> rdf = do_GetService("@mozilla.org/rdf/rdf-service;1", &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ nsAutoCString uri(mURI);
+ uri.Append('/');
+
+ // If AddSubFolder starts getting called for folders other than virtual folders,
+ // we'll have to do convert those names to modified utf-7. For now, the account manager code
+ // that loads the virtual folders for each account, expects utf8 not modified utf-7.
+ 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<nsIRDFResource> res;
+ rv = rdf->GetResource(uri, getter_AddRefs(res));
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsCOMPtr<nsIMsgFolder> folder(do_QueryInterface(res, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ 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.swap(*aChild);
+
+ nsCOMPtr <nsIMsgImapMailFolder> imapChild = do_QueryInterface(*aChild);
+ if (imapChild)
+ {
+ imapChild->SetOnlineName(NS_LossyConvertUTF16toASCII(aName));
+ imapChild->SetHierarchyDelimiter(m_hierarchyDelimiter);
+ }
+ NotifyItemAdded(*aChild);
+ return rv;
+}
+
+nsresult nsImapMailFolder::AddSubfolderWithPath(nsAString& name, nsIFile *dbPath,
+ nsIMsgFolder **child, bool brandNew)
+{
+ NS_ENSURE_ARG_POINTER(child);
+ nsresult rv;
+ nsCOMPtr<nsIRDFService> rdf(do_GetService(kRDFServiceCID, &rv));
+ NS_ENSURE_SUCCESS(rv, 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<nsIRDFResource> res;
+ rv = rdf->GetResource(uri, getter_AddRefs(res));
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsCOMPtr<nsIMsgFolder> folder(do_QueryInterface(res, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ folder->SetFilePath(dbPath);
+ nsCOMPtr<nsIMsgImapMailFolder> imapFolder = do_QueryInterface(folder, &rv);
+ 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.swap(*child);
+ return NS_OK;
+}
+
+nsresult nsImapMailFolder::CreateSubFolders(nsIFile *path)
+{
+ nsresult rv = NS_OK;
+ nsAutoString currentFolderNameStr; // online name
+ nsAutoString currentFolderDBNameStr; // possibly munged name
+ nsCOMPtr<nsIMsgFolder> child;
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr <nsISimpleEnumerator> children;
+ rv = path->GetDirectoryEntries(getter_AddRefs(children));
+ bool more = false;
+ if (children)
+ children->HasMoreElements(&more);
+ nsCOMPtr<nsIFile> dirEntry;
+
+ while (more)
+ {
+ nsCOMPtr<nsISupports> supports;
+ rv = children->GetNext(getter_AddRefs(supports));
+ dirEntry = do_QueryInterface(supports);
+ if (NS_FAILED(rv) || !dirEntry)
+ break;
+ rv = children->HasMoreElements(&more);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr <nsIFile> currentFolderPath = do_QueryInterface(dirEntry);
+ currentFolderPath->GetLeafName(currentFolderNameStr);
+ 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 <nsIMsgFolderCacheElement> cacheElement;
+ 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 utf7LeafName = currentFolderNameStr;
+
+ if (curFolder)
+ {
+ rv = GetFolderCacheElemFromFile(dbFile, getter_AddRefs(cacheElement));
+ if (NS_SUCCEEDED(rv) && cacheElement)
+ {
+ nsCString onlineFullUtf7Name;
+
+ uint32_t folderFlags;
+ rv = cacheElement->GetInt32Property("flags", (int32_t *) &folderFlags);
+ if (NS_SUCCEEDED(rv) && folderFlags & nsMsgFolderFlags::Virtual) //ignore virtual folders
+ continue;
+ int32_t hierarchyDelimiter;
+ rv = cacheElement->GetInt32Property("hierDelim", &hierarchyDelimiter);
+ if (NS_SUCCEEDED(rv) && hierarchyDelimiter == kOnlineHierarchySeparatorUnknown)
+ {
+ currentFolderPath->Remove(false);
+ continue; // blow away .msf files for folders with unknown delimiter.
+ }
+ rv = cacheElement->GetStringProperty("onlineName", onlineFullUtf7Name);
+ if (NS_SUCCEEDED(rv) && !onlineFullUtf7Name.IsEmpty())
+ {
+ CopyMUTF7toUTF16(onlineFullUtf7Name, currentFolderNameStr);
+ char delimiter = 0;
+ GetHierarchyDelimiter(&delimiter);
+ int32_t leafPos = currentFolderNameStr.RFindChar(delimiter);
+ if (leafPos > 0)
+ currentFolderNameStr.Cut(0, leafPos + 1);
+
+ // take the utf7 full online name, and determine the utf7 leaf name
+ CopyASCIItoUTF16(onlineFullUtf7Name, utf7LeafName);
+ leafPos = utf7LeafName.RFindChar(delimiter);
+ if (leafPos > 0)
+ utf7LeafName.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 utf7 name as the uri for the folder.
+ AddSubfolderWithPath(utf7LeafName, 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, which is in utf7 form.
+ if (!currentFolderNameStr.IsEmpty())
+ child->SetPrettyName(currentFolderNameStr);
+ child->SetMsgDatabase(nullptr);
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapMailFolder::GetSubFolders(nsISimpleEnumerator **aResult)
+{
+ 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(NS_LITERAL_CSTRING("INBOX"), kOnlineHierarchySeparatorUnknown, 0, true);
+ }
+ }
+
+ int32_t count = mSubFolders.Count();
+ nsCOMPtr<nsISimpleEnumerator> dummy;
+ for (int32_t i = 0; i < count; i++)
+ mSubFolders[i]->GetSubFolders(getter_AddRefs(dummy));
+
+ UpdateSummaryTotals(false);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ return aResult ? NS_NewArrayEnumerator(aResult, mSubFolders) : NS_ERROR_NULL_POINTER;
+}
+
+//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(NS_MSGDB_SERVICE_CONTRACTID, &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;
+ bool selectFolder = false;
+
+ // 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");
+
+ if (mFlags & nsMsgFolderFlags::Inbox || m_applyIncomingFilters)
+ {
+ if (!m_filterList)
+ rv = GetFilterList(aMsgWindow, getter_AddRefs(m_filterList));
+ // 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)
+ {
+ 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(NS_MSGFILTERSERVICE_CONTRACTID, &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;
+ nsCOMPtr<nsISupportsArray> searchTerms;
+ uint32_t numSearchTerms = 0;
+ filter->GetSearchTerms(getter_AddRefs(searchTerms));
+ if (searchTerms)
+ searchTerms->Count(&numSearchTerms);
+ for (uint32_t termIndex = 0;
+ termIndex < numSearchTerms && !m_filterListRequiresBody;
+ termIndex++)
+ {
+ nsCOMPtr<nsIMsgSearchTerm> term;
+ rv = searchTerms->QueryElementAt(termIndex,
+ NS_GET_IID(nsIMsgSearchTerm),
+ getter_AddRefs(term));
+ 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;
+ }
+ }
+
+ // 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;
+ }
+ }
+ }
+ }
+
+ selectFolder = true;
+
+ bool isServer;
+ rv = GetIsServer(&isServer);
+ if (NS_SUCCEEDED(rv) && isServer)
+ {
+ if (!m_haveDiscoveredAllFolders)
+ {
+ bool hasSubFolders = false;
+ GetHasSubFolders(&hasSubFolders);
+ if (!hasSubFolders)
+ {
+ rv = CreateClientSubfolderInfo(NS_LITERAL_CSTRING("Inbox"), kOnlineHierarchySeparatorUnknown,0, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ m_haveDiscoveredAllFolders = true;
+ }
+ selectFolder = false;
+ }
+ rv = GetDatabase();
+ if (NS_FAILED(rv))
+ {
+ ThrowAlertMsg("errorGettingDB", aMsgWindow);
+ return rv;
+ }
+ bool canOpenThisFolder = true;
+ GetCanOpenFolder(&canOpenThisFolder);
+
+ 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(aMsgWindow, this, this);
+ if (goOnline)
+ {
+ m_urlListener = aUrlListener;
+ return goOnline->ProcessNextOperation();
+ }
+ }
+ }
+
+ // Check it we're password protecting the local store.
+ if (!PromptForMasterPasswordIfNecessary())
+ return NS_ERROR_FAILURE;
+
+ if (!canOpenThisFolder)
+ selectFolder = false;
+ // don't run select if we can't select the folder...
+ if (NS_SUCCEEDED(rv) && !m_urlRunning && selectFolder)
+ {
+ nsCOMPtr<nsIImapService> imapService = do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ 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(mFolderLoadedAtom);
+ }
+ }
+ else if (NS_SUCCEEDED(rv)) // 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(mFolderLoadedAtom);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapMailFolder::GetMessages(nsISimpleEnumerator* *result)
+{
+ NS_ENSURE_ARG_POINTER(result);
+ if (!mDatabase)
+ GetDatabase();
+ if (mDatabase)
+ return mDatabase->EnumerateMessages(result);
+ return NS_ERROR_UNEXPECTED;
+}
+
+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;
+ }
+ else if (mIsServer && folderName.LowerCaseEqualsLiteral("inbox")) // Inbox, a special folder
+ {
+ ThrowAlertMsg("folderExists", msgWindow);
+ return NS_MSG_FOLDER_EXISTS;
+ }
+
+ nsCOMPtr<nsIImapService> imapService = do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ return imapService->CreateFolder(this, folderName, this, nullptr);
+}
+
+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_ConvertASCIItoUTF16 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<nsIRDFService> rdf(do_GetService(kRDFServiceCID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIRDFResource> res;
+ nsCOMPtr<nsIMsgImapMailFolder> parentFolder;
+ nsAutoCString uri (mURI);
+ leafName.Assign(Substring(parentName, folderStart + 1));
+ parentName.SetLength(folderStart);
+
+ rv = CreateDirectoryForFolder(getter_AddRefs(path));
+ if (NS_FAILED(rv))
+ return rv;
+ uri.Append('/');
+ uri.Append(NS_LossyConvertUTF16toASCII(parentName));
+ rv = rdf->GetResource(uri, getter_AddRefs(res));
+ if (NS_FAILED(rv))
+ return rv;
+ parentFolder = do_QueryInterface(res, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsAutoCString leafnameC;
+ LossyCopyUTF16toASCII(leafName, leafnameC);
+ return parentFolder->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(NS_MSGDB_SERVICE_CONTRACTID, &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_LossyConvertUTF16toASCII(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 = CopyMUTF7toUTF16(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_ConvertASCIItoUTF16(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)
+ {
+ nsCOMPtr <nsIAtom> folderCreateAtom;
+ if(NS_SUCCEEDED(rv) && child)
+ {
+ NotifyItemAdded(child);
+ folderCreateAtom = MsgGetAtom("FolderCreateCompleted");
+ child->NotifyFolderEvent(folderCreateAtom);
+ nsCOMPtr<nsIMsgFolderNotificationService> notifier(do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID));
+ if (notifier)
+ notifier->NotifyFolderAdded(child);
+ }
+ else
+ {
+ folderCreateAtom = MsgGetAtom("FolderCreateFailed");
+ NotifyFolderEvent(folderCreateAtom);
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapMailFolder::List()
+{
+ nsresult rv;
+ nsCOMPtr<nsIImapService> imapService = do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+ return imapService->ListFolder(this, this, nullptr);
+}
+
+NS_IMETHODIMP nsImapMailFolder::RemoveSubFolder (nsIMsgFolder *which)
+{
+ nsresult rv;
+ nsCOMPtr<nsIMutableArray> folders(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv));
+ NS_ENSURE_TRUE(folders, rv);
+ nsCOMPtr<nsISupports> folderSupport = do_QueryInterface(which, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ folders->AppendElement(folderSupport, false);
+ rv = nsMsgDBFolder::DeleteSubFolders(folders, nullptr);
+ which->Delete();
+ return rv;
+}
+
+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);
+ // get the corresponding RDF resource
+ // RDF will create the folder resource if it doesn't already exist
+ nsCOMPtr<nsIRDFService> rdf(do_GetService(kRDFServiceCID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIRDFResource> resource;
+ rv = rdf->GetResource(parentName, getter_AddRefs(resource));
+ if (NS_FAILED(rv)) return rv;
+ msgParent = do_QueryInterface(resource, &rv);
+ }
+ }
+ if (msgParent)
+ {
+ nsString folderName;
+ GetName(folderName);
+ nsresult rv;
+ nsCOMPtr<nsIImapService> imapService = do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr <nsIURI> uri;
+ imapService->EnsureFolderExists(msgParent, folderName, urlListener, getter_AddRefs(uri));
+ }
+ 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 <nsISimpleEnumerator> hdrs;
+ rv = mDatabase->EnumerateMessages(getter_AddRefs(hdrs));
+ NS_ENSURE_SUCCESS(rv, rv);
+ bool hasMore = false;
+
+ PRTime cutOffDay =
+ MsgConvertAgeInDaysToCutoffDate(numDaysToKeepOfflineMsgs);
+
+ nsCOMPtr <nsIMsgDBHdr> pHeader;
+ // 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<nsISupports> supports;
+ rv = hdrs->GetNext(getter_AddRefs(supports));
+ NS_ENSURE_SUCCESS(rv, rv);
+ pHeader = do_QueryInterface(supports, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t msgFlags;
+ PRTime msgDate;
+ pHeader->GetFlags(&msgFlags);
+ if (msgFlags & nsMsgMessageFlags::Offline)
+ {
+ pHeader->GetDate(&msgDate);
+ MarkPendingRemoval(pHeader, 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).
+ */
+NS_IMETHODIMP nsImapMailFolder::Compact(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();
+
+ m_urlListener = aListener;
+ // We should be able to compact the offline store now that this should
+ // just be called by the UI.
+ if (aMsgWindow && (mFlags & nsMsgFolderFlags::Offline))
+ {
+ m_compactingOfflineStore = true;
+ CompactOfflineStore(aMsgWindow, this);
+ }
+ if (WeAreOffline())
+ return NS_OK;
+ m_expunging = true;
+ return Expunge(this, aMsgWindow);
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::NotifyCompactCompleted()
+{
+ if (!m_expunging && m_urlListener)
+ {
+ m_urlListener->OnStopRunningUrl(nullptr, NS_OK);
+ m_urlListener = nullptr;
+ }
+ m_compactingOfflineStore = false;
+ 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" : "");
+ 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(NS_IMAPSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return imapService->Expunge(this, aListener, aMsgWindow, nullptr);
+}
+
+NS_IMETHODIMP nsImapMailFolder::CompactAll(nsIUrlListener *aListener,
+ nsIMsgWindow *aMsgWindow,
+ bool aCompactOfflineAlso)
+{
+ nsresult rv;
+ nsCOMPtr<nsIMutableArray> folderArray, offlineFolderArray;
+
+ nsCOMPtr<nsIMsgFolder> rootFolder;
+ nsCOMPtr<nsIArray> allDescendents;
+ rv = GetRootFolder(getter_AddRefs(rootFolder));
+ if (NS_SUCCEEDED(rv) && rootFolder)
+ {
+ rootFolder->GetDescendants(getter_AddRefs(allDescendents));
+ uint32_t cnt = 0;
+ rv = allDescendents->GetLength(&cnt);
+ NS_ENSURE_SUCCESS(rv, rv);
+ folderArray = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ NS_ENSURE_TRUE(folderArray, rv);
+ if (aCompactOfflineAlso)
+ {
+ offlineFolderArray = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ NS_ENSURE_TRUE(offlineFolderArray, rv);
+ }
+ for (uint32_t i = 0; i < cnt; i++)
+ {
+ nsCOMPtr<nsIMsgFolder> folder = do_QueryElementAt(allDescendents, i, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ uint32_t folderFlags;
+ folder->GetFlags(&folderFlags);
+ if (! (folderFlags & (nsMsgFolderFlags::Virtual | nsMsgFolderFlags::ImapNoselect)))
+ {
+ rv = folderArray->AppendElement(folder, false);
+ if (aCompactOfflineAlso)
+ offlineFolderArray->AppendElement(folder, false);
+ }
+ }
+ rv = folderArray->GetLength(&cnt);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (cnt == 0)
+ return NotifyCompactCompleted();
+ }
+ nsCOMPtr <nsIMsgFolderCompactor> folderCompactor =
+ do_CreateInstance(NS_MSGLOCALFOLDERCOMPACTOR_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return folderCompactor->CompactFolders(folderArray, offlineFolderArray,
+ aListener, aMsgWindow);
+}
+
+NS_IMETHODIMP nsImapMailFolder::UpdateStatus(nsIUrlListener *aListener, nsIMsgWindow *aMsgWindow)
+{
+ nsresult rv;
+ nsCOMPtr<nsIImapService> imapService = do_GetService(NS_IMAPSERVICE_CONTRACTID, &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(nsIMsgWindow *aMsgWindow, nsIUrlListener *aListener)
+{
+ nsCOMPtr<nsIMsgFolder> trashFolder;
+ nsresult rv = GetTrashFolder(getter_AddRefs(trashFolder));
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr<nsIMsgAccountManager> accountManager = do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &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)
+ {
+ nsMsgKey fakeKey;
+ trashDB->GetNextFakeOfflineMsgKey(&fakeKey);
+
+ nsCOMPtr <nsIMsgOfflineImapOperation> op;
+ rv = trashDB->GetOfflineOpForKey(fakeKey, true, getter_AddRefs(op));
+ trashFolder->SetFlag(nsMsgFolderFlags::OfflineEvents);
+ op->SetOperation(nsIMsgOfflineImapOperation::kDeleteAllMsgs);
+ }
+ return rv;
+ }
+
+ nsCOMPtr<nsIImapService> imapService = do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (aListener)
+ rv = imapService->DeleteAllMessages(trashFolder, aListener, nullptr);
+ else
+ {
+ nsCOMPtr<nsIUrlListener> urlListener = do_QueryInterface(trashFolder);
+ rv = imapService->DeleteAllMessages(trashFolder, urlListener, nullptr);
+ }
+ // 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);
+
+ bool hasSubfolders = false;
+ rv = trashFolder->GetHasSubFolders(&hasSubfolders);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (hasSubfolders)
+ {
+ nsCOMPtr<nsISimpleEnumerator> enumerator;
+ nsCOMPtr<nsISupports> item;
+ nsCOMArray<nsIMsgFolder> array;
+
+ rv = trashFolder->GetSubFolders(getter_AddRefs(enumerator));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool hasMore;
+ while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMore)) && hasMore)
+ {
+ rv = enumerator->GetNext(getter_AddRefs(item));
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr<nsIMsgFolder> folder(do_QueryInterface(item, &rv));
+ if (NS_SUCCEEDED(rv))
+ array.AppendObject(folder);
+ }
+ }
+ for (int32_t i = array.Count() - 1; i >= 0; i--)
+ {
+ trashFolder->PropagateDelete(array[i], true, aMsgWindow);
+ // Remove the object, presumably to free it up before we delete the next.
+ array.RemoveObjectAt(i);
+ }
+ }
+
+ 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->Delete();
+ NS_ENSURE_SUCCESS(rv, rv);
+ trashFolder->SetDBTransferInfo(transferInfo);
+ trashFolder->SetSizeOnDisk(0);
+
+ // The trash folder has effectively been deleted.
+ nsCOMPtr<nsIMsgFolderNotificationService> notifier(do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID));
+ if (notifier)
+ notifier->NotifyFolderDeleted(trashFolder);
+
+ return NS_OK;
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapMailFolder::Delete()
+{
+ nsresult rv = nsMsgDBFolder::Delete();
+
+ // 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)
+ {
+ const char16_t *formatStrings[] =
+ {
+ (const char16_t*)(intptr_t)m_hierarchyDelimiter
+ };
+ nsString alertString;
+ rv = bundle->FormatStringFromName(
+ u"imapSpecialChar",
+ formatStrings, 1, getter_Copies(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);
+ const char16_t *titleParams[] = { accountName.get() };
+ rv = bundle->FormatStringFromName(
+ u"imapAlertDialogTitle",
+ titleParams, 1, getter_Copies(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(NS_IMAPSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+ return imapService->RenameLeaf(this, newName, this, msgWindow, nullptr);
+}
+
+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)
+{
+ // XXX Here it's assumed that IMAP folder names are stored locally
+ // in modified UTF-7 (ASCII-only) as is stored remotely. If we ever change
+ // this, we have to work with nsString instead of nsCString
+ // (ref. bug 264071)
+ 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));
+
+ 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 += ".sbd";
+ 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);
+ incomingServer.swap(*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, 1, nullptr);
+ else if (aDispositionFlag == nsIMsgFolder::nsMsgDispositionState_Forwarded)
+ StoreImapFlags(kImapMsgForwardedFlag, true, &msgKey, 1, nullptr);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::MarkMessagesRead(nsIArray *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.Elements(), keysToMarkRead.Length(), nullptr);
+ rv = GetDatabase();
+ if (NS_SUCCEEDED(rv))
+ mDatabase->Commit(nsMsgDBCommitType::kLargeCommit);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::SetLabelForMessages(nsIArray *aMessages, nsMsgLabelValue aLabel)
+{
+ NS_ENSURE_ARG(aMessages);
+
+ nsresult rv = nsMsgDBFolder::SetLabelForMessages(aMessages, aLabel);
+ if (NS_SUCCEEDED(rv))
+ {
+ nsAutoCString messageIds;
+ nsTArray<nsMsgKey> keysToLabel;
+ nsresult rv = BuildIdsAndKeyArray(aMessages, messageIds, keysToLabel);
+ NS_ENSURE_SUCCESS(rv, rv);
+ StoreImapFlags((aLabel << 9), true, keysToLabel.Elements(), keysToLabel.Length(), 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))
+ {
+ nsMsgKey *thoseMarked;
+ uint32_t numMarked;
+ EnableNotifications(allMessageCountNotifications, false, true /*dbBatching*/);
+ rv = mDatabase->MarkAllRead(&numMarked, &thoseMarked);
+ EnableNotifications(allMessageCountNotifications, true, true /*dbBatching*/);
+ if (NS_SUCCEEDED(rv) && numMarked)
+ {
+ rv = StoreImapFlags(kImapMsgSeenFlag, true, thoseMarked,
+ numMarked, nullptr);
+ mDatabase->Commit(nsMsgDBCommitType::kLargeCommit);
+
+ // Setup a undo-state
+ if (aMsgWindow)
+ rv = AddMarkAllReadUndoAction(aMsgWindow, thoseMarked, numMarked);
+ free(thoseMarked);
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapMailFolder::MarkThreadRead(nsIMsgThread *thread)
+{
+ nsresult rv = GetDatabase();
+ if(NS_SUCCEEDED(rv))
+ {
+ nsMsgKey *keys;
+ uint32_t numKeys;
+ rv = mDatabase->MarkThreadRead(thread, nullptr, &numKeys, &keys);
+ if (NS_SUCCEEDED(rv) && numKeys)
+ {
+ rv = StoreImapFlags(kImapMsgSeenFlag, true, keys, numKeys, nullptr);
+ mDatabase->Commit(nsMsgDBCommitType::kLargeCommit);
+ free(keys);
+ }
+ }
+ return rv;
+}
+
+
+NS_IMETHODIMP nsImapMailFolder::ReadFromFolderCacheElem(nsIMsgFolderCacheElement *element)
+{
+ nsresult rv = nsMsgDBFolder::ReadFromFolderCacheElem(element);
+ int32_t hierarchyDelimiter = kOnlineHierarchySeparatorUnknown;
+ nsCString onlineName;
+
+ element->GetInt32Property("boxFlags", &m_boxFlags);
+ if (NS_SUCCEEDED(element->GetInt32Property("hierDelim", &hierarchyDelimiter))
+ && hierarchyDelimiter != kOnlineHierarchySeparatorUnknown)
+ m_hierarchyDelimiter = (char) hierarchyDelimiter;
+ rv = element->GetStringProperty("onlineName", onlineName);
+ if (NS_SUCCEEDED(rv) && !onlineName.IsEmpty())
+ m_onlineFolderName.Assign(onlineName);
+
+ m_aclFlags = kAclInvalid; // init to invalid value.
+ element->GetInt32Property("aclFlags", (int32_t *) &m_aclFlags);
+ element->GetInt32Property("serverTotal", &m_numServerTotalMessages);
+ element->GetInt32Property("serverUnseen", &m_numServerUnseenMessages);
+ element->GetInt32Property("serverRecent", &m_numServerRecentMessages);
+ element->GetInt32Property("nextUID", &m_nextUID);
+ int32_t lastSyncTimeInSec;
+ if ( NS_FAILED(element->GetInt32Property("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->SetInt32Property("boxFlags", m_boxFlags);
+ element->SetInt32Property("hierDelim", (int32_t) m_hierarchyDelimiter);
+ element->SetStringProperty("onlineName", m_onlineFolderName);
+ element->SetInt32Property("aclFlags", (int32_t) m_aclFlags);
+ element->SetInt32Property("serverTotal", m_numServerTotalMessages);
+ element->SetInt32Property("serverUnseen", m_numServerUnseenMessages);
+ element->SetInt32Property("serverRecent", m_numServerRecentMessages);
+ if (m_nextUID != (int32_t) nsMsgKey_None)
+ element->SetInt32Property("nextUID", m_nextUID);
+
+ // store folder's last sync time
+ if (m_autoSyncStateObj)
+ {
+ PRTime lastSyncTime;
+ m_autoSyncStateObj->GetLastSyncTime(&lastSyncTime);
+ // store in sec
+ element->SetInt32Property("lastSyncTimeInSec", (int32_t) (lastSyncTime / PR_USEC_PER_SEC));
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::MarkMessagesFlagged(nsIArray *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.Elements(),
+ keysToMarkFlagged.Length(), 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;
+ CopyASCIItoUTF16(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));
+ if (m_hierarchyDelimiter != '/')
+ MsgReplaceChar(onlineCName, '/', m_hierarchyDelimiter);
+ m_onlineFolderName.Assign(onlineCName);
+ CopyASCIItoUTF16(onlineCName, autoOnlineName);
+ }
+ (*folderInfo)->SetProperty("onlineName", autoOnlineName);
+ }
+ return rv;
+}
+
+/* static */ nsresult
+nsImapMailFolder::BuildIdsAndKeyArray(nsIArray* messages,
+ nsCString& msgIds,
+ nsTArray<nsMsgKey>& keyArray)
+{
+ NS_ENSURE_ARG_POINTER(messages);
+ nsresult rv;
+ uint32_t count = 0;
+ uint32_t i;
+ rv = messages->GetLength(&count);
+ if (NS_FAILED(rv)) return rv;
+
+ // build up message keys.
+ for (i = 0; i < count; i++)
+ {
+ nsMsgKey key;
+ nsCOMPtr <nsIMsgDBHdr> msgDBHdr = do_QueryElementAt(messages, i, &rv);
+ if (msgDBHdr)
+ rv = msgDBHdr->GetMessageKey(&key);
+ if (NS_SUCCEEDED(rv))
+ keyArray.AppendElement(key);
+ }
+ return AllocateUidStringFromKeys(keyArray.Elements(), keyArray.Length(), msgIds);
+}
+
+static int CompareKey (const void *v1, const void *v2, void *)
+{
+ // QuickSort callback to compare array values
+ nsMsgKey i1 = *(nsMsgKey *)v1;
+ nsMsgKey i2 = *(nsMsgKey *)v2;
+ return i1 - i2;
+}
+
+/* static */nsresult
+nsImapMailFolder::AllocateUidStringFromKeys(nsMsgKey *keys, uint32_t numKeys, nsCString &msgIds)
+{
+ if (!numKeys)
+ return NS_ERROR_INVALID_ARG;
+ nsresult rv = NS_OK;
+ uint32_t startSequence;
+ startSequence = keys[0];
+ uint32_t curSequenceEnd = startSequence;
+ uint32_t total = numKeys;
+ // sort keys and then generate ranges instead of singletons!
+ NS_QuickSort(keys, numKeys, sizeof(nsMsgKey), CompareKey, nullptr);
+ for (uint32_t keyIndex = 0; keyIndex < total; keyIndex++)
+ {
+ uint32_t curKey = keys[keyIndex];
+ uint32_t nextKey = (keyIndex + 1 < total) ? keys[keyIndex + 1] : 0xFFFFFFFF;
+ bool lastKey = (nextKey == 0xFFFFFFFF);
+
+ if (lastKey)
+ curSequenceEnd = curKey;
+ if (nextKey == (uint32_t) curSequenceEnd + 1 && !lastKey)
+ {
+ curSequenceEnd = nextKey;
+ continue;
+ }
+ else if (curSequenceEnd > startSequence)
+ {
+ AppendUid(msgIds, startSequence);
+ msgIds += ':';
+ AppendUid(msgIds,curSequenceEnd);
+ if (!lastKey)
+ msgIds += ',';
+ startSequence = nextKey;
+ curSequenceEnd = startSequence;
+ }
+ else
+ {
+ startSequence = nextKey;
+ curSequenceEnd = startSequence;
+ AppendUid(msgIds, keys[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(nsIArray *messages,
+ nsIMsgWindow *msgWindow,
+ bool deleteStorage, bool isMove,
+ nsIMsgCopyServiceListener* listener,
+ bool allowUndo)
+{
+ // *** jt - assuming delete is move to the trash folder for now
+ nsCOMPtr<nsIRDFResource> res;
+ 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(messages, 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)
+ {
+ uint32_t cnt, flags;
+ rv = messages->GetLength(&cnt);
+ NS_ENSURE_SUCCESS(rv, rv);
+ deleteMsgs = false;
+ for (uint32_t i=0; i <cnt; i++)
+ {
+ nsCOMPtr <nsIMsgDBHdr> msgHdr = do_QueryElementAt(messages, i);
+ if (msgHdr)
+ {
+ 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.Elements(),
+ srcKeyArray.Length(), urlListener);
+
+ if (NS_SUCCEEDED(rv))
+ {
+ if (mDatabase)
+ {
+ nsCOMPtr<nsIMsgDatabase> database(mDatabase);
+ if (deleteModel == nsMsgImapDeleteModels::IMAPDelete)
+ MarkMessagesImapDeleted(&srcKeyArray, deleteMsgs, database);
+ else
+ {
+ EnableNotifications(allMessageCountNotifications, false, true /*dbBatching*/); //"remove it immediately" model
+ // Notify if this is an actual delete.
+ if (!isMove)
+ {
+ nsCOMPtr<nsIMsgFolderNotificationService> notifier(do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID));
+ if (notifier)
+ notifier->NotifyMsgsDeleted(messages);
+ }
+ DeleteStoreMessages(messages);
+ database->DeleteMessages(srcKeyArray.Length(), srcKeyArray.Elements(), nullptr);
+ EnableNotifications(allMessageCountNotifications, true, true /*dbBatching*/);
+ }
+ NotifyFolderEvent(mDeleteOrMoveMsgCompletedAtom);
+ }
+ }
+ return rv;
+ }
+ else // have to move the messages to the trash
+ {
+ if(trashFolder)
+ {
+ nsCOMPtr<nsIMsgFolder> srcFolder;
+ nsCOMPtr<nsISupports>srcSupport;
+ uint32_t count = 0;
+ rv = messages->GetLength(&count);
+
+ rv = QueryInterface(NS_GET_IID(nsIMsgFolder), getter_AddRefs(srcFolder));
+ nsCOMPtr<nsIMsgCopyService> copyService = do_GetService(NS_MSGCOPYSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = copyService->CopyMessages(srcFolder, messages, 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::DeleteSubFolders(nsIArray* folders, nsIMsgWindow *msgWindow)
+{
+ nsCOMPtr<nsIMsgFolder> curFolder;
+ nsCOMPtr<nsIUrlListener> urlListener;
+ nsCOMPtr<nsIMsgFolder> trashFolder;
+ int32_t i;
+ uint32_t folderCount = 0;
+ nsresult rv;
+ // "this" is the folder we're deleting from
+ bool deleteNoTrash = TrashOrDescendentOfTrash(this) || !DeleteIsMoveToTrash();
+ bool confirmed = false;
+ bool confirmDeletion = true;
+
+ nsCOMPtr<nsIMutableArray> foldersRemaining(do_CreateInstance(NS_ARRAY_CONTRACTID));
+ folders->GetLength(&folderCount);
+
+ for (i = folderCount - 1; i >= 0; i--)
+ {
+ curFolder = do_QueryElementAt(folders, i, &rv);
+ if (NS_SUCCEEDED(rv))
+ {
+ uint32_t folderFlags;
+ curFolder->GetFlags(&folderFlags);
+ if (folderFlags & nsMsgFolderFlags::Virtual)
+ {
+ RemoveSubFolder(curFolder);
+ // since the folder pane only allows single selection, we can do this
+ deleteNoTrash = confirmed = true;
+ confirmDeletion = false;
+ }
+ else
+ foldersRemaining->InsertElementAt(curFolder, 0, false);
+ }
+ }
+
+ foldersRemaining->GetLength(&folderCount);
+
+ nsCOMPtr<nsIImapService> imapService = do_GetService(NS_IMAPSERVICE_CONTRACTID, &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 (!confirmed && (confirmDeletion || deleteNoTrash)) //let us alert the user if we are deleting folder immediately
+ {
+ nsCOMPtr<nsIStringBundle> bundle;
+ rv = IMAPGetStringBundle(getter_AddRefs(bundle));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString folderName;
+ rv = curFolder->GetName(folderName);
+ NS_ENSURE_SUCCESS(rv, rv);
+ const char16_t *formatStrings[1] = { folderName.get() };
+
+ nsAutoString deleteFolderDialogTitle;
+ rv = bundle->GetStringFromName(
+ u"imapDeleteFolderDialogTitle",
+ getter_Copies(deleteFolderDialogTitle));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString deleteFolderButtonLabel;
+ rv = bundle->GetStringFromName(
+ u"imapDeleteFolderButtonLabel",
+ getter_Copies(deleteFolderButtonLabel));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString confirmationStr;
+ rv = bundle->FormatStringFromName((deleteNoTrash) ?
+ u"imapDeleteNoTrash" :
+ u"imapMoveFolderToTrash",
+ formatStrings, 1, getter_Copies(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)
+ {
+ for (i = 0; i < (int32_t) folderCount; i++)
+ {
+ curFolder = do_QueryElementAt(foldersRemaining, i, &rv);
+ if (NS_SUCCEEDED(rv))
+ {
+ urlListener = do_QueryInterface(curFolder);
+ if (deleteNoTrash)
+ rv = imapService->DeleteFolder(curFolder,
+ urlListener,
+ msgWindow,
+ nullptr);
+ else
+ {
+ bool confirm = false;
+ bool match = false;
+ rv = curFolder->MatchOrChangeFilterDestination(nullptr, false, &match);
+ if (match)
+ {
+ curFolder->ConfirmFolderDeletionForFilter(msgWindow, &confirm);
+ if (!confirm)
+ return NS_OK;
+ }
+ rv = imapService->MoveFolder(curFolder,
+ trashFolder,
+ urlListener,
+ msgWindow,
+ nullptr);
+ }
+ }
+ }
+ }
+ //delete subfolders only if you are deleting things from trash
+ return confirmed && deleteNoTrash ? nsMsgDBFolder::DeleteSubFolders(foldersRemaining, msgWindow) : 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;
+ NS_IF_RELEASE(m_moveCoalescer);
+ 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 <nsISimpleEnumerator> enumerator;
+ nsresult rv = mDatabase->EnumerateMessages(getter_AddRefs(enumerator));
+ if (NS_SUCCEEDED(rv) && enumerator)
+ {
+ bool hasMore;
+ while (NS_SUCCEEDED(rv = enumerator->HasMoreElements(&hasMore)) && hasMore)
+ {
+ nsCOMPtr <nsISupports> supports;
+ rv = enumerator->GetNext(getter_AddRefs(supports));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr <nsIMsgDBHdr> pHeader = do_QueryInterface(supports, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ bool shouldStoreMsgOffline = false;
+ nsMsgKey msgKey;
+ pHeader->GetMessageKey(&msgKey);
+ // MsgFitsDownloadCriteria ignores nsMsgFolderFlags::Offline, which we want
+ if (m_downloadingFolderForOfflineUse)
+ MsgFitsDownloadCriteria(msgKey, &shouldStoreMsgOffline);
+ else
+ ShouldStoreMsgOffline(msgKey, &shouldStoreMsgOffline);
+ if (shouldStoreMsgOffline)
+ keysOfMessagesToDownload->AppendElement(msgKey);
+ }
+ }
+ 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.
+ m_numServerUnseenMessages = 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);
+ char intStrBuf[40];
+ PR_snprintf(intStrBuf, sizeof(intStrBuf), "%llu", mailboxHighestModSeq);
+ dbFolderInfo->SetCharProperty(kModSeqPropertyName, nsDependentCString(intStrBuf));
+ }
+ RefPtr<nsMsgKeyArray> keys = new nsMsgKeyArray;
+ if (!keys)
+ return NS_ERROR_OUT_OF_MEMORY;
+ rv = mDatabase->ListAllKeys(keys);
+ NS_ENSURE_SUCCESS(rv, rv);
+ existingKeys.AppendElements(keys->m_keys);
+ mDatabase->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(NS_MSGDB_SERVICE_CONTRACTID, &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.
+ 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;
+ 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)
+ {
+ nsCOMPtr<nsIMutableArray> hdrsToDelete(do_CreateInstance(NS_ARRAY_CONTRACTID));
+ MsgGetHeadersFromKeys(mDatabase, keysToDelete, hdrsToDelete);
+ // Notify nsIMsgFolderListeners of a mass delete, but only if we actually have headers
+ uint32_t numHdrs;
+ hdrsToDelete->GetLength(&numHdrs);
+ if (numHdrs)
+ {
+ nsCOMPtr<nsIMsgFolderNotificationService> notifier(do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID));
+ if (notifier)
+ notifier->NotifyMsgsDeleted(hdrsToDelete);
+ }
+ DeleteStoreMessages(hdrsToDelete);
+ EnableNotifications(nsIMsgFolder::allMessageCountNotifications, false, false);
+ mDatabase->DeleteMessages(keysToDelete.Length(), keysToDelete.Elements(), nullptr);
+ EnableNotifications(nsIMsgFolder::allMessageCountNotifications, true, false);
+ }
+ int32_t numUnreadFromServer;
+ aSpec->GetNumUnseenMessages(&numUnreadFromServer);
+
+ bool partialUIDFetch;
+ flagState->GetPartialUIDFetch(&partialUIDFetch);
+
+ // For partial UID fetches, we can only trust the numUnread from the server.
+ 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)
+ {
+ // 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 && (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);
+ aSpec->GetNumUnseenMessages(&m_numServerUnseenMessages);
+ 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;
+}
+
+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 != 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;
+ const char *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, strlen(msgHdrs));
+ 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, 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);
+
+ 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));
+
+ 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, 1, 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, 1, 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.
+ m_filterList->ApplyFiltersToHdr(nsMsgFilterType::InboxRule, newMsgHdr,
+ this, mDatabase, headers, headersSize,
+ this, msgWindow);
+ NotifyFolderEvent(mFiltersAppliedAtom);
+ }
+ }
+ }
+ }
+ // here we need to tweak flags from uid state..
+ if (mDatabase && (!m_msgMovedByFilter || ShowDeletedMessages()))
+ {
+ nsCOMPtr<nsIMsgFolderNotificationService> notifier(do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID));
+ // 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;
+ nsMsgKey pseudoKey = nsMsgKey_None;
+ newMsgHdr->GetMessageId(getter_Copies(newMessageId));
+ m_pseudoHdrs.Get(newMessageId, &pseudoKey);
+ 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)
+ 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, NS_LITERAL_CSTRING("X-GM-MSGID"), msgIDValue);
+ flagState->GetCustomAttribute(m_curMsgUid, NS_LITERAL_CSTRING("X-GM-THRID"), threadIDValue);
+ flagState->GetCustomAttribute(m_curMsgUid, NS_LITERAL_CSTRING("X-GM-LABELS"), labelsValue);
+ newMsgHdr->SetStringProperty("X-GM-MSGID", msgIDValue.get());
+ newMsgHdr->SetStringProperty("X-GM-THRID", threadIDValue.get());
+ newMsgHdr->SetStringProperty("X-GM-LABELS", labelsValue.get());
+ }
+
+ 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(nsIMsgDBHdr *message)
+{
+ 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->GetNativePath(nativePath);
+ MOZ_LOG(IMAP, mozilla::LogLevel::Info, ("couldn't remove prev temp file %s: %lx\n", nativePath.get(), rv));
+ }
+ m_copyState->m_tmpFile = nullptr;
+ }
+ if (message)
+ m_copyState->m_message = do_QueryInterface(message, &rv);
+
+ rv = GetSpecialDirectoryWithFileName(NS_OS_TEMP_DIR,
+ "nscpmsg.txt",
+ getter_AddRefs(m_copyState->m_tmpFile));
+ if (NS_FAILED(rv))
+ MOZ_LOG(IMAP, mozilla::LogLevel::Info, ("couldn't find nscpmsg.txt:%lx\n", rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // create a unique file, since multiple copies may be open on multiple folders
+ rv = m_copyState->m_tmpFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 00600);
+ if (NS_FAILED(rv))
+ {
+ MOZ_LOG(IMAP, mozilla::LogLevel::Info, ("couldn't create temp nscpmsg.txt:%lx\n", rv));
+ // Last ditch attempt to create a temp file, because virus checker might
+ // be locking the previous temp file, and CreateUnique fails if the file
+ // is locked. Use the message key to make a unique name.
+ if (message)
+ {
+ nsCString tmpFileName("nscpmsg-");
+ nsMsgKey msgKey;
+ message->GetMessageKey(&msgKey);
+ tmpFileName.AppendInt(msgKey);
+ tmpFileName.Append(".txt");
+ m_copyState->m_tmpFile->SetNativeLeafName(tmpFileName);
+ rv = m_copyState->m_tmpFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 00600);
+ if (NS_FAILED(rv))
+ {
+ MOZ_LOG(IMAP, mozilla::LogLevel::Info, ("couldn't create temp nscpmsg.txt:%lx\n", rv));
+ OnCopyCompleted(m_copyState->m_srcSupport, rv);
+ return 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:%lx\n", 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:%lx\n", 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(NS_IMAPSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ rv = QueryInterface(NS_GET_IID(nsIUrlListener), getter_AddRefs(urlListener));
+ nsCOMPtr<nsISupports> copySupport;
+ if (m_copyState)
+ copySupport = do_QueryInterface(m_copyState);
+ rv = imapService->AppendMessageFromFile(m_copyState->m_tmpFile,
+ this, EmptyCString(), true,
+ m_copyState->m_selectedState,
+ urlListener, nullptr,
+ copySupport,
+ m_copyState->m_msgWindow);
+ }
+ if (NS_FAILED(rv) || !copySucceeded)
+ MOZ_LOG(IMAP, mozilla::LogLevel::Info, ("EndCopy failed:%lx\n", 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;
+
+ // look at action - currently handle move
+#ifdef DEBUG_bienvenu
+ printf("got a rule hit!\n");
+#endif
+
+ 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();
+
+ nsCOMPtr<nsIArray> filterActionList;
+
+ rv = filter->GetSortedActionList(getter_AddRefs(filterActionList));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t numActions;
+ rv = filterActionList->GetLength(&numActions);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool loggingEnabled = false;
+ if (m_filterList && numActions)
+ (void)m_filterList->GetLoggingEnabled(&loggingEnabled);
+
+ bool msgIsNew = true;
+
+ for (uint32_t actionIndex = 0; actionIndex < numActions; actionIndex++)
+ {
+ nsCOMPtr<nsIMsgRuleAction> filterAction;
+ rv = filterActionList->QueryElementAt(actionIndex, NS_GET_IID(nsIMsgRuleAction),
+ getter_AddRefs(filterAction));
+ if (NS_FAILED(rv) || !filterAction)
+ continue;
+
+ nsMsgRuleActionType actionType;
+ if (NS_SUCCEEDED(filterAction->GetType(&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())
+ {
+ NS_ASSERTION(false, "actionTargetFolderUri is empty");
+ continue;
+ }
+ }
+
+ uint32_t msgFlags;
+ nsMsgKey msgKey;
+ nsAutoCString trashNameVal;
+
+ msgHdr->GetFlags(&msgFlags);
+ msgHdr->GetMessageKey(&msgKey);
+ bool isRead = (msgFlags & nsMsgMessageFlags::Read);
+ nsresult rv = GetDatabase();
+ NS_ENSURE_SUCCESS(rv, rv);
+ 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);
+ // msgHdr->OrFlags(nsMsgMessageFlags::Read, &newFlags); // mark read in trash.
+ }
+ else // (!deleteToTrash)
+ {
+ mDatabase->MarkHdrRead(msgHdr, true, nullptr);
+ mDatabase->MarkImapDeleted(msgKey, true, nullptr);
+ StoreImapFlags(kImapMsgSeenFlag | kImapMsgDeletedFlag, true,
+ &msgKey, 1, nullptr);
+ m_msgMovedByFilter = true; // this will prevent us from adding the header to the db.
+ }
+ msgIsNew = false;
+ }
+ // note that delete falls through to move.
+ MOZ_FALLTHROUGH;
+ case nsMsgFilterAction::MoveToFolder:
+ {
+ // if moving to a different file, do it.
+ nsCString uri;
+ rv = GetURI(uri);
+
+ if (!actionTargetFolderUri.Equals(uri))
+ {
+ msgHdr->GetFlags(&msgFlags);
+
+ if (msgFlags & nsMsgMessageFlags::MDNReportNeeded && !isRead)
+ {
+ mDatabase->MarkMDNNeeded(msgKey, false, nullptr);
+ mDatabase->MarkMDNSent(msgKey, true, nullptr);
+ }
+ nsresult err = MoveIncorporatedMessage(msgHdr, mDatabase, actionTargetFolderUri, filter, msgWindow);
+ if (NS_SUCCEEDED(err))
+ m_msgMovedByFilter = true;
+ }
+ // 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 (!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<nsIMutableArray> messageArray(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv));
+ NS_ENSURE_TRUE(messageArray, rv);
+ messageArray->AppendElement(msgHdr, false);
+
+ nsCOMPtr<nsIMsgFolder> dstFolder;
+ rv = GetExistingFolder(actionTargetFolderUri, getter_AddRefs(dstFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgCopyService> copyService =
+ do_GetService(NS_MSGCOPYSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = copyService->CopyMessages(this, messageArray, dstFolder,
+ false, nullptr, msgWindow, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+ break;
+ case nsMsgFilterAction::MarkRead:
+ {
+ mDatabase->MarkHdrRead(msgHdr, true, nullptr);
+ StoreImapFlags(kImapMsgSeenFlag, true, &msgKey, 1, nullptr);
+ msgIsNew = false;
+ }
+ break;
+ case nsMsgFilterAction::MarkUnread:
+ {
+ mDatabase->MarkHdrRead(msgHdr, false, nullptr);
+ StoreImapFlags(kImapMsgSeenFlag, false, &msgKey, 1, nullptr);
+ msgIsNew = true;
+ }
+ break;
+ case nsMsgFilterAction::MarkFlagged:
+ {
+ mDatabase->MarkHdrMarked(msgHdr, true, nullptr);
+ StoreImapFlags(kImapMsgFlaggedFlag, true, &msgKey, 1, 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)
+ mDatabase->MarkThreadIgnored(msgThread, threadKey, true, nullptr);
+ else
+ mDatabase->MarkThreadWatched(msgThread, threadKey, true, nullptr);
+ }
+ else
+ {
+ if (actionType == nsMsgFilterAction::KillThread)
+ msgHdr->SetUint32Property("ProtoThreadFlags", nsMsgMessageFlags::Ignored);
+ else
+ msgHdr->SetUint32Property("ProtoThreadFlags", nsMsgMessageFlags::Watched);
+ }
+ if (actionType == nsMsgFilterAction::KillThread)
+ {
+ mDatabase->MarkHdrRead(msgHdr, true, nullptr);
+ StoreImapFlags(kImapMsgSeenFlag, true, &msgKey, 1, nullptr);
+ msgIsNew = false;
+ }
+ }
+ break;
+ case nsMsgFilterAction::KillSubthread:
+ {
+ mDatabase->MarkHeaderKilled(msgHdr, true, nullptr);
+ mDatabase->MarkHdrRead(msgHdr, true, nullptr);
+ StoreImapFlags(kImapMsgSeenFlag, true, &msgKey, 1, nullptr);
+ msgIsNew = false;
+ }
+ break;
+ case nsMsgFilterAction::ChangePriority:
+ {
+ nsMsgPriorityValue filterPriority; // a int32_t
+ filterAction->GetPriority(&filterPriority);
+ mDatabase->SetUint32PropertyByHdr(msgHdr, "priority",
+ static_cast<uint32_t>(filterPriority));
+ }
+ break;
+ case nsMsgFilterAction::Label:
+ {
+ nsMsgLabelValue filterLabel;
+ filterAction->GetLabel(&filterLabel);
+ mDatabase->SetUint32PropertyByHdr(msgHdr, "label",
+ static_cast<uint32_t>(filterLabel));
+ StoreImapFlags((filterLabel << 9), true, &msgKey, 1, nullptr);
+ }
+ break;
+ case nsMsgFilterAction::AddTag:
+ {
+ nsCString keyword;
+ filterAction->GetStrValue(keyword);
+ nsCOMPtr<nsIMutableArray> messageArray(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv));
+ NS_ENSURE_TRUE(messageArray, rv);
+ messageArray->AppendElement(msgHdr, false);
+ AddKeywordsToMessages(messageArray, keyword);
+ break;
+ }
+ case nsMsgFilterAction::JunkScore:
+ {
+ nsAutoCString junkScoreStr;
+ int32_t junkScore;
+ filterAction->GetJunkScore(&junkScore);
+ junkScoreStr.AppendInt(junkScore);
+ mDatabase->SetStringProperty(msgKey, "junkscore", junkScoreStr.get());
+ mDatabase->SetStringProperty(msgKey, "junkscoreorigin", "filter");
+
+ // 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));
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!forwardTo.IsEmpty())
+ {
+ nsCOMPtr<nsIMsgComposeService> compService =
+ do_GetService (NS_MSGCOMPOSESERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = compService->ForwardMessage(NS_ConvertASCIItoUTF16(forwardTo),
+ msgHdr, msgWindow, server,
+ nsIMsgComposeService::kForwardAsDefault);
+ }
+ }
+ break;
+
+ case nsMsgFilterAction::Reply:
+ {
+ nsCString replyTemplateUri;
+ filterAction->GetStrValue(replyTemplateUri);
+ nsCOMPtr <nsIMsgIncomingServer> server;
+ GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!replyTemplateUri.IsEmpty())
+ {
+ nsCOMPtr <nsIMsgComposeService> compService = do_GetService (NS_MSGCOMPOSESERVICE_CONTRACTID) ;
+ if (compService) {
+ rv = compService->ReplyWithTemplate(msgHdr, replyTemplateUri.get(), msgWindow, server);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("ReplyWithTemplate failed");
+ if (rv == NS_ERROR_ABORT) {
+ filter->LogRuleHitFail(filterAction, msgHdr, rv, "Sending reply aborted");
+ } else {
+ filter->LogRuleHitFail(filterAction, msgHdr, rv, "Error sending reply");
+ }
+ }
+ }
+ }
+ }
+ break;
+
+ case nsMsgFilterAction::StopExecution:
+ {
+ // don't apply any more filters
+ *applyMore = false;
+ }
+ break;
+
+ case nsMsgFilterAction::Custom:
+ {
+ nsCOMPtr<nsIMsgFilterCustomAction> customAction;
+ rv = filterAction->GetCustomAction(getter_AddRefs(customAction));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString value;
+ filterAction->GetStrValue(value);
+
+ nsCOMPtr<nsIMutableArray> messageArray(
+ do_CreateInstance(NS_ARRAY_CONTRACTID, &rv));
+ NS_ENSURE_TRUE(messageArray, rv);
+ messageArray->AppendElement(msgHdr, false);
+
+ customAction->Apply(messageArray, value, nullptr,
+ nsMsgFilterType::InboxRule, msgWindow);
+ // allow custom action to affect new
+ msgHdr->GetFlags(&msgFlags);
+ if (!(msgFlags & nsMsgMessageFlags::New))
+ msgIsNew = false;
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+ }
+ 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);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailFolder::SetImapFlags(const char *uids, int32_t flags, nsIURI **url)
+{
+ nsresult rv;
+ nsCOMPtr<nsIImapService> imapService = do_GetService(NS_IMAPSERVICE_CONTRACTID, &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(NS_IMAPSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+ return imapService->CreateFolder(this, aFolderName, this, url);
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::ReplayOfflineMoveCopy(nsMsgKey *aMsgKeys, uint32_t aNumKeys,
+ bool isMove, nsIMsgFolder *aDstFolder,
+ nsIUrlListener *aUrlListener, nsIMsgWindow *aWindow)
+{
+ nsresult rv;
+
+ nsCOMPtr <nsIMsgImapMailFolder> imapFolder = do_QueryInterface(aDstFolder);
+ if (imapFolder)
+ {
+ nsImapMailFolder *destImapFolder = static_cast<nsImapMailFolder*>(aDstFolder);
+ nsCOMPtr<nsIMutableArray> messages(do_CreateInstance(NS_ARRAY_CONTRACTID));
+ nsCOMPtr<nsIMsgDatabase> dstFolderDB;
+ aDstFolder->GetMsgDatabase(getter_AddRefs(dstFolderDB));
+ if (dstFolderDB)
+ {
+ // 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(dstFolderDB->ListAllOfflineOpIds(&offlineOps)))
+ {
+ nsCString srcFolderUri;
+ GetURI(srcFolderUri);
+ nsCOMPtr<nsIMsgOfflineImapOperation> currentOp;
+ for (uint32_t opIndex = 0; opIndex < offlineOps.Length(); opIndex++)
+ {
+ dstFolderDB->GetOfflineOpForKey(offlineOps[opIndex], false,
+ getter_AddRefs(currentOp));
+ if (currentOp)
+ {
+ nsCString opSrcUri;
+ currentOp->GetSourceFolderURI(getter_Copies(opSrcUri));
+ if (opSrcUri.Equals(srcFolderUri))
+ {
+ nsMsgKey srcMessageKey;
+ currentOp->GetSrcMessageKey(&srcMessageKey);
+ for (uint32_t msgIndex = 0; msgIndex < aNumKeys; msgIndex++)
+ {
+ if (srcMessageKey == aMsgKeys[msgIndex])
+ {
+ nsCOMPtr<nsIMsgDBHdr> fakeDestHdr;
+ dstFolderDB->GetMsgHdrForKey(offlineOps[opIndex],
+ getter_AddRefs(fakeDestHdr));
+ if (fakeDestHdr)
+ messages->AppendElement(fakeDestHdr, false);
+ break;
+ }
+ }
+ }
+ }
+ }
+ destImapFolder->SetPendingAttributes(messages, isMove);
+ }
+ }
+ // if we can't get the dst folder db, we should still try to playback
+ // the offline move/copy.
+ }
+
+ nsCOMPtr<nsIImapService> imapService = do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr <nsIURI> resultUrl;
+ nsAutoCString uids;
+ AllocateUidStringFromKeys(aMsgKeys, aNumKeys, 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.Put(messageId, aMsgKey);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailFolder::StoreImapFlags(int32_t flags, bool addFlags,
+ nsMsgKey *keys, uint32_t numKeys,
+ nsIUrlListener *aUrlListener)
+{
+ nsresult rv;
+ if (!WeAreOffline())
+ {
+ nsCOMPtr<nsIImapService> imapService = do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsAutoCString msgIds;
+ AllocateUidStringFromKeys(keys, numKeys, msgIds);
+ if (addFlags)
+ imapService->AddMessageFlags(this, aUrlListener ? aUrlListener : this,
+ nullptr, msgIds, flags, true);
+ else
+ imapService->SubtractMessageFlags(this, aUrlListener ? aUrlListener : this,
+ nullptr, msgIds, flags, true);
+ }
+ else
+ {
+ rv = GetDatabase();
+ if (NS_SUCCEEDED(rv) && mDatabase)
+ {
+ uint32_t total = numKeys;
+ for (uint32_t keyIndex = 0; keyIndex < total; keyIndex++)
+ {
+ nsCOMPtr <nsIMsgOfflineImapOperation> op;
+ rv = mDatabase->GetOfflineOpForKey(keys[keyIndex], true, getter_AddRefs(op));
+ SetFlag(nsMsgFolderFlags::OfflineEvents);
+ if (NS_SUCCEEDED(rv) && op)
+ {
+ imapMessageFlagsType newFlags;
+ op->GetNewFlags(&newFlags);
+ op->SetFlagOperation(addFlags ? newFlags | flags : newFlags & ~flags);
+ }
+ }
+ mDatabase->Commit(nsMsgDBCommitType::kLargeCommit); // flush offline flags
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapMailFolder::LiteSelect(nsIUrlListener *aUrlListener,
+ nsIMsgWindow *aMsgWindow)
+{
+ nsresult rv;
+ nsCOMPtr<nsIImapService> imapService = do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return imapService->LiteSelectFolder(this, aUrlListener,
+ aMsgWindow, nullptr);
+}
+
+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->LoadUrl(uri);
+ }
+ }
+ }
+ else
+ {
+ nsCOMPtr<nsIImapService> imapService = do_GetService(NS_IMAPSERVICE_CONTRACTID, &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(NS_IMAPSERVICE_CONTRACTID, &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(NS_IMAPSERVICE_CONTRACTID, &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<nsIRDFService> rdf(do_GetService(kRDFServiceCID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIRDFResource> res;
+ rv = rdf->GetResource(destFolderUri, getter_AddRefs(res));
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsCOMPtr<nsIMsgFolder> destIFolder(do_QueryInterface(res, &rv));
+ if (NS_FAILED(rv))
+ return 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 <nsISimpleEnumerator> hdrs;
+ nsresult rv = GetMessages(getter_AddRefs(hdrs));
+ NS_ENSURE_SUCCESS_VOID(rv);
+ bool hasMore = false;
+ nsCOMPtr <nsIMsgDBHdr> pHeader;
+ while (NS_SUCCEEDED(rv = hdrs->HasMoreElements(&hasMore)) && hasMore)
+ {
+ nsCOMPtr <nsISupports> supports;
+ rv = hdrs->GetNext(getter_AddRefs(supports));
+ NS_ENSURE_SUCCESS_VOID(rv);
+ pHeader = do_QueryInterface(supports, &rv);
+ NS_ENSURE_SUCCESS_VOID(rv);
+ uint32_t msgFlags;
+ pHeader->GetFlags(&msgFlags);
+ if (msgFlags & nsMsgMessageFlags::IMAPDeleted)
+ {
+ nsMsgKey msgKey;
+ pHeader->GetMessageKey(&msgKey);
+ keysToDelete.AppendElement(msgKey);
+ }
+ }
+ }
+ return;
+ }
+ // otherwise, we have a complete set of uid's and flags, so we delete
+ // anything thats 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) &&
+ (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;
+ else
+ 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,
+ uint32_t *aLength,
+ nsMsgKey **aKeys)
+{
+ NS_ENSURE_ARG_POINTER(aMoreToDownload);
+ NS_ENSURE_ARG_POINTER(aTotalCount);
+ NS_ENSURE_ARG_POINTER(aLength);
+ NS_ENSURE_ARG_POINTER(aKeys);
+
+ *aMoreToDownload = false;
+ *aTotalCount = m_totalKeysToFetch;
+ if (m_keysToFetch.IsEmpty())
+ {
+ *aLength = 0;
+ 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(NS_MSGMAILSESSION_CONTRACTID);
+ 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 = (nsMsgKey *) nsMemory::Clone(&m_keysToFetch[startIndex],
+ numKeysToFetch * sizeof(nsMsgKey));
+ NS_ENSURE_TRUE(*aKeys, NS_ERROR_OUT_OF_MEMORY);
+ // 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);
+ *aLength = numKeysToFetch;
+
+ return NS_OK;
+}
+
+void nsImapMailFolder::PrepareToAddHeadersToMailDB(nsIImapProtocol* aProtocol)
+{
+ // now, tell it we don't need any bodies.
+ aProtocol->NotifyBodysToDownload(nullptr, 0);
+}
+
+void nsImapMailFolder::TweakHeaderFlags(nsIImapProtocol* aProtocol, nsIMsgDBHdr *tweakMe)
+{
+ if (mDatabase && aProtocol && tweakMe)
+ {
+ tweakMe->SetMessageKey(m_curMsgUid);
+ tweakMe->SetMessageSize(m_nextMessageByteLength);
+
+ bool foundIt = false;
+ imapMessageFlagsType imap_flags;
+
+ nsCString customFlags;
+ nsresult rv = aProtocol->GetFlagsForUID(m_curMsgUid, &foundIt, &imap_flags, getter_Copies(customFlags));
+ if (NS_SUCCEEDED(rv) && foundIt)
+ {
+ // 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:
+ 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;
+
+ // db label flags are 0x0E000000 and imap label flags are 0x0E00
+ // so we need to shift 16 bits to the left to convert them.
+ if (imap_flags & kImapMsgLabelFlags)
+ {
+ // we need to set label attribute on header because the dbview code
+ // does msgHdr->GetLabel when asked to paint a row
+ tweakMe->SetLabel((imap_flags & kImapMsgLabelFlags) >> 9);
+ newFlags |= (imap_flags & kImapMsgLabelFlags) << 16;
+ }
+ 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);
+ 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;
+
+ m_tempMessageStream->Write(result.get(), result.Length(), &writeCount);
+ result = "X-Mozilla-Status: 0001";
+ result += MSG_LINEBREAK;
+ m_tempMessageStream->Write(result.get(), result.Length(), &writeCount);
+ result = "X-Mozilla-Status2: 00000000";
+ result += MSG_LINEBREAK;
+ m_tempMessageStream->Write(result.get(), result.Length(), &writeCount);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapMailFolder::DownloadMessagesForOffline(nsIArray *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(NS_IMAPSERVICE_CONTRACTID, &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(NS_IMAPSERVICE_CONTRACTID, &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)
+ {
+ nsCOMPtr <nsISeekableStream> seekable (do_QueryInterface(m_tempMessageStream));
+ if (seekable)
+ seekable->Seek(PR_SEEK_END, 0);
+ rv = m_tempMessageStream->Write(adoptedMessageLine,
+ PL_strlen(adoptedMessageLine), &count);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ 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();
+
+ 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, nullptr, 0, this,
+ msgWindow);
+ NotifyFolderEvent(mFiltersAppliedAtom);
+ }
+ // 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(NS_IMAPSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+ return imapService->AddMessageFlags(this, nullptr, nullptr,
+ messageIds,
+ kImapMsgDeletedFlag,
+ true);
+ }
+ /* unhandled copystate */
+ else 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(mDeleteOrMoveMsgCompletedAtom);
+ }
+ 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;
+ // Mac Mail uses "NotJunk"
+ if (keywords.Find("NonJunk", CaseInsensitiveCompare) != kNotFound ||
+ keywords.Find("NotJunk", CaseInsensitiveCompare) != kNotFound)
+ {
+ nsAutoCString msgJunkScore;
+ msgJunkScore.AppendInt(nsIJunkMailPlugin::IS_HAM_SCORE);
+ mDatabase->SetStringProperty(uidOfMessage, "junkscore", msgJunkScore.get());
+ }
+ // ### TODO: we really should parse the keywords into space delimited keywords before checking
+ else if (keywords.Find("Junk", CaseInsensitiveCompare) != kNotFound)
+ {
+ uint32_t newFlags;
+ dbHdr->AndFlags(~nsMsgMessageFlags::New, &newFlags);
+ nsAutoCString msgJunkScore;
+ msgJunkScore.AppendInt(nsIJunkMailPlugin::IS_SPAM_SCORE);
+ mDatabase->SetStringProperty(uidOfMessage, "junkscore", msgJunkScore.get());
+ }
+ else
+ messageClassified = false;
+ if (messageClassified)
+ {
+ // only set the junkscore origin if it wasn't set before.
+ nsCString existingProperty;
+ dbHdr->GetStringProperty("junkscoreorigin", getter_Copies(existingProperty));
+ if (existingProperty.IsEmpty())
+ dbHdr->SetStringProperty("junkscoreorigin", "imapflag");
+ }
+ return (userFlags & kImapMsgSupportUserFlag) ?
+ dbHdr->SetStringProperty("keywords", keywords.get()) : 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);
+ nsCOMPtr<nsIMsgDBHdr> dbHdr;
+ 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;
+
+ rv = mDatabase->GetMsgHdrForKey(uidOfMessage, getter_AddRefs(dbHdr));
+ 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(kFolderSizeAtom, 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);
+ // this turns on labels, but it doesn't handle the case where the user
+ // unlabels a message on one machine, and expects it to be unlabeled
+ // on their other machines. If I turn that on, I'll be removing all the labels
+ // that were assigned before we started storing them on the server, which will
+ // make some people very unhappy.
+ if (flags & kImapMsgLabelFlags)
+ database->SetLabel(msgKey, (flags & kImapMsgLabelFlags) >> 9);
+ else
+ {
+ if (supportedFlags & kImapMsgLabelFlags)
+ database->SetLabel(msgKey, 0);
+ }
+ 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);
+ 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.Length(), affectedMessages.Elements(), 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::SetContentModified(nsIImapUrl *aImapUrl, nsImapContentModifiedType modified)
+{
+ return aImapUrl->SetContentModified(modified);
+}
+
+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_message)
+ {
+ nsMsgLabelValue label;
+ mailCopyState->m_message->GetFlags(aResult);
+ if (supportedFlags & (kImapMsgSupportUserFlag | kImapMsgLabelFlags))
+ {
+ mailCopyState->m_message->GetLabel(&label);
+ if (label != 0)
+ *aResult |= label << 25;
+ }
+ if (aDate)
+ mailCopyState->m_message->GetDate(aDate);
+ if (supportedFlags & kImapMsgSupportUserFlag)
+ {
+ // setup the custom imap keywords, which includes the message keywords
+ // plus any junk status
+ nsCString junkscore;
+ mailCopyState->m_message->GetStringProperty("junkscore",
+ getter_Copies(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
+ mailCopyState->m_message->GetStringProperty("keywords",
+ getter_Copies(keywords));
+ int32_t start;
+ int32_t length;
+ bool hasJunk = MsgFindKeyword(NS_LITERAL_CSTRING("junk"),
+ keywords, &start, &length);
+ if (hasJunk && !isJunk)
+ keywords.Cut(start, length);
+ else if (!hasJunk && isJunk)
+ keywords.AppendLiteral(" Junk");
+ bool hasNonJunk = MsgFindKeyword(NS_LITERAL_CSTRING("nonjunk"),
+ keywords, &start, &length);
+ if (!hasNonJunk)
+ hasNonJunk = MsgFindKeyword(NS_LITERAL_CSTRING("notjunk"),
+ 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_LITERAL_CSTRING(" "))) >= 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;
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::OnStartRunningUrl(nsIURI *aUrl)
+{
+ NS_PRECONDITION(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;
+}
+
+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(NS_MSGMAILSESSION_CONTRACTID, &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(NS_MSGNOTIFICATIONSERVICE_CONTRACTID));
+ if (notifier && m_copyState)
+ {
+ if (imapAction == nsIImapUrl::nsImapOnlineMove)
+ notifier->NotifyMsgsMoveCopyCompleted(true, m_copyState->m_messages, this, nullptr);
+ else if (imapAction == nsIImapUrl::nsImapOnlineCopy)
+ notifier->NotifyMsgsMoveCopyCompleted(false, m_copyState->m_messages, this, nullptr);
+ 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.Length(), srcKeyArray.Elements(), nullptr);
+ }
+ else
+ MarkMessagesImapDeleted(&srcKeyArray, true, srcDB);
+ }
+ srcFolder->EnableNotifications(allMessageCountNotifications, true, true/* dbBatching*/);
+ // even if we're showing deleted messages,
+ // we still need to notify FE so it will show the imap deleted flag
+ srcFolder->NotifyFolderEvent(mDeleteOrMoveMsgCompletedAtom);
+ // 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, true/* dbBatching*/);
+ srcFolder->NotifyFolderEvent(mDeleteOrMoveMsgFailedAtom);
+ }
+
+ }
+ 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)
+ {
+ mozilla::DebugOnly<nsresult> rv2 = txnMgr->DoTransaction(m_copyState->m_undoMsgTxn);
+ NS_ASSERTION(NS_SUCCEEDED(rv2), "doing transaction failed");
+ }
+ }
+ (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;
+ uint32_t numHdrs = 0;
+ nsCOMPtr<nsIMsgPluggableStore> offlineStore;
+ (void) GetMsgStore(getter_AddRefs(offlineStore));
+ if (offlineStore)
+ offlineStore->GetSupportsCompaction(&supportsCompaction);
+
+ nsCOMPtr<nsIMutableArray> msgHdrs;
+ if (notifier || !supportsCompaction)
+ {
+ msgHdrs = do_CreateInstance(NS_ARRAY_CONTRACTID);
+ NS_ENSURE_STATE(msgHdrs);
+ MsgGetHeadersFromKeys(db, keyArray, msgHdrs);
+ msgHdrs->GetLength(&numHdrs);
+ }
+
+ // Notify listeners of delete.
+ if (notifier && numHdrs)
+ {
+ // 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 && numHdrs)
+ DeleteStoreMessages(msgHdrs);
+
+ db->DeleteMessages(keyArray.Length(), keyArray.Elements(), 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_totalCount)
+ {
+ nsCOMPtr<nsIUrlListener> saveUrlListener = m_urlListener;
+ if (folderOpen)
+ {
+ // This gives a way for the caller to get notified
+ // when the UpdateFolder url is done.
+ 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)
+ txnMgr->DoTransaction(m_copyState->m_undoMsgTxn);
+ }
+ (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(NS_MSGCOPYSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIMsgFolder> srcFolder = do_QueryInterface(m_copyState->m_srcSupport);
+ if (srcFolder)
+ {
+ nsCOMPtr<nsIMsgFolder> destFolder;
+ nsString srcName;
+ srcFolder->GetName(srcName);
+ GetChildNamed(srcName, getter_AddRefs(destFolder));
+ if (destFolder)
+ copyService->NotifyCompletion(m_copyState->m_srcSupport, destFolder, aExitCode);
+ }
+ m_copyState = nullptr;
+ }
+ break;
+ case nsIImapUrl::nsImapRenameFolder:
+ if (NS_FAILED(aExitCode))
+ {
+ nsCOMPtr <nsIAtom> folderRenameAtom;
+ folderRenameAtom = MsgGetAtom("RenameCompleted");
+ NotifyFolderEvent(folderRenameAtom);
+ }
+ 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)
+ imapParent->RemoveSubFolder(this);
+ }
+ }
+ }
+ 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
+ {
+ nsCOMPtr <nsIAtom> folderCreateAtom;
+ folderCreateAtom = MsgGetAtom("FolderCreateFailed");
+ NotifyFolderEvent(folderCreateAtom);
+ }
+ 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)
+ {
+ msgFolder->GetURI(uri);
+ nsCOMPtr<nsIMsgWindowCommands> windowCommands;
+ msgWindow->GetWindowCommands(getter_AddRefs(windowCommands));
+ if (windowCommands)
+ windowCommands->SelectFolder(uri);
+ }
+ }
+ }
+ }
+ }
+ break;
+ case nsIImapUrl::nsImapExpungeFolder:
+ m_expunging = false;
+ 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);
+ // don't send OnStopRunning notification if still compacting offline store.
+ if (m_urlListener && (imapAction != nsIImapUrl::nsImapExpungeFolder ||
+ !m_compactingOfflineStore))
+ {
+ nsCOMPtr<nsIUrlListener> saveListener = m_urlListener;
+ m_urlListener = nullptr;
+ saveListener->OnStopRunningUrl(aUrl, aExitCode);
+ }
+ return rv;
+}
+
+void nsImapMailFolder::UpdatePendingCounts()
+{
+ if (m_copyState)
+ {
+ ChangePendingTotal(m_copyState->m_isCrossServerOp ? 1 : m_copyState->m_totalCount);
+
+ // 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);
+ // this is the case when DownloadAllForOffline is called.
+ if (!keysToDownload.IsEmpty() && (m_downloadingFolderForOfflineUse ||
+ autoDownloadNewHeaders))
+ {
+ notifiedBodies = true;
+ aProtocol->NotifyBodysToDownload(keysToDownload.Elements(), keysToDownload.Length());
+ }
+ else
+ {
+ // create auto-sync state object lazily
+ InitAutoSyncState();
+
+ // make enough room for new downloads
+ m_autoSyncStateObj->ManageStorageSpace();
+ m_autoSyncStateObj->SetServerCounts(m_numServerTotalMessages,
+ m_numServerRecentMessages,
+ m_numServerUnseenMessages,
+ m_nextUID);
+ m_autoSyncStateObj->OnNewHeaderFetchCompleted(keysToDownload);
+ }
+ }
+ if (!notifiedBodies)
+ aProtocol->NotifyBodysToDownload(nullptr, 0/*keysToFetch.Length() */);
+
+ 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);
+
+ // get the host session list and get server capabilities.
+ eIMAPCapabilityFlags capability = kCapabilityUndefined;
+
+ 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))
+ {
+ bool haveACL = false;
+ bool haveQuota = false;
+ imapServer->GetCapabilityACL(&haveACL);
+ imapServer->GetCapabilityQuota(&haveQuota);
+
+ // Figure out what to display in the Quota tab of the folder properties.
+ // Does the server support quotas?
+ if (haveQuota)
+ {
+ // Have we asked the server for quota information?
+ if(m_folderQuotaCommandIssued)
+ {
+ // Has the server replied with storage quota info?
+ if(m_folderQuotaDataIsValid)
+ {
+ // If so, set quota data
+ folderQuotaStatusStringID = nullptr;
+ aFolderProps->SetQuotaData(m_folderQuotaRoot, m_folderQuotaUsedKB, m_folderQuotaMaxKB);
+ }
+ else
+ {
+ // If not, there is no storage quota set on this folder
+ folderQuotaStatusStringID = "imapQuotaStatusNoQuota";
+ }
+ }
+ 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 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())
+ {
+ rv = 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
+ {
+ // is this right? It doesn't leak, does it?
+ CopyASCIItoUTF16(owner, uniOwner);
+ }
+ const char16_t *params[] = { uniOwner.get() };
+ rv = bundle->FormatStringFromName(
+ u"imapOtherUsersFolderTypeDescription",
+ params, 1, getter_Copies(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)
+ rv = 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);
+
+ // we need the real user name to match with what the imap server returns
+ // in the acl response.
+ server->GetRealUsername(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, &oldValue);
+ if (!oldValue.IsEmpty())
+ {
+ m_rightsHash.Remove(ourUserName);
+ m_aclCount--;
+ NS_ASSERTION(m_aclCount >= 0, "acl count can't go negative");
+ }
+ m_aclCount++;
+ m_rightsHash.Put(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);
+}
+
+class AdoptUTF8StringEnumerator final : public nsIUTF8StringEnumerator
+{
+public:
+ explicit AdoptUTF8StringEnumerator(nsTArray<nsCString>* array) :
+ mStrings(array), mIndex(0)
+ {}
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIUTF8STRINGENUMERATOR
+private:
+ ~AdoptUTF8StringEnumerator()
+ {
+ delete mStrings;
+ }
+
+ nsTArray<nsCString>* mStrings;
+ uint32_t mIndex;
+};
+
+NS_IMPL_ISUPPORTS(AdoptUTF8StringEnumerator, nsIUTF8StringEnumerator)
+
+NS_IMETHODIMP
+AdoptUTF8StringEnumerator::HasMore(bool *aResult)
+{
+ *aResult = mIndex < mStrings->Length();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+AdoptUTF8StringEnumerator::GetNext(nsACString& aResult)
+{
+ if (mIndex >= mStrings->Length())
+ return NS_ERROR_UNEXPECTED;
+
+ aResult.Assign((*mStrings)[mIndex]);
+ ++mIndex;
+ return NS_OK;
+}
+
+nsresult nsMsgIMAPFolderACL::GetOtherUsers(nsIUTF8StringEnumerator** aResult)
+{
+ nsTArray<nsCString>* resultArray = new nsTArray<nsCString>;
+ for (auto iter = m_rightsHash.Iter(); !iter.Done(); iter.Next()) {
+ resultArray->AppendElement(iter.Key());
+ }
+
+ // enumerator will free resultArray
+ *aResult = new AdoptUTF8StringEnumerator(resultArray);
+ return NS_OK;
+}
+
+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->GetRealUsername(userName);
+ }
+ ToLowerCase(userName);
+ m_rightsHash.Get(userName, &rights);
+ 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(NS_LITERAL_CSTRING(IMAP_ACL_ANYONE_STRING), anyoneFlags);
+ if (anyoneFlags.IsEmpty())
+ return defaultIfNotFound;
+ else
+ return (anyoneFlags.FindChar(flag) != kNotFound);
+ }
+ else
+ 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(NS_LITERAL_CSTRING(IMAP_ACL_ANYONE_STRING), &anyonesRights);
+ 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(u"imapAclFullRights",
+ getter_Copies(result));
+ aRightsString.Assign(result);
+ return rv;
+ }
+ else
+ {
+ if (GetCanIReadFolder())
+ {
+ bundle->GetStringFromName(u"imapAclReadRight",
+ getter_Copies(curRight));
+ aRightsString.Append(curRight);
+ }
+ if (GetCanIWriteFolder())
+ {
+ if (!aRightsString.IsEmpty()) aRightsString.AppendLiteral(", ");
+ bundle->GetStringFromName(u"imapAclWriteRight",
+ getter_Copies(curRight));
+ aRightsString.Append(curRight);
+ }
+ if (GetCanIInsertInFolder())
+ {
+ if (!aRightsString.IsEmpty()) aRightsString.AppendLiteral(", ");
+ bundle->GetStringFromName(u"imapAclInsertRight",
+ getter_Copies(curRight));
+ aRightsString.Append(curRight);
+ }
+ if (GetCanILookupFolder())
+ {
+ if (!aRightsString.IsEmpty()) aRightsString.AppendLiteral(", ");
+ bundle->GetStringFromName(u"imapAclLookupRight",
+ getter_Copies(curRight));
+ aRightsString.Append(curRight);
+ }
+ if (GetCanIStoreSeenInFolder())
+ {
+ if (!aRightsString.IsEmpty()) aRightsString.AppendLiteral(", ");
+ bundle->GetStringFromName(u"imapAclSeenRight",
+ getter_Copies(curRight));
+ aRightsString.Append(curRight);
+ }
+ if (GetCanIDeleteInFolder())
+ {
+ if (!aRightsString.IsEmpty()) aRightsString.AppendLiteral(", ");
+ bundle->GetStringFromName(u"imapAclDeleteRight",
+ getter_Copies(curRight));
+ aRightsString.Append(curRight);
+ }
+ if (GetCanIExpungeFolder())
+ {
+ if (!aRightsString.IsEmpty())
+ aRightsString.AppendLiteral(", ");
+ bundle->GetStringFromName(u"imapAclExpungeRight",
+ getter_Copies(curRight));
+ aRightsString.Append(curRight);
+ }
+ if (GetCanICreateSubfolder())
+ {
+ if (!aRightsString.IsEmpty()) aRightsString.AppendLiteral(", ");
+ bundle->GetStringFromName(u"imapAclCreateRight",
+ getter_Copies(curRight));
+ aRightsString.Append(curRight);
+ }
+ if (GetCanIPostToFolder())
+ {
+ if (!aRightsString.IsEmpty()) aRightsString.AppendLiteral(", ");
+ bundle->GetStringFromName(u"imapAclPostRight",
+ getter_Copies(curRight));
+ aRightsString.Append(curRight);
+ }
+ if (GetCanIAdministerFolder())
+ {
+ if (!aRightsString.IsEmpty()) aRightsString.AppendLiteral(", ");
+ bundle->GetStringFromName(u"imapAclAdministerRight",
+ getter_Copies(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)
+ {
+ nsCOMPtr<nsIRequest> request = do_QueryInterface(mockChannel);
+ if (!request) return NS_ERROR_FAILURE;
+ progressSink->OnStatus(request, nullptr, 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)
+ {
+ char16_t *printfString = nsTextFormatter::smprintf(progressMsg.get(), extraInfo);
+ if (printfString)
+ progressMsg.Adopt(printfString);
+ }
+
+ DisplayStatusMsg(imapUrl, progressMsg);
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::PercentProgress(nsIImapProtocol* aProtocol,
+ const char16_t * aMessage,
+ 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)
+ {
+ nsCOMPtr<nsIRequest> request = do_QueryInterface(mockChannel);
+ if (!request) return NS_ERROR_FAILURE;
+ progressSink->OnProgress(request, nullptr,
+ aCurrentProgress,
+ aMaxProgress);
+ if (aMessage)
+ progressSink->OnStatus(request, nullptr, NS_OK, aMessage); // XXX i18n message
+ }
+ }
+ }
+ }
+ 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:%lx\n", rv));
+ return rv; // this can fail...
+ }
+
+ if (!mailCopyState->m_streamCopy)
+ return NS_OK;
+
+ MOZ_LOG(IMAP, mozilla::LogLevel::Info, ("CopyNextStreamMessage: Copying %ld of %ld\n", mailCopyState->m_curIndex, mailCopyState->m_totalCount));
+ if (mailCopyState->m_curIndex < mailCopyState->m_totalCount)
+ {
+ mailCopyState->m_message = do_QueryElementAt(mailCopyState->m_messages,
+ mailCopyState->m_curIndex,
+ &rv);
+ if (NS_SUCCEEDED(rv))
+ {
+ bool isRead;
+ mailCopyState->m_message->GetIsRead(&isRead);
+ mailCopyState->m_unreadCount = (isRead) ? 0 : 1;
+ rv = CopyStreamMessage(mailCopyState->m_message,
+ this, mailCopyState->m_msgWindow, mailCopyState->m_isMove);
+ }
+ else
+ {
+ MOZ_LOG(IMAP, mozilla::LogLevel::Info, ("QueryElementAt %ld failed:%lx\n", mailCopyState->m_curIndex, rv));
+ }
+ }
+ else
+ {
+ // Notify of move/copy completion in case we have some source headers
+ nsCOMPtr<nsIMsgFolderNotificationService> notifier(do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID));
+ if (notifier)
+ {
+ uint32_t numHdrs;
+ mailCopyState->m_messages->GetLength(&numHdrs);
+ if (numHdrs)
+ notifier->NotifyMsgsMoveCopyCompleted(mailCopyState->m_isMove, mailCopyState->m_messages, this, nullptr);
+ }
+ if (mailCopyState->m_isMove)
+ {
+ nsCOMPtr<nsIMsgFolder> srcFolder(do_QueryInterface(mailCopyState->m_srcSupport, &rv));
+ if (NS_SUCCEEDED(rv) && srcFolder)
+ {
+ srcFolder->DeleteMessages(mailCopyState->m_messages, nullptr,
+ true, true, nullptr, false);
+ // we want to send this notification after the source messages have
+ // been deleted.
+ nsCOMPtr<nsIMsgLocalMailFolder> popFolder(do_QueryInterface(srcFolder));
+ if (popFolder) //needed if move pop->imap to notify FE
+ srcFolder->NotifyFolderEvent(mDeleteOrMoveMsgCompletedAtom);
+ }
+ }
+ }
+ 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,
+ nsIArray* messages,
+ bool isMove,
+ bool isCrossServerOp,
+ nsIMsgWindow *msgWindow,
+ nsIMsgCopyServiceListener* listener,
+ bool allowUndo)
+{
+ NS_ENSURE_ARG_POINTER(srcFolder);
+ NS_ENSURE_ARG_POINTER(messages);
+ nsresult rv;
+ nsCOMPtr<nsISupports> aSupport(do_QueryInterface(srcFolder, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = InitCopyState(aSupport, 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;
+ }
+ nsCOMPtr<nsIMsgDBHdr> msg;
+ msg = do_QueryElementAt(messages, 0, &rv);
+ if (NS_SUCCEEDED(rv))
+ CopyStreamMessage(msg, 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(getter_Copies(sourceFolderURI));
+
+ nsCOMPtr<nsIRDFResource> res;
+ nsresult rv;
+ nsCOMPtr<nsIRDFService> rdf(do_GetService(kRDFServiceCID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = rdf->GetResource(sourceFolderURI, getter_AddRefs(res));
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr<nsIMsgFolder> sourceFolder(do_QueryInterface(res, &rv));
+ if (NS_SUCCEEDED(rv) && sourceFolder)
+ {
+ if (sourceFolder)
+ {
+ nsCOMPtr <nsIDBFolderInfo> folderInfo;
+ sourceFolder->GetDBFolderInfoAndDB(getter_AddRefs(folderInfo), originalDB);
+ if (*originalDB)
+ {
+ nsMsgKey originalKey;
+ op->GetMessageKey(&originalKey);
+ rv = (*originalDB)->GetOfflineOpForKey(originalKey, false, getter_AddRefs(returnOp));
+ if (NS_SUCCEEDED(rv) && returnOp)
+ {
+ nsCString moveDestination;
+ nsCString thisFolderURI;
+ GetURI(thisFolderURI);
+ returnOp->GetDestinationFolderURI(getter_Copies(moveDestination));
+ if (moveDestination.Equals(thisFolderURI))
+ returnOp->ClearOperation(nsIMsgOfflineImapOperation::kMoveResult);
+ }
+ }
+ }
+ }
+ }
+ returnOp.swap(*originalOp);
+ return rv;
+}
+
+nsresult nsImapMailFolder::GetOriginalOp(nsIMsgOfflineImapOperation *op, nsIMsgOfflineImapOperation **originalOp, nsIMsgDatabase **originalDB)
+{
+ nsCOMPtr<nsIMsgOfflineImapOperation> returnOp;
+ nsCString sourceFolderURI;
+ op->GetSourceFolderURI(getter_Copies(sourceFolderURI));
+
+ nsCOMPtr<nsIRDFResource> res;
+ nsresult rv;
+
+ nsCOMPtr<nsIRDFService> rdf(do_GetService(kRDFServiceCID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = rdf->GetResource(sourceFolderURI, getter_AddRefs(res));
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr<nsIMsgFolder> sourceFolder(do_QueryInterface(res, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr <nsIDBFolderInfo> folderInfo;
+ sourceFolder->GetDBFolderInfoAndDB(getter_AddRefs(folderInfo), originalDB);
+ if (*originalDB)
+ {
+ nsMsgKey originalKey;
+ op->GetMessageKey(&originalKey);
+ rv = (*originalDB)->GetOfflineOpForKey(originalKey, false, getter_AddRefs(returnOp));
+ }
+ }
+ returnOp.swap(*originalOp);
+ return rv;
+}
+
+nsresult nsImapMailFolder::CopyOfflineMsgBody(nsIMsgFolder *srcFolder,
+ nsIMsgDBHdr *destHdr,
+ nsIMsgDBHdr *origHdr,
+ nsIInputStream *inputStream,
+ nsIOutputStream *outputStream)
+{
+ nsresult rv;
+ nsCOMPtr <nsISeekableStream> seekable (do_QueryInterface(outputStream, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ uint64_t messageOffset;
+ uint32_t messageSize;
+ origHdr->GetMessageOffset(&messageOffset);
+ if (!messageOffset)
+ {
+ // Some offline stores may contain a bug where the storeToken is set but
+ // the messageOffset is zero. Detect cases like this, and use storeToken
+ // to set the missing messageOffset. Note this assumes mbox.
+ nsCOMPtr<nsIMsgPluggableStore> offlineStore;
+ (void) GetMsgStore(getter_AddRefs(offlineStore));
+ if (offlineStore)
+ {
+ nsAutoCString type;
+ offlineStore->GetStoreType(type);
+ if (type.EqualsLiteral("mbox"))
+ {
+ nsCString storeToken;
+ origHdr->GetStringProperty("storeToken", getter_Copies(storeToken));
+ if (!storeToken.IsEmpty())
+ messageOffset = ParseUint64Str(storeToken.get());
+ }
+ }
+ }
+ origHdr->GetOfflineMessageSize(&messageSize);
+ if (!messageSize)
+ {
+ nsCOMPtr<nsIMsgLocalMailFolder> localFolder = do_QueryInterface(srcFolder);
+ if (localFolder) //can just use regular message size
+ origHdr->GetMessageSize(&messageSize);
+ }
+ int64_t tellPos;
+ seekable->Tell(&tellPos);
+ destHdr->SetMessageOffset(tellPos);
+ nsCOMPtr<nsISeekableStream> seekStream = do_QueryInterface(inputStream);
+ NS_ASSERTION(seekStream, "non seekable stream - can't read from offline msg");
+ if (seekStream)
+ {
+ rv = seekStream->Seek(nsISeekableStream::NS_SEEK_SET, messageOffset);
+ if (NS_SUCCEEDED(rv))
+ {
+ // now, copy the dest folder offline store msg to the temp file
+ char *inputBuffer = (char *) PR_Malloc(FILE_IO_BUFFER_SIZE);
+ int32_t bytesLeft;
+ uint32_t bytesRead, bytesWritten;
+ bytesLeft = messageSize;
+ rv = (inputBuffer) ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
+ while (bytesLeft > 0 && NS_SUCCEEDED(rv))
+ {
+ rv = inputStream->Read(inputBuffer, FILE_IO_BUFFER_SIZE, &bytesRead);
+ if (NS_SUCCEEDED(rv) && bytesRead > 0)
+ {
+ rv = outputStream->Write(inputBuffer, std::min((int32_t) bytesRead, bytesLeft), &bytesWritten);
+ NS_ASSERTION((int32_t) bytesWritten == std::min((int32_t) bytesRead, bytesLeft), "wrote out incorrect number of bytes");
+ }
+ else
+ break;
+ bytesLeft -= bytesRead;
+ }
+ PR_FREEIF(inputBuffer);
+ }
+ }
+ if (NS_SUCCEEDED(rv))
+ {
+ outputStream->Flush();
+ uint32_t resultFlags;
+ destHdr->OrFlags(nsMsgMessageFlags::Offline, &resultFlags);
+ destHdr->SetOfflineMessageSize(messageSize);
+ }
+ 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;
+}
+
+// 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,
+ nsIArray* messages,
+ bool isMove,
+ nsIMsgWindow *msgWindow,
+ nsIMsgCopyServiceListener* listener)
+{
+ NS_ENSURE_ARG(messages);
+ 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->GetLength(&srcCount);
+ nsCOMPtr<nsIImapIncomingServer> imapServer;
+ rv = GetImapIncomingServer(getter_AddRefs(imapServer));
+ nsCOMPtr<nsIMutableArray> msgHdrsCopied(do_CreateInstance(NS_ARRAY_CONTRACTID));
+ nsCOMPtr<nsIMutableArray> destMsgHdrs(do_CreateInstance(NS_ARRAY_CONTRACTID));
+
+ if (!msgHdrsCopied || !destMsgHdrs)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ 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);
+ nsCOMPtr<nsIInputStream> inputStream;
+ bool reusable = false;
+ nsCOMPtr<nsIOutputStream> outputStream;
+ 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, false);
+ for (uint32_t sourceKeyIndex = 0; NS_SUCCEEDED(stopit) && (sourceKeyIndex < srcCount); sourceKeyIndex++)
+ {
+ bool messageReturningHome = false;
+ nsCString originalSrcFolderURI;
+ srcFolder->GetURI(originalSrcFolderURI);
+ nsCOMPtr<nsIMsgDBHdr> message;
+ message = do_QueryElementAt(messages, sourceKeyIndex);
+ nsMsgKey originalKey;
+ if (message)
+ rv = message->GetMessageKey(&originalKey);
+ else
+ {
+ NS_ERROR("bad msg in src array");
+ continue;
+ }
+ nsMsgKey msgKey;
+ message->GetMessageKey(&msgKey);
+ nsCOMPtr <nsIMsgOfflineImapOperation> sourceOp;
+ rv = sourceMailDB->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;
+ rv = GetClearedOriginalOp(sourceOp, getter_AddRefs(originalOp), getter_AddRefs(originalDB));
+ if (originalOp)
+ {
+ nsCString srcFolderURI;
+ srcFolder->GetURI(srcFolderURI);
+ sourceOp->GetSourceFolderURI(getter_Copies(originalSrcFolderURI));
+ sourceOp->GetMessageKey(&originalKey);
+ if (isMove)
+ sourceMailDB->RemoveOfflineOp(sourceOp);
+ sourceOp = originalOp;
+ if (originalSrcFolderURI.Equals(srcFolderURI))
+ {
+ messageReturningHome = true;
+ originalDB->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.get()); // 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.get()); // offline copy
+
+ sourceOp->GetOperation(&moveCopyOpType);
+ srcMsgs.AppendObject(message);
+ }
+ bool hasMsgOffline = false;
+ srcFolder->HasMsgOffline(originalKey, &hasMsgOffline);
+ }
+ else
+ stopit = NS_ERROR_FAILURE;
+
+ nsCOMPtr <nsIMsgDBHdr> mailHdr;
+ rv = sourceMailDB->GetMsgHdrForKey(originalKey, getter_AddRefs(mailHdr));
+ if (NS_SUCCEEDED(rv) && mailHdr)
+ {
+ 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, false);
+ srcFolder->HasMsgOffline(originalKey, &hasMsgOffline);
+ newMailHdr->SetUint32Property("pseudoHdr", 1);
+ if (!reusable)
+ (void)srcFolder->GetMsgInputStream(newMailHdr, &reusable,
+ getter_AddRefs(inputStream));
+
+ if (inputStream && hasMsgOffline && !isLocked)
+ {
+ rv = GetOfflineStoreOutputStream(newMailHdr,
+ getter_AddRefs(outputStream));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ CopyOfflineMsgBody(srcFolder, newMailHdr, mailHdr, inputStream,
+ outputStream);
+ nsCOMPtr<nsIMsgPluggableStore> offlineStore;
+ (void) GetMsgStore(getter_AddRefs(offlineStore));
+ if (offlineStore)
+ offlineStore->FinishNewMessage(outputStream, newMailHdr);
+ }
+ else
+ database->MarkOffline(fakeBase + sourceKeyIndex, false, nullptr);
+
+ nsCOMPtr <nsIMsgOfflineImapOperation> destOp;
+ database->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)
+ database->RemoveOfflineOp(destOp);
+ else
+ {
+ SetFlag(nsMsgFolderFlags::OfflineEvents);
+ destOp->SetSourceFolderURI(originalSrcFolderURI.get());
+ 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, false);
+ }
+ }
+ EnableNotifications(nsIMsgFolder::allMessageCountNotifications, true, false);
+ 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 (outputStream)
+ outputStream->Close();
+
+ if (isMove)
+ sourceMailDB->Commit(nsMsgDBCommitType::kLargeCommit);
+ database->Commit(nsMsgDBCommitType::kLargeCommit);
+ SummaryChanged();
+ srcFolder->SummaryChanged();
+ }
+ if (txnMgr)
+ txnMgr->EndBatch(false);
+ }
+
+ // Do this before delete, as it destroys the messages
+ uint32_t numHdrs;
+ msgHdrsCopied->GetLength(&numHdrs);
+ if (numHdrs)
+ {
+ nsCOMPtr<nsIMsgFolderNotificationService> notifier(do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID));
+ if (notifier)
+ notifier->NotifyMsgsMoveCopyCompleted(isMove, msgHdrsCopied, this, destMsgHdrs);
+ }
+
+ if (isMove && NS_SUCCEEDED(rv) && (deleteToTrash || deleteImmediately))
+ {
+ DeleteStoreMessages(keysToDelete, srcFolder);
+ srcFolder->EnableNotifications(nsIMsgFolder::allMessageCountNotifications, false, false);
+ sourceMailDB->DeleteMessages(keysToDelete.Length(), keysToDelete.Elements(),
+ nullptr);
+ srcFolder->EnableNotifications(nsIMsgFolder::allMessageCountNotifications, true, false);
+ }
+
+ nsCOMPtr<nsISupports> srcSupport = do_QueryInterface(srcFolder);
+ OnCopyCompleted(srcSupport, rv);
+
+ if (isMove)
+ srcFolder->NotifyFolderEvent(NS_SUCCEEDED(rv) ?
+ mDeleteOrMoveMsgCompletedAtom :
+ mDeleteOrMoveMsgFailedAtom);
+ return rv;
+}
+
+void nsImapMailFolder::SetPendingAttributes(nsIArray* messages, bool aIsMove)
+{
+
+ 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",
+ getter_Copies(dontPreserve));
+ else
+ prefBranch->GetCharPref("mailnews.database.summary.dontPreserveOnCopy",
+ getter_Copies(dontPreserve));
+
+ // We'll add spaces at beginning and end so we can search for space-name-space
+ nsCString dontPreserveEx(NS_LITERAL_CSTRING(" "));
+ dontPreserveEx.Append(dontPreserve);
+ dontPreserveEx.AppendLiteral(" ");
+
+ // 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 ");
+
+ uint32_t i, count;
+
+ rv = messages->GetLength(&count);
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ // check if any msg hdr has special flags or properties set
+ // that we need to set on the dest hdr
+ for (i = 0; i < count; i++)
+ {
+ nsCOMPtr <nsIMsgDBHdr> msgDBHdr = do_QueryElementAt(messages, i, &rv);
+ if (mDatabase && msgDBHdr)
+ {
+ if (!(supportedUserFlags & kImapMsgSupportUserFlag))
+ {
+ nsMsgLabelValue label;
+ msgDBHdr->GetLabel(&label);
+ if (label != 0)
+ {
+ nsAutoCString labelStr;
+ labelStr.AppendInt(label);
+ mDatabase->SetAttributeOnPendingHdr(msgDBHdr, "label", labelStr.get());
+ }
+ nsCString keywords;
+ msgDBHdr->GetStringProperty("keywords", getter_Copies(keywords));
+ if (!keywords.IsEmpty())
+ mDatabase->SetAttributeOnPendingHdr(msgDBHdr, "keywords", keywords.get());
+ }
+
+ // do this even if the server supports user-defined flags.
+ nsCOMPtr<nsIUTF8StringEnumerator> propertyEnumerator;
+ nsresult rv = msgDBHdr->GetPropertyEnumerator(getter_AddRefs(propertyEnumerator));
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ nsAutoCString property;
+ nsCString sourceString;
+ bool hasMore;
+ while (NS_SUCCEEDED(propertyEnumerator->HasMore(&hasMore)) && hasMore)
+ {
+ propertyEnumerator->GetNext(property);
+ nsAutoCString propertyEx(NS_LITERAL_CSTRING(" "));
+ propertyEx.Append(property);
+ propertyEx.AppendLiteral(" ");
+ if (dontPreserveEx.Find(propertyEx) != kNotFound)
+ continue;
+
+ nsCString sourceString;
+ msgDBHdr->GetStringProperty(property.get(), getter_Copies(sourceString));
+ mDatabase->SetAttributeOnPendingHdr(msgDBHdr, property.get(), sourceString.get());
+ }
+
+ uint32_t messageSize;
+ uint64_t messageOffset;
+ nsCString storeToken;
+ msgDBHdr->GetMessageOffset(&messageOffset);
+ msgDBHdr->GetOfflineMessageSize(&messageSize);
+ msgDBHdr->GetStringProperty("storeToken", getter_Copies(storeToken));
+ if (messageSize)
+ {
+ mDatabase->SetUint32AttributeOnPendingHdr(msgDBHdr, "offlineMsgSize",
+ messageSize);
+ mDatabase->SetUint64AttributeOnPendingHdr(msgDBHdr, "msgOffset",
+ messageOffset);
+ mDatabase->SetUint32AttributeOnPendingHdr(msgDBHdr, "flags",
+ 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,
+ nsIArray* 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 <nsIMsgIncomingServer> srcServer;
+ nsCOMPtr <nsIMsgIncomingServer> dstServer;
+ nsCOMPtr<nsISupports> srcSupport = do_QueryInterface(srcFolder);
+ bool sameServer = false;
+
+ rv = srcFolder->GetServer(getter_AddRefs(srcServer));
+ if(NS_FAILED(rv)) goto done;
+
+ rv = GetServer(getter_AddRefs(dstServer));
+ if(NS_FAILED(rv)) goto done;
+
+ NS_ENSURE_TRUE(dstServer, NS_ERROR_NULL_POINTER);
+
+ rv = dstServer->Equals(srcServer, &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);
+
+ // lazily create playback timer if it is not already
+ // created
+ if (!srcImapFolder->m_playbackTimer)
+ {
+ rv = srcImapFolder->CreatePlaybackTimer();
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+
+ if (srcImapFolder->m_playbackTimer)
+ {
+ // 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);
+ if (!srcImapFolder->m_pendingPlaybackReq)
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ srcImapFolder->m_playbackTimer->InitWithFuncCallback(PlaybackTimerCallback, (void *) srcImapFolder->m_pendingPlaybackReq,
+ PLAYBACK_TIMER_INTERVAL_IN_MS, nsITimer::TYPE_ONE_SHOT);
+ }
+ return rv;
+ }
+ else
+ {
+ // sort the message array by key
+
+ uint32_t numMsgs = 0;
+ messages->GetLength(&numMsgs);
+ nsTArray<nsMsgKey> keyArray(numMsgs);
+ for (uint32_t i = 0; i < numMsgs; i++)
+ {
+ nsCOMPtr<nsIMsgDBHdr> aMessage = do_QueryElementAt(messages, i, &rv);
+ if (NS_SUCCEEDED(rv) && aMessage)
+ {
+ nsMsgKey key;
+ aMessage->GetMessageKey(&key);
+ keyArray.AppendElement(key);
+ }
+ }
+ keyArray.Sort();
+
+ nsCOMPtr<nsIMutableArray> sortedMsgs(do_CreateInstance(NS_ARRAY_CONTRACTID));
+ rv = MessagesInKeyOrder(keyArray, srcFolder, sortedMsgs);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (WeAreOffline())
+ return CopyMessagesOffline(srcFolder, sortedMsgs, isMove, msgWindow, listener);
+
+ nsCOMPtr<nsIImapService> imapService = do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ SetPendingAttributes(sortedMsgs, isMove);
+
+ // 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.Elements(), numMsgs, 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_totalCount;
+
+ if (isMove)
+ srcFolder->EnableNotifications(allMessageCountNotifications, false, true/* dbBatching*/); //disable message count notification
+
+ nsCOMPtr<nsISupports> copySupport = do_QueryInterface(m_copyState);
+ rv = imapService->OnlineMessageCopy(srcFolder, messageIds,
+ this, true, isMove,
+ urlListener, nullptr,
+ 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, true/* dbBatching*/); //enable message count notification
+ NotifyFolderEvent(mDeleteOrMoveMsgFailedAtom);
+ }
+ }
+ return rv;
+}
+
+class nsImapFolderCopyState final : public nsIUrlListener, public nsIMsgCopyServiceListener
+{
+public:
+ nsImapFolderCopyState(nsIMsgFolder *destParent, nsIMsgFolder *srcFolder,
+ bool isMoveFolder, 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_isMoveFolder;
+ 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 isMoveFolder, nsIMsgWindow *msgWindow, nsIMsgCopyServiceListener *listener)
+{
+ m_origSrcFolder = do_QueryInterface(srcFolder);
+ m_curDestParent = destParent;
+ m_curSrcFolder = srcFolder;
+ m_isMoveFolder = isMoveFolder;
+ m_msgWindow = msgWindow;
+ m_copySrvcListener = listener;
+ m_childIndex = -1;
+}
+
+nsImapFolderCopyState::~nsImapFolderCopyState()
+{
+}
+
+nsresult
+nsImapFolderCopyState::StartNextCopy()
+{
+ nsresult rv;
+ // first make sure dest folder exists.
+ nsCOMPtr <nsIImapService> imapService = do_GetService (NS_IMAPSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsString folderName;
+ m_curSrcFolder->GetName(folderName);
+
+ return imapService->EnsureFolderExists(m_curDestParent,
+ folderName,
+ this, nullptr);
+}
+
+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);
+ Release();
+ }
+ else
+ {
+ m_curDestParent = m_destParents[m_childIndex];
+ m_curSrcFolder = m_srcChildFolders[m_childIndex];
+ rv = StartNextCopy();
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsImapFolderCopyState::OnStartRunningUrl(nsIURI *aUrl)
+{
+ NS_PRECONDITION(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);
+ Release();
+ 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:
+ {
+ nsCOMPtr<nsIMsgFolder> newMsgFolder;
+ nsString folderName;
+ nsCString utf7LeafName;
+ m_curSrcFolder->GetName(folderName);
+ rv = CopyUTF16toMUTF7(folderName, utf7LeafName);
+ rv = m_curDestParent->FindSubFolder(utf7LeafName, 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.
+ nsCOMPtr<nsISimpleEnumerator> enumerator;
+ rv = m_curSrcFolder->GetSubFolders(getter_AddRefs(enumerator));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsISupports> item;
+ bool hasMore = false;
+ uint32_t childIndex = 0;
+ while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMore)) && hasMore)
+ {
+ rv = enumerator->GetNext(getter_AddRefs(item));
+ nsCOMPtr<nsIMsgFolder> folder(do_QueryInterface(item, &rv));
+ if (NS_SUCCEEDED(rv))
+ {
+ m_srcChildFolders.InsertElementAt(m_childIndex + childIndex + 1, folder);
+ m_destParents.InsertElementAt(m_childIndex + childIndex + 1, newMsgFolder);
+ }
+ ++childIndex;
+ }
+
+ rv = m_curSrcFolder->GetMessages(getter_AddRefs(enumerator));
+ nsCOMPtr<nsIMutableArray> msgArray(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv));
+ NS_ENSURE_TRUE(msgArray, rv);
+ hasMore = false;
+
+ if (enumerator)
+ rv = enumerator->HasMoreElements(&hasMore);
+
+ if (!hasMore)
+ return AdvanceToNextFolder(NS_OK);
+
+ while (NS_SUCCEEDED(rv) && hasMore)
+ {
+ rv = enumerator->GetNext(getter_AddRefs(item));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = msgArray->AppendElement(item, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = enumerator->HasMoreElements(&hasMore);
+ }
+
+ nsCOMPtr<nsIMsgCopyService> copyService = do_GetService(NS_MSGCOPYSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = copyService->CopyMessages(m_curSrcFolder,
+ msgArray, newMsgFolder,
+ m_isMoveFolder,
+ 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;
+ }
+ Release();
+
+ return NS_OK;
+}
+
+// "this" is the parent of the copied folder.
+NS_IMETHODIMP
+nsImapMailFolder::CopyFolder(nsIMsgFolder* srcFolder,
+ bool isMoveFolder,
+ nsIMsgWindow *msgWindow,
+ nsIMsgCopyServiceListener* listener)
+{
+ NS_ENSURE_ARG_POINTER(srcFolder);
+
+ nsresult rv = NS_OK;
+
+ if (isMoveFolder) //move folder permitted when dstFolder and the srcFolder are on same server
+ {
+ 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);
+
+ NotifyItemAdded(newMsgFolder);
+
+ // now remove the old folder
+ nsCOMPtr<nsIMsgFolder> msgParent;
+ srcFolder->GetParent(getter_AddRefs(msgParent));
+ srcFolder->SetParent(nullptr);
+ if (msgParent)
+ {
+ msgParent->PropagateDelete(srcFolder, false, msgWindow); // The files have already been moved, so delete storage false
+ oldPathFile->Remove(false); //berkeley mailbox
+ nsCOMPtr <nsIMsgDatabase> srcDB; // we need to force closed the source db
+ srcFolder->Delete();
+
+ nsCOMPtr<nsIFile> parentPathFile;
+ rv = msgParent->GetFilePath(getter_AddRefs(parentPathFile));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ AddDirectorySeparator(parentPathFile);
+ nsCOMPtr <nsISimpleEnumerator> 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 (NS_IMAPSERVICE_CONTRACTID, &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, nullptr, false, false, false,
+ 0, EmptyCString(), listener, msgWindow, false);
+ if (NS_FAILED(rv))
+ return OnCopyCompleted(srcSupport, rv);
+
+ rv = imapService->MoveFolder(srcFolder,
+ this,
+ this,
+ msgWindow,
+ nullptr);
+ }
+ }
+ else // copying folder (should only be across server?)
+ {
+ nsImapFolderCopyState *folderCopier = new nsImapFolderCopyState(this, srcFolder, isMoveFolder, msgWindow, listener);
+ NS_ADDREF(folderCopier); // it owns itself.
+ return 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;
+ nsCOMPtr<nsIUrlListener> urlListener;
+ nsCOMPtr<nsIMutableArray> messages(do_CreateInstance(NS_ARRAY_CONTRACTID));
+ nsCOMPtr<nsISupports> srcSupport = do_QueryInterface(file, &rv);
+
+ if (!messages)
+ return OnCopyCompleted(srcSupport, rv);
+
+ nsCOMPtr<nsIImapService> imapService = do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
+ if (NS_FAILED(rv))
+ return OnCopyCompleted(srcSupport, rv);
+
+ rv = QueryInterface(NS_GET_IID(nsIUrlListener), getter_AddRefs(urlListener));
+
+ if (msgToReplace)
+ {
+ rv = msgToReplace->GetMessageKey(&key);
+ if (NS_SUCCEEDED(rv))
+ {
+ messageId.AppendInt((int32_t) key);
+ // Perhaps we have the message offline, but even if we do it is
+ // not valid, since the only time we do a file copy for an
+ // existing message is when we are changing the message.
+ // So set the offline size to 0 to force SetPendingAttributes to
+ // clear the offline message flag.
+ msgToReplace->SetOfflineMessageSize(0);
+ messages->AppendElement(msgToReplace, false);
+ SetPendingAttributes(messages, false);
+ }
+ }
+
+ bool isMove = (msgToReplace ? true : false);
+ rv = InitCopyState(srcSupport, messages, isMove, isDraftOrTemplate,
+ false, aNewMsgFlags, aNewMsgKeywords, listener,
+ msgWindow, false);
+ if (NS_FAILED(rv))
+ return OnCopyCompleted(srcSupport, rv);
+
+ m_copyState->m_streamCopy = true;
+ nsCOMPtr<nsISupports> copySupport;
+ if( m_copyState )
+ copySupport = do_QueryInterface(m_copyState);
+ if (!isDraftOrTemplate)
+ {
+ m_copyState->m_totalCount = 1;
+ // This makes the IMAP APPEND set the INTERNALDATE for the msg copy
+ // we make when detaching/deleting attachments to the original msg date.
+ m_copyState->m_message = msgToReplace;
+ }
+ rv = imapService->AppendMessageFromFile(file, this, messageId,
+ true, isDraftOrTemplate,
+ urlListener, nullptr,
+ copySupport,
+ msgWindow);
+ if (NS_FAILED(rv))
+ return OnCopyCompleted(srcSupport, rv);
+
+ return rv;
+}
+
+nsresult
+nsImapMailFolder::CopyStreamMessage(nsIMsgDBHdr* message,
+ nsIMsgFolder* dstFolder, // should be this
+ nsIMsgWindow *aMsgWindow,
+ bool isMove)
+{
+ 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(NS_COPYMESSAGESTREAMLISTENER_CONTRACTID, &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(srcFolder, copyListener, nullptr);
+ if (NS_FAILED(rv))
+ MOZ_LOG(IMAP, mozilla::LogLevel::Info, ("CopyStreaMessage failed in copyStreamListener->Init"));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIMsgDBHdr> msgHdr(do_QueryInterface(message, &rv));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCString uri;
+ srcFolder->GetUriForMsg(msgHdr, 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_totalCount > 1)
+ {
+ nsString dstFolderName, progressText;
+ GetName(dstFolderName);
+ nsAutoString curMsgString;
+ nsAutoString totalMsgString;
+ totalMsgString.AppendInt(m_copyState->m_totalCount);
+ curMsgString.AppendInt(m_copyState->m_curIndex + 1);
+
+ const char16_t *formatStrings[3] = {curMsgString.get(),
+ totalMsgString.get(),
+ dstFolderName.get()
+ };
+
+ nsCOMPtr <nsIStringBundle> bundle;
+ rv = IMAPGetStringBundle(getter_AddRefs(bundle));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = bundle->FormatStringFromName(
+ u"imapCopyingMessageOf2",
+ formatStrings, 3, getter_Copies(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_totalCount;
+ statusFeedback->ShowProgress(percent);
+ }
+ }
+ nsCOMPtr<nsIURI> dummyNull;
+ rv = m_copyState->m_msgService->CopyMessage(uri.get(), streamListener,
+ isMove && !m_copyState->m_isCrossServerOp, nullptr, aMsgWindow,
+ getter_AddRefs(dummyNull));
+ if (NS_FAILED(rv))
+ MOZ_LOG(IMAP, mozilla::LogLevel::Info, ("CopyMessage failed: uri %s\n", uri.get()));
+ }
+ return rv;
+}
+
+nsImapMailCopyState::nsImapMailCopyState() :
+ m_isMove(false), m_selectedState(false),
+ m_isCrossServerOp(false), m_curIndex(0),
+ m_totalCount(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_msgService && m_message)
+ {
+ nsCOMPtr <nsIMsgFolder> srcFolder = do_QueryInterface(m_srcSupport);
+ if (srcFolder)
+ {
+ nsCString uri;
+ srcFolder->GetUriForMsg(m_message, uri);
+ }
+ }
+ if (m_tmpFile)
+ m_tmpFile->Remove(false);
+}
+
+
+NS_IMPL_ISUPPORTS(nsImapMailCopyState, nsImapMailCopyState)
+
+nsresult
+nsImapMailFolder::InitCopyState(nsISupports* srcSupport,
+ nsIArray* 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);
+ nsresult rv;
+
+ m_copyState = new nsImapMailCopyState();
+ NS_ENSURE_TRUE(m_copyState,NS_ERROR_OUT_OF_MEMORY);
+
+ m_copyState->m_isCrossServerOp = acrossServers;
+ m_copyState->m_srcSupport = do_QueryInterface(srcSupport, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ m_copyState->m_messages = messages;
+ if (messages)
+ rv = messages->GetLength(&m_copyState->m_totalCount);
+ if (!m_copyState->m_isCrossServerOp)
+ {
+ if (NS_SUCCEEDED(rv))
+ {
+ uint32_t numUnread = 0;
+ for (uint32_t keyIndex=0; keyIndex < m_copyState->m_totalCount; keyIndex++)
+ {
+ nsCOMPtr<nsIMsgDBHdr> message = do_QueryElementAt(m_copyState->m_messages, keyIndex, &rv);
+ // 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;
+ }
+ if (!isRead)
+ numUnread++;
+ }
+ m_copyState->m_unreadCount = numUnread;
+ }
+ }
+ else
+ {
+ nsCOMPtr<nsIMsgDBHdr> message =
+ do_QueryElementAt(m_copyState->m_messages,
+ m_copyState->m_curIndex, &rv);
+ // 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 = do_QueryInterface(listener, &rv);
+ return rv;
+}
+
+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;
+ rv = mDatabase->GetOfflineOpForKey(msgKey, true, getter_AddRefs(op));
+ if (NS_SUCCEEDED(rv) && op)
+ {
+ nsCString destFolderUri;
+ GetURI(destFolderUri);
+ op->SetOperation(nsIMsgOfflineImapOperation::kMoveResult);
+ op->SetDestinationFolderURI(destFolderUri.get());
+ SetFlag(nsMsgFolderFlags::OfflineEvents);
+ }
+
+ nsCOMPtr<nsIInputStream> inputStream;
+ nsCOMPtr<nsIMsgParseMailMsgState> msgParser =
+ do_CreateInstance(NS_PARSEMAILMSGSTATE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ msgParser->SetMailDB(mDatabase);
+
+ uint64_t offset = 0;
+ if (offlineStore)
+ {
+ // Tell the parser to use the offset that will be in the dest stream,
+ // not the temp file.
+ fakeHdr->GetMessageOffset(&offset);
+ }
+ msgParser->SetEnvelopePos(offset);
+
+ 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);
+
+ NS_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);
+ }
+
+ nsCOMPtr<nsIMutableArray> messages(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ messages->AppendElement(fakeHdr, false);
+
+ SetPendingAttributes(messages, false);
+ // Gloda needs this notification to index the fake message.
+ nsCOMPtr<nsIMsgFolderNotificationService>
+ notifier(do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID));
+ if (notifier)
+ notifier->NotifyMsgsClassified(messages, 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(NS_MSGCOPYSERVICE_CONTRACTID, &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;
+
+ nsCOMPtr<nsISimpleEnumerator> enumerator;
+ GetSubFolders(getter_AddRefs(enumerator));
+ if (!enumerator)
+ return NS_OK;
+
+ nsresult rv;
+ bool hasMore;
+ while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMore)) && hasMore)
+ {
+ nsCOMPtr<nsISupports> item;
+ rv = enumerator->GetNext(getter_AddRefs(item));
+ if (NS_FAILED(rv))
+ break;
+
+ nsCOMPtr<nsIMsgImapMailFolder> folder(do_QueryInterface(item, &rv));
+ if (NS_FAILED(rv))
+ return rv;
+
+ folder->ResetNamespaceReferences();
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapMailFolder::FindOnlineSubFolder(const nsACString& targetOnlineName, nsIMsgImapMailFolder **aResultFolder)
+{
+ nsresult rv = NS_OK;
+
+ nsCString onlineName;
+ GetOnlineName(onlineName);
+
+ if (onlineName.Equals(targetOnlineName))
+ return QueryInterface(NS_GET_IID(nsIMsgImapMailFolder), (void **) aResultFolder);
+
+ nsCOMPtr<nsISimpleEnumerator> enumerator;
+ GetSubFolders(getter_AddRefs(enumerator));
+ if (!enumerator)
+ return NS_OK;
+
+ bool hasMore;
+ while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMore)) && hasMore)
+ {
+ nsCOMPtr<nsISupports> item;
+ rv = enumerator->GetNext(getter_AddRefs(item));
+ if (NS_FAILED(rv))
+ break;
+
+ nsCOMPtr<nsIMsgImapMailFolder> folder(do_QueryInterface(item, &rv));
+ if (NS_FAILED(rv))
+ return rv;
+
+ rv = folder->FindOnlineSubFolder(targetOnlineName, aResultFolder);
+ if (*aResultFolder)
+ return rv;
+ }
+ return rv;
+}
+
+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(const nsACString &aFolderQuotaRoot,
+ uint32_t aFolderQuotaUsedKB,
+ uint32_t aFolderQuotaMaxKB)
+{
+ m_folderQuotaDataIsValid = true;
+ m_folderQuotaRoot = aFolderQuotaRoot;
+ m_folderQuotaUsedKB = aFolderQuotaUsedKB;
+ m_folderQuotaMaxKB = aFolderQuotaMaxKB;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailFolder::GetQuota(bool* aValid,
+ uint32_t* aUsed, uint32_t* aMax)
+{
+ NS_ENSURE_ARG_POINTER(aValid);
+ NS_ENSURE_ARG_POINTER(aUsed);
+ NS_ENSURE_ARG_POINTER(aMax);
+ *aValid = m_folderQuotaDataIsValid;
+ *aUsed = m_folderQuotaUsedKB;
+ *aMax = m_folderQuotaMaxKB;
+ 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(NS_IMAPSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = imapService->DiscoverChildren( this, this, m_onlineFolderName, nullptr);
+ }
+ 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_ConvertASCIItoUTF16 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(NS_MSGDB_SERVICE_CONTRACTID, &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 = CopyMUTF7toUTF16(NS_LossyConvertUTF16toASCII(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_LossyConvertUTF16toASCII(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;
+ CopyASCIItoUTF16(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(NS_MSGNOTIFICATIONSERVICE_CONTRACTID));
+ 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, nullptr);
+ NotifyItemAdded(child);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapMailFolder::RenameSubFolders(nsIMsgWindow *msgWindow, nsIMsgFolder *oldFolder)
+{
+ m_initialized = true;
+ nsCOMPtr<nsISimpleEnumerator> enumerator;
+ nsresult rv = oldFolder->GetSubFolders(getter_AddRefs(enumerator));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool hasMore;
+ while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMore)) && hasMore)
+ {
+ nsCOMPtr<nsISupports> item;
+ if (NS_FAILED(enumerator->GetNext(getter_AddRefs(item))))
+ continue;
+
+ nsCOMPtr<nsIMsgFolder> msgFolder(do_QueryInterface(item, &rv));
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsCOMPtr<nsIMsgImapMailFolder> folder(do_QueryInterface(msgFolder, &rv));
+ if (NS_FAILED(rv))
+ return 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);
+
+ nsCString newPathStr;
+ newParentPathFile->GetNativePath(newPathStr);
+
+ 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 utf7LeafName;
+ rv = CopyUTF16toMUTF7(folderName, utf7LeafName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // XXX : Fix this non-sense by fixing AddSubfolderWithPath
+ nsAutoString unicodeLeafName;
+ CopyASCIItoUTF16(utf7LeafName, unicodeLeafName);
+
+ rv = AddSubfolderWithPath(unicodeLeafName, dbFilePath, getter_AddRefs(child));
+ if (!child || NS_FAILED(rv)) return rv;
+
+ child->SetName(folderName);
+ nsCOMPtr <nsIMsgImapMailFolder> imapFolder = do_QueryInterface(child);
+ nsCString onlineName;
+ GetOnlineName(onlineName);
+ nsAutoCString onlineCName(onlineName);
+ onlineCName.Append(hierarchyDelimiter);
+ onlineCName.Append(utf7LeafName);
+ 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 */);
+ NS_ENSURE_TRUE (m_moveCoalescer, NS_ERROR_OUT_OF_MEMORY);
+ m_moveCoalescer->AddRef();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::StoreCustomKeywords(nsIMsgWindow *aMsgWindow, const nsACString& aFlagsToAdd,
+ const nsACString& aFlagsToSubtract, nsMsgKey *aKeysToStore, uint32_t aNumKeys, nsIURI **_retval)
+{
+ nsresult rv;
+ if (WeAreOffline())
+ {
+ GetDatabase();
+ if (mDatabase)
+ {
+ for (uint32_t keyIndex = 0; keyIndex < aNumKeys; keyIndex++)
+ {
+ nsCOMPtr <nsIMsgOfflineImapOperation> op;
+ rv = mDatabase->GetOfflineOpForKey(aKeysToStore[keyIndex], true, getter_AddRefs(op));
+ SetFlag(nsMsgFolderFlags::OfflineEvents);
+ if (NS_SUCCEEDED(rv) && 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(NS_IMAPSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsAutoCString msgIds;
+ AllocateUidStringFromKeys(aKeysToStore, aNumKeys, msgIds);
+ return imapService->StoreCustomKeywords(this, aMsgWindow, aFlagsToAdd,
+ aFlagsToSubtract, msgIds, _retval);
+}
+
+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(), NS_LITERAL_CSTRING("Junk"), EmptyCString(), junkKeysToClassify->Elements(), junkKeysToClassify->Length(), nullptr);
+ junkKeysToClassify->Clear();
+ nsTArray<nsMsgKey> *nonJunkKeysToClassify = m_moveCoalescer->GetKeyBucket(1);
+ if (nonJunkKeysToClassify && !nonJunkKeysToClassify->IsEmpty())
+ StoreCustomKeywords(m_moveCoalescer->GetMsgWindow(), NS_LITERAL_CSTRING("NonJunk"), EmptyCString(), nonJunkKeysToClassify->Elements(), nonJunkKeysToClassify->Length(), nullptr);
+ nonJunkKeysToClassify->Clear();
+ return m_moveCoalescer->PlaybackMoves(ShowPreviewText());
+ }
+ return NS_OK; // must not be any coalesced operations
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::SetJunkScoreForMessages(nsIArray *aMessages, const nsACString& aJunkScore)
+{
+ NS_ENSURE_ARG(aMessages);
+
+ 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.Equals("0") ? NS_LITERAL_CSTRING("NonJunk") : NS_LITERAL_CSTRING("Junk"), EmptyCString(), keys.Elements(),
+ keys.Length(), nullptr);
+ if (mDatabase)
+ mDatabase->Commit(nsMsgDBCommitType::kLargeCommit);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::OnMessageClassified(const char * aMsgURI,
+ nsMsgJunkStatus aClassification,
+ uint32_t aJunkPercent)
+{
+ nsCOMPtr <nsIMsgIncomingServer> server;
+ nsresult rv = GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (aMsgURI) // 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)
+ {
+ if (!m_junkMessagesToMarkAsRead)
+ {
+ m_junkMessagesToMarkAsRead = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ m_junkMessagesToMarkAsRead->AppendElement(msgHdr, false);
+ }
+
+ 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(getter_Copies(spamFolderURI));
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ if (!spamFolderURI.IsEmpty())
+ {
+ rv = GetExistingFolder(spamFolderURI, getter_AddRefs(mSpamFolder));
+ if (NS_SUCCEEDED(rv) && 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 = GetOrCreateFolder(spamFolderURI, nullptr /* aListener */);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "GetOrCreateFolder failed");
+ }
+ }
+ }
+ }
+ rv = spamSettings->LogJunkHit(msgHdr, willMoveMessage);
+ NS_ENSURE_SUCCESS(rv,rv);
+ }
+ }
+ }
+
+ else // end of batch
+ {
+ // Parent will apply post bayes filters.
+ nsMsgDBFolder::OnMessageClassified(nullptr, nsIJunkMailPlugin::UNCLASSIFIED, 0);
+
+ if (m_junkMessagesToMarkAsRead)
+ {
+ uint32_t count;
+ m_junkMessagesToMarkAsRead->GetLength(&count);
+ if (count > 0)
+ {
+ 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 arbitary 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(nsMsgKey *aKeysToFetch, uint32_t aNumKeys,
+ bool aLocalOnly, nsIUrlListener *aUrlListener,
+ bool *aAsyncResults)
+{
+ NS_ENSURE_ARG_POINTER(aKeysToFetch);
+ NS_ENSURE_ARG_POINTER(aAsyncResults);
+
+ nsTArray<nsMsgKey> keysToFetchFromServer;
+
+ *aAsyncResults = false;
+ nsresult rv = NS_OK;
+
+ nsCOMPtr<nsIImapService> imapService = do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr <nsIMsgMessageService> msgService = do_QueryInterface(imapService, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (uint32_t i = 0; i < aNumKeys; 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", getter_Copies(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.get(), getter_AddRefs(url), nullptr);
+ NS_ENSURE_SUCCESS(rv,rv);
+
+ // Lets look in the offline store.
+ uint32_t msgFlags;
+ msgHdr->GetFlags(&msgFlags);
+ nsMsgKey msgKey;
+ msgHdr->GetMessageKey(&msgKey);
+ if (msgFlags & nsMsgMessageFlags::Offline)
+ {
+ int64_t messageOffset;
+ uint32_t messageSize;
+ GetOfflineFileStream(msgKey, &messageOffset, &messageSize, getter_AddRefs(inputStream));
+ if (inputStream)
+ rv = GetMsgPreviewTextFromStream(msgHdr, inputStream);
+ }
+ else if (!aLocalOnly) {
+ keysToFetchFromServer.AppendElement(msgKey);
+ }
+ }
+ if (!keysToFetchFromServer.IsEmpty())
+ {
+ uint32_t msgCount = keysToFetchFromServer.Length();
+ nsAutoCString messageIds;
+ AllocateImapUidString(keysToFetchFromServer.Elements(), msgCount,
+ nullptr, messageIds);
+ rv = imapService->GetBodyStart(this, aUrlListener,
+ messageIds, 2048, nullptr);
+ *aAsyncResults = true; // the preview text will be available async...
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailFolder::AddKeywordsToMessages(nsIArray *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.Elements(), keys.Length(), nullptr);
+ if (mDatabase)
+ mDatabase->Commit(nsMsgDBCommitType::kLargeCommit);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapMailFolder::RemoveKeywordsFromMessages(nsIArray *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.Elements(), keys.Length(), 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(NS_MSGACCOUNTMANAGER_CONTRACTID, &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()));
+ nsCOMPtr<nsIArray> identities;
+ rv = accountManager->GetIdentitiesForServer(server, getter_AddRefs(identities));
+ NS_ENSURE_SUCCESS(rv, rv);
+ uint32_t numIdentities;
+ rv = identities->GetLength(&numIdentities);
+ NS_ENSURE_SUCCESS(rv, rv);
+ for (uint32_t identityIndex = 0; identityIndex < numIdentities; identityIndex++)
+ {
+ nsCOMPtr<nsIMsgIdentity> identity = do_QueryElementAt(identities, identityIndex);
+ 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.swap(*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, ("Updating folder: %s\n", 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, ("Cannot update folder: %s\n", 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;
+}
+
+nsresult nsImapMailFolder::CreatePlaybackTimer()
+{
+ nsresult rv = NS_OK;
+ if (!m_playbackTimer)
+ {
+ m_playbackTimer = do_CreateInstance(NS_TIMER_CONTRACTID, &rv);
+ NS_ASSERTION(NS_SUCCEEDED(rv),"failed to create pseudo-offline operation timer in nsImapMailFolder");
+ }
+ return rv;
+}
+
+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(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
+ request->SrcFolder->m_pendingPlaybackReq = nullptr;
+ 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;
+
+}
+
+NS_IMETHODIMP 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", getter_Copies(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].Equals("\"\\\\Draft\""))
+ rv = rootFolder->GetFolderWithFlags(nsMsgFolderFlags::Drafts,
+ getter_AddRefs(subMsgFolder));
+ if (labelNames[i].Equals("\"\\\\Inbox\""))
+ rv = rootFolder->GetFolderWithFlags(nsMsgFolderFlags::Inbox,
+ getter_AddRefs(subMsgFolder));
+ if (labelNames[i].Equals("\"\\\\All Mail\""))
+ rv = rootFolder->GetFolderWithFlags(nsMsgFolderFlags::Archive,
+ getter_AddRefs(subMsgFolder));
+ if (labelNames[i].Equals("\"\\\\Trash\""))
+ rv = rootFolder->GetFolderWithFlags(nsMsgFolderFlags::Trash,
+ getter_AddRefs(subMsgFolder));
+ if (labelNames[i].Equals("\"\\\\Spam\""))
+ rv = rootFolder->GetFolderWithFlags(nsMsgFolderFlags::Junk,
+ getter_AddRefs(subMsgFolder));
+ if (labelNames[i].Equals("\"\\\\Sent\""))
+ rv = rootFolder->GetFolderWithFlags(nsMsgFolderFlags::SentMail,
+ getter_AddRefs(subMsgFolder));
+ if (labelNames[i].Find("[Imap]/", CaseInsensitiveCompare) != kNotFound)
+ {
+ MsgReplaceSubstring(labelNames[i], "[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", getter_Copies(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;
+}
+
+NS_IMETHODIMP nsImapMailFolder::GetOfflineFileStream(nsMsgKey msgKey, int64_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);
+
+ nsCOMPtr<nsIMsgDBHdr> hdr;
+ rv = mDatabase->GetMsgHdrForKey(msgKey, getter_AddRefs(hdr));
+ if (NS_FAILED(rv))
+ return rv;
+ if (hdr)
+ {
+ nsCString gmMsgID;
+ hdr->GetStringProperty("X-GM-MSGID", getter_Copies(gmMsgID));
+ nsCOMPtr<nsIMsgDatabase> db;
+ offlineFolder->GetMsgDatabase(getter_AddRefs(db));
+ rv = db->GetMsgHdrForGMMsgID(gmMsgID.get(), getter_AddRefs(hdr));
+ if (NS_FAILED(rv))
+ return rv;
+ nsMsgKey newMsgKey;
+ hdr->GetMessageKey(&newMsgKey);
+ return offlineFolder->GetOfflineFileStream(newMsgKey, offset, size, aFileStream);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailFolder::GetIncomingServerType(nsACString& serverType)
+{
+ serverType.AssignLiteral("imap");
+ return NS_OK;
+}
+
+void nsImapMailFolder::DeleteStoreMessages(nsIArray* 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(nsTArray<nsMsgKey> &aMessages)
+{
+ DeleteStoreMessages(aMessages, this);
+}
+
+void nsImapMailFolder::DeleteStoreMessages(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;
+ nsCOMPtr<nsIMutableArray> messages(do_CreateInstance(NS_ARRAY_CONTRACTID));
+ if (db)
+ rv = MsgGetHeadersFromKeys(db, aMessages, messages);
+ if (NS_SUCCEEDED(rv))
+ offlineStore->DeleteMessages(messages);
+ else
+ NS_WARNING("Failed to get database");
+ }
+ }
+}
diff --git a/mailnews/imap/src/nsImapMailFolder.h b/mailnews/imap/src/nsImapMailFolder.h
new file mode 100644
index 000000000..71665ee79
--- /dev/null
+++ b/mailnews/imap/src/nsImapMailFolder.h
@@ -0,0 +1,550 @@
+/* -*- 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"
+#include "nsMsgDBFolder.h"
+#include "nsIImapMailFolderSink.h"
+#include "nsIImapMessageSink.h"
+#include "nsICopyMessageListener.h"
+#include "nsIImapService.h"
+#include "nsIUrlListener.h"
+#include "nsAutoPtr.h"
+#include "nsIImapIncomingServer.h" // we need this for its IID
+#include "nsIMsgParseMailMsgState.h"
+#include "nsITransactionManager.h"
+#include "nsImapUndoTxn.h"
+#include "nsIMsgMessageService.h"
+#include "nsIMsgFilterHitNotify.h"
+#include "nsIMsgFilterList.h"
+#include "prmon.h"
+#include "nsIMsgImapMailFolder.h"
+#include "nsIMsgLocalMailFolder.h"
+#include "nsIMsgThread.h"
+#include "nsIImapMailFolderSink.h"
+#include "nsIImapServerSink.h"
+#include "nsIMsgFilterPlugin.h"
+#include "nsIThread.h"
+#include "nsDataHashtable.h"
+#include "nsIMutableArray.h"
+#include "nsITimer.h"
+#include "nsCOMArray.h"
+#include "nsAutoSyncState.h"
+#include "nsIRequestObserver.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
+ nsCOMPtr<nsIArray> m_messages; // array of source messages
+ RefPtr<nsImapMoveCopyMsgTxn> m_undoMsgTxn; // undo object with this copy operation
+ nsCOMPtr<nsIMsgDBHdr> m_message; // current message to be copied
+ 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_totalCount;// total count of messages we have to do
+ 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 there's no m_message
+ 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;
+
+#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
+
+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....()
+ 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?
+
+ 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:
+ nsDataHashtable <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 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(nsISimpleEnumerator **aResult) override;
+
+ NS_IMETHOD GetMessages(nsISimpleEnumerator* *result) 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,
+ bool aCompactOfflineAlso) override;
+ NS_IMETHOD EmptyTrash(nsIMsgWindow *msgWindow, nsIUrlListener *aListener) override;
+ NS_IMETHOD CopyDataToOutputStreamForAppend(nsIInputStream *aIStream,
+ int32_t aLength, nsIOutputStream *outputStream) override;
+ NS_IMETHOD CopyDataDone() override;
+ NS_IMETHOD Delete () 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(nsIArray *messages, bool markRead) override;
+ NS_IMETHOD MarkAllMessagesRead(nsIMsgWindow *aMsgWindow) override;
+ NS_IMETHOD MarkMessagesFlagged(nsIArray *messages, bool markFlagged) override;
+ NS_IMETHOD MarkThreadRead(nsIMsgThread *thread) override;
+ NS_IMETHOD SetLabelForMessages(nsIArray *aMessages, nsMsgLabelValue aLabel) override;
+ NS_IMETHOD SetJunkScoreForMessages(nsIArray *aMessages, const nsACString& aJunkScore) override;
+ NS_IMETHOD DeleteSubFolders(nsIArray *folders, nsIMsgWindow *msgWindow) override;
+ NS_IMETHOD ReadFromFolderCacheElem(nsIMsgFolderCacheElement *element) override;
+ NS_IMETHOD WriteToFolderCacheElem(nsIMsgFolderCacheElement *element) override;
+
+ NS_IMETHOD GetDBFolderInfoAndDB(nsIDBFolderInfo **folderInfo,
+ nsIMsgDatabase **db) override;
+ NS_IMETHOD DeleteMessages(nsIArray *messages,
+ nsIMsgWindow *msgWindow, bool
+ deleteStorage, bool isMove,
+ nsIMsgCopyServiceListener* listener, bool allowUndo) override;
+ NS_IMETHOD CopyMessages(nsIMsgFolder *srcFolder,
+ nsIArray* 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(nsIArray *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(nsMsgKey *aKeysToFetch, uint32_t aNumKeys,
+ bool aLocalOnly, nsIUrlListener *aUrlListener,
+ bool *aAsyncResults) override;
+
+ NS_IMETHOD AddKeywordsToMessages(nsIArray *aMessages, const nsACString& aKeywords) override;
+ NS_IMETHOD RemoveKeywordsFromMessages(nsIArray *aMessages, const nsACString& aKeywords) override;
+
+ NS_IMETHOD NotifyCompactCompleted() override;
+
+ // overrides nsMsgDBFolder::HasMsgOffline()
+ NS_IMETHOD HasMsgOffline(nsMsgKey msgKey, bool *_retval) override;
+ // overrides nsMsgDBFolder::GetOfflineFileStream()
+ NS_IMETHOD GetOfflineFileStream(nsMsgKey msgKey, int64_t *offset, uint32_t *size, nsIInputStream **aFileStream) override;
+
+ NS_DECL_NSIMSGIMAPMAILFOLDER
+ NS_DECL_NSIIMAPMAILFOLDERSINK
+ NS_DECL_NSIIMAPMESSAGESINK
+ NS_DECL_NSICOPYMESSAGELISTENER
+
+ // nsIUrlListener methods
+ NS_IMETHOD OnStartRunningUrl(nsIURI * aUrl) override;
+ 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;
+
+ /**
+ * 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.
+ * overrides nsMsgDBFolder::GetOfflineMsgFolder()
+ * @param msgKey key of the msg for which we are trying to get the folder;
+ * @param aMsgFolder required folder;
+ */
+ NS_IMETHOD GetOfflineMsgFolder(nsMsgKey msgKey, nsIMsgFolder **aMsgFolder) 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(nsMsgKey *keys, uint32_t numKeys, nsCString &msgIds);
+ static nsresult BuildIdsAndKeyArray(nsIArray* 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
+
+ virtual nsresult CreateChildFromURI(const nsCString &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,
+ nsIArray* 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,
+ nsIArray* 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);
+ nsresult CopyMessagesOffline(nsIMsgFolder* srcFolder,
+ nsIArray* messages,
+ bool isMove,
+ nsIMsgWindow *msgWindow,
+ nsIMsgCopyServiceListener* listener);
+ void SetPendingAttributes(nsIArray* messages, bool aIsMove);
+
+ 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);
+
+ nsresult CreatePlaybackTimer();
+
+ // Allocate and initialize associated auto-sync state object.
+ void InitAutoSyncState();
+
+ 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;
+ nsImapMoveCoalescer *m_moveCoalescer; // strictly owned by the nsImapMailFolder
+ nsCOMPtr<nsIMutableArray> 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_folderQuotaCommandIssued;
+ bool m_folderQuotaDataIsValid;
+ bool m_updatingFolder;
+ // These two vars are used to keep track of compaction state so we can know
+ // when to send a done notification.
+ bool m_compactingOfflineStore;
+ bool m_expunging;
+ 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
+ nsCString m_folderQuotaRoot;
+ uint32_t m_folderQuotaUsedKB;
+ uint32_t m_folderQuotaMaxKB;
+
+ // 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.
+ nsDataHashtable<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(nsIArray* aMessages);
+ void DeleteStoreMessages(nsTArray<nsMsgKey> &aMessages);
+ static void DeleteStoreMessages(nsTArray<nsMsgKey> &aMessages, nsIMsgFolder* aFolder);
+};
+#endif
diff --git a/mailnews/imap/src/nsImapOfflineSync.cpp b/mailnews/imap/src/nsImapOfflineSync.cpp
new file mode 100644
index 000000000..2d86ce1b5
--- /dev/null
+++ b/mailnews/imap/src/nsImapOfflineSync.cpp
@@ -0,0 +1,1290 @@
+/* -*- 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/. */
+
+#include "msgCore.h"
+#include "netCore.h"
+#include "nsNetUtil.h"
+#include "nsImapOfflineSync.h"
+#include "nsImapMailFolder.h"
+#include "nsMsgFolderFlags.h"
+#include "nsIRDFService.h"
+#include "nsMsgBaseCID.h"
+#include "nsRDFCID.h"
+#include "nsIMsgMailNewsUrl.h"
+#include "nsIMsgAccountManager.h"
+#include "nsINntpIncomingServer.h"
+#include "nsIRequestObserver.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsISeekableStream.h"
+#include "nsIMsgCopyService.h"
+#include "nsImapProtocol.h"
+#include "nsMsgUtils.h"
+#include "nsIMutableArray.h"
+#include "nsIAutoSyncManager.h"
+#include "nsArrayUtils.h"
+
+static NS_DEFINE_CID(kRDFServiceCID, NS_RDFSERVICE_CID);
+
+NS_IMPL_ISUPPORTS(nsImapOfflineSync, nsIUrlListener, nsIMsgCopyServiceListener, nsIDBChangeListener)
+
+nsImapOfflineSync::nsImapOfflineSync(nsIMsgWindow *window, nsIUrlListener *listener, nsIMsgFolder *singleFolderOnly, bool isPseudoOffline)
+{
+ m_singleFolderToUpdate = singleFolderOnly;
+ m_window = window;
+ // not the perfect place for this, but I think it will work.
+ if (m_window)
+ m_window->SetStopped(false);
+
+ mCurrentPlaybackOpType = nsIMsgOfflineImapOperation::kFlagsChanged;
+ m_mailboxupdatesStarted = false;
+ m_mailboxupdatesFinished = false;
+ m_createdOfflineFolders = false;
+ m_pseudoOffline = isPseudoOffline;
+ m_KeyIndex = 0;
+ mCurrentUIDValidity = nsMsgKey_None;
+ m_listener = listener;
+}
+
+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. If no more servers,
+ * m_currentServer will be left at nullptr and the function returns false.
+ * Also, sets up m_serverEnumerator to enumerate over the server.
+ */
+bool nsImapOfflineSync::AdvanceToNextServer()
+{
+ nsresult rv = NS_OK;
+
+ if (!m_allServers)
+ {
+ NS_ASSERTION(!m_currentServer, "this shouldn't be set");
+ m_currentServer = nullptr;
+ nsCOMPtr<nsIMsgAccountManager> accountManager =
+ do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
+ NS_ASSERTION(accountManager && NS_SUCCEEDED(rv), "couldn't get account mgr");
+ if (!accountManager || NS_FAILED(rv))
+ return false;
+
+ rv = accountManager->GetAllServers(getter_AddRefs(m_allServers));
+ NS_ENSURE_SUCCESS(rv, false);
+ }
+ uint32_t serverIndex = 0;
+ if (m_currentServer)
+ {
+ rv = m_allServers->IndexOf(0, m_currentServer, &serverIndex);
+ if (NS_FAILED(rv))
+ serverIndex = -1;
+
+ // Move to the next server
+ ++serverIndex;
+ }
+ m_currentServer = nullptr;
+ uint32_t numServers;
+ m_allServers->GetLength(&numServers);
+ nsCOMPtr <nsIMsgFolder> rootFolder;
+
+ while (serverIndex < numServers)
+ {
+ nsCOMPtr<nsIMsgIncomingServer> server(do_QueryElementAt(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(getter_AddRefs(m_allFolders));
+ if (NS_SUCCEEDED(rv))
+ {
+ rv = m_allFolders->Enumerate(getter_AddRefs(m_serverEnumerator));
+ if (NS_SUCCEEDED(rv) && m_serverEnumerator)
+ {
+ bool hasMore = false;
+ rv = m_serverEnumerator->HasMoreElements(&hasMore);
+ if (NS_SUCCEEDED(rv) && hasMore)
+ 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)
+ m_serverEnumerator->HasMoreElements(&hasMore);
+ if (!hasMore)
+ hasMore = AdvanceToNextServer();
+
+ if (hasMore)
+ {
+ nsCOMPtr<nsISupports> supports;
+ nsresult rv = m_serverEnumerator->GetNext(getter_AddRefs(supports));
+ if (NS_SUCCEEDED(rv))
+ m_currentFolder = do_QueryInterface(supports);
+ }
+ 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);
+ imapMessageFlagsType flagOperation;
+ imapMessageFlagsType newFlags;
+ bool flagsMatch = true;
+ do
+ { // loop for all messsages with the same flags
+ if (flagsMatch)
+ {
+ nsMsgKey curKey;
+ currentOp->GetMessageKey(&curKey);
+ matchingFlagKeys.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)
+ {
+ currentOp->GetFlagOperation(&flagOperation);
+ currentOp->GetNewFlags(&newFlags);
+ }
+ flagsMatch = (flagOperation & nsIMsgOfflineImapOperation::kFlagsChanged)
+ && (newFlags == matchingFlags);
+ } while (currentOp);
+
+ if (!matchingFlagKeys.IsEmpty())
+ {
+ nsAutoCString uids;
+ nsImapMailFolder::AllocateUidStringFromKeys(matchingFlagKeys.Elements(), matchingFlagKeys.Length(), 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 messsages 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.Elements(),
+ matchingKeywordKeys.Length(), getter_AddRefs(uriToStoreCustomKeywords));
+ if (NS_SUCCEEDED(rv) && uriToStoreCustomKeywords)
+ {
+ nsCOMPtr <nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(uriToStoreCustomKeywords);
+ if (mailnewsUrl)
+ mailnewsUrl->RegisterListener(this);
+ }
+ }
+ }
+ }
+ else
+ ProcessNextOperation();
+}
+
+void
+nsImapOfflineSync::ProcessAppendMsgOperation(nsIMsgOfflineImapOperation *currentOp, int32_t opType)
+{
+ nsCOMPtr <nsIMsgDBHdr> mailHdr;
+ nsMsgKey msgKey;
+ currentOp->GetMessageKey(&msgKey);
+ nsresult rv = m_currentDB->GetMsgHdrForKey(msgKey, getter_AddRefs(mailHdr));
+ if (NS_SUCCEEDED(rv) && mailHdr)
+ {
+ uint64_t messageOffset;
+ uint32_t messageSize;
+ mailHdr->GetMessageOffset(&messageOffset);
+ mailHdr->GetOfflineMessageSize(&messageSize);
+ nsCOMPtr<nsIFile> tmpFile;
+
+ if (NS_FAILED(GetSpecialDirectoryWithFileName(NS_OS_TEMP_DIR,
+ "nscpmsg.txt",
+ getter_AddRefs(tmpFile))))
+ return;
+
+ 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_SUCCEEDED(rv) && outputStream)
+ {
+ nsCString moveDestination;
+ currentOp->GetDestinationFolderURI(getter_Copies(moveDestination));
+ nsCOMPtr<nsIRDFService> rdf(do_GetService(kRDFServiceCID, &rv));
+ nsCOMPtr<nsIRDFResource> res;
+ if (NS_FAILED(rv)) return ; // ### return error code.
+ rv = rdf->GetResource(moveDestination, getter_AddRefs(res));
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr<nsIMsgFolder> destFolder(do_QueryInterface(res, &rv));
+ if (NS_SUCCEEDED(rv) && destFolder)
+ {
+ nsCOMPtr <nsIInputStream> offlineStoreInputStream;
+ bool reusable;
+ rv = destFolder->GetMsgInputStream(
+ mailHdr, &reusable, getter_AddRefs(offlineStoreInputStream));
+ if (NS_SUCCEEDED(rv) && offlineStoreInputStream)
+ {
+ nsCOMPtr<nsISeekableStream> seekStream = do_QueryInterface(offlineStoreInputStream);
+ NS_ASSERTION(seekStream, "non seekable stream - can't read from offline msg");
+ if (seekStream)
+ {
+ rv = seekStream->Seek(PR_SEEK_SET, messageOffset);
+ if (NS_SUCCEEDED(rv))
+ {
+ // now, 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_SUCCEEDED(rv) && bytesRead > 0)
+ {
+ rv = outputStream->Write(inputBuffer, bytesRead, &bytesWritten);
+ NS_ASSERTION(bytesWritten == bytesRead, "wrote out incorrect number of bytes");
+ }
+ else
+ break;
+ bytesLeft -= bytesRead;
+ }
+ PR_FREEIF(inputBuffer);
+
+ outputStream->Flush();
+ outputStream->Close();
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr<nsIFile> cloneTmpFile;
+ // clone the tmp file to defeat nsIFile's stat/size caching.
+ tmpFile->Clone(getter_AddRefs(cloneTmpFile));
+ m_curTempFile = do_QueryInterface(cloneTmpFile);
+ nsCOMPtr<nsIMsgCopyService> copyService = do_GetService(NS_MSGCOPYSERVICE_CONTRACTID);
+ if (copyService)
+ rv = copyService->CopyFileMessage(cloneTmpFile, destFolder,
+ /* nsIMsgDBHdr* msgToReplace */ nullptr,
+ true /* isDraftOrTemplate */,
+ 0, // new msg flags - are there interesting flags here?
+ EmptyCString(), /* are there keywords we should get? */
+ this,
+ m_window);
+ }
+ else
+ tmpFile->Remove(false);
+ }
+ currentOp->SetPlayingBack(true);
+ m_currentOpsToClear.AppendObject(currentOp);
+ m_currentDB->DeleteHeader(mailHdr, nullptr, true, true);
+ }
+ }
+ // want to close in failure case too
+ outputStream->Close();
+ }
+ }
+ }
+ }
+ else
+ {
+ m_currentDB->RemoveOfflineOp(currentOp);
+ ProcessNextOperation();
+ }
+}
+
+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(getter_Copies(moveDestination));
+ bool moveMatches = true;
+ nsCOMPtr <nsIMsgOfflineImapOperation> currentOp = op;
+ do
+ { // loop for all messsages 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(getter_Copies(nextDestination));
+ moveMatches = moveDestination.Equals(nextDestination);
+ }
+ }
+ }
+ }
+ while (currentOp);
+
+ nsCOMPtr<nsIMsgFolder> destFolder;
+ GetExistingFolder(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_ERROR("trying to playing back move to non-existent folder");
+ ClearCurrentOps();
+ ProcessNextOperation();
+ return;
+ }
+ nsCOMPtr<nsIMsgImapMailFolder> imapFolder = do_QueryInterface(m_currentFolder);
+ if (imapFolder && DestFolderOnSameServer(destFolder))
+ {
+ imapFolder->ReplayOfflineMoveCopy(matchingFlagKeys.Elements(), matchingFlagKeys.Length(), true, destFolder,
+ this, m_window);
+ }
+ else
+ {
+ nsresult rv;
+ nsCOMPtr<nsIMutableArray> messages(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv));
+ if (NS_SUCCEEDED(rv))
+ {
+ 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, false);
+ }
+ }
+ nsCOMPtr<nsIMsgCopyService> copyService = do_GetService(NS_MSGCOPYSERVICE_CONTRACTID, &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 messsages 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;
+ GetExistingFolder(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))
+ {
+ rv = imapFolder->ReplayOfflineMoveCopy(matchingFlagKeys.Elements(), matchingFlagKeys.Length(), false, destFolder,
+ this, m_window);
+ }
+ else
+ {
+ nsCOMPtr<nsIMutableArray> messages(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv));
+ if (messages && NS_SUCCEEDED(rv))
+ {
+ 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, false);
+ }
+ nsCOMPtr<nsIMsgCopyService> copyService = do_GetService(NS_MSGCOPYSERVICE_CONTRACTID, &rv);
+ if (copyService)
+ copyService->CopyMessages(m_currentFolder, messages, destFolder, false, this, m_window, false);
+ }
+ }
+}
+
+void nsImapOfflineSync::ProcessEmptyTrash()
+{
+ m_currentFolder->EmptyTrash(m_window, 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)
+ */
+nsresult 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 */))
+ {
+ m_currentFolder->GetDBFolderInfoAndDB(getter_AddRefs(folderInfo), getter_AddRefs(m_currentDB));
+ if (m_currentDB)
+ 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 notifcation 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_ERROR("invalid playback op type");
+ }
+ }
+ else
+ currentFolderFinished = true;
+ }
+ else
+ currentFolderFinished = true;
+
+ if (currentFolderFinished)
+ {
+ ClearDB();
+ if (!m_singleFolderToUpdate)
+ {
+ AdvanceToNextFolder();
+ ProcessNextOperation();
+ return NS_OK;
+ }
+ else
+ 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);
+ nsCOMPtr<nsIMsgImapMailFolder> imapFolder(do_QueryInterface(m_singleFolderToUpdate));
+ if (imapFolder)
+ {
+ nsCOMPtr<nsIUrlListener> saveListener = m_listener;
+// m_listener = nullptr;
+// imapFolder->UpdateFolderWithListener(m_window, saveListener);
+ }
+ }
+ }
+ // 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(aMsgWindow, aListener)
+{
+ // pause auto-sync service
+ nsresult rv;
+ nsCOMPtr<nsIAutoSyncManager> autoSyncMgr = do_GetService(NS_AUTOSYNCMANAGER_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv))
+ autoSyncMgr->Pause();
+}
+
+nsImapOfflineDownloader::~nsImapOfflineDownloader()
+{
+}
+
+nsresult nsImapOfflineDownloader::ProcessNextOperation()
+{
+ nsresult rv = NS_OK;
+ if (!m_mailboxupdatesStarted)
+ {
+ m_mailboxupdatesStarted = true;
+ // Update the INBOX first so the updates on the remaining
+ // folders pickup the results of any filter moves.
+ nsCOMPtr<nsIMsgAccountManager> accountManager =
+ do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIArray> servers;
+ rv = accountManager->GetAllServers(getter_AddRefs(servers));
+ if (NS_FAILED(rv)) return rv;
+ }
+ if (!m_mailboxupdatesFinished)
+ {
+ if (AdvanceToNextServer())
+ {
+ nsCOMPtr <nsIMsgFolder> rootMsgFolder;
+ m_currentServer->GetRootFolder(getter_AddRefs(rootMsgFolder));
+ nsCOMPtr<nsIMsgFolder> inbox;
+ if (rootMsgFolder)
+ {
+ 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.
+ }
+ else
+ {
+ m_allServers = nullptr;
+ 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,
+ 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/mailnews/imap/src/nsImapOfflineSync.h b/mailnews/imap/src/nsImapOfflineSync.h
new file mode 100644
index 000000000..c339e8463
--- /dev/null
+++ b/mailnews/imap/src/nsImapOfflineSync.h
@@ -0,0 +1,92 @@
+/* -*- 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"
+
+class nsImapOfflineSync : public nsIUrlListener,
+ public nsIMsgCopyServiceListener,
+ public nsIDBChangeListener {
+public: // set to one folder to playback one folder only
+ nsImapOfflineSync(nsIMsgWindow *window, nsIUrlListener *listener,
+ nsIMsgFolder *singleFolderOnly = nullptr,
+ bool isPseudoOffline = false);
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIURLLISTENER
+ NS_DECL_NSIMSGCOPYSERVICELISTENER
+ NS_DECL_NSIDBCHANGELISTENER
+ virtual nsresult ProcessNextOperation(); // this kicks off playback
+
+ 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;
+ nsCOMPtr <nsIArray> m_allServers;
+ nsCOMPtr <nsIArray> m_allFolders;
+ nsCOMPtr <nsIMsgIncomingServer> m_currentServer;
+ nsCOMPtr <nsISimpleEnumerator> m_serverEnumerator;
+ nsCOMPtr <nsIFile> m_curTempFile;
+
+ nsTArray<nsMsgKey> m_CurrentKeys;
+ nsCOMArray<nsIMsgOfflineImapOperation> m_currentOpsToClear;
+ uint32_t m_KeyIndex;
+ nsCOMPtr <nsIMsgDatabase> 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();
+ virtual nsresult ProcessNextOperation() override; // this kicks off download
+};
+
+#endif
diff --git a/mailnews/imap/src/nsImapProtocol.cpp b/mailnews/imap/src/nsImapProtocol.cpp
new file mode 100644
index 000000000..4cfa9dab2
--- /dev/null
+++ b/mailnews/imap/src/nsImapProtocol.cpp
@@ -0,0 +1,10093 @@
+/* -*- 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/. */
+
+// as does this
+#include "msgCore.h" // for pre-compiled headers
+#include "nsMsgUtils.h"
+
+#include "nsIServiceManager.h"
+#include "nsICharsetConverterManager.h"
+#include "nsIStringBundle.h"
+#include "nsVersionComparator.h"
+
+#include "nsMsgImapCID.h"
+#include "nsThreadUtils.h"
+#include "nsIMsgStatusFeedback.h"
+#include "nsImapCore.h"
+#include "nsImapProtocol.h"
+#include "nsIMsgMailNewsUrl.h"
+#include "nsIMAPHostSessionList.h"
+#include "nsIMAPBodyShell.h"
+#include "nsImapMailFolder.h"
+#include "nsIMsgAccountManager.h"
+#include "nsImapServerResponseParser.h"
+#include "nspr.h"
+#include "plbase64.h"
+#include "nsIImapService.h"
+#include "nsISocketTransportService.h"
+#include "nsIStreamListenerTee.h"
+#include "nsNetUtil.h"
+#include "nsIDBFolderInfo.h"
+#include "nsIPipe.h"
+#include "nsIMsgFolder.h"
+#include "nsMsgMessageFlags.h"
+#include "nsImapStringBundle.h"
+#include "nsICopyMsgStreamListener.h"
+#include "nsTextFormatter.h"
+#include "nsIMsgHdr.h"
+#include "nsMsgI18N.h"
+// for the memory cache...
+#include "nsICacheEntry.h"
+#include "nsICacheStorage.h"
+#include "nsICacheEntryOpenCallback.h"
+
+#include "nsIPrompt.h"
+#include "nsIDocShell.h"
+#include "nsIDocShellLoadInfo.h"
+#include "nsILoadInfo.h"
+#include "nsIMessengerWindowService.h"
+#include "nsIWindowMediator.h"
+#include "nsIWindowWatcher.h"
+#include "nsCOMPtr.h"
+#include "nsMimeTypes.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsXPCOMCIDInternal.h"
+#include "nsIXULAppInfo.h"
+#include "nsSyncRunnableHelpers.h"
+
+PRLogModuleInfo *IMAP;
+
+// 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 "nsISSLSocketControl.h"
+#include "nsProxyRelease.h"
+#include "nsDebug.h"
+#include "nsMsgCompressIStream.h"
+#include "nsMsgCompressOStream.h"
+#include "mozilla/Logging.h"
+#include "mozilla/Attributes.h"
+#include "nsIPrincipal.h"
+#include "nsContentSecurityManager.h"
+
+using namespace mozilla;
+
+#define ONE_SECOND ((uint32_t)1000) // one second
+
+#define OUTPUT_BUFFER_SIZE (4096*2) // mscott - i should be able to remove this if I can use nsMsgLineBuffer???
+
+#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(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);
+
+ *aResult = m_hdrInfos.SafeObjectAt(hdrIndex);
+ if (!*aResult)
+ return NS_ERROR_NULL_POINTER;
+
+ NS_ADDREF(*aResult);
+ 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()
+{
+ 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)
+{
+ NS_ASSERTION((PL_strlen(line) + 1) <= SpaceAvailable(),
+ "Oops... line length greater than space available");
+
+ fLineInfo->uidOfMessage = uid;
+
+ AppendString(line);
+ return NS_OK;
+}
+
+/* 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;
+}
+
+/* attribute string msgHdrs; */
+NS_IMETHODIMP nsMsgImapLineDownloadCache::GetMsgHdrs(const char **aMsgHdrs)
+{
+ // this doesn't copy the string
+ AppendBuffer("", 1); // null terminate the buffer
+ *aMsgHdrs = 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(nsIRunnable)
+ NS_INTERFACE_MAP_ENTRY(nsIImapProtocol)
+ NS_INTERFACE_MAP_ENTRY(nsIInputStreamCallback)
+ NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+ NS_INTERFACE_MAP_ENTRY(nsIImapProtocolSink)
+ NS_INTERFACE_MAP_ENTRY(nsIMsgAsyncPromptListener)
+NS_INTERFACE_MAP_END_THREADSAFE
+
+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 = 60;
+static nsCString gForceSelectDetect;
+static nsTArray<nsCString> gForceSelectServersArray;
+
+// let delete model control expunging, i.e., don't ever expunge when the
+// user chooses the imap delete model, otherwise, expunge when over the
+// threshhold. This is the normal TB behavior.
+static const int32_t kAutoExpungeDeleteModel = 0;
+// Expunge whenever the folder is opened
+static const int32_t kAutoExpungeAlways = 1;
+// Expunge when over the threshhold, independent of the delete model.
+static const int32_t kAutoExpungeOnThreshold = 2;
+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.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);
+ aPrefBranch->GetCharPref("mail.imap.force_select_detect",
+ getter_Copies(gForceSelectDetect));
+ ParseString(gForceSelectDetect, ';', gForceSelectServersArray);
+
+ 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;
+}
+
+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_flagState = nullptr;
+ m_fetchBodyIdList = nullptr;
+ m_isGmailServer = false;
+ m_fetchingWholeMessage = 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",
+ getter_Copies(customDBHeaders));
+
+ ParseString(customDBHeaders, ' ', mCustomDBHeaders);
+ prefBranch->GetBoolPref("mailnews.display.prefer_plaintext", &m_preferPlainText);
+
+ nsAutoCString customHeaders;;
+ prefBranch->GetCharPref("mailnews.customHeaders",
+ getter_Copies(customHeaders));
+ customHeaders.StripWhitespace();
+ ParseString(customHeaders, ':', mCustomHeaders);
+ }
+
+ // ***** 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_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_checkForNewMailDownloadsHeaders = true; // this should be on by default
+ m_hierarchyNameState = kNoOperationInProgress;
+ m_discoveryStatus = eContinue;
+
+ m_overRideUrlConnectionInfo = false;
+ // m_dataOutputBuf is used by Send Data
+ m_dataOutputBuf = (char *) PR_CALLOC(sizeof(char) * OUTPUT_BUFFER_SIZE);
+ m_allocatedSize = 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();
+
+ // 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;
+
+ // where should we do this? Perhaps in the factory object?
+ if (!IMAP)
+ IMAP = PR_NewLogModule("IMAP");
+}
+
+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_PRECONDITION(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_forceSelectValue);
+ aServer->GetUseCondStore(&m_useCondStore);
+ aServer->GetUseCompressDeflate(&m_useCompressDeflate);
+ NS_ADDREF(m_flagState);
+
+ 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(u"imapEmptyMimePart",
+ getter_Copies(m_emptyMimePartString));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Now initialize the thread for the connection
+ if (m_thread == nullptr)
+ {
+ nsresult rv = NS_NewThread(getter_AddRefs(m_iThread), this);
+ if (NS_FAILED(rv))
+ {
+ NS_ASSERTION(m_iThread, "Unable to create imap thread.\n");
+ return rv;
+ }
+ m_iThread->GetPRThread(&m_thread);
+
+ }
+ return NS_OK;
+}
+
+nsImapProtocol::~nsImapProtocol()
+{
+ PR_Free(m_fetchBodyIdList);
+
+ NS_IF_RELEASE(m_flagState);
+
+ PR_Free(m_dataOutputBuf);
+
+ // **** We must be out of the thread main loop function
+ NS_ASSERTION(!m_imapThreadIsRunning, "Oops, thread is still running.\n");
+}
+
+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()
+{
+ nsresult res;
+ if (m_runningUrl)
+ {
+ if (!m_imapMailFolderSink)
+ {
+ nsCOMPtr<nsIImapMailFolderSink> aImapMailFolderSink;
+ (void) m_runningUrl->GetImapMailFolderSink(getter_AddRefs(aImapMailFolderSink));
+ if (aImapMailFolderSink)
+ {
+ m_imapMailFolderSink = new ImapMailFolderSinkProxy(aImapMailFolderSink);
+ }
+ }
+
+ 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;
+ MsgNewNotificationCallbacksAggregation(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 LoadUrl, right
+// before the url gets run - i.e., the url is next in line to run.
+nsresult nsImapProtocol::SetupWithUrl(nsIURI * aURL, nsISupports* aConsumer)
+{
+ nsresult rv = NS_ERROR_FAILURE;
+ NS_PRECONDITION(aURL, "null URL passed into Imap Protocol");
+ if (aURL)
+ {
+ m_runningUrl = do_QueryInterface(aURL, &rv);
+ m_runningUrlLatest = m_runningUrl;
+ if (NS_FAILED(rv)) return rv;
+ 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 =
+ do_CreateInstance("@mozilla.org/nullprincipal;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // 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_DATA_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER);
+ m_mockChannel = do_QueryInterface(channel);
+
+ // 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
+ nsCOMPtr<nsIRequest> ourRequest = do_QueryInterface(m_mockChannel);
+ if (loadGroup)
+ loadGroup->AddRequest(ourRequest, 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;
+ m_mockChannel->GetChannelContext(getter_AddRefs(m_channelContext));
+ nsCOMPtr<nsIMsgWindow> msgWindow;
+ GetMsgWindow(getter_AddRefs(msgWindow));
+ if (!msgWindow)
+ GetTopmostMsgWindow(getter_AddRefs(msgWindow));
+ if (msgWindow)
+ {
+ 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;
+ MsgNewInterfaceRequestorAggregation(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->GetRealHostName(m_realHostName);
+ int32_t authMethod;
+ (void) server->GetAuthMethod(&authMethod);
+#ifdef MOZ_MAILNEWS_OAUTH2
+ InitPrefAuthMethods(authMethod, server);
+#else
+ InitPrefAuthMethods(authMethod);
+#endif
+ (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 trashFolderName;
+ if (NS_SUCCEEDED(imapServer->GetTrashFolderName(trashFolderName)))
+ CopyUTF16toMUTF7(trashFolderName, m_trashFolderName);
+
+ 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.
+ if (preferPlainText != m_preferPlainText)
+ {
+ m_hostSessionList->ClearShellCacheForHost(GetImapServerKey());
+ m_preferPlainText = preferPlainText;
+ }
+ }
+
+ if ( m_runningUrl && !m_transport /* and we don't have a transport yet */)
+ {
+ // extract the file name and create a file transport...
+ int32_t port=-1;
+ server->GetPort(&port);
+
+ if (port <= 0)
+ {
+ int32_t socketType;
+ // Be a bit smarter about setting the default port
+ port = (NS_SUCCEEDED(server->GetSocketType(&socketType)) &&
+ socketType == nsMsgSocketType::SSL) ?
+ nsIImapUrl::DEFAULT_IMAPS_PORT : nsIImapUrl::DEFAULT_IMAP_PORT;
+ }
+ nsCOMPtr<nsISocketTransportService> socketService =
+ do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv) && aURL)
+ {
+ aURL->GetPort(&port);
+
+ Log("SetupWithUrl", 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";
+
+ nsCOMPtr<nsIProxyInfo> proxyInfo;
+ if (m_mockChannel)
+ rv = MsgExamineForProxy(m_mockChannel, getter_AddRefs(proxyInfo));
+ if (NS_FAILED(rv))
+ proxyInfo = nullptr;
+
+ const nsACString *socketHost;
+ uint16_t socketPort;
+
+ if (m_overRideUrlConnectionInfo)
+ {
+ socketHost = &m_logonHost;
+ socketPort = m_logonPort;
+ }
+ else
+ {
+ socketHost = &m_realHostName;
+ socketPort = port;
+ }
+ rv = socketService->CreateTransport(&connectionType, connectionType != nullptr,
+ *socketHost, socketPort, proxyInfo,
+ getter_AddRefs(m_transport));
+ if (NS_FAILED(rv) && m_socketType == nsMsgSocketType::trySTARTTLS)
+ {
+ connectionType = nullptr;
+ m_socketType = nsMsgSocketType::plain;
+ rv = socketService->CreateTransport(&connectionType, connectionType != nullptr,
+ *socketHost, socketPort, proxyInfo,
+ 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);
+ }
+ }
+ } // if m_runningUrl
+
+ 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 way too long
+ // for things like APPEND, which should come back immediately.
+ if (m_imapAction == nsIImapUrl::nsImapAppendMsgFromFile ||
+ m_imapAction == nsIImapUrl::nsImapAppendDraftFromFile)
+ {
+ readWriteTimeout = 20;
+ }
+ 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 status for our underlying transport.
+ nsCOMPtr<nsISupports> securityInfo;
+ m_transport->GetSecurityInfo(getter_AddRefs(securityInfo));
+ m_mockChannel->SetSecurityInfo(securityInfo);
+
+ SetSecurityCallbacksFromChannel(m_transport, m_mockChannel);
+
+ nsCOMPtr<nsITransportEventSink> sink = do_QueryInterface(m_mockChannel);
+ if (sink) {
+ nsCOMPtr<nsIThread> thread = do_GetMainThread();
+ m_transport->SetEventSink(sink, thread);
+ }
+
+ // and if we have a cache entry that we are saving the message to, set the security info on it too.
+ // since imap only uses the memory cache, passing this on is the right thing to do.
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_runningUrl);
+ if (mailnewsUrl)
+ {
+ nsCOMPtr<nsICacheEntry> cacheEntry;
+ mailnewsUrl->GetMemCacheEntry(getter_AddRefs(cacheEntry));
+ if (cacheEntry)
+ cacheEntry->SetSecurityInfo(securityInfo);
+ }
+ }
+ } // if aUR
+
+ 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(m_mockChannel.forget());
+ }
+ }
+ }
+
+ m_channelContext = nullptr; // this might be the url - null it out before the final release of the url
+ 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(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(mailnewsurl.forget());
+ }
+ saveFolderSink = nullptr;
+}
+
+
+class nsImapThreadShutdownEvent : public mozilla::Runnable {
+public:
+ explicit nsImapThreadShutdownEvent(nsIThread *thread) : mThread(thread) {
+ }
+ NS_IMETHOD Run() {
+ mThread->Shutdown();
+ return NS_OK;
+ }
+private:
+ nsCOMPtr<nsIThread> mThread;
+};
+
+
+NS_IMETHODIMP nsImapProtocol::Run()
+{
+ PR_CEnterMonitor(this);
+ NS_ASSERTION(!m_imapThreadIsRunning,
+ "Oh. oh. thread is already running. What's wrong here?");
+ if (m_imapThreadIsRunning)
+ {
+ PR_CExitMonitor(this);
+ return NS_OK;
+ }
+
+ m_imapThreadIsRunning = true;
+ PR_CExitMonitor(this);
+
+ // call the platform specific main loop ....
+ ImapThreadMainLoop();
+
+ if (m_runningUrl)
+ {
+ NS_ReleaseOnMainThread(m_runningUrl.forget());
+ }
+
+ // close streams via UI thread if it's not already done
+ if (m_imapProtocolSink)
+ m_imapProtocolSink->CloseStreams();
+
+ m_imapMailFolderSink = nullptr;
+ m_imapMessageSink = nullptr;
+
+ // shutdown this thread, but do it from the main thread
+ nsCOMPtr<nsIRunnable> ev = new nsImapThreadShutdownEvent(m_iThread);
+ if (NS_FAILED(NS_DispatchToMainThread(ev)))
+ NS_WARNING("Failed to dispatch nsImapThreadShutdownEvent");
+ m_iThread = nullptr;
+
+ // Release protocol object on the main thread to avoid destruction of 'this'
+ // on the IMAP thread, which causes grief for weak references.
+ nsCOMPtr<nsIImapProtocol> releaseOnMain(this);
+ NS_ReleaseOnMainThread(releaseOnMain.forget());
+ return NS_OK;
+}
+
+//
+// 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;
+ m_channelContext = 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_nextUrlReadyToRun = 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)
+{
+ NS_WARNING_ASSERTION(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;
+}
+
+void
+nsImapProtocol::TellThreadToDie()
+{
+ nsresult rv = NS_OK;
+ NS_WARNING_ASSERTION(!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 = m_transport->IsAlive(&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)
+ Close(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)
+ {
+ PseudoInterrupt(true);
+ *interrupted = true;
+ }
+ // If we're interrupted, doom any incomplete cache entry.
+ 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=%x]\n", this));
+
+ PRIntervalTime sleepTime = kImapSleepTime;
+ while (!DeathSignalReceived())
+ {
+ nsresult rv = NS_OK;
+ bool readyToRun;
+
+ // wait for an URL to process...
+ {
+ ReentrantMonitorAutoEnter mon(m_urlReadyToRunMonitor);
+
+ while (NS_SUCCEEDED(rv) && !DeathSignalReceived() &&
+ !m_nextUrlReadyToRun && !m_threadShouldDie)
+ rv = mon.Wait(sleepTime);
+
+ readyToRun = 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 (readyToRun && m_runningUrl)
+ {
+ if (m_currentServerCommandTagNumber && m_transport)
+ {
+ bool isAlive;
+ rv = m_transport->IsAlive(&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: Though we cleared m_nextUrlReadyToRun above, it may have been
+ // set by LoadImapUrl, which runs on the main thread. Because of this,
+ // we must not try to clear m_nextUrlReadyToRun here.
+ //
+ if (ProcessCurrentURL())
+ {
+ m_nextUrlReadyToRun = true;
+ m_imapMailFolderSink = nullptr;
+ }
+ else
+ {
+ // see if we want to go into idle mode. Might want to check a pref here too.
+ if (m_useIdle && !m_urlInProgress && GetServerStateParser().GetCapabilityFlag() & kHasIdleCapability
+ && GetServerStateParser().GetIMAPstate()
+ == nsImapServerResponseParser::kFolderSelected)
+ {
+ Idle(); // for now, lets just do it. We'll probably want to use a timer
+ if (!m_idle)
+ {
+ // Server rejected IDLE. Treat like IDLE not enabled or available.
+ m_imapMailFolderSink = nullptr;
+ }
+ }
+ else // if not idle, don't need to remember folder sink
+ m_imapMailFolderSink = nullptr;
+ }
+ }
+ else if (m_idle && !m_threadShouldDie)
+ {
+ HandleIdleResponses();
+ }
+ 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=%x]\n", this));
+}
+
+void nsImapProtocol::HandleIdleResponses()
+{
+ // int32_t oldRecent = GetServerStateParser().NumberOfRecentMessages();
+ nsAutoCString commandBuffer(GetServerCommandTag());
+ commandBuffer.Append(" IDLE" CRLF);
+
+ do
+ {
+ ParseIMAPandCheckForNewMail(commandBuffer.get());
+ }
+ while (m_inputStreamBuffer->NextLineAvailable() && GetServerStateParser().Connected());
+
+ // if (oldRecent != GetServerStateParser().NumberOfRecentMessages())
+ // We might check that something actually changed, but for now we can
+ // just assume it. OnNewIdleMessages must run a url, so that
+ // we'll go back into asyncwait mode.
+ if (GetServerStateParser().Connected() && m_imapMailFolderSink)
+ m_imapMailFolderSink->OnNewIdleMessages();
+}
+
+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))
+ {
+ // we've been pre-authenticated.
+ // we can skip the whole password step, right into the
+ // kAuthenticated state
+ GetServerStateParser().PreauthSetAuthenticatedState();
+
+ if (GetServerStateParser().GetCapabilityFlag() == kCapabilityUndefined)
+ Capability();
+
+ if ( !(GetServerStateParser().GetCapabilityFlag() &
+ (kIMAP4Capability | kIMAP4rev1Capability | kIMAP4other) ) )
+ {
+ // AlertUserEvent_UsingId(MK_MSG_IMAP_SERVER_NOT_IMAP4);
+ SetConnectionStatus(NS_ERROR_FAILURE); // stop netlib
+ }
+ else
+ {
+ // let's record the user as authenticated.
+ m_imapServerSink->SetUserAuthenticated(true);
+
+ ProcessAfterAuthenticated();
+ // the connection was a success
+ SetConnectionStatus(NS_OK);
+ }
+ }
+
+ 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)
+ cacheEntry->AsyncDoom(nullptr);
+ }
+}
+
+// returns 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)
+ {
+ nsCOMPtr<nsIRequest> request = do_QueryInterface(m_mockChannel);
+ m_channelListener->OnStartRequest(request, m_channelContext);
+ }
+ 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\n" : " is not valid\n");
+ 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...
+ {
+ nsCOMPtr<nsIRequest> request = do_QueryInterface(m_mockChannel);
+ m_channelListener->OnStartRequest(request, m_channelContext);
+ }
+ // 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 (GetServerStateParser().GetCapabilityFlag() == kCapabilityUndefined)
+ Capability();
+
+ if ( !(GetServerStateParser().GetCapabilityFlag() & (kIMAP4Capability | kIMAP4rev1Capability |
+ kIMAP4other) ) )
+ {
+ if (!DeathSignalReceived() && NS_SUCCEEDED(GetConnectionStatus()))
+ AlertUserEventUsingName("imapServerNotImap4");
+
+ SetConnectionStatus(NS_ERROR_FAILURE); // stop netlib
+ }
+ else
+ {
+ if ((m_connectionType.Equals("starttls")
+ && (m_socketType == nsMsgSocketType::trySTARTTLS
+ && (GetServerStateParser().GetCapabilityFlag() & kHasStartTLSCapability)))
+ || m_socketType == nsMsgSocketType::alwaysSTARTTLS)
+ {
+ StartTLS();
+ if (GetServerStateParser().LastCommandSuccessful())
+ {
+ nsCOMPtr<nsISupports> secInfo;
+ nsCOMPtr<nsISocketTransport> strans = do_QueryInterface(m_transport, &rv);
+ if (NS_FAILED(rv))
+ return false;
+
+ rv = strans->GetSecurityInfo(getter_AddRefs(secInfo));
+
+ if (NS_SUCCEEDED(rv) && secInfo)
+ {
+ nsCOMPtr<nsISSLSocketControl> sslControl = do_QueryInterface(secInfo, &rv);
+
+ if (NS_SUCCEEDED(rv) && sslControl)
+ {
+ rv = sslControl->StartTLS();
+ if (NS_SUCCEEDED(rv))
+ {
+ if (m_socketType == nsMsgSocketType::trySTARTTLS)
+ m_imapServerSink->UpdateTrySTARTTLSPref(true);
+ // force re-issue of "capability", because servers may
+ // enable other auth features (e.g. remove LOGINDISABLED
+ // and add AUTH=PLAIN) after we upgraded to SSL.
+ Capability();
+ eIMAPCapabilityFlags capabilityFlag = GetServerStateParser().GetCapabilityFlag();
+ // 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.
+ if (!(capabilityFlag & kHasStartTLSCapability))
+ {
+ capabilityFlag |= kHasStartTLSCapability;
+ GetServerStateParser().SetCapabilityFlag(capabilityFlag);
+ CommitCapability();
+ }
+ }
+ }
+ }
+ if (NS_FAILED(rv))
+ {
+ nsAutoCString logLine("STARTTLS negotiation 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
+ 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();
+ }
+ else
+ {
+ // trySTARTTLS set, but server doesn't have TLS capability,
+ // so downgrade socket type
+ m_imapServerSink->UpdateTrySTARTTLSPref(false);
+ m_socketType = nsMsgSocketType::plain;
+ }
+ }
+ logonFailed = !TryToLogon();
+ if (m_retryUrlOnError)
+ return RetryUrl();
+ }
+ } // if death signal not received
+
+ 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)
+ 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)
+ {
+ nsCOMPtr<nsIRequest> request = do_QueryInterface(m_mockChannel);
+ NS_ASSERTION(request, "no request");
+ if (request) {
+ nsresult status;
+ request->GetStatus(&status);
+ if (!GetServerStateParser().LastCommandSuccessful() && NS_SUCCEEDED(status))
+ status = NS_MSG_ERROR_IMAP_COMMAND_FAILED;
+ rv = m_channelListener->OnStopRequest(request, m_channelContext, status);
+ }
+ }
+ bool suspendUrl = false;
+ m_runningUrl->GetMoreHeadersToDownload(&suspendUrl);
+ if (mailnewsurl && m_imapMailFolderSink)
+ {
+ if (logonFailed)
+ rv = NS_ERROR_FAILURE;
+ else if (GetServerStateParser().CommandFailed())
+ rv = NS_MSG_ERROR_IMAP_COMMAND_FAILED;
+ else
+ rv = GetConnectionStatus();
+ // we are done with this url.
+ m_imapMailFolderSink->SetUrlState(this, mailnewsurl, false, suspendUrl,
+ rv);
+ // doom the cache entry
+ if (NS_FAILED(rv) && DeathSignalReceived() && m_mockChannel)
+ 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:%lx\n", rv));
+
+ NS_ReleaseOnMainThread(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\n"));
+
+ // 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);
+ }
+ 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);
+ else
+ 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);
+ else
+ 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)
+ : 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.
+// If we don't have a connection yet, we open the connection. 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 b4 we
+// attempt to load a url....
+NS_IMETHODIMP nsImapProtocol::LoadImapUrl(nsIURI * aURL, nsISupports * aConsumer)
+{
+ nsresult rv;
+ if (aURL)
+ {
+#ifdef DEBUG_bienvenu
+ printf("loading url %s\n", aURL->GetSpecOrDefault().get());
+#endif
+ if (TryToRunUrlLocally(aURL, aConsumer))
+ return NS_OK;
+ m_urlInProgress = true;
+ m_imapMailFolderSink = nullptr;
+ rv = SetupWithUrl(aURL, aConsumer);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "error setting up imap url");
+ if (NS_FAILED(rv))
+ return rv;
+
+ rv = SetupSinkProxy(); // generate proxies for all of the event sinks in the url
+ if (NS_FAILED(rv)) // URL can be invalid.
+ return rv;
+
+ m_lastActiveTime = PR_Now();
+ 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(NS_MSGACCOUNTMANAGER_CONTRACTID, &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");
+
+ } // if we received a url!
+ else
+ rv = NS_ERROR_UNEXPECTED;
+
+ 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
+void nsImapProtocol::IncrementCommandTagNumber()
+{
+ sprintf(m_currentServerCommandTag, "%u", ++m_currentServerCommandTagNumber);
+}
+
+const char *nsImapProtocol::GetServerCommandTag()
+{
+ return m_currentServerCommandTag;
+}
+
+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)
+ Close();
+ 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
+ {
+ nsMsgKey *msgIdList = nullptr;
+ uint32_t msgCount = 0;
+ bool more;
+ m_imapMailFolderSink->GetMsgHdrsToDownload(&more, &m_progressCount,
+ &msgCount, &msgIdList);
+ if (msgIdList)
+ {
+ FolderHeaderDump(msgIdList, msgCount);
+ NS_Free(msgIdList);
+ 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. For other
+ // servers that simply don't support IDLE, doing select here will
+ // cause emails to be properly marked "read" after they have been
+ // read in another email client.
+ 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("imapFolderReceivingMessageOf2");
+
+ m_progressIndex = 0;
+ m_progressCount = CountMessagesInIdString(messageIdString.get());
+
+ // we need to set this so we'll get the msg from the memory cache.
+ if (m_imapAction == nsIImapUrl::nsImapMsgFetchPeek)
+ SetContentModified(IMAP_CONTENT_NOT_MODIFIED);
+
+ FetchMessage(messageIdString,
+ (m_imapAction == nsIImapUrl::nsImapMsgPreview)
+ ? kBodyStart : kEveryThingRFC822Peek);
+ if (m_imapAction == nsIImapUrl::nsImapMsgPreview)
+ HeaderFetchCompleted();
+ SetProgressString(nullptr);
+ }
+ else
+ {
+ // A single message ID
+ nsIMAPeFetchFields whatToFetch = kEveryThingRFC822;
+ if(m_imapAction == nsIImapUrl::nsImapMsgFetchPeek)
+ whatToFetch = kEveryThingRFC822Peek;
+
+ // First, let's see if we're requesting a specific MIME part
+ char *imappart = nullptr;
+ m_runningUrl->GetImapPartToFetch(&imappart);
+ if (imappart)
+ {
+ if (bMessageIdsAreUids)
+ {
+ // We actually want a specific MIME part of the message.
+ // The Body Shell will generate it, even though we haven't downloaded it yet.
+
+ IMAP_ContentModifiedType modType = GetShowAttachmentsInline() ?
+ IMAP_CONTENT_MODIFIED_VIEW_INLINE :
+ IMAP_CONTENT_MODIFIED_VIEW_AS_LINKS ;
+
+ RefPtr<nsIMAPBodyShell> foundShell;
+ res = m_hostSessionList->FindShellInCacheForHost(GetImapServerKey(),
+ GetServerStateParser().GetSelectedMailboxName(),
+ messageIdString.get(), modType, getter_AddRefs(foundShell));
+ if (!foundShell)
+ {
+ // The shell wasn't in the cache. Deal with this case later.
+ Log("SHELL",NULL,"Loading part, shell not found in cache!");
+ //MOZ_LOG(IMAP, out, ("BODYSHELL: Loading part, shell not found in cache!"));
+ // The parser will extract the part number from the current URL.
+ SetContentModified(modType);
+ Bodystructure(messageIdString, bMessageIdsAreUids);
+ }
+ else
+ {
+ Log("SHELL", NULL, "Loading Part, using cached shell.");
+ //MOZ_LOG(IMAP, out, ("BODYSHELL: Loading part, using cached shell."));
+ SetContentModified(modType);
+ foundShell->SetConnection(this);
+ GetServerStateParser().UseCachedShell(foundShell);
+ //Set the current uid in server state parser (in case it was used for new mail msgs earlier).
+ GetServerStateParser().SetCurrentResponseUID(strtoul(messageIdString.get(), nullptr, 10));
+ foundShell->Generate(imappart);
+ GetServerStateParser().UseCachedShell(NULL);
+ }
+ }
+ else
+ {
+ // Message IDs are not UIDs.
+ NS_ASSERTION(false, "message ids aren't uids");
+ }
+ PR_Free(imappart);
+ }
+ else
+ {
+ // downloading a single message: try to do it by bodystructure, and/or do it by chunks
+ uint32_t messageSize = GetMessageSize(messageIdString.get(), bMessageIdsAreUids);
+ // We need to check the format_out bits to see if we are allowed to leave out parts,
+ // or if we are required to get the whole thing. Some instances where we are allowed
+ // to do it by parts: when viewing a message, replying to a message, or viewing its source
+ // Some times when we're NOT allowed: when forwarding a message, saving it, moving it, etc.
+ // need to set a flag in the url, I guess, equiv to allow_content_changed.
+ bool allowedToBreakApart = true; // (ce && !DeathSignalReceived()) ? ce->URL_s->allow_content_change : false;
+ bool mimePartSelectorDetected;
+ bool urlOKToFetchByParts = false;
+ m_runningUrl->GetMimePartSelectorDetected(&mimePartSelectorDetected);
+ m_runningUrl->GetFetchPartsOnDemand(&urlOKToFetchByParts);
+
+#ifdef PR_LOGGING
+ {
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsurl = do_QueryInterface(m_runningUrl);
+ nsAutoCString urlSpec;
+ if (mailnewsurl)
+ urlSpec = mailnewsurl->GetSpecOrDefault();
+ MOZ_LOG(IMAP, LogLevel::Debug,
+ ("SHELL: URL %s, OKToFetchByParts %d, allowedToBreakApart %d, ShouldFetchAllParts %d",
+ urlSpec.get(), urlOKToFetchByParts, allowedToBreakApart,
+ GetShouldFetchAllParts()));
+ }
+#endif
+
+ if (urlOKToFetchByParts &&
+ allowedToBreakApart &&
+ !GetShouldFetchAllParts() &&
+ GetServerStateParser().ServerHasIMAP4Rev1Capability() /* &&
+ !mimePartSelectorDetected */) // if a ?part=, don't do BS.
+ {
+ // OK, we're doing bodystructure
+
+ // Before fetching the bodystructure, let's check our body shell cache to see if
+ // we already have it around.
+ RefPtr<nsIMAPBodyShell> foundShell;
+ IMAP_ContentModifiedType modType = GetShowAttachmentsInline() ?
+ IMAP_CONTENT_MODIFIED_VIEW_INLINE :
+ IMAP_CONTENT_MODIFIED_VIEW_AS_LINKS ;
+
+ bool wasStoringMsgOffline;
+ m_runningUrl->GetStoreResultsOffline(&wasStoringMsgOffline);
+ m_runningUrl->SetStoreOfflineOnFallback(wasStoringMsgOffline);
+ m_runningUrl->SetStoreResultsOffline(false);
+ SetContentModified(modType); // This will be looked at by the cache
+ if (bMessageIdsAreUids)
+ {
+ res = m_hostSessionList->FindShellInCacheForHost(GetImapServerKey(),
+ GetServerStateParser().GetSelectedMailboxName(),
+ messageIdString.get(), modType, getter_AddRefs(foundShell));
+ if (foundShell)
+ {
+ Log("SHELL",NULL,"Loading message, using cached shell.");
+ //MOZ_LOG(IMAP, out, ("BODYSHELL: Loading message, using cached shell."));
+ foundShell->SetConnection(this);
+ GetServerStateParser().UseCachedShell(foundShell);
+ //Set the current uid in server state parser (in case it was used for new mail msgs earlier).
+ GetServerStateParser().SetCurrentResponseUID(strtoul(messageIdString.get(), nullptr, 10));
+ foundShell->Generate(NULL);
+ GetServerStateParser().UseCachedShell(NULL);
+ }
+ }
+
+ if (!foundShell)
+ Bodystructure(messageIdString, bMessageIdsAreUids);
+ }
+ else
+ {
+ // Not doing bodystructure. Fetch the whole thing, and try to do
+ // it in chunks.
+ SetContentModified(IMAP_CONTENT_NOT_MODIFIED);
+ FetchTryChunking(messageIdString, whatToFetch,
+ bMessageIdsAreUids, NULL, messageSize, true);
+ }
+ }
+ if (GetServerStateParser().LastCommandSuccessful()
+ && m_imapAction != nsIImapUrl::nsImapMsgPreview
+ && 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.
+ MOZ_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 set them.
+ uint16_t userFlags;
+ GetSupportedUserFlags(&userFlags);
+ if (! (userFlags & kImapMsgSupportUserFlag))
+ break;
+ nsCString messageIdString;
+ nsCString addFlags;
+ nsCString subtractFlags;
+
+ m_runningUrl->GetListOfMessageIds(messageIdString);
+ m_runningUrl->GetCustomAddFlags(addFlags);
+ m_runningUrl->GetCustomSubtractFlags(subtractFlags);
+ if (!addFlags.IsEmpty())
+ {
+ nsAutoCString storeString("+FLAGS (");
+ storeString.Append(addFlags);
+ storeString.Append(")");
+ Store(messageIdString, storeString.get(), true);
+ }
+ if (!subtractFlags.IsEmpty())
+ {
+ 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())
+ {
+ //delete_message_struct *deleteMsg = (delete_message_struct *) PR_Malloc (sizeof(delete_message_struct));
+ // convert name back from utf7
+ 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(NS_LITERAL_CSTRING("1:*"), "+FLAGS.SILENT (\\Deleted)",
+ false); // use sequence #'s
+
+ if (GetServerStateParser().LastCommandSuccessful())
+ Expunge(); // expunge messages with deleted flag
+ if (GetServerStateParser().LastCommandSuccessful())
+ {
+ // convert name back from utf7
+ 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("imapFolderReceivingMessageOf2");
+ m_progressIndex = 0;
+ m_progressCount = CountMessagesInIdString(messageIdString.get());
+
+ FetchMessage(messageIdString, kEveryThingRFC822Peek);
+
+ SetProgressString(nullptr);
+ 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 messsage into one end of the pipe
+ // and they will read it out of the other end.
+ else 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;
+ 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.Append(" select \"");
+ commandBuffer.Append(escapedName.get());
+ commandBuffer.Append("\"");
+ if (UseCondStore())
+ commandBuffer.Append(" (CONDSTORE)");
+ commandBuffer.Append(CRLF);
+
+ nsresult res;
+ res = SendData(commandBuffer.get());
+ if (NS_FAILED(res)) return;
+ ParseIMAPandCheckForNewMail();
+
+ 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);
+ }
+}
+
+// Please call only with a single message ID
+void nsImapProtocol::Bodystructure(const nsCString &messageId, bool idIsUid)
+{
+ IncrementCommandTagNumber();
+
+ nsCString commandString(GetServerCommandTag());
+ if (idIsUid)
+ commandString.Append(" UID");
+ commandString.Append(" fetch ");
+
+ commandString.Append(messageId);
+ commandString.Append(" (BODYSTRUCTURE)" CRLF);
+
+ nsresult rv = SendData(commandString.get());
+ if (NS_SUCCEEDED(rv))
+ ParseIMAPandCheckForNewMail(commandString.get());
+}
+
+void nsImapProtocol::PipelinedFetchMessageParts(const char *uid, nsIMAPMessagePartIDArray *parts)
+{
+ // assumes no chunking
+
+ // build up a string to fetch
+ nsCString stringToFetch, what;
+ uint32_t currentPartNum = 0;
+ while ((parts->GetNumParts() > currentPartNum) && !DeathSignalReceived())
+ {
+ nsIMAPMessagePartID *currentPart = parts->GetPart(currentPartNum);
+ if (currentPart)
+ {
+ // Do things here depending on the type of message part
+ // Append it to the fetch string
+ if (currentPartNum > 0)
+ stringToFetch.Append(" ");
+
+ switch (currentPart->GetFields())
+ {
+ case kMIMEHeader:
+ what = "BODY.PEEK[";
+ what.Append(currentPart->GetPartNumberString());
+ what.Append(".MIME]");
+ stringToFetch.Append(what);
+ break;
+ case kRFC822HeadersOnly:
+ if (currentPart->GetPartNumberString())
+ {
+ what = "BODY.PEEK[";
+ what.Append(currentPart->GetPartNumberString());
+ what.Append(".HEADER]");
+ stringToFetch.Append(what);
+ }
+ else
+ {
+ // headers for the top-level message
+ stringToFetch.Append("BODY.PEEK[HEADER]");
+ }
+ break;
+ default:
+ NS_ASSERTION(false, "we should only be pipelining MIME headers and Message headers");
+ break;
+ }
+
+ }
+ currentPartNum++;
+ }
+
+ // Run the single, pipelined fetch command
+ if ((parts->GetNumParts() > 0) && !DeathSignalReceived() && !GetPseudoInterrupted() && stringToFetch.get())
+ {
+ IncrementCommandTagNumber();
+
+ nsCString commandString(GetServerCommandTag());
+ commandString.Append(" UID fetch ");
+ commandString.Append(uid, 10);
+ commandString.Append(" (");
+ commandString.Append(stringToFetch);
+ commandString.Append(")" CRLF);
+ nsresult rv = SendData(commandString.get());
+ if (NS_SUCCEEDED(rv))
+ ParseIMAPandCheckForNewMail(commandString.get());
+ }
+}
+
+
+void nsImapProtocol::FetchMsgAttribute(const nsCString &messageIds, const nsCString &attribute)
+{
+ IncrementCommandTagNumber();
+
+ nsAutoCString commandString (GetServerCommandTag());
+ commandString.Append(" UID fetch ");
+ commandString.Append(messageIds);
+ commandString.Append(" (");
+ commandString.Append(attribute);
+ commandString.Append(")" 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.Append(" %s (XSENDER UID RFC822.SIZE BODY[]");
+ else
+ commandString.Append(" %s (UID RFC822.SIZE BODY[]");
+ }
+ else
+ {
+ if (GetServerStateParser().GetCapabilityFlag() & kHasXSenderCapability)
+ commandString.Append(" %s (XSENDER UID RFC822.SIZE RFC822");
+ else
+ commandString.Append(" %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 (arbitraryHeaders.Find(mCustomDBHeaders[i], CaseInsensitiveCompare) == kNotFound)
+ {
+ if (!arbitraryHeaders.IsEmpty())
+ arbitraryHeaders.Append(' ');
+ arbitraryHeaders.Append(mCustomDBHeaders[i]);
+ }
+ }
+ for (uint32_t i = 0; i < mCustomHeaders.Length(); i++)
+ {
+ if (arbitraryHeaders.Find(mCustomHeaders[i], CaseInsensitiveCompare) == kNotFound)
+ {
+ 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);
+ NS_Free(headersToDL);
+ if (what)
+ {
+ commandString.Append(" %s (UID ");
+ if (m_isGmailServer)
+ commandString.Append("X-GM-MSGID X-GM-THRID X-GM-LABELS ");
+ if (aolImapServer)
+ commandString.Append(" XAOL.SIZE") ;
+ else
+ commandString.Append("RFC822.SIZE");
+ commandString.Append(" FLAGS");
+ commandString.Append(what);
+ PR_Free(what);
+ }
+ else
+ {
+ commandString.Append(" %s (UID RFC822.SIZE BODY.PEEK[HEADER] FLAGS)");
+ }
+ }
+ else
+ commandString.Append(" %s (UID RFC822.SIZE BODY.PEEK[HEADER] FLAGS)");
+ }
+ else
+ commandString.Append(" %s (UID RFC822.SIZE RFC822.HEADER FLAGS)");
+ break;
+ case kUid:
+ commandString.Append(" %s (UID)");
+ break;
+ case kFlags:
+ GetServerStateParser().SetFetchingFlags(true);
+ commandString.Append(" %s (FLAGS)");
+ break;
+ case kRFC822Size:
+ commandString.Append(" %s (RFC822.SIZE)");
+ break;
+ case kBodyStart:
+ {
+ int32_t numBytesToFetch;
+ m_runningUrl->GetNumBytesToFetch(&numBytesToFetch);
+
+ commandString.Append(" %s (UID BODY.PEEK[HEADER.FIELDS (Content-Type Content-Transfer-Encoding)] BODY.PEEK[TEXT]<0.");
+ commandString.AppendInt(numBytesToFetch);
+ commandString.Append(">)");
+ }
+ break;
+ case kRFC822HeadersOnly:
+ if (GetServerStateParser().ServerHasIMAP4Rev1Capability())
+ {
+ if (part)
+ {
+ commandString.Append(" %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.Append(" %s (BODY[HEADER])");
+ }
+ }
+ else
+ commandString.Append(" %s (RFC822.HEADER)");
+ break;
+ case kMIMEPart:
+ commandString.Append(" %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.Append(" %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));
+ 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())
+ {
+ FetchMessage(messageIds,
+ whatToFetch,
+ nullptr,
+ startByte, m_chunkSize,
+ part);
+ startByte += m_chunkSize;
+ }
+
+ // 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::PipelinedFetchMessageParts(nsCString &uid, nsIMAPMessagePartIDArray *parts)
+{
+ // assumes no chunking
+
+ // build up a string to fetch
+ nsCString stringToFetch;
+ nsCString what;
+
+ uint32_t currentPartNum = 0;
+ while ((parts->GetNumParts() > currentPartNum) && !DeathSignalReceived())
+ {
+ nsIMAPMessagePartID *currentPart = parts->GetPart(currentPartNum);
+ if (currentPart)
+ {
+ // Do things here depending on the type of message part
+ // Append it to the fetch string
+ if (currentPartNum > 0)
+ stringToFetch += " ";
+
+ switch (currentPart->GetFields())
+ {
+ case kMIMEHeader:
+ what = "BODY.PEEK[";
+ what += currentPart->GetPartNumberString();
+ what += ".MIME]";
+ stringToFetch += what;
+ break;
+ case kRFC822HeadersOnly:
+ if (currentPart->GetPartNumberString())
+ {
+ what = "BODY.PEEK[";
+ what += currentPart->GetPartNumberString();
+ what += ".HEADER]";
+ stringToFetch += what;
+ }
+ else
+ {
+ // headers for the top-level message
+ stringToFetch += "BODY.PEEK[HEADER]";
+ }
+ break;
+ default:
+ NS_ASSERTION(false, "we should only be pipelining MIME headers and Message headers");
+ break;
+ }
+
+ }
+ currentPartNum++;
+ }
+
+ // Run the single, pipelined fetch command
+ if ((parts->GetNumParts() > 0) && !DeathSignalReceived() && !GetPseudoInterrupted() && stringToFetch.get())
+ {
+ IncrementCommandTagNumber();
+
+ char *commandString = PR_smprintf("%s UID fetch %s (%s)%s",
+ GetServerCommandTag(), uid.get(),
+ stringToFetch.get(), CRLF);
+
+ if (commandString)
+ {
+ nsresult rv = SendData(commandString);
+ if (NS_SUCCEEDED(rv))
+ ParseIMAPandCheckForNewMail(commandString);
+ PR_Free(commandString);
+ }
+ else
+ HandleMemoryFailure();
+ }
+}
+
+
+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))
+ {
+ nsCOMPtr<nsIRequest> request = do_QueryInterface(m_mockChannel);
+ m_channelListener->OnDataAvailable(request, m_channelContext, 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_PRECONDITION(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_PRECONDITION(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)
+ m_curHdrInfo->CacheLine(messageLine, GetServerStateParser().CurrentResponseUID());
+ 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 to big
+ if (m_downloadLineCache->SpaceAvailable() < lineLength + 1)
+ PostLineDownLoadEvent(messageLine, GetServerStateParser().CurrentResponseUID());
+ else
+ m_downloadLineCache->CacheLine(messageLine, GetServerStateParser().CurrentResponseUID());
+
+ 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;
+#ifdef PR_LOGGING
+ if (m_bytesToChannel != GetServerStateParser().SizeOfMostRecentMessage()) {
+ MOZ_LOG(IMAP, LogLevel::Debug, ("STREAM:CLOSE Server's RFC822.SIZE %u, actual size %u",
+ GetServerStateParser().SizeOfMostRecentMessage(),
+ m_bytesToChannel));
+ }
+#endif
+ }
+ // 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)
+ 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;
+ // Figure out if we need to do any kind of sync.
+ bool needFolderSync = (flagStateEmpty || added == deleted) && (!UseCondStore() ||
+ (GetServerStateParser().fHighestModSeq != mFolderLastModSeq) ||
+ (GetShowDeletedMessages() &&
+ GetServerStateParser().NumberOfMessages() != mFolderTotalMsgCount));
+
+ // 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 need a full sync.
+ bool needFullFolderSync = !mFolderHighestUID || (flagStateEmpty && !UseCondStore());
+
+ if (needFullFolderSync || needFolderSync)
+ {
+ nsCString idsToFetch("1:*");
+ char fetchModifier[40] = "";
+ if (!needFullFolderSync && !GetShowDeletedMessages() && UseCondStore())
+ 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())
+ {
+ if (m_flagState->NumberOfDeletedMessages() +
+ mFolderTotalMsgCount != GetServerStateParser().NumberOfMessages())
+ {
+ // sanity check failed - fall back to full flag sync
+ m_flagState->Reset();
+ m_flagState->SetPartialUIDFetch(false);
+ FetchMessage(NS_LITERAL_CSTRING("1:*"), kFlags);
+ }
+ }
+ int32_t numDeleted = m_flagState->NumberOfDeletedMessages();
+ // Don't do expunge when we are lite selecting folder because we
+ // could be doing undo.
+ // Expunge if we're always expunging, or the number of deleted messages
+ // is over the threshhold, and we're either always respecting the
+ // threshhold, or we're expunging based on the delete model, and
+ // the delete model is not the imap delete model.
+ if (m_imapAction != nsIImapUrl::nsImapLiteSelectFolder &&
+ (gExpungeOption == kAutoExpungeAlways ||
+ (numDeleted >= gExpungeThreshold &&
+ (gExpungeOption == kAutoExpungeOnThreshold ||
+ (gExpungeOption == kAutoExpungeDeleteModel && !GetShowDeletedMessages())))))
+ Expunge();
+ }
+ }
+ else
+ {
+ uint32_t highestRecordedUID = GetServerStateParser().HighestRecordedUID();
+ // if we're using CONDSTORE, and the parser hasn't seen any UIDs, use
+ // the highest UID we've seen from the folder.
+ if (UseCondStore() && !highestRecordedUID)
+ highestRecordedUID = mFolderHighestUID;
+
+ AppendUid(fetchStr, highestRecordedUID + 1);
+ fetchStr.Append(":*");
+ 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;
+ }
+
+ bool entered_waitForBodyIdsMonitor = false;
+
+ uint32_t *msgIdList = nullptr;
+ uint32_t msgCount = 0;
+
+ nsImapMailboxSpec *new_spec = GetServerStateParser().CreateCurrentMailboxSpec();
+ if (new_spec && GetServerStateParser().LastCommandSuccessful())
+ {
+ nsImapAction imapAction;
+ nsresult res = m_runningUrl->GetImapAction(&imapAction);
+ if (NS_SUCCEEDED(res) && imapAction == nsIImapUrl::nsImapExpungeFolder)
+ new_spec->mBoxFlags |= kJustExpunged;
+ m_waitForBodyIdsMonitor.Enter();
+ entered_waitForBodyIdsMonitor = true;
+
+ if (m_imapMailFolderSink)
+ {
+ bool more;
+ m_imapMailFolderSink->UpdateImapMailboxInfo(this, new_spec);
+ m_imapMailFolderSink->GetMsgHdrsToDownload(&more, &m_progressCount,
+ &msgCount, &msgIdList);
+ m_progressIndex = 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);
+ }
+ }
+ else if (!new_spec)
+ HandleMemoryFailure();
+
+ if (GetServerStateParser().LastCommandSuccessful())
+ {
+ if (entered_waitForBodyIdsMonitor)
+ m_waitForBodyIdsMonitor.Exit();
+
+ if (msgIdList && !DeathSignalReceived() && GetServerStateParser().LastCommandSuccessful())
+ {
+ FolderHeaderDump(msgIdList, msgCount);
+ NS_Free( msgIdList);
+ }
+ HeaderFetchCompleted();
+ // this might be bogus, how are we going to do pane notification and stuff when we fetch bodies without
+ // headers!
+ }
+ else if (entered_waitForBodyIdsMonitor) // need to exit this monitor if death signal received
+ m_waitForBodyIdsMonitor.Exit();
+
+ // wait for a list of bodies to fetch.
+ if (GetServerStateParser().LastCommandSuccessful())
+ {
+ WaitForPotentialListOfBodysToFetch(&msgIdList, msgCount);
+ if ( msgCount && 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);
+ m_progressIndex = 0;
+ m_progressCount = msgCount;
+ FolderMsgDump(msgIdList, msgCount, kEveryThingRFC822Peek);
+ m_runningUrl->SetStoreResultsOffline(wasStoringOffline);
+ }
+ }
+ if (!GetServerStateParser().LastCommandSuccessful())
+ GetServerStateParser().ResetFlagInfo();
+
+ NS_IF_RELEASE(new_spec);
+}
+
+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("imapReceivingMessageHeaders2");
+ break;
+ case kFlags:
+ SetProgressString("imapReceivingMessageFlags2");
+ break;
+ default:
+ SetProgressString("imapFolderReceivingMessageOf2");
+ break;
+ }
+
+ FolderMsgDumpLoop(msgUids, msgCount, fields);
+
+ SetProgressString(nullptr);
+}
+
+void nsImapProtocol::WaitForPotentialListOfBodysToFetch(uint32_t **msgIdList, uint32_t &msgCount)
+{
+ PRIntervalTime sleepTime = kImapSleepTime;
+
+ ReentrantMonitorAutoEnter fetchListMon(m_fetchBodyListMonitor);
+ while(!m_fetchBodyListIsNew && !DeathSignalReceived())
+ fetchListMon.Wait(sleepTime);
+ m_fetchBodyListIsNew = false;
+
+ *msgIdList = m_fetchBodyIdList;
+ msgCount = m_fetchBodyCount;
+}
+
+// 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(uint32_t *keys, uint32_t keyCount)
+{
+ ReentrantMonitorAutoEnter fetchListMon(m_fetchBodyListMonitor);
+ PR_FREEIF(m_fetchBodyIdList);
+ m_fetchBodyIdList = (uint32_t *) PR_MALLOC(keyCount * sizeof(uint32_t));
+ if (m_fetchBodyIdList)
+ memcpy(m_fetchBodyIdList, keys, keyCount * sizeof(uint32_t));
+ m_fetchBodyCount = keyCount;
+ 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.Append(":*");
+ 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);
+}
+
+// We get called to see if there is mail waiting for us at the server, even if it may have been
+// read elsewhere. We just want to know if we should download headers or not.
+
+bool nsImapProtocol::CheckNewMail()
+{
+ return m_checkForNewMailDownloadsHeaders;
+}
+
+/* static */ void nsImapProtocol::LogImapUrl(const char *logMsg, nsIImapUrl *imapUrl)
+{
+ // nsImapProtocol is not always constructed before this static method is called
+ if (!IMAP)
+ IMAP = PR_NewLogModule("IMAP");
+
+ 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(); // initilize to empty string
+
+ int32_t logDataLen = PL_strlen(logData); // PL_strlen checks for null
+ nsCString logDataLines;
+ const char *logDataToLog;
+ int32_t lastLineEnd;
+
+ const int kLogDataChunkSize = 400; // nspr line length is 512, and we
+ // allow some space for the log preamble.
+
+ // break up buffers > 400 bytes on line boundaries.
+ if (logDataLen > kLogDataChunkSize)
+ {
+ logDataLines.Assign(logData);
+ lastLineEnd = MsgRFindChar(logDataLines, '\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, ("%x:%s:%s-%s:%s:%s: %.400s", this, hostName.get(),
+ selectedStateName, GetServerStateParser().GetSelectedMailboxName(),
+ logSubName, extraInfo, logDataToLog));
+ else
+ MOZ_LOG(IMAP, LogLevel::Info, ("%x:%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, ("%x:%s:%s:%s:%s: %.400s", this,
+ hostName.get(),stateName,logSubName,extraInfo,logDataToLog));
+ else
+ MOZ_LOG(IMAP, LogLevel::Info, ("%x:%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) ? MsgRFindChar(logDataLines, '\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 char * messageId, bool idsAreUids)
+{
+ const char *folderFromParser = GetServerStateParser().GetSelectedMailboxName();
+ if (!folderFromParser || !messageId || !m_runningUrl || !m_hostSessionList)
+ return 0;
+
+ char *folderName = nullptr;
+ uint32_t size;
+
+ nsIMAPNamespace *nsForMailbox = nullptr;
+ m_hostSessionList->GetNamespaceForMailboxForHost(GetImapServerKey(),
+ folderFromParser,
+ nsForMailbox);
+
+ m_runningUrl->AllocateCanonicalPath(folderFromParser,
+ nsForMailbox
+ ? nsForMailbox->GetDelimiter()
+ : kOnlineHierarchySeparatorUnknown,
+ &folderName);
+
+ if (folderName && m_imapMessageSink)
+ m_imapMessageSink->GetMessageSizeFromDB(messageId, &size);
+
+ PR_FREEIF(folderName);
+
+ 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)
+ {
+ nsCOMPtr<nsIRequest> request = do_QueryInterface(m_mockChannel);
+ if (request)
+ {
+ nsresult returnValue;
+ request->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;
+}
+
+void nsImapProtocol::PseudoInterrupt(bool the_interrupt)
+{
+ ReentrantMonitorAutoEnter pseudoInterruptMon(m_pseudoInterruptMonitor);
+ m_pseudoInterrupted = the_interrupt;
+ if (the_interrupt)
+ Log("CONTROL", NULL, "PSEUDO-Interrupted");
+}
+
+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;
+
+}
+
+void nsImapProtocol::SetContentModified(IMAP_ContentModifiedType modified)
+{
+ if (m_runningUrl && m_imapMessageSink)
+ m_imapMessageSink->SetContentModified(m_runningUrl, modified);
+}
+
+
+bool nsImapProtocol::GetShouldFetchAllParts()
+{
+ if (m_runningUrl && !DeathSignalReceived())
+ {
+ nsImapContentModifiedType contentModified;
+ if (NS_SUCCEEDED(m_runningUrl->GetContentModified(&contentModified)))
+ return (contentModified == IMAP_CONTENT_FORCE_CONTENT_NOT_MODIFIED);
+ }
+ return true;
+}
+
+// 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();
+}
+
+
+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;
+ do
+ {
+ newLine = m_inputStreamBuffer->ReadNextLine(m_inputStream, numBytesInLine, needMoreData, &rv);
+ MOZ_LOG(IMAP, LogLevel::Debug, ("ReadNextLine [stream=%x nb=%u needmore=%u]\n",
+ 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:
+ 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)
+{
+ 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_imapAction != nsIImapUrl::nsImapMsgFetch ||
+ (flags & ~kImapMsgRecentFlag) != kImapMsgSeenFlag))
+ 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)
+ {
+ int32_t hashValue = 0;
+ nsCString strHashKey(adoptedBoxSpec->mAllocatedPathName);
+ m_specialXListMailboxes.Get(strHashKey, &hashValue);
+ adoptedBoxSpec->mBoxFlags |= hashValue;
+ }
+
+ switch (m_hierarchyNameState)
+ {
+ case kXListing:
+ if (adoptedBoxSpec->mBoxFlags &
+ (kImapXListTrash|kImapAllMail|kImapInbox|kImapSent|kImapSpam|kImapDrafts))
+ {
+ nsCString mailboxName(adoptedBoxSpec->mAllocatedPathName);
+ m_specialXListMailboxes.Put(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);
+
+ }
+ NS_IF_RELEASE(adoptedBoxSpec);
+ break;
+ case kListingForFolderFlags:
+ {
+ // store mailbox flags from LIST for use by LSUB
+ nsCString mailboxName(adoptedBoxSpec->mAllocatedPathName);
+ m_standardListMailboxes.Put(mailboxName, adoptedBoxSpec->mBoxFlags);
+ }
+ NS_IF_RELEASE(adoptedBoxSpec);
+ 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 &&
+ adoptedBoxSpec->mAllocatedPathName.Find(m_trashFolderName, CaseInsensitiveCompare) != -1)
+ {
+ bool trashExists = false;
+ nsCString trashMatch(CreatePossibleTrashName(nsPrefix));
+ nsCString serverTrashName;
+ m_runningUrl->AllocateCanonicalPath(trashMatch.get(),
+ ns->GetDelimiter(),
+ getter_Copies(serverTrashName));
+ if (StringBeginsWith(serverTrashName,
+ NS_LITERAL_CSTRING("INBOX/"),
+ nsCaseInsensitiveCStringComparator()))
+ {
+ nsAutoCString pathName(adoptedBoxSpec->mAllocatedPathName.get() + 6);
+ trashExists =
+ StringBeginsWith(adoptedBoxSpec->mAllocatedPathName,
+ serverTrashName,
+ nsCaseInsensitiveCStringComparator()) && /* "INBOX/" */
+ pathName.Equals(Substring(serverTrashName, 6), nsCaseInsensitiveCStringComparator());
+ }
+ else
+ trashExists = adoptedBoxSpec->mAllocatedPathName.Equals(serverTrashName, 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);
+ }
+ }
+ }
+ }
+ NS_IF_RELEASE( adoptedBoxSpec);
+ break;
+ case kDiscoverBaseFolderInProgress:
+ break;
+ case kDeleteSubFoldersInProgress:
+ {
+ NS_ASSERTION(m_deletableChildren, "Oops .. null m_deletableChildren\n");
+ m_deletableChildren->AppendElement(ToNewCString(adoptedBoxSpec->mAllocatedPathName));
+ NS_IF_RELEASE(adoptedBoxSpec);
+ }
+ break;
+ case kListingForInfoOnly:
+ {
+ //UpdateProgressWindowForUpgrade(adoptedBoxSpec->allocatedPathName);
+ ProgressEventFunctionUsingNameWithString("imapDiscoveringMailbox",
+ adoptedBoxSpec->mAllocatedPathName.get());
+ nsIMAPMailboxInfo *mb = new nsIMAPMailboxInfo(adoptedBoxSpec->mAllocatedPathName,
+ adoptedBoxSpec->mHierarchySeparator);
+ m_listedMailboxList.AppendElement(mb);
+ NS_IF_RELEASE(adoptedBoxSpec);
+ }
+ break;
+ case kDiscoveringNamespacesOnly:
+ {
+ NS_IF_RELEASE(adoptedBoxSpec);
+ }
+ 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(const char * stringName)
+{
+ m_progressStringName.Assign(stringName);
+ if (!m_progressStringName.IsEmpty() && m_imapServerSink)
+ m_imapServerSink->GetImapStringByName(stringName,
+ m_progressString);
+}
+
+void
+nsImapProtocol::ShowProgress()
+{
+ if (!m_progressString.IsEmpty() && !m_progressStringName.IsEmpty())
+ {
+ char16_t *progressString = NULL;
+ const char *mailboxName = GetServerStateParser().GetSelectedMailboxName();
+ nsString unicodeMailboxName;
+ nsresult rv = CopyMUTF7toUTF16(nsDependentCString(mailboxName),
+ unicodeMailboxName);
+ if (NS_SUCCEEDED(rv))
+ {
+ // ### should convert mailboxName to char16_t and change %s to %S in msg text
+ progressString = nsTextFormatter::smprintf(m_progressString.get(),
+ unicodeMailboxName.get(), ++m_progressIndex, m_progressCount);
+ if (progressString)
+ {
+ PercentProgressUpdateEvent(progressString, m_progressIndex, m_progressCount);
+ nsTextFormatter::smprintf_free(progressString);
+ }
+ }
+ }
+}
+
+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 = CopyMUTF7toUTF16(nsDependentCString(aExtraInfo), unicodeStr);
+ if (NS_SUCCEEDED(rv))
+ m_imapMailFolderSink->ProgressStatusString(this, aMsgName, unicodeStr.get());
+ }
+}
+
+void
+nsImapProtocol::PercentProgressUpdateEvent(char16_t *message, 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, message, 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.Append(" 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.Append(" 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 command(GetServerCommandTag());
+
+ command.Append(" STARTTLS" CRLF);
+
+ nsresult rv = SendData(command.get());
+ if (NS_SUCCEEDED(rv))
+ ParseIMAPandCheckForNewMail();
+}
+
+void nsImapProtocol::Capability()
+{
+
+ ProgressEventFunctionUsingName("imapStatusCheckCompat");
+ IncrementCommandTagNumber();
+ nsCString command(GetServerCommandTag());
+
+ command.Append(" capability" CRLF);
+
+ nsresult rv = SendData(command.get());
+ if (NS_SUCCEEDED(rv))
+ ParseIMAPandCheckForNewMail();
+}
+
+void nsImapProtocol::ID()
+{
+ if (!gAppName[0])
+ return;
+ IncrementCommandTagNumber();
+ nsCString command(GetServerCommandTag());
+ command.Append(" ID (\"name\" \"");
+ command.Append(gAppName);
+ command.Append("\" \"version\" \"");
+ command.Append(gAppVersion);
+ command.Append("\")" CRLF);
+
+ nsresult rv = SendData(command.get());
+ if (NS_SUCCEEDED(rv))
+ ParseIMAPandCheckForNewMail();
+}
+
+void nsImapProtocol::EnableCondStore()
+{
+ IncrementCommandTagNumber();
+ nsCString command(GetServerCommandTag());
+
+ command.Append(" 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.Append(" 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.Append(" 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]);
+ }
+ }
+}
+
+#ifdef MOZ_MAILNEWS_OAUTH2
+void nsImapProtocol::InitPrefAuthMethods(int32_t authMethodPrefValue,
+ nsIMsgIncomingServer *aServer)
+#else
+void nsImapProtocol::InitPrefAuthMethods(int32_t authMethodPrefValue)
+#endif
+{
+ // for m_prefAuthMethods, using the same flags as server capablities.
+ 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;
+ default:
+ NS_ASSERTION(false, "IMAP: authMethod pref invalid");
+ // TODO log to error console
+ MOZ_LOG(IMAP, LogLevel::Error,
+ ("IMAP: bad pref authMethod = %d\n", authMethodPrefValue));
+ // fall to any
+ MOZ_FALLTHROUGH;
+ case nsMsgAuthMethod::anything:
+ m_prefAuthMethods = kHasAuthOldLoginCapability |
+ kHasAuthLoginCapability | kHasAuthPlainCapability |
+ kHasCRAMCapability | kHasAuthGssApiCapability |
+ kHasAuthNTLMCapability | kHasAuthMSNCapability |
+#ifdef MOZ_MAILNEWS_OAUTH2
+ kHasAuthExternalCapability | kHasXOAuth2Capability;
+ break;
+ case nsMsgAuthMethod::OAuth2:
+ m_prefAuthMethods = kHasXOAuth2Capability;
+#else
+ kHasAuthExternalCapability;
+#endif
+ break;
+
+ }
+
+#ifdef MOZ_MAILNEWS_OAUTH2
+ if (m_prefAuthMethods & kHasXOAuth2Capability)
+ mOAuth2Support = new mozilla::mailnews::OAuth2ThreadHelper(aServer);
+
+ // Disable OAuth2 support if we don't have the prefs installed.
+ if (m_prefAuthMethods & kHasXOAuth2Capability &&
+ (!mOAuth2Support || !mOAuth2Support->SupportsOAuth2()))
+ m_prefAuthMethods &= ~kHasXOAuth2Capability;
+#endif
+
+ NS_ASSERTION(m_prefAuthMethods != kCapabilityUndefined,
+ "IMAP: InitPrefAuthMethods() didn't work");
+}
+
+/**
+ * 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%llx, pref 0x%llx, failed 0x%llx, avail caps 0x%llx",
+ serverCaps, m_prefAuthMethods, m_failedAuthMethods, availCaps));
+#ifdef MOZ_MAILNEWS_OAUTH2
+ MOZ_LOG(IMAP, LogLevel::Debug, ("(GSSAPI = 0x%llx, CRAM = 0x%llx, NTLM = 0x%llx, "
+ "MSN = 0x%llx, PLAIN = 0x%llx,\n LOGIN = 0x%llx, old-style IMAP login = 0x%llx"
+ ", auth external IMAP login = 0x%llx, OAUTH2 = 0x%llx)",
+ kHasAuthGssApiCapability, kHasCRAMCapability, kHasAuthNTLMCapability,
+ kHasAuthMSNCapability, kHasAuthPlainCapability, kHasAuthLoginCapability,
+ kHasAuthOldLoginCapability, kHasAuthExternalCapability, kHasXOAuth2Capability));
+#else
+ MOZ_LOG(IMAP, LogLevel::Debug, ("(GSSAPI = 0x%llx, CRAM = 0x%llx, NTLM = 0x%llx, "
+ "MSN = 0x%llx, PLAIN = 0x%llx,\n LOGIN = 0x%llx, old-style IMAP login = 0x%llx"
+ ", auth external IMAP login = 0x%llx",
+ kHasAuthGssApiCapability, kHasCRAMCapability, kHasAuthNTLMCapability,
+ kHasAuthMSNCapability, kHasAuthPlainCapability, kHasAuthLoginCapability,
+ kHasAuthOldLoginCapability, kHasAuthExternalCapability));
+#endif
+
+ 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;
+#ifdef MOZ_MAILNEWS_OAUTH2
+ else if (kHasXOAuth2Capability & availCaps)
+ m_currentAuthMethod = kHasXOAuth2Capability;
+#endif
+ 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%llx", m_currentAuthMethod));
+ return NS_OK;
+}
+
+void nsImapProtocol::MarkAuthMethodAsFailed(eIMAPCapabilityFlags failedAuthMethod)
+{
+ MOZ_LOG(IMAP, LogLevel::Debug, ("marking auth method 0x%llx 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::AuthLogin(const char *userName, const nsCString &password, eIMAPCapabilityFlag flag)
+{
+ ProgressEventFunctionUsingName("imapStatusSendingAuthLogin");
+ IncrementCommandTagNumber();
+
+ char * currentCommand=nullptr;
+ nsresult rv;
+
+ MOZ_LOG(IMAP, LogLevel::Debug, ("IMAP: trying auth method 0x%llx", m_currentAuthMethod));
+
+ if (flag & kHasAuthExternalCapability)
+ {
+ char *base64UserName = PL_Base64Encode(userName, strlen(userName), nullptr);
+ nsAutoCString command (GetServerCommandTag());
+ command.Append(" 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.Append(" 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);
+ nsAutoCString encodedDigest;
+ char hexVal[8];
+
+ for (uint32_t j=0; j<16; j++)
+ {
+ PR_snprintf (hexVal,8, "%.2x", 0x0ff & (unsigned short)(digest[j]));
+ encodedDigest.Append(hexVal);
+ }
+
+ PR_snprintf(m_dataOutputBuf, OUTPUT_BUFFER_SIZE, "%s %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_realHostName);
+ rv = DoGSSAPIStep1(service.get(), userName, response);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString command (GetServerCommandTag());
+ command.Append(" 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(userName, password.get(), 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 plainstr[512]; // placeholder for "<NUL>userName<NUL>password" TODO nsAutoCString
+ int len = 1; // count for first <NUL> char
+ memset(plainstr, 0, 512);
+ PR_snprintf(&plainstr[1], 510, "%s", userName);
+ len += PL_strlen(userName);
+ len++; // count for second <NUL> char
+ PR_snprintf(&plainstr[len], 511-len, "%s", password.get());
+ len += password.Length();
+ char *base64Str = PL_Base64Encode(plainstr, len, nullptr);
+ PR_snprintf(m_dataOutputBuf, OUTPUT_BUFFER_SIZE, "%s" CRLF, base64Str);
+ PR_Free(base64Str);
+
+ bool isResend = false;
+ while (true)
+ {
+ // Send authentication string (true: suppress logging the string).
+ rv = SendData(m_dataOutputBuf, true);
+ if (NS_FAILED(rv))
+ break;
+ ParseIMAPandCheckForNewMail(currentCommand);
+ if (!GetServerStateParser().WaitingForMoreClientInput())
+ break;
+
+ // Server is asking for authentication string again. So we send the
+ // same string again although we already know that it will be
+ // rejected again. We do that to get a firm authentication failure
+ // instead of a resend request. That keeps things in order before
+ // failing "authenticate PLAIN" and trying another method if capable.
+ if (isResend)
+ {
+ rv = NS_ERROR_FAILURE;
+ break;
+ }
+ isResend = true;
+ }
+ } // 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, PL_strlen(userName), 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(), password.Length(), 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.Append(" login \"");
+ EscapeUserNamePasswordString(userName, &escapedUserName);
+ command.Append(escapedUserName);
+ command.Append("\" \"");
+
+ // if the password contains a \, login will fail
+ // turn foo\bar into foo\\bar
+ nsAutoCString correctedPassword;
+ EscapeUserNamePasswordString(password.get(), &correctedPassword);
+ command.Append(correctedPassword);
+ command.Append("\"" CRLF);
+ rv = SendData(command.get(), true /* suppress logging */);
+ NS_ENSURE_SUCCESS(rv, rv);
+ ParseIMAPandCheckForNewMail();
+ }
+#ifdef MOZ_MAILNEWS_OAUTH2
+ 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 = SendData(command.get(), true /* suppress logging */);
+ NS_ENSURE_SUCCESS(rv, rv);
+ ParseIMAPandCheckForNewMail();
+ }
+#endif
+ 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 eof = false;
+ nsCString flagString;
+
+ nsCOMPtr <nsIInputStream> fileInputStream;
+
+ if (!escapedName.IsEmpty())
+ {
+ command.Append(" append \"");
+ command.Append(escapedName);
+ command.Append("\"");
+ if (flags || keywords.Length())
+ {
+ command.Append(" (");
+
+ 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 (superceding 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);
+ }
+ command.Append(" {");
+
+ 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+ useage is enabled in the config editor,
+ // i.e., "mail.imap.use_literal_plus" = true.
+ bool useLiteralPlus = (GetServerStateParser().GetCapabilityFlag() &
+ kLiteralPlusCapability) && gUseLiteralPlus;
+ if (useLiteralPlus)
+ command.Append("+}" CRLF);
+ else
+ command.Append("}" 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) && !eof && totalSize > 0)
+ {
+ 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(nullptr, fileSize - totalSize, fileSize);
+ }
+ }
+ if (NS_SUCCEEDED(rv))
+ {
+ rv = SendData(CRLF); // complete the append
+ ParseIMAPandCheckForNewMail(command.get());
+
+ nsImapAction imapAction;
+ m_runningUrl->GetImapAction(&imapAction);
+
+ if (GetServerStateParser().LastCommandSuccessful() && (
+ 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);
+ }
+ }
+ // 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.
+ else if (m_imapMailFolderSink && imapAction == nsIImapUrl::nsImapAppendDraftFromFile )
+ { // *** code me to search for the newly appended message
+ // go to selected state
+ nsCString messageId;
+ rv = m_imapMailFolderSink->GetMessageId(m_runningUrl, messageId);
+ if (NS_SUCCEEDED(rv) && !messageId.IsEmpty() &&
+ GetServerStateParser().LastCommandSuccessful())
+ {
+ // 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);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+done:
+ 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)
+{
+
+ List(aSourceMailbox, false); // how to tell if that succeeded?
+ bool exists = false;
+
+ // try converting aSourceMailbox to canonical format
+
+ nsIMAPNamespace *nsForMailbox = nullptr;
+ m_hostSessionList->GetNamespaceForMailboxForHost(GetImapServerKey(),
+ aSourceMailbox, nsForMailbox);
+ // NS_ASSERTION (nsForMailbox, "Oops .. null nsForMailbox\n");
+
+ nsCString name;
+
+ if (nsForMailbox)
+ m_runningUrl->AllocateCanonicalPath(aSourceMailbox,
+ nsForMailbox->GetDelimiter(),
+ getter_Copies(name));
+ else
+ m_runningUrl->AllocateCanonicalPath(aSourceMailbox,
+ kOnlineHierarchySeparatorUnknown,
+ getter_Copies(name));
+
+ if (m_imapServerSink)
+ m_imapServerSink->FolderVerifiedOnline(name, &exists);
+
+ if (exists)
+ {
+ Subscribe(aSourceMailbox);
+ }
+ else
+ {
+ bool created = CreateMailboxRespectingSubscriptions(aSourceMailbox);
+ if (created)
+ {
+ List(aSourceMailbox, false);
+ }
+ }
+ if (!GetServerStateParser().LastCommandSuccessful())
+ 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.Append(" getacl \"");
+ command.Append(escapedName);
+ command.Append("\"" 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);
+ NS_Free(onlineName);
+ }
+ PercentProgressUpdateEvent(NULL, count, total);
+ delete mb;
+ count++;
+ }
+ }
+ m_listedMailboxList.Clear();
+
+ PercentProgressUpdateEvent(NULL, 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 issule a close 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())
+ Close();
+********************/
+
+ IncrementCommandTagNumber();
+
+ nsCString command(GetServerCommandTag());
+
+ command.Append(" 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.Append(" noop" CRLF);
+
+ nsresult rv = SendData(command.get());
+ if (NS_SUCCEEDED(rv))
+ ParseIMAPandCheckForNewMail();
+}
+
+void nsImapProtocol::XServerInfo()
+{
+
+ ProgressEventFunctionUsingName("imapGettingServerInfo");
+ IncrementCommandTagNumber();
+ nsCString command(GetServerCommandTag());
+
+ command.Append(" 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.Append(" 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.Append(" XMAILBOXINFO \"");
+ command.Append(mailboxName);
+ command.Append("\" MANAGEURL POSTURL" CRLF);
+
+ nsresult rv = SendData(command.get());
+ if (NS_SUCCEEDED(rv))
+ ParseIMAPandCheckForNewMail();
+}
+
+void nsImapProtocol::Namespace()
+{
+
+ IncrementCommandTagNumber();
+
+ nsCString command(GetServerCommandTag());
+ command.Append(" namespace" CRLF);
+
+ nsresult rv = SendData(command.get());
+ if (NS_SUCCEEDED(rv))
+ ParseIMAPandCheckForNewMail();
+}
+
+
+void nsImapProtocol::MailboxData()
+{
+ IncrementCommandTagNumber();
+
+ nsCString command(GetServerCommandTag());
+ command.Append(" 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.Append(" myrights \"");
+ command.Append(escapedName);
+ command.Append("\"" 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)
+{
+
+ if (FolderIsSelected(mailboxName))
+ {
+ int32_t prevNumMessages = GetServerStateParser().NumberOfMessages();
+ Noop();
+ // OnNewIdleMessages will cause the ui thread to update the folder
+ if (m_imapMailFolderSink && (GetServerStateParser().NumberOfRecentMessages()
+ || prevNumMessages != GetServerStateParser().NumberOfMessages()))
+ m_imapMailFolderSink->OnNewIdleMessages();
+ return;
+ }
+
+ IncrementCommandTagNumber();
+
+ nsAutoCString command(GetServerCommandTag());
+ nsCString escapedName;
+ CreateEscapedMailboxName(mailboxName, escapedName);
+
+ command.Append(" STATUS \"");
+ command.Append(escapedName);
+ command.Append("\" (UIDNEXT MESSAGES UNSEEN RECENT)" CRLF);
+
+ nsresult rv = SendData(command.get());
+ if (NS_SUCCEEDED(rv))
+ ParseIMAPandCheckForNewMail();
+
+ if (GetServerStateParser().LastCommandSuccessful())
+ {
+ nsImapMailboxSpec *new_spec = GetServerStateParser().CreateCurrentMailboxSpec(mailboxName);
+ if (new_spec && m_imapMailFolderSink)
+ m_imapMailFolderSink->UpdateImapMailboxStatus(this, new_spec);
+ NS_IF_RELEASE(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\n");
+
+ 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<char*>();
+
+ bool nonHierarchicalRename =
+ ((GetServerStateParser().GetCapabilityFlag() & kNoHierarchyRename)
+ || MailboxIsNoSelectMailbox(oldParentMailboxName));
+
+ if (m_deletableChildren)
+ {
+ 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();
+ size_t childIndex;
+
+ for (childIndex = 0;
+ (childIndex < numberToDelete) && renameSucceeded; childIndex++)
+ {
+ // the imap parser has already converted to a non UTF7 string in the canonical
+ // format so convert it back
+ char *currentName = m_deletableChildren->ElementAt(childIndex);
+ if (currentName)
+ {
+ char *serverName = nullptr;
+ m_runningUrl->AllocateServerPath(currentName,
+ onlineDirSeparator,
+ &serverName);
+ PR_FREEIF(currentName);
+ 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
+ // reanme, 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<char*>();
+
+ if (m_deletableChildren)
+ {
+ 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++ )
+ {
+ char *currentName = m_deletableChildren->ElementAt(i);
+ 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++)
+ {
+ char *currentName = m_deletableChildren->ElementAt(innerIndex);
+ if (!longestName || strlen(longestName) < strlen(currentName))
+ {
+ longestName = currentName;
+ longestIndex = innerIndex;
+ }
+ }
+ // the imap parser has already converted to a non UTF7 string in
+ // the canonical format so convert it back
+ if (longestName)
+ {
+ char *serverName = nullptr;
+
+ m_deletableChildren->RemoveElementAt(longestIndex);
+ m_runningUrl->AllocateServerPath(longestName,
+ onlineDirSeparator,
+ &serverName);
+ PR_FREEIF(longestName);
+ 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<char*> *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(NS_LITERAL_CSTRING("1:*"), "+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);
+
+ 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();
+}
+
+void nsImapProtocol::FindMailboxesIfNecessary()
+{
+ // biff should not discover mailboxes
+ bool foundMailboxesAlready = false;
+ nsImapAction imapAction;
+
+ // need to do this for every connection in order to see folders.
+ (void) m_runningUrl->GetImapAction(&imapAction);
+ nsresult rv = m_hostSessionList->GetHaveWeEverDiscoveredFoldersForHost(GetImapServerKey(), foundMailboxesAlready);
+ if (NS_SUCCEEDED(rv) && !foundMailboxesAlready &&
+ (imapAction != nsIImapUrl::nsImapBiff) &&
+ (imapAction != nsIImapUrl::nsImapVerifylogon) &&
+ (imapAction != nsIImapUrl::nsImapDiscoverAllBoxesUrl) &&
+ (imapAction != nsIImapUrl::nsImapUpgradeToSubscription) &&
+ !GetSubscribingNow())
+ 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 &&
+ gHideOtherUsersFromList ? (ns->GetType() != kOtherUsersNamespace)
+ : true)
+ {
+ const char *prefix = ns->GetPrefix();
+ if (prefix)
+ {
+ nsAutoCString inboxNameWithDelim("INBOX");
+ inboxNameWithDelim.Append(ns->GetDelimiter());
+
+ if (!gHideUnusedNamespaces && *prefix &&
+ PL_strcasecmp(prefix, inboxNameWithDelim.get())) /* only do it for
+ non-empty namespace prefixes */
+ {
+ // Explicitly discover each Namespace, just so they're
+ // there in the subscribe UI
+ nsImapMailboxSpec *boxSpec = new nsImapMailboxSpec;
+ if (boxSpec)
+ {
+ NS_ADDREF(boxSpec);
+ 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);
+ }
+ else
+ HandleMemoryFailure();
+ }
+
+ nsAutoCString allPattern(prefix);
+ allPattern += '*';
+
+ nsAutoCString topLevelPattern(prefix);
+ topLevelPattern += '%';
+
+ nsAutoCString secondLevelPattern;
+
+ char delimiter = ns->GetDelimiter();
+ if (delimiter)
+ {
+ // Hierarchy delimiter might be NIL, in which case there's no hierarchy anyway
+ secondLevelPattern = prefix;
+ secondLevelPattern += '%';
+ secondLevelPattern += delimiter;
+ secondLevelPattern += '%';
+ }
+
+ if (!m_imapServerSink) return;
+
+ if (!allPattern.IsEmpty())
+ {
+ m_imapServerSink->SetServerDoingLsub(true);
+ Lsub(allPattern.get(), true); // LSUB all the subscribed
+ }
+ if (!topLevelPattern.IsEmpty())
+ {
+ m_imapServerSink->SetServerDoingLsub(false);
+ List(topLevelPattern.get(), true); // LIST the top level
+ }
+ if (!secondLevelPattern.IsEmpty())
+ {
+ m_imapServerSink->SetServerDoingLsub(false);
+ List(secondLevelPattern.get(), true); // LIST the second level
+ }
+ }
+ }
+ }
+}
+
+// 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;
+ if (!gHideUnusedNamespaces && *prefix &&
+ PL_strcasecmp(prefix, inboxNameWithDelim.get())) // only do it for
+ // non-empty namespace prefixes, and for non-INBOX prefix
+ {
+ // Explicitly discover each Namespace, so that we can
+ // create subfolders of them,
+ nsImapMailboxSpec *boxSpec = new nsImapMailboxSpec;
+ if (boxSpec)
+ {
+ NS_ADDREF(boxSpec);
+ 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);
+ }
+ else
+ HandleMemoryFailure();
+ }
+
+ // 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(NULL, 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)
+ {
+ nsCString originalTrashName(CreatePossibleTrashName(personalDir));
+ m_hierarchyNameState = kDiscoverTrashFolderInProgress;
+ List(originalTrashName.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 trashName(CreatePossibleTrashName(ns->GetPrefix()));
+ nsCString onlineTrashName;
+ m_runningUrl->AllocateServerPath(trashName.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();
+ }
+}
+
+// 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))
+ Close();
+
+
+ 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))
+ Close();
+
+ 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();
+}
+
+nsCString nsImapProtocol::CreatePossibleTrashName(const char *prefix)
+{
+ nsCString returnTrash(prefix);
+ returnTrash += m_trashFolderName;
+ return returnTrash;
+}
+
+bool nsImapProtocol::GetListSubscribedIsBrokenOnServer()
+{
+ // This is a workaround for an issue with LIST(SUBSCRIBED) crashing older versions of Zimbra
+ if (GetServerStateParser().GetServerID().Find("\"NAME\" \"Zimbra\"", CaseInsensitiveCompare) != kNotFound) {
+ nsCString serverID(GetServerStateParser().GetServerID());
+ int start = serverID.Find("\"VERSION\" \"", CaseInsensitiveCompare) + 11;
+ int length = serverID.Find("\" ", start, CaseInsensitiveCompare);
+ 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;
+}
+
+// This identifies servers that require an extra imap SELECT to detect new
+// email in a mailbox. Servers requiring this are found by comparing their
+// ID string, returned with imap ID command, to strings entered in
+// mail.imap.force_select_detect. Only openwave servers used by
+// Charter/Spectrum ISP returning an ID containing the strings ""name" "Email Mx""
+// and ""vendor" "Openwave Messaging"" are now known to have this issue. The
+// compared strings can be modified with the config editor if necessary
+// (e.g., a "version" substring could be added). Also, additional servers
+// having a different set of strings can be added if ever needed.
+// The mail.imap.force_select_detect uses semicolon delimiter between
+// servers and within a server substrings to compare are comma delimited.
+// This example force_select_detect value shows how two servers types
+// could be detected:
+// "name" "Email Mx","vendor" "Openwave Messaging";"vendor" "Yahoo! Inc.","name" "Y!IMAP";
+bool nsImapProtocol::IsExtraSelectNeeded()
+{
+ bool retVal;
+ for (uint32_t i = 0; i < gForceSelectServersArray.Length(); i++)
+ {
+ retVal = true;
+ nsTArray<nsCString> forceSelectStringsArray;
+ ParseString(gForceSelectServersArray[i], ',', forceSelectStringsArray);
+ for (uint32_t j = 0; j < forceSelectStringsArray.Length(); j++)
+ {
+ // Each substring within the server string must be contained in ID string.
+ // First un-escape any comma (%2c) or semicolon (%3b) within the substring.
+ nsAutoCString unescapedString;
+ MsgUnescapeString(forceSelectStringsArray[j], 0, unescapedString);
+ if (GetServerStateParser().GetServerID()
+ .Find(unescapedString, CaseInsensitiveCompare) == kNotFound)
+ {
+ retVal = false;
+ break;
+ }
+ }
+ // Matches found for all substrings for the server.
+ if (retVal)
+ return true;
+ }
+
+ // If reached, no substrings match for any server.
+ 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))
+ {
+ // we'll just get back a continuation char at first.
+ // + idling...
+ ParseIMAPandCheckForNewMail();
+ if (GetServerStateParser().LastCommandSuccessful())
+ {
+ m_idle = true;
+ // this will cause us to get notified of data or the socket getting closed.
+ // That notification will occur on the socket transport thread - we just
+ // need to poke a monitor so the imap thread will do a blocking read
+ // and parse the data.
+ nsCOMPtr <nsIAsyncInputStream> asyncInputStream = do_QueryInterface(m_inputStream);
+ if (asyncInputStream)
+ asyncInputStream->AsyncWait(this, 0, 0, nullptr);
+ }
+ else
+ {
+ m_idle = false;
+ }
+ }
+}
+
+// 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 seem to have trouble doing a blocking read
+ 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();
+ }
+ 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.Append(" 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), crlfIndex != 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.Append(" uid");
+ // If it's a MOVE operation on aol servers then use 'xaol-move' cmd.
+ if ((m_imapAction == nsIImapUrl::nsImapOnlineMove) &&
+ GetServerStateParser().ServerIsAOLServer())
+ protocolString.Append(" xaol-move ");
+ else if ((m_imapAction == nsIImapUrl::nsImapOnlineMove) &&
+ GetServerStateParser().GetCapabilityFlag() & kHasMoveCapability)
+ protocolString.Append(" move ");
+ else
+ protocolString.Append(" copy ");
+
+
+ protocolString.Append(idString);
+ protocolString.Append(" \"");
+ protocolString.Append(escapedDestination);
+ protocolString.Append("\"" 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);
+ }
+}
+
+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 (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();
+
+ bool haveIdResponse = false;
+ if ((GetServerStateParser().GetCapabilityFlag() & kHasIDCapability) &&
+ m_sendID)
+ {
+ ID();
+ if (m_imapServerSink && !GetServerStateParser().GetServerID().IsEmpty())
+ {
+ haveIdResponse = true;
+ // Determine value for m_forceSelect based on config editor
+ // entries and comparison to imap ID string returned by the server.
+ m_imapServerSink->SetServerID(GetServerStateParser().GetServerID());
+ switch (m_forceSelectValue.get()[0])
+ {
+ // Yes: Set to always force even if imap server doesn't need it.
+ case 'y':
+ case 'Y':
+ m_forceSelect = true;
+ break;
+
+ // No: Set to never force a select for this imap server.
+ case 'n':
+ case 'N':
+ m_forceSelect = false;
+ break;
+
+ // Auto: Set to force only if imap server requires it.
+ default:
+ nsAutoCString statusString;
+ m_forceSelect = IsExtraSelectNeeded();
+ // Setting to "yes-auto" or "no-auto" avoids doing redundant calls to
+ // IsExtraSelectNeeded() on subsequent ID() occurrences. It also
+ // provides feedback to the user regarding the detection status.
+ if (m_forceSelect)
+ {
+ // Set preference value to "yes-auto".
+ statusString.Assign("yes-auto");
+ }
+ else
+ {
+ // Set preference value to "no-auto".
+ statusString.Assign("no-auto");
+ }
+ m_imapServerSink->SetServerForceSelect(statusString);
+ break;
+ }
+ }
+ }
+
+ // If no ID capability or empty ID response, user may still want to
+ // change "force select".
+ if (!haveIdResponse)
+ {
+ switch (m_forceSelectValue.get()[0])
+ {
+ case 'a':
+ {
+ // If default "auto", set to "no-auto" so visible in config editor
+ // and set/keep m_forceSelect false.
+ nsAutoCString statusString;
+ statusString.Assign("no-auto");
+ m_imapServerSink->SetServerForceSelect(statusString);
+ m_forceSelect = false;
+ }
+ break;
+ case 'y':
+ case 'Y':
+ m_forceSelect = true;
+ break;
+ default:
+ m_forceSelect = false;
+ }
+ }
+}
+
+void nsImapProtocol::SetupMessageFlagsString(nsCString& flagString,
+ imapMessageFlagsType flags,
+ uint16_t userFlags)
+{
+ if (flags & kImapMsgSeenFlag)
+ flagString.Append("\\Seen ");
+ if (flags & kImapMsgAnsweredFlag)
+ flagString.Append("\\Answered ");
+ if (flags & kImapMsgFlaggedFlag)
+ flagString.Append("\\Flagged ");
+ if (flags & kImapMsgDeletedFlag)
+ flagString.Append("\\Deleted ");
+ if (flags & kImapMsgDraftFlag)
+ flagString.Append("\\Draft ");
+ if (flags & kImapMsgRecentFlag)
+ flagString.Append("\\Recent ");
+ if ((flags & kImapMsgForwardedFlag) &&
+ (userFlags & kImapMsgSupportForwardedFlag))
+ flagString.Append("$Forwarded "); // Not always available
+ if ((flags & kImapMsgMDNSentFlag) && (
+ userFlags & kImapMsgSupportMDNSentFlag))
+ flagString.Append("$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 .Append("\\Seen ");
+ if (flags & kImapMsgAnsweredFlag && kImapMsgAnsweredFlag & settableFlags)
+ flagString .Append("\\Answered ");
+ if (flags & kImapMsgFlaggedFlag && kImapMsgFlaggedFlag & settableFlags)
+ flagString .Append("\\Flagged ");
+ if (flags & kImapMsgDeletedFlag && kImapMsgDeletedFlag & settableFlags)
+ flagString .Append("\\Deleted ");
+ if (flags & kImapMsgDraftFlag && kImapMsgDraftFlag & settableFlags)
+ flagString .Append("\\Draft ");
+ if (flags & kImapMsgForwardedFlag && kImapMsgSupportForwardedFlag & userFlags)
+ flagString .Append("$Forwarded "); // if supported
+ if (flags & kImapMsgMDNSentFlag && kImapMsgSupportMDNSentFlag & userFlags)
+ flagString .Append("$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;
+ }
+ }
+
+ }
+ }
+}
+
+
+void nsImapProtocol::Close(bool shuttingDown /* = false */,
+ bool waitForResponse /* = true */)
+{
+ IncrementCommandTagNumber();
+
+ nsCString command(GetServerCommandTag());
+ command.Append(" 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.Append(" 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.Append(" 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;
+ 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(nsCString &password,
+ bool newPasswordRequested)
+{
+ // we are in the imap thread so *NEVER* try to extract the password with UI
+ // if logon redirection has changed the password, use the cookie as the password
+ if (m_overRideUrlConnectionInfo)
+ {
+ password.Assign(m_logonCookie);
+ return NS_OK;
+ }
+
+ NS_ENSURE_TRUE(m_imapServerSink, NS_ERROR_NULL_POINTER);
+ NS_ENSURE_TRUE(m_server, NS_ERROR_NULL_POINTER);
+ nsresult rv;
+
+ // Get the password already stored in mem
+ rv = m_imapServerSink->GetServerPassword(password);
+ if (NS_FAILED(rv) || password.IsEmpty())
+ {
+ AutoProxyReleaseMsgWindow msgWindow;
+ GetMsgWindow(getter_AddRefs(msgWindow));
+ NS_ENSURE_TRUE(msgWindow, NS_ERROR_NOT_AVAILABLE); // biff case
+
+ // Get the password from pw manager (harddisk) or user (dialog)
+ m_passwordObtained = false;
+ rv = m_imapServerSink->AsyncGetPassword(this,
+ newPasswordRequested,
+ password);
+ if (password.IsEmpty())
+ {
+ PRIntervalTime sleepTime = kImapSleepTime;
+ m_passwordStatus = NS_OK;
+ ReentrantMonitorAutoEnter mon(m_passwordReadyMonitor);
+ while (!m_passwordObtained && !NS_FAILED(m_passwordStatus) &&
+ m_passwordStatus != NS_MSG_PASSWORD_PROMPT_CANCELLED &&
+ !DeathSignalReceived())
+ mon.Wait(sleepTime);
+ rv = m_passwordStatus;
+ password = m_password;
+ }
+ }
+ if (!password.IsEmpty())
+ m_lastPasswordSent = password;
+ return rv;
+}
+
+// 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));
+ nsCString password = m_lastPasswordSent;
+ rv = imapServer->PromptPassword(msgWindow, password);
+ m_password = password;
+ m_passwordStatus = rv;
+ if (!m_password.IsEmpty())
+ *aResult = true;
+
+ // Notify the imap thread that we have a password.
+ m_passwordObtained = true;
+ ReentrantMonitorAutoEnter passwordMon(m_passwordReadyMonitor);
+ passwordMon.Notify();
+ return rv;
+}
+
+NS_IMETHODIMP
+nsImapProtocol::OnPromptAuthAvailable()
+{
+ nsresult rv;
+ nsCOMPtr<nsIMsgIncomingServer> imapServer = do_QueryReferent(m_server, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ m_passwordStatus = imapServer->GetPassword(m_password);
+ // Notify the imap thread that we have a password.
+ m_passwordObtained = true;
+ ReentrantMonitorAutoEnter passwordMon(m_passwordReadyMonitor);
+ passwordMon.Notify();
+ return m_passwordStatus;
+}
+
+NS_IMETHODIMP
+nsImapProtocol::OnPromptCanceled()
+{
+ // A prompt was cancelled, so notify the imap thread.
+ m_passwordStatus = NS_MSG_PASSWORD_PROMPT_CANCELLED;
+ ReentrantMonitorAutoEnter passwordMon(m_passwordReadyMonitor);
+ passwordMon.Notify();
+ return NS_OK;
+}
+
+bool nsImapProtocol::TryToLogon()
+{
+ MOZ_LOG(IMAP, LogLevel::Debug, ("try to log in"));
+ NS_ENSURE_TRUE(m_imapServerSink, false);
+ bool loginSucceeded = false;
+ bool skipLoop = false;
+ nsAutoCString password;
+ nsAutoCString userName;
+
+ 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 resetted failed ones, but ChooseAuthMethod still fails."));
+ return false;
+ }
+ }
+ }
+
+ // 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;
+ }
+
+ /*
+ * 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.
+ */
+
+ 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 &&
+#ifdef MOZ_MAILNEWS_OAUTH2
+ m_currentAuthMethod != kHasXOAuth2Capability &&
+#endif
+ 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;
+ }
+
+#ifdef MOZ_MAILNEWS_OAUTH2
+ if (m_prefAuthMethods & kHasXOAuth2Capability)
+ {
+ // OAuth2 failed. We don't have an error message for this, and we
+ // in a string freeze, so use a generic error message. Entering
+ // a password does not help.
+ AlertUserEventUsingName("imapUnknownHostError");
+ break;
+ }
+#endif
+
+ // 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(), nullptr);
+ m_imapServerSink->ForgetPassword();
+ m_password.Truncate();
+ MOZ_LOG(IMAP, LogLevel::Warning, ("password resetted (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.get());
+ rv = m_hostSessionList->GetPasswordVerifiedOnline(GetImapServerKey(), passwordAlreadyVerified);
+ if (NS_SUCCEEDED(rv) && !passwordAlreadyVerified)
+ 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(nsCString& aQuotaRoot, uint32_t aUsed, uint32_t aMax)
+{
+ NS_ASSERTION(m_imapMailFolderSink, "m_imapMailFolderSink is null!");
+
+ m_imapMailFolderSink->SetFolderQuotaData(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.Append(NS_LITERAL_CSTRING(" getquotaroot \""));
+ quotacommand.Append(escapedName);
+ quotacommand.Append(NS_LITERAL_CSTRING("\"" 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;
+}
+
+NS_IMETHODIMP nsImapProtocol::OverrideConnectionInfo(const char16_t *pHost, uint16_t pPort, const char *pCookieData)
+{
+ m_logonHost = NS_LossyConvertUTF16toASCII(pHost);
+ m_logonPort = pPort;
+ m_logonCookie = pCookieData;
+ m_overRideUrlConnectionInfo = true;
+ return NS_OK;
+}
+
+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)
+//////////////////////////////////////////////////////////////////////////////////////////////
+
+// 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);
+protected:
+ virtual ~nsImapCacheStreamListener();
+ nsCOMPtr<nsIImapMockChannel> mChannelToUse;
+ nsCOMPtr<nsIStreamListener> mListener;
+};
+
+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()
+{
+}
+
+nsImapCacheStreamListener::~nsImapCacheStreamListener()
+{}
+
+nsresult nsImapCacheStreamListener::Init(nsIStreamListener * aStreamListener, nsIImapMockChannel * aMockChannelToUse)
+{
+ NS_ENSURE_ARG(aStreamListener);
+ NS_ENSURE_ARG(aMockChannelToUse);
+
+ mChannelToUse = aMockChannelToUse;
+ mListener = aStreamListener;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapCacheStreamListener::OnStartRequest(nsIRequest *request, nsISupports * aCtxt)
+{
+ if (!mChannelToUse)
+ {
+ NS_ERROR("OnStartRequest called after OnStopRequest");
+ return NS_ERROR_NULL_POINTER;
+ }
+ nsCOMPtr<nsILoadGroup> loadGroup;
+ mChannelToUse->GetLoadGroup(getter_AddRefs(loadGroup));
+ nsCOMPtr<nsIRequest> ourRequest = do_QueryInterface(mChannelToUse);
+ if (loadGroup)
+ loadGroup->AddRequest(ourRequest, nullptr /* context isupports */);
+ return mListener->OnStartRequest(ourRequest, aCtxt);
+}
+
+NS_IMETHODIMP
+nsImapCacheStreamListener::OnStopRequest(nsIRequest *request, nsISupports * aCtxt, nsresult aStatus)
+{
+ if (!mListener)
+ {
+ NS_ERROR("OnStopRequest called twice");
+ return NS_ERROR_NULL_POINTER;
+ }
+ nsresult rv = mListener->OnStopRequest(mChannelToUse, aCtxt, aStatus);
+ nsCOMPtr <nsILoadGroup> loadGroup;
+ mChannelToUse->GetLoadGroup(getter_AddRefs(loadGroup));
+ if (loadGroup)
+ loadGroup->RemoveRequest(mChannelToUse, nullptr, aStatus);
+
+ mListener = nullptr;
+ mChannelToUse->Close();
+ mChannelToUse = nullptr;
+ return rv;
+}
+
+NS_IMETHODIMP
+nsImapCacheStreamListener::OnDataAvailable(nsIRequest *request, nsISupports * aCtxt, nsIInputStream * aInStream, uint64_t aSourceOffset, uint32_t aCount)
+{
+ return mListener->OnDataAvailable(mChannelToUse, aCtxt, aInStream, aSourceOffset, aCount);
+}
+
+NS_IMPL_ISUPPORTS(nsImapMockChannel, nsIImapMockChannel, nsIChannel,
+ nsIRequest, nsICacheEntryOpenCallback, nsITransportEventSink, nsISupportsWeakReference)
+
+
+nsImapMockChannel::nsImapMockChannel()
+{
+ m_channelContext = nullptr;
+ m_cancelStatus = NS_OK;
+ mLoadFlags = 0;
+ mChannelClosed = false;
+ mReadingFromCache = false;
+ mTryingToReadPart = 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.
+ NS_WARNING_ASSERTION(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;
+ mCacheRequest = nullptr;
+ if (mTryingToReadPart)
+ {
+ // clear mem cache entry on imap part url in case it's holding
+ // onto last reference in mem cache. Need to do this on ui thread
+ nsresult rv;
+ nsCOMPtr <nsIImapUrl> imapUrl = do_QueryInterface(m_url, &rv);
+ if (imapUrl)
+ {
+ nsCOMPtr <nsIImapMailFolderSink> folderSink;
+ rv = imapUrl->GetImapMailFolderSink(getter_AddRefs(folderSink));
+ if (folderSink)
+ {
+ nsCOMPtr <nsIMsgMailNewsUrl> mailUrl = do_QueryInterface(m_url);
+ rv = folderSink->ReleaseUrlCacheEntry(mailUrl);
+ }
+ }
+ }
+ mChannelClosed = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMockChannel::GetProgressEventSink(nsIProgressEventSink ** aProgressEventSink)
+{
+ *aProgressEventSink = mProgressEventSink;
+ NS_IF_ADDREF(*aProgressEventSink);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMockChannel::SetProgressEventSink(nsIProgressEventSink * aProgressEventSink)
+{
+ mProgressEventSink = aProgressEventSink;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMockChannel::GetChannelListener(nsIStreamListener **aChannelListener)
+{
+ *aChannelListener = m_channelListener;
+ NS_IF_ADDREF(*aChannelListener);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMockChannel::GetChannelContext(nsISupports **aChannelContext)
+{
+ *aChannelContext = m_channelContext;
+ NS_IF_ADDREF(*aChannelContext);
+ 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)
+{
+ *aLoadGroup = m_loadGroup;
+ NS_IF_ADDREF(*aLoadGroup);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMockChannel::GetLoadInfo(nsILoadInfo * *aLoadInfo)
+{
+ *aLoadInfo = m_loadInfo;
+ NS_IF_ADDREF(*aLoadInfo);
+ 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;
+ *aURI = m_url;
+ NS_IF_ADDREF(*aURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMockChannel::SetOriginalURI(nsIURI* aURI)
+{
+ // IMap does not seem to have the notion of an original URI :-(
+ // NS_NOTREACHED("nsImapMockChannel::SetOriginalURI");
+ // return NS_ERROR_NOT_IMPLEMENTED;
+ return NS_OK; // ignore
+}
+
+NS_IMETHODIMP nsImapMockChannel::GetURI(nsIURI* *aURI)
+{
+ *aURI = m_url;
+ NS_IF_ADDREF(*aURI);
+ 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)
+{
+ return NS_ImplementChannelOpen(this, _retval);
+}
+
+NS_IMETHODIMP nsImapMockChannel::Open2(nsIInputStream **_retval)
+{
+ nsCOMPtr<nsIStreamListener> listener;
+ nsresult rv = nsContentSecurityManager::doContentSecurityCheck(this, listener);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return Open(_retval);
+}
+
+NS_IMETHODIMP
+nsImapMockChannel::OnCacheEntryAvailable(nsICacheEntry *entry, bool aNew, nsIApplicationCache* aAppCache, nsresult status)
+{
+ nsresult rv = NS_OK;
+
+ // 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)
+ {
+ 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 receive 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.
+ if (NS_FAILED(status))
+ break;
+
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_url, &rv);
+ mailnewsUrl->SetMemCacheEntry(entry);
+
+ // For URLs not related to parts, the processing is easy:
+ // aNew==true means that we need to write to the entry,
+ // aNew==false means that we can read it.
+ //
+ // Parts processing is a little complicated, we distinguish two cases:
+ // 1) The caller a) knows that the part is there or
+ // b) it is not there and can also not be read from the
+ // entire message.
+ // In this case, the URL used as cache key addresses the part and
+ // mTryingToReadPart==false.
+ // The caller has already set up part extraction.
+ // This case is no different to non-part processing.
+ // 2) The caller wants to try to extract the part from the cache entry
+ // of the entire message.
+ // In this case, the URL used as cache key addresses the message and
+ // mTryingToReadPart==true.
+ if (mTryingToReadPart)
+ {
+ // We are here with the URI of the entire message which we know exists.
+ MOZ_ASSERT(!aNew,
+ "Logic error: Trying to read part from entire message which doesn't exist");
+ if (!aNew)
+ {
+ // Check the meta data.
+ nsCString annotation;
+ rv = entry->GetMetaDataElement("ContentModified", getter_Copies(annotation));
+ if (NS_FAILED(rv) || !annotation.EqualsLiteral("Not Modified"))
+ {
+ // The cache entry is not marked "Not Modified", that means it doesn't
+ // contain the entire message, so we can't use it.
+ // Call OpenCacheEntry() a second time to get the part.
+ rv = OpenCacheEntry();
+ if (NS_SUCCEEDED(rv))
+ return rv;
+
+ // Something has gone wrong, fall back to reading from the imap
+ // connection so the protocol doesn't hang.
+ break;
+ }
+ }
+ }
+
+ if (aNew)
+ {
+ // If we are writing, then insert a "stream listener Tee" into the flow
+ // to force data into the cache and to our current channel listener.
+ nsCOMPtr<nsIStreamListenerTee> tee = do_CreateInstance(NS_STREAMLISTENERTEE_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr<nsIOutputStream> out;
+ // This will fail with the cache turned off, so we need to fall through
+ // to ReadFromImapConnection instead of aborting with NS_ENSURE_SUCCESS(rv,rv).
+ rv = entry->OpenOutputStream(0, getter_AddRefs(out));
+ if (NS_SUCCEEDED(rv))
+ {
+ rv = tee->Init(m_channelListener, out, nullptr);
+ m_channelListener = do_QueryInterface(tee);
+ }
+ else
+ NS_WARNING("IMAP Protocol failed to open output stream to Necko cache");
+ }
+ }
+ else
+ {
+ rv = ReadFromMemCache(entry);
+ if (NS_SUCCEEDED(rv))
+ {
+ NotifyStartEndReadFromCache(true);
+ entry->MarkValid();
+ return NS_OK; // Kick out if 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, default to ReadFromImapConnection.
+ return ReadFromImapConnection();
+}
+
+NS_IMETHODIMP
+nsImapMockChannel::OnCacheEntryCheck(nsICacheEntry* entry, nsIApplicationCache* appCache,
+ 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;
+
+ return NS_OK;
+}
+
+nsresult nsImapMockChannel::OpenCacheEntry()
+{
+ nsresult rv;
+ // get the cache session from our imap service...
+ nsCOMPtr<nsIImapService> imapService = do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsICacheStorage> cacheStorage;
+ rv = imapService->GetCacheStorage(getter_AddRefs(cacheStorage));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t uidValidity = -1;
+ nsCacheAccessMode cacheAccess = nsICacheStorage::OPEN_NORMALLY;
+
+ nsCOMPtr<nsIImapUrl> imapUrl = do_QueryInterface(m_url, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool storeResultsOffline;
+ nsCOMPtr<nsIImapMailFolderSink> folderSink;
+
+ rv = imapUrl->GetImapMailFolderSink(getter_AddRefs(folderSink));
+ if (folderSink)
+ folderSink->GetUidValidity(&uidValidity);
+ imapUrl->GetStoreResultsOffline(&storeResultsOffline);
+ // If we're storing the message in the offline store, don't
+ // write to the memory cache.
+ if (storeResultsOffline)
+ cacheAccess = nsICacheStorage::OPEN_READONLY;
+
+ // 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.
+ nsCOMPtr<nsIURI> newUri;
+ m_url->Clone(getter_AddRefs(newUri));
+ nsAutoCString path;
+ newUri->GetPath(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
+ nsAutoCString 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);
+ }
+ }
+ nsAutoCString filenameQuery = MsgExtractQueryPart(path, "&filename=");
+
+ // Truncate path at either /; or ?
+ int32_t ind = path.FindChar('?');
+ if (ind != kNotFound)
+ path.SetLength(ind);
+ ind = path.Find("/;");
+ if (ind != kNotFound)
+ path.SetLength(ind);
+
+ if (partQuery.IsEmpty())
+ {
+ // Not looking for a part. That's the easy part.
+ newUri->SetPath(path);
+ return cacheStorage->AsyncOpenURI(newUri, extension, cacheAccess, this);
+ }
+
+ /**
+ * Part processing (rest of this function).
+ */
+ if (mTryingToReadPart)
+ {
+ // If mTryingToReadPart is set, we are here for the second time.
+ // We tried to read a part from the entire message but the meta data didn't
+ // allow it. So we come back here.
+ // Now request the part with its full URL.
+ mTryingToReadPart = false;
+
+ // Note that part extraction was already set the first time.
+ newUri->SetPath(path + partQuery + filenameQuery);
+ return cacheStorage->AsyncOpenURI(newUri, extension, cacheAccess, this);
+ }
+
+ // First time processing. Set up part extraction.
+ SetupPartExtractorListener(imapUrl, m_channelListener);
+
+ // Check whether part is in the cache.
+ bool exists = false;
+ newUri->SetPath(path + partQuery + filenameQuery);
+ rv = cacheStorage->Exists(newUri, extension, &exists);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (exists) {
+ return cacheStorage->AsyncOpenURI(newUri, extension, cacheAccess, this);
+ }
+
+ // Let's see whether we have the entire message instead.
+ newUri->SetPath(path);
+ rv = cacheStorage->Exists(newUri, extension, &exists);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!exists) {
+ // The entire message is not in the cache. Request the part.
+ newUri->SetPath(path + partQuery + filenameQuery);
+ return cacheStorage->AsyncOpenURI(newUri, extension, cacheAccess, this);
+ }
+
+ // This is where is gets complicated. The entire message is in the cache,
+ // but we don't know whether it's suitable for use. Its meta data
+ // might indicate that the message is incomplete.
+ mTryingToReadPart = true;
+ return cacheStorage->AsyncOpenURI(newUri, extension, cacheAccess, this);
+}
+
+nsresult nsImapMockChannel::ReadFromMemCache(nsICacheEntry *entry)
+{
+ NS_ENSURE_ARG(entry);
+
+ nsCString annotation;
+ nsAutoCString entryKey;
+ nsAutoCString contentType;
+ nsresult rv = NS_OK;
+ bool shouldUseCacheEntry = false;
+
+ entry->GetKey(entryKey);
+ if (entryKey.FindChar('?') != kNotFound)
+ {
+ // Part processing: If we have a part, then we should use the cache entry.
+ entry->GetMetaDataElement("contentType", getter_Copies(contentType));
+ if (!contentType.IsEmpty())
+ SetContentType(contentType);
+ shouldUseCacheEntry = true;
+ }
+ else
+ {
+ // Whole message processing: We should make sure the content isn't modified.
+ rv = entry->GetMetaDataElement("ContentModified", getter_Copies(annotation));
+ if (NS_SUCCEEDED(rv) && !annotation.IsEmpty())
+ shouldUseCacheEntry = annotation.EqualsLiteral("Not Modified");
+
+ // Compare cache entry size with message size.
+ if (shouldUseCacheEntry)
+ {
+ int64_t entrySize;
+
+ rv = entry->GetDataSize(&entrySize);
+ // We don't expect concurrent read here, so this call should always work.
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ 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)) &&
+ messageSize != entrySize)
+ {
+ MOZ_LOG(IMAP, LogLevel::Warning,
+ ("ReadFromMemCache size mismatch for %s: message %d, cache %ld\n",
+ entryKey.get(), messageSize, entrySize));
+ shouldUseCacheEntry = false;
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Common processing for full messages and message parts.
+ */
+
+ // Check header of full message or part.
+ if (shouldUseCacheEntry)
+ {
+ nsCOMPtr<nsIInputStream> in;
+ uint32_t readCount;
+ rv = entry->OpenInputStream(0, getter_AddRefs(in));
+ NS_ENSURE_SUCCESS(rv, rv);
+ const int kFirstBlockSize = 100;
+ char firstBlock[kFirstBlockSize + 1];
+
+ // Note: This will not work for a cache2 disk cache.
+ // (see bug 1302422 comment #4)
+ rv = in->Read(firstBlock, sizeof(firstBlock), &readCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+ firstBlock[kFirstBlockSize] = '\0';
+ int32_t findPos = MsgFindCharInSet(nsDependentCString(firstBlock),
+ ":\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.
+ shouldUseCacheEntry = ((findPos != -1 && firstBlock[findPos] == ':') ||
+ !(strncmp(firstBlock, "From ", 5)));
+ in->Close();
+ }
+
+ if (shouldUseCacheEntry)
+ {
+ nsCOMPtr<nsIInputStream> in;
+ rv = entry->OpenInputStream(0, getter_AddRefs(in));
+ NS_ENSURE_SUCCESS(rv, rv);
+ // if mem cache entry is broken or empty, return error.
+ uint64_t bytesAvailable;
+ rv = in->Available(&bytesAvailable);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!bytesAvailable)
+ return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIInputStreamPump> pump;
+ rv = NS_NewInputStreamPump(getter_AddRefs(pump), in);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // if we are going to read from the cache, then create a mock stream listener class and use it
+ nsImapCacheStreamListener * cacheListener = new nsImapCacheStreamListener();
+ NS_ADDREF(cacheListener);
+ cacheListener->Init(m_channelListener, this);
+ rv = pump->AsyncRead(cacheListener, m_channelContext);
+ NS_RELEASE(cacheListener);
+
+ if (NS_SUCCEEDED(rv)) // ONLY if we succeeded in actually starting the read should we return
+ {
+ mCacheRequest = pump;
+ nsCOMPtr<nsIImapUrl> imapUrl = do_QueryInterface(m_url);
+ // if the msg is unread, we should mark it read on the server. This lets
+ // the code running this url we're loading from the cache, if it cares.
+ imapUrl->SetMsgLoadingFromCache(true);
+
+ // be sure to set the cache entry's security info status as our security info status...
+ nsCOMPtr<nsISupports> securityInfo;
+ entry->GetSecurityInfo(getter_AddRefs(securityInfo));
+ SetSecurityInfo(securityInfo);
+ return NS_OK;
+ } // if AsyncRead succeeded.
+ } // if content is not modified
+ else
+ {
+ // Content is modified so return an error so we try to open it the
+ // old fashioned way.
+ rv = NS_ERROR_FAILURE;
+ }
+
+ return rv;
+}
+
+class nsReadFromImapConnectionFailure : public mozilla::Runnable
+{
+public:
+ explicit nsReadFromImapConnectionFailure(nsImapMockChannel *aChannel)
+ : 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, m_channelContext,
+ NS_MSG_ERROR_MSG_NOT_OFFLINE);
+ }
+ return NS_OK;
+}
+
+// the requested url isn't in any of our caches so create an imap connection
+// to process it.
+nsresult 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)
+ {
+ nsAutoCString messageIdString;
+
+ SetupPartExtractorListener(imapUrl, m_channelListener);
+
+ imapUrl->GetListOfMessageIds(messageIdString);
+ nsCOMPtr <nsIMsgFolder> folder;
+ rv = mailnewsUrl->GetFolder(getter_AddRefs(folder));
+ if (folder && NS_SUCCEEDED(rv))
+ {
+ // we want to create a file channel and read the msg from there.
+ nsCOMPtr<nsIInputStream> fileStream;
+ nsMsgKey msgKey = strtoul(messageIdString.get(), nullptr, 10);
+ uint32_t size;
+ int64_t offset;
+ rv = folder->GetOfflineFileStream(msgKey, &offset, &size, getter_AddRefs(fileStream));
+ // get the file channel from the folder, somehow (through the message or
+ // folder sink?) We also need to set the transfer offset to the message offset
+ if (fileStream && NS_SUCCEEDED(rv))
+ {
+ // dougt - This may break the ablity to "cancel" a read from offline mail reading.
+ // fileChannel->SetLoadGroup(m_loadGroup);
+ nsImapCacheStreamListener * cacheListener = new nsImapCacheStreamListener();
+ NS_ADDREF(cacheListener);
+ cacheListener->Init(m_channelListener, this);
+
+ // create a stream pump that will async read the specified amount of data.
+ // XXX make offset/size 64-bit ints
+ nsCOMPtr<nsIInputStreamPump> pump;
+ rv = NS_NewInputStreamPump(getter_AddRefs(pump), fileStream,
+ offset, (int64_t) size);
+ if (NS_SUCCEEDED(rv))
+ rv = pump->AsyncRead(cacheListener, m_channelContext);
+
+ NS_RELEASE(cacheListener);
+
+ if (NS_SUCCEEDED(rv)) // ONLY if we succeeded in actually starting the read should we return
+ {
+ // if the msg is unread, we should mark it read on the server. This lets
+ // the code running this url we're loading from the cache, if it cares.
+ imapUrl->SetMsgLoadingFromCache(true);
+ return true;
+ }
+ } // if we got an offline file transport
+ } // if we got the folder for this url
+ } // if use local cache
+
+ return false;
+}
+
+NS_IMETHODIMP nsImapMockChannel::AsyncOpen(nsIStreamListener *listener, nsISupports *ctxt)
+{
+ nsresult rv = NS_OK;
+
+ 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
+ m_channelContext = ctxt;
+ 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();
+}
+
+NS_IMETHODIMP nsImapMockChannel::AsyncOpen2(nsIStreamListener *aListener)
+{
+ nsCOMPtr<nsIStreamListener> listener = aListener;
+ nsresult rv = nsContentSecurityManager::doContentSecurityCheck(this, listener);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return AsyncOpen(listener, nullptr);
+}
+
+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)
+{
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsImapMockChannel::SetContentDisposition(uint32_t aContentDisposition)
+{
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+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)
+{
+ *aPrincipal = mOwner;
+ NS_IF_ADDREF(*aPrincipal);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMockChannel::SetOwner(nsISupports * aPrincipal)
+{
+ mOwner = aPrincipal;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMockChannel::GetSecurityInfo(nsISupports * *aSecurityInfo)
+{
+ NS_IF_ADDREF(*aSecurityInfo = mSecurityInfo);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMockChannel::SetSecurityInfo(nsISupports *aSecurityInfo)
+{
+ mSecurityInfo = aSecurityInfo;
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// 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::SetImapProtocol(nsIImapProtocol *aProtocol)
+{
+ mProtocol = do_GetWeakReference(aProtocol);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMockChannel::Cancel(nsresult status)
+{
+ NS_WARNING_ASSERTION(NS_IsMainThread(),
+ "nsImapMockChannel::Cancel should only be called from UI thread");
+ 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 (m_url)
+ {
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_url);
+ DoomCacheEntry(mailnewsUrl);
+ }
+
+ // Required for killing ImapProtocol thread
+ if (imapProtocol)
+ imapProtocol->TellThreadToDie(false);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMockChannel::Suspend()
+{
+ NS_NOTREACHED("nsImapMockChannel::Suspend");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsImapMockChannel::Resume()
+{
+ NS_NOTREACHED("nsImapMockChannel::Resume");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsImapMockChannel::GetNotificationCallbacks(nsIInterfaceRequestor* *aNotificationCallbacks)
+{
+ *aNotificationCallbacks = mCallbacks.get();
+ NS_IF_ADDREF(*aNotificationCallbacks);
+ 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->GetRealHostName(host);
+ }
+ mProgressEventSink->OnStatus(this, nullptr, 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/mailnews/imap/src/nsImapProtocol.h b/mailnews/imap/src/nsImapProtocol.h
new file mode 100644
index 000000000..ba2594c89
--- /dev/null
+++ b/mailnews/imap/src/nsImapProtocol.h
@@ -0,0 +1,772 @@
+/* -*- 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 "nsStringGlue.h"
+#include "nsIProgressEventSink.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsISocketTransport.h"
+#include "nsIInputStreamPump.h"
+
+// imap event sinks
+#include "nsIImapMailFolderSink.h"
+#include "nsIImapServerSink.h"
+#include "nsIImapMessageSink.h"
+
+// UI Thread proxy helper
+#include "nsIImapProtocolSink.h"
+
+#include "nsImapServerResponseParser.h"
+#include "nsImapFlagAndUidState.h"
+#include "nsIMAPNamespace.h"
+#include "nsTArray.h"
+#include "nsWeakPtr.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 "nsIImapIncomingServer.h"
+#include "nsIMsgWindow.h"
+#include "nsIImapHeaderXferInfo.h"
+#include "nsMsgLineBuffer.h"
+#include "nsIAsyncInputStream.h"
+#include "nsITimer.h"
+#include "nsAutoPtr.h"
+#include "nsIMsgFolder.h"
+#include "nsIMsgAsyncPrompter.h"
+#include "mozilla/ReentrantMonitor.h"
+#include "nsSyncRunnableHelpers.h"
+#include "nsICacheEntryOpenCallback.h"
+
+class nsIMAPMessagePartIDArray;
+class nsIMsgIncomingServer;
+class nsIPrefBranch;
+class nsIMAPMailboxInfo;
+
+#define kDownLoadCacheSize 16000 // 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;
+};
+
+// 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....
+
+#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
+
+class nsImapProtocol : public nsIImapProtocol,
+ public nsIRunnable,
+ public nsIInputStreamCallback,
+ public nsSupportsWeakReference,
+ public nsMsgProtocol,
+ public nsIImapProtocolSink,
+ public nsIMsgAsyncPromptListener
+{
+public:
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIINPUTSTREAMCALLBACK
+ nsImapProtocol();
+
+ virtual nsresult ProcessProtocolState(nsIURI * url, nsIInputStream * inputStream,
+ uint64_t sourceOffset, uint32_t length) override;
+
+ // nsIRunnable method
+ NS_IMETHOD Run() 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);
+ virtual void PipelinedFetchMessageParts(nsCString &uid, nsIMAPMessagePartIDArray *parts);
+ 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();
+ void PseudoInterrupt(bool the_interrupt);
+
+ uint32_t GetMessageSize(const char * messageId, bool idsAreUids);
+ 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(char16_t *message, 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 Close(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);
+ void PipelinedFetchMessageParts(const char *uid, nsIMAPMessagePartIDArray *parts);
+
+
+ // 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(nsCString& aQuotaRoot, uint32_t aUsed, uint32_t aMax);
+
+ bool GetPreferPlainText() { return m_preferPlainText; }
+
+ int32_t GetCurFetchSize() { return m_curFetchSize; }
+
+ const nsString &GetEmptyMimePartString() { return m_emptyMimePartString; }
+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;
+ nsCOMPtr<nsIImapUrl> m_runningUrl; // the nsIImapURL that is currently running
+ nsCOMPtr<nsIImapUrl> m_runningUrlLatest;
+ nsImapAction m_imapAction; // current imap action associated with this connnection...
+
+ nsCString m_hostName;
+ nsCString m_userName;
+ nsCString m_serverKey;
+ nsCString m_realHostName;
+ char *m_dataOutputBuf;
+ RefPtr<nsMsgLineStreamBuffer> m_inputStreamBuffer;
+ uint32_t m_allocatedSize; // allocated size
+ uint32_t m_totalDataSize; // total data size
+ uint32_t m_curReadIndex; // current read index
+ nsCString m_trashFolderName;
+
+ // Ouput stream for writing commands to the socket
+ nsCOMPtr<nsISocketTransport> m_transport;
+
+ nsCOMPtr<nsIAsyncInputStream> m_channelInputStream;
+ nsCOMPtr<nsIAsyncOutputStream> m_channelOutputStream;
+ nsCOMPtr<nsIImapMockChannel> m_mockChannel; // this is the channel we should forward to people
+ 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 *******
+ nsCOMPtr<nsIThread> m_iThread;
+ 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.
+ nsCString 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;
+ nsWeakPtr m_server;
+
+ RefPtr<ImapMailFolderSinkProxy> m_imapMailFolderSink;
+ 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
+
+ bool GetDeleteIsMoveToTrash();
+ bool GetShowDeletedMessages();
+ nsCString m_currentCommand;
+ nsImapServerResponseParser m_parser;
+ nsImapServerResponseParser& GetServerStateParser() { return m_parser; }
+
+ void HandleIdleResponses();
+ virtual bool ProcessCurrentURL();
+ void EstablishServerConnection();
+ virtual void ParseIMAPandCheckForNewMail(const char* commandString =
+ nullptr, bool ignoreBadNOResponses = false);
+ // biff
+ void PeriodicBiff();
+ void SendSetBiffIndicatorEvent(nsMsgBiffState newState);
+ bool CheckNewMail();
+
+ // 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(uint32_t **msgIdList, uint32_t &msgCount);
+ 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;
+ uint32_t m_fetchBodyCount;
+ uint32_t *m_fetchBodyIdList;
+
+ // initialization function given a new url and transport layer
+ nsresult SetupWithUrl(nsIURI * aURL, nsISupports* aConsumer);
+ 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 authenication 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;
+
+ 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(nsCString &password, bool aNewPasswordRequested);
+#ifdef MOZ_MAILNEWS_OAUTH2
+ void InitPrefAuthMethods(int32_t authMethodPrefValue, nsIMsgIncomingServer *aServer);
+#else
+ void InitPrefAuthMethods(int32_t authMethodPrefValue);
+#endif
+ 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 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 AuthLogin(const char *userName, const nsCString &password, eIMAPCapabilityFlag flag);
+ void ProcessAuthenticatedStateURL();
+ void ProcessAfterAuthenticated();
+ void ProcessSelectedStateURL();
+ bool TryToLogon();
+
+ // Process Authenticated State Url 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);
+ nsCString CreatePossibleTrashName(const char *prefix);
+ 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();
+ bool IsExtraSelectNeeded();
+ 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;
+ uint32_t mFolderNumDeleted;
+
+ 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
+ nsDataHashtable<nsCStringHashKey, int32_t> m_standardListMailboxes;
+ // mapping between special xlist mailboxes and the corresponding folder flags
+ nsDataHashtable<nsCStringHashKey, int32_t> m_specialXListMailboxes;
+
+
+ nsCOMPtr<nsIImapHostSessionList> m_hostSessionList;
+
+ bool m_fromHeaderSeen;
+
+ // these settings allow clients to override various pieces of the connection info from the url
+ bool m_overRideUrlConnectionInfo;
+
+ nsCString m_logonHost;
+ nsCString m_logonCookie;
+ int16_t m_logonPort;
+
+ nsString mAcceptLanguages;
+
+ // progress stuff
+ void SetProgressString(const char* stringName);
+
+ nsString m_progressString;
+ nsCString m_progressStringName;
+ int32_t m_progressIndex;
+ int32_t m_progressCount;
+ nsCString m_lastProgressStringName;
+ int32_t m_lastPercent;
+ int64_t m_lastProgressTime;
+
+ bool m_notifySearchHit;
+ bool m_checkForNewMailDownloadsHeaders;
+ 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;
+ nsCString m_forceSelectValue;
+ bool m_forceSelect;
+
+ int32_t m_uidValidity; // stored uid validity for the selected folder.
+
+ enum EMailboxHierarchyNameState {
+ kNoOperationInProgress,
+ kDiscoverBaseFolderInProgress,
+ kDiscoverTrashFolderInProgress,
+ kDeleteSubFoldersInProgress,
+ kListingForInfoOnly,
+ kListingForInfoAndDiscovery,
+ kDiscoveringNamespacesOnly,
+ kXListing,
+ kListingForFolderFlags,
+ kListingForCreate
+ };
+ EMailboxHierarchyNameState m_hierarchyNameState;
+ EMailboxDiscoverStatus m_discoveryStatus;
+ nsTArray<nsIMAPMailboxInfo*> m_listedMailboxList;
+ nsTArray<char*> * m_deletableChildren;
+ uint32_t m_flagChangeCount;
+ PRTime m_lastCheckTime;
+
+ bool CheckNeeded();
+
+ nsString m_emptyMimePartString;
+
+#ifdef MOZ_MAILNEWS_OAUTH2
+ RefPtr<mozilla::mailnews::OAuth2ThreadHelper> mOAuth2Support;
+#endif
+};
+
+// 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:
+ friend class nsImapProtocol;
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ 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;
+ nsISupports * m_channelContext;
+ nsresult m_cancelStatus;
+ nsLoadFlags mLoadFlags;
+ nsCOMPtr<nsIProgressEventSink> mProgressEventSink;
+ nsCOMPtr<nsIInterfaceRequestor> mCallbacks;
+ nsCOMPtr<nsISupports> mOwner;
+ nsCOMPtr<nsISupports> mSecurityInfo;
+ nsCOMPtr<nsIRequest> mCacheRequest; // the request associated with a read from the cache
+ nsCString mContentType;
+ nsCString mCharset;
+ nsWeakPtr mProtocol;
+
+ bool mChannelClosed;
+ bool mReadingFromCache;
+ bool mTryingToReadPart;
+ int64_t mContentLength;
+
+ // 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 ReadFromImapConnection(); // creates a new imap connection to read the url
+ nsresult ReadFromMemCache(nsICacheEntry *entry); // attempts to read the url out of our memory cache
+ nsresult NotifyStartEndReadFromCache(bool start);
+
+ // we end up daisy chaining multiple nsIStreamListeners into the load process.
+ nsresult SetupPartExtractorListener(nsIImapUrl * aUrl, nsIStreamListener * aConsumer);
+};
+
+// 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;
+};
+
+#endif // nsImapProtocol_h___
diff --git a/mailnews/imap/src/nsImapSearchResults.cpp b/mailnews/imap/src/nsImapSearchResults.cpp
new file mode 100644
index 000000000..e5e4a76be
--- /dev/null
+++ b/mailnews/imap/src/nsImapSearchResults.cpp
@@ -0,0 +1,92 @@
+/* -*- 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 "msgCore.h" // for pre-compiled headers
+
+#include "nsImapCore.h"
+#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/mailnews/imap/src/nsImapSearchResults.h b/mailnews/imap/src/nsImapSearchResults.h
new file mode 100644
index 000000000..b03268d4d
--- /dev/null
+++ b/mailnews/imap/src/nsImapSearchResults.h
@@ -0,0 +1,42 @@
+/* -*- 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/mailnews/imap/src/nsImapServerResponseParser.cpp b/mailnews/imap/src/nsImapServerResponseParser.cpp
new file mode 100644
index 000000000..138ee596f
--- /dev/null
+++ b/mailnews/imap/src/nsImapServerResponseParser.cpp
@@ -0,0 +1,3483 @@
+/* -*- 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 "nsIMAPBodyShell.h"
+#include "nsImapFlagAndUidState.h"
+#include "nsIMAPNamespace.h"
+#include "nsImapStringBundle.h"
+#include "nsImapUtils.h"
+#include "nsCRT.h"
+#include "nsMsgUtils.h"
+#include "mozilla/Logging.h"
+
+////////////////// nsImapServerResponseParser /////////////////////////
+
+extern PRLogModuleInfo* IMAP;
+
+nsImapServerResponseParser::nsImapServerResponseParser(nsImapProtocol &imapProtocolConnection)
+ : nsIMAPGenericParser(),
+ fReportingErrors(true),
+ fCurrentFolderReadOnly(false),
+ fCurrentLineContainedFlagInfo(false),
+ fServerIsNetscape3xServer(false),
+ fNumberOfUnseenMessages(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;
+ fStatusExistingMessages = 0;
+ fReceivedHeaderOrSizeForUID = nsMsgKey_None;
+ fCondStoreEnabled = 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);
+
+ NS_IF_RELEASE (fHostSessionList);
+}
+
+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;
+}
+
+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;
+}
+
+// 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 != ' ', "Invailid 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);
+
+ // For checking expected response to IDLE command below.
+ bool untagged = false;
+
+ if (ContinueParse())
+ {
+ ResetLexAnalyzer();
+
+ if (aGreetingWithCapability)
+ {
+ PR_FREEIF(fCurrentLine);
+ fCurrentLine = aGreetingWithCapability;
+ }
+
+ 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();
+ }
+ untagged = 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 == '+') || untagged))
+ {
+ // 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 notication 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();
+ }
+ }
+ if (GetFillingInShell())
+ {
+ // There is a BODYSTRUCTURE response. Now let's generate the stream...
+ // that is, if we're not doing it already
+ if (!m_shell->IsBeingGenerated())
+ {
+ nsImapProtocol *navCon = &fServerConnection;
+
+ char *imapPart = nullptr;
+
+ fServerConnection.GetCurrentUrl()->GetImapPartToFetch(&imapPart);
+ m_shell->Generate(imapPart);
+ PR_Free(imapPart);
+
+ if ((navCon && navCon->GetPseudoInterrupted())
+ || fServerConnection.DeathSignalReceived())
+ {
+ // we were pseudointerrupted or interrupted
+ // if it's not in the cache, then we were (pseudo)interrupted while generating
+ // for the first time. Release it.
+ if (!m_shell->IsShellCached())
+ m_shell = nullptr;
+ navCon->PseudoInterrupt(false);
+ }
+ else if (m_shell->GetIsValid())
+ {
+ // If we have a valid shell that has not already been cached, then cache it.
+ if (!m_shell->IsShellCached() && fHostSessionList) // cache is responsible for destroying it
+ {
+ MOZ_LOG(IMAP, mozilla::LogLevel::Info,
+ ("BODYSHELL: Adding shell to cache."));
+ const char *serverKey = fServerConnection.GetImapServerKey();
+ fHostSessionList->AddShellToCacheForHost(
+ serverKey, m_shell);
+ }
+ }
+ m_shell = nullptr;
+ }
+ }
+}
+
+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
+ if (GetFillingInShell() && !m_shell->IsBeingGenerated())
+ m_shell = nullptr;
+}
+
+
+
+// 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 accomodate 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 thes flags.
+ if (fGotPermanentFlags)
+ skip_to_CRLF();
+ else
+ parse_folder_flags();
+ }
+ 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)
+{
+ nsImapMailboxSpec *boxSpec = new nsImapMailboxSpec;
+ NS_ADDREF(boxSpec);
+ bool needsToFreeBoxSpec = true;
+ if (!boxSpec)
+ HandleMemoryFailure();
+ else
+ {
+ 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())
+ {
+ // nsImapProtocol::DiscoverMailboxSpec() eventually frees the
+ // boxSpec
+ needsToFreeBoxSpec = false;
+ mailbox(boxSpec);
+ }
+ }
+ }
+ if (needsToFreeBoxSpec)
+ NS_RELEASE(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)
+ {
+ // should the namespace check go before or after the Utf7 conversion?
+ 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;
+ }
+
+ // char *convertedName =
+ // fServerConnection.CreateUtf7ConvertedString(boxname, false);
+ // char16_t *unicharName;
+ // unicharName = fServerConnection.CreatePRUnicharStringFromUTF7(boxname);
+ // PL_strfree(boxname);
+ // boxname = convertedName;
+ }
+
+ 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);
+ if (boxname)
+ PL_strfree( boxname);
+ // 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);
+ }
+}
+
+
+/*
+ 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,
+ NS_LITERAL_CSTRING("X-GM-MSGID"), 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,
+ NS_LITERAL_CSTRING("X-GM-THRID"), 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,
+ NS_LITERAL_CSTRING("X-GM-LABELS"), 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_strcasecmp(fNextToken, "BODYSTRUCTURE"))
+ {
+ if (fCurrentResponseUID == 0)
+ fFlagState->GetUidOfMessage(fFetchResponseIndex - 1, &fCurrentResponseUID);
+ bodystructure_data();
+ }
+ else if (!PL_strncasecmp(fNextToken, "BODY[TEXT", 9))
+ {
+ mime_data();
+ }
+ else if (!PL_strncasecmp(fNextToken, "BODY[", 5) && PL_strncasecmp(fNextToken, "BODY[]", 6))
+ {
+ fDownloadingHeaders = false;
+ // A specific MIME part, or MIME part header
+ mime_data();
+ }
+ 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;
+ else if (*fNextToken == ')')
+ {
+ SetSyntaxError(true); // envelope too short
+ break;
+ }
+ else
+ {
+ 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.Append("To: ");
+ nsAutoCString fakeFromLine(NS_LITERAL_CSTRING("From: "));
+ fakeFromLine.Append(fServerConnection.GetImapUserName());
+ fakeFromLine.Append(NS_LITERAL_CSTRING("@aol.com"));
+ fServerConnection.HandleMessageDownLoadLine(fakeFromLine.get(), false);
+ }
+ else
+ {
+ fromLine.Append("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)
+{
+ 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();
+ addressLine += mailboxName;
+ if (hostName)
+ {
+ addressLine += '@';
+ addressLine += hostName;
+ NS_Free(hostName);
+ }
+ if (personalName)
+ {
+ addressLine += " (";
+ addressLine += personalName;
+ addressLine += ')';
+ }
+ }
+ }
+ 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;
+ NS_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()
+{
+ uint16_t labelFlags = 0;
+
+ do
+ {
+ AdvanceToNextToken();
+ if (*fNextToken == '(')
+ fNextToken++;
+ if (!PL_strncasecmp(fNextToken, "$MDNSent", 8))
+ fSupportsUserDefinedFlags |= kImapMsgSupportMDNSentFlag;
+ else if (!PL_strncasecmp(fNextToken, "$Forwarded", 10))
+ fSupportsUserDefinedFlags |= kImapMsgSupportForwardedFlag;
+ else 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, "$Label1", 7))
+ labelFlags |= 1;
+ else if (!PL_strncasecmp(fNextToken, "$Label2", 7))
+ labelFlags |= 2;
+ else if (!PL_strncasecmp(fNextToken, "$Label3", 7))
+ labelFlags |= 4;
+ else if (!PL_strncasecmp(fNextToken, "$Label4", 7))
+ labelFlags |= 8;
+ else if (!PL_strncasecmp(fNextToken, "$Label5", 7))
+ labelFlags |= 16;
+ else if (!PL_strncasecmp(fNextToken, "\\*", 2))
+ {
+ fSupportsUserDefinedFlags |= kImapMsgSupportUserFlag;
+ fSupportsUserDefinedFlags |= kImapMsgSupportForwardedFlag;
+ fSupportsUserDefinedFlags |= kImapMsgSupportMDNSentFlag;
+ fSupportsUserDefinedFlags |= kImapMsgLabelFlags;
+ }
+ } while (!fAtEndOfLine && ContinueParse());
+
+ if (labelFlags == 31)
+ fSupportsUserDefinedFlags |= kImapMsgLabelFlags;
+
+ if (fFlagState)
+ fFlagState->OrSupportedUserFlags(fSupportsUserDefinedFlags);
+}
+/*
+ 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();
+ // 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())
+ {
+ fNumberOfUnseenMessages = strtoul(fNextToken, nullptr, 10);
+ AdvanceToNextToken();
+ }
+ }
+ else if (!PL_strcasecmp(fNextToken,"UIDNEXT"))
+ {
+ AdvanceToNextToken();
+ if (ContinueParse())
+ {
+ fStatusNextUID = 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)
+{
+ if (GetFillingInShell())
+ {
+ char *headerData = CreateAstring();
+ AdvanceToNextToken();
+ m_shell->AdoptMessageHeaders(headerData, partNum);
+ }
+ else
+ {
+ 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.
+ // Don't do it if we are filling in a shell or downloading a part.
+ // DO do it if we are downloading a whole message as a result of
+ // an invalid shell trying to generate.
+ if ((!chunk || (origin == 0)) && !GetDownloadingHeaders() &&
+ (GetFillingInShell() ? m_shell->GetGeneratingWholeMessage() : true))
+ {
+ 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 && (GetFillingInShell() ? m_shell->GetGeneratingWholeMessage() : true))
+ {
+ // complete the message download
+ if (ContinueParse())
+ {
+ if (fReceivedHeaderOrSizeForUID == CurrentResponseUID())
+ {
+ fServerConnection.NormalMessageEndDownload();
+ fReceivedHeaderOrSizeForUID = nsMsgKey_None;
+ }
+ else
+ fReceivedHeaderOrSizeForUID = CurrentResponseUID();
+ }
+ else
+ fServerConnection.AbortMessageDownLoad();
+ }
+}
+
+
+/*
+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;
+#ifdef MOZ_MAILNEWS_OAUTH2
+ else if (token.Equals("AUTH=XOAUTH2", nsCaseInsensitiveCStringComparator()))
+ fCapabilityFlag |= kHasXOAuth2Capability;
+#endif
+ 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;
+ }
+ } 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();
+ 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 (!strcmp("CONDSTORE", fNextToken))
+ fCondStoreEnabled = 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.
+
+ nsIMAPNamespace *newNamespace = new nsIMAPNamespace(namespaceType, namespacePrefix, namespaceDelimiter, false);
+ // add it to a temporary list in the host
+ if (newNamespace && fHostSessionList)
+ 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();
+ }
+}
+
+
+void nsImapServerResponseParser::mime_data()
+{
+ if (PL_strstr(fNextToken, "MIME"))
+ mime_header_data();
+ else
+ mime_part_data();
+}
+
+// mime_header_data should not be streamed out; rather, it should be
+// buffered in the nsIMAPBodyShell.
+// This is because we are still in the process of generating enough
+// information from the server (such as the MIME header's size) so that
+// we can construct the final output stream.
+void nsImapServerResponseParser::mime_header_data()
+{
+ char *partNumber = PL_strdup(fNextToken);
+ if (partNumber)
+ {
+ char *start = partNumber + 5, *end = partNumber + 5; // 5 == strlen("BODY[")
+ while (ContinueParse() && end && *end != 'M' && *end != 'm')
+ {
+ end++;
+ }
+ if (end && (*end == 'M' || *end == 'm'))
+ {
+ *(end-1) = 0;
+ AdvanceToNextToken();
+ char *mimeHeaderData = CreateAstring(); // is it really this simple?
+ AdvanceToNextToken();
+ if (m_shell)
+ {
+ m_shell->AdoptMimeHeader(start, mimeHeaderData);
+ }
+ }
+ else
+ {
+ SetSyntaxError(true);
+ }
+ PR_Free(partNumber); // partNumber is not adopted by the body shell.
+ }
+ else
+ {
+ HandleMemoryFailure();
+ }
+}
+
+// Actual mime parts are filled in on demand (either from shell generation
+// or from explicit user download), so we need to stream these out.
+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();
+}
+
+// parse FETCH BODYSTRUCTURE response, "a parenthesized list that describes
+// the [MIME-IMB] body structure of a message" [RFC 3501].
+void nsImapServerResponseParser::bodystructure_data()
+{
+ AdvanceToNextToken();
+ if (ContinueParse() && fNextToken && *fNextToken == '(') // It has to start with an open paren.
+ {
+ // Turn the BODYSTRUCTURE response into a form that the nsIMAPBodypartMessage can be constructed from.
+ // FIXME: Follow up on bug 384210 to investigate why the caller has to duplicate the two in-param strings.
+ nsIMAPBodypartMessage *message =
+ new nsIMAPBodypartMessage(NULL, NULL, true, strdup("message"),
+ strdup("rfc822"),
+ NULL, NULL, NULL, 0,
+ fServerConnection.GetPreferPlainText());
+ nsIMAPBodypart *body = bodystructure_part(PL_strdup("1"), message);
+ if (body)
+ message->SetBody(body);
+ else
+ {
+ delete message;
+ message = nullptr;
+ }
+ m_shell = new nsIMAPBodyShell(&fServerConnection, message, CurrentResponseUID(), GetSelectedMailboxName());
+ // ignore syntax errors in parsing the body structure response. If there's an error
+ // we'll just fall back to fetching the whole message.
+ SetSyntaxError(false);
+ }
+ else
+ SetSyntaxError(true);
+}
+
+// RFC3501: body = "(" (body-type-1part / body-type-mpart) ")"
+nsIMAPBodypart *
+nsImapServerResponseParser::bodystructure_part(char *partNum, nsIMAPBodypart *parentPart)
+{
+ // Check to see if this buffer is a leaf or container
+ // (Look at second character - if an open paren, then it is a container)
+ if (*fNextToken != '(')
+ {
+ NS_ASSERTION(false, "bodystructure_part must begin with '('");
+ return NULL;
+ }
+
+ if (fNextToken[1] == '(')
+ return bodystructure_multipart(partNum, parentPart);
+ else
+ return bodystructure_leaf(partNum, parentPart);
+}
+
+// RFC3501: body-type-1part = (body-type-basic / body-type-msg / body-type-text)
+// [SP body-ext-1part]
+nsIMAPBodypart *
+nsImapServerResponseParser::bodystructure_leaf(char *partNum, nsIMAPBodypart *parentPart)
+{
+ // historical note: this code was originally in nsIMAPBodypartLeaf::ParseIntoObjects()
+ char *bodyType = nullptr, *bodySubType = nullptr, *bodyID = nullptr, *bodyDescription = nullptr, *bodyEncoding = nullptr;
+ int32_t partLength = 0;
+ bool isValid = true;
+
+ // body type ("application", "text", "image", etc.)
+ if (ContinueParse())
+ {
+ fNextToken++; // eat the first '('
+ bodyType = CreateNilString();
+ if (ContinueParse())
+ AdvanceToNextToken();
+ }
+
+ // body subtype ("gif", "html", etc.)
+ if (isValid && ContinueParse())
+ {
+ bodySubType = CreateNilString();
+ if (ContinueParse())
+ AdvanceToNextToken();
+ }
+
+ // body parameter: parenthesized list
+ if (isValid && ContinueParse())
+ {
+ if (fNextToken[0] == '(')
+ {
+ fNextToken++;
+ skip_to_close_paren();
+ }
+ else if (!PL_strcasecmp(fNextToken, "NIL"))
+ AdvanceToNextToken();
+ }
+
+ // body id
+ if (isValid && ContinueParse())
+ {
+ bodyID = CreateNilString();
+ if (ContinueParse())
+ AdvanceToNextToken();
+ }
+
+ // body description
+ if (isValid && ContinueParse())
+ {
+ bodyDescription = CreateNilString();
+ if (ContinueParse())
+ AdvanceToNextToken();
+ }
+
+ // body encoding
+ if (isValid && ContinueParse())
+ {
+ bodyEncoding = CreateNilString();
+ if (ContinueParse())
+ AdvanceToNextToken();
+ }
+
+ // body size
+ if (isValid && ContinueParse())
+ {
+ char *bodySizeString = CreateAtom();
+ if (!bodySizeString)
+ isValid = false;
+ else
+ {
+ partLength = atoi(bodySizeString);
+ PR_Free(bodySizeString);
+ if (ContinueParse())
+ AdvanceToNextToken();
+ }
+ }
+
+ if (!isValid || !ContinueParse())
+ {
+ PR_FREEIF(partNum);
+ PR_FREEIF(bodyType);
+ PR_FREEIF(bodySubType);
+ PR_FREEIF(bodyID);
+ PR_FREEIF(bodyDescription);
+ PR_FREEIF(bodyEncoding);
+ }
+ else
+ {
+ if (PL_strcasecmp(bodyType, "message") || PL_strcasecmp(bodySubType, "rfc822"))
+ {
+ skip_to_close_paren();
+ return new nsIMAPBodypartLeaf(partNum, parentPart, bodyType, bodySubType,
+ bodyID, bodyDescription, bodyEncoding,
+ partLength,
+ fServerConnection.GetPreferPlainText());
+ }
+
+ // This part is of type "message/rfc822" (probably a forwarded message)
+ nsIMAPBodypartMessage *message =
+ new nsIMAPBodypartMessage(partNum, parentPart, false,
+ bodyType, bodySubType, bodyID, bodyDescription,
+ bodyEncoding, partLength,
+ fServerConnection.GetPreferPlainText());
+
+ // there are three additional fields: envelope structure, bodystructure, and size in lines
+ // historical note: this code was originally in nsIMAPBodypartMessage::ParseIntoObjects()
+
+ // envelope (ignored)
+ if (*fNextToken == '(')
+ {
+ fNextToken++;
+ skip_to_close_paren();
+ }
+ else
+ isValid = false;
+
+ // bodystructure
+ if (isValid && ContinueParse())
+ {
+ if (*fNextToken != '(')
+ isValid = false;
+ else
+ {
+ char *bodyPartNum = PR_smprintf("%s.1", partNum);
+ if (bodyPartNum)
+ {
+ nsIMAPBodypart *body = bodystructure_part(bodyPartNum, message);
+ if (body)
+ message->SetBody(body);
+ else
+ isValid = false;
+ }
+ }
+ }
+
+ // ignore "size in text lines"
+
+ if (isValid && ContinueParse()) {
+ skip_to_close_paren();
+ return message;
+ }
+ delete message;
+ }
+
+ // parsing failed, just move to the end of the parentheses group
+ if (ContinueParse())
+ skip_to_close_paren();
+ return nullptr;
+}
+
+
+// RFC3501: body-type-mpart = 1*body SP media-subtype
+// [SP body-ext-mpart]
+nsIMAPBodypart *
+nsImapServerResponseParser::bodystructure_multipart(char *partNum, nsIMAPBodypart *parentPart)
+{
+ nsIMAPBodypartMultipart *multipart = new nsIMAPBodypartMultipart(partNum, parentPart);
+ bool isValid = multipart->GetIsValid();
+ // historical note: this code was originally in nsIMAPBodypartMultipart::ParseIntoObjects()
+ if (ContinueParse())
+ {
+ fNextToken++; // eat the first '('
+ // Parse list of children
+ int childCount = 0;
+ while (isValid && fNextToken[0] == '(' && ContinueParse())
+ {
+ childCount++;
+ char *childPartNum = NULL;
+ // note: the multipart constructor does some magic on partNumber
+ if (PL_strcmp(multipart->GetPartNumberString(), "0")) // not top-level
+ childPartNum = PR_smprintf("%s.%d", multipart->GetPartNumberString(), childCount);
+ else // top-level
+ childPartNum = PR_smprintf("%d", childCount);
+ if (!childPartNum)
+ isValid = false;
+ else
+ {
+ nsIMAPBodypart *child = bodystructure_part(childPartNum, multipart);
+ if (child)
+ multipart->AppendPart(child);
+ else
+ isValid = false;
+ }
+ }
+
+ // RFC3501: media-subtype = string
+ // (multipart subtype: mixed, alternative, etc.)
+ if (isValid && ContinueParse())
+ {
+ char *bodySubType = CreateNilString();
+ multipart->SetBodySubType(bodySubType);
+ if (ContinueParse())
+ AdvanceToNextToken();
+ }
+
+ // extension data:
+ // RFC3501: body-ext-mpart = body-fld-param [SP body-fld-dsp [SP body-fld-lang
+ // [SP body-fld-loc *(SP body-extension)]]]
+
+ // body parameter parenthesized list (optional data), includes boundary parameter
+ // RFC3501: body-fld-param = "(" string SP string *(SP string SP string) ")" / nil
+ char *boundaryData = nullptr;
+ if (isValid && ContinueParse() && *fNextToken == '(')
+ {
+ fNextToken++;
+ while (ContinueParse() && *fNextToken != ')')
+ {
+ char *attribute = CreateNilString();
+ if (ContinueParse())
+ AdvanceToNextToken();
+ if (ContinueParse() && !PL_strcasecmp(attribute, "BOUNDARY"))
+ {
+ char *boundary = CreateNilString();
+ if (boundary)
+ boundaryData = PR_smprintf("--%s", boundary);
+ PR_FREEIF(boundary);
+ }
+ else if (ContinueParse())
+ {
+ char *value = CreateNilString();
+ PR_FREEIF(value);
+ }
+ PR_FREEIF(attribute);
+ if (ContinueParse())
+ AdvanceToNextToken();
+ }
+ if (ContinueParse())
+ fNextToken++; // skip closing ')'
+ }
+ if (boundaryData)
+ multipart->SetBoundaryData(boundaryData);
+ else
+ isValid = false; // Actually, we should probably generate a boundary here.
+ }
+
+ // always move to closing ')', even if part was not successfully read
+ if (ContinueParse())
+ skip_to_close_paren();
+
+ if (isValid)
+ return multipart;
+ delete multipart;
+ return nullptr;
+}
+
+
+// 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
+// Only the STORAGE resource is considered. The current implementation is
+// slightly broken because it assumes that STORAGE is the first resource;
+// a reponse QUOTA (MESSAGE 5 100 STORAGE 10 512) would be ignored.
+void nsImapServerResponseParser::quota_data()
+{
+ if (!PL_strcasecmp(fNextToken, "QUOTAROOT"))
+ {
+ // ignore QUOTAROOT response
+ nsCString quotaroot;
+ AdvanceToNextToken();
+ while (ContinueParse() && !fAtEndOfLine)
+ {
+ quotaroot.Adopt(CreateAstring());
+ AdvanceToNextToken();
+ }
+ }
+ else if(!PL_strcasecmp(fNextToken, "QUOTA"))
+ {
+ uint32_t used, max;
+ char *parengroup;
+
+ AdvanceToNextToken();
+ if (ContinueParse())
+ {
+ nsCString quotaroot;
+ quotaroot.Adopt(CreateAstring());
+
+ if(ContinueParse() && !fAtEndOfLine)
+ {
+ AdvanceToNextToken();
+ if(fNextToken)
+ {
+ if(!PL_strcasecmp(fNextToken, "(STORAGE"))
+ {
+ parengroup = CreateParenGroup();
+ if(parengroup && (PR_sscanf(parengroup, "(STORAGE %lu %lu)", &used, &max) == 2) )
+ {
+ fServerConnection.UpdateFolderQuotaData(quotaroot, used, max);
+ skip_to_CRLF();
+ }
+ else
+ SetSyntaxError(true);
+
+ PR_Free(parengroup);
+ }
+ else
+ // Ignore other limits, we just check STORAGE for now
+ skip_to_CRLF();
+ }
+ else
+ SetSyntaxError(true);
+ }
+ else
+ HandleMemoryFailure();
+ }
+ }
+ else
+ SetSyntaxError(true);
+}
+
+void nsImapServerResponseParser::id_data()
+{
+ AdvanceToNextToken();
+ if (!PL_strcasecmp(fNextToken, "NIL"))
+ AdvanceToNextToken();
+ else
+ fServerIdResponse.Adopt(CreateParenGroup());
+ skip_to_CRLF();
+}
+
+bool nsImapServerResponseParser::GetFillingInShell()
+{
+ return (m_shell != nullptr);
+}
+
+bool nsImapServerResponseParser::GetDownloadingHeaders()
+{
+ return fDownloadingHeaders;
+}
+
+// Tells the server state parser to use a previously cached shell.
+void nsImapServerResponseParser::UseCachedShell(nsIMAPBodyShell *cachedShell)
+{
+ // We shouldn't already have another shell we're dealing with.
+ if (m_shell && cachedShell)
+ {
+ MOZ_LOG(IMAP, mozilla::LogLevel::Info, ("PARSER: Shell Collision"));
+ NS_ASSERTION(false, "shell collision");
+ }
+ m_shell = cachedShell;
+}
+
+
+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 proccessed 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()));
+
+ if (lastChunk)
+ MOZ_LOG(IMAP, mozilla::LogLevel::Debug,
+ ("PARSER: msg_fetch_literal() chunking=%s, requested=%d, receiving=%d",
+ chunk ? "true":"false", fServerConnection.GetCurFetchSize(),
+ numberOfCharsInThisChunk));
+
+ 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(0, 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::NumberOfUnseenMessages()
+{
+ return fNumberOfUnseenMessages;
+}
+
+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;
+}
+
+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;
+}
+
+
+nsImapMailboxSpec *nsImapServerResponseParser::CreateCurrentMailboxSpec(const char *mailboxName /* = nullptr */)
+{
+ nsImapMailboxSpec *returnSpec = new nsImapMailboxSpec;
+ if (!returnSpec)
+ {
+ HandleMemoryFailure();
+ return nullptr;
+ }
+ NS_ADDREF(returnSpec);
+ 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; // if mailboxName is null, we're doing a Status
+ returnSpec->mFolder_UIDVALIDITY = fFolderUIDValidity;
+ returnSpec->mHighestModSeq = fHighestModSeq;
+ returnSpec->mNumOfMessages = (mailboxName) ? fStatusExistingMessages : fNumberOfExistingMessages;
+ returnSpec->mNumOfUnseenMessages = (mailboxName) ? fStatusUnseenMessages : fNumberOfUnseenMessages;
+ returnSpec->mNumOfRecentMessages = (mailboxName) ? fStatusRecentMessages : fNumberOfRecentMessages;
+ returnSpec->mNextUID = fStatusNextUID;
+
+ returnSpec->mSupportedUserFlags = fSupportsUserDefinedFlags;
+
+ returnSpec->mBoxFlags = kNoFlags; // stub
+ returnSpec->mOnlineVerified = false; // we're fabricating this. The 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;
+
+}
+// Reset the flag state.
+void nsImapServerResponseParser::ResetFlagInfo()
+{
+ if (fFlagState)
+ fFlagState->Reset();
+}
+
+
+bool nsImapServerResponseParser::GetLastFetchChunkReceived()
+{
+ return fLastChunk;
+}
+
+void nsImapServerResponseParser::ClearLastFetchChunkReceived()
+{
+ fLastChunk = false;
+}
+
+void nsImapServerResponseParser::SetHostSessionList(nsIImapHostSessionList*
+ aHostSessionList)
+{
+ NS_IF_RELEASE (fHostSessionList);
+ fHostSessionList = aHostSessionList;
+ NS_IF_ADDREF (fHostSessionList);
+}
+
+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/mailnews/imap/src/nsImapServerResponseParser.h b/mailnews/imap/src/nsImapServerResponseParser.h
new file mode 100644
index 000000000..4a21de905
--- /dev/null
+++ b/mailnews/imap/src/nsImapServerResponseParser.h
@@ -0,0 +1,272 @@
+/* -*- 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 "nsIMAPHostSessionList.h"
+#include "nsImapSearchResults.h"
+#include "nsStringGlue.h"
+#include "MailNewsTypes.h"
+#include "nsTArray.h"
+#include "nsImapUtils.h"
+#include "nsAutoPtr.h"
+
+class nsIMAPNamespace;
+class nsIMAPNamespaceList;
+class nsIMAPBodyShell;
+class nsIMAPBodypart;
+class nsImapSearchResultIterator;
+class nsImapFlagAndUidState;
+class nsCString;
+
+#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);
+
+ enum eIMAPstate {
+ kNonAuthenticated,
+ kAuthenticated,
+ kFolderSelected
+ } ;
+
+ virtual eIMAPstate GetIMAPstate();
+ virtual bool WaitingForMoreClientInput() { return fWaitingForMoreClientInput; }
+ const char *GetSelectedMailboxName(); // can be NULL
+
+ // 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 NumberOfUnseenMessages();
+ int32_t FolderUID();
+ uint32_t CurrentResponseUID();
+ uint32_t HighestRecordedUID();
+ 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
+ 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();
+ virtual uint16_t SupportsUserFlags() { return fSupportsUserDefinedFlags; }
+ virtual uint16_t SettablePermanentFlags() { return fSettablePermanentFlags;}
+ void SetFlagState(nsIImapFlagAndUidState *state);
+ bool GetDownloadingHeaders();
+ bool GetFillingInShell();
+ void UseCachedShell(nsIMAPBodyShell *cachedShell);
+ void SetHostSessionList(nsIImapHostSessionList *aHostSession);
+ char *fAuthChallenge; // the challenge returned by the server in
+ //response to authenticate using CRAM-MD5 or NTLM
+ bool fCondStoreEnabled;
+ 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();
+ 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 bodystructure_data();
+ nsIMAPBodypart *bodystructure_part(char *partNum, nsIMAPBodypart *parentPart);
+ nsIMAPBodypart *bodystructure_leaf(char *partNum, nsIMAPBodypart *parentPart);
+ nsIMAPBodypart *bodystructure_multipart(char *partNum, nsIMAPBodypart *parentPart);
+ virtual void mime_data();
+ virtual void mime_part_data();
+ virtual void mime_header_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;
+ // overriden to do logging
+ virtual void SetSyntaxError(bool error, const char *msg = nullptr) override;
+
+private:
+ bool fCurrentCommandFailed;
+ 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;
+ imapMessageFlagsType fSavedFlagInfo;
+ nsTArray<nsCString> fCustomFlags;
+
+ uint16_t fSupportsUserDefinedFlags;
+ uint16_t fSettablePermanentFlags;
+
+ int32_t fFolderUIDValidity;
+ int32_t fNumberOfUnseenMessages;
+ 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;
+ uint32_t fStatusExistingMessages;
+
+ 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;
+
+ // points to the current body shell, if any
+ RefPtr<nsIMAPBodyShell> m_shell;
+
+ // The connection object
+ nsImapProtocol &fServerConnection;
+
+ nsIImapHostSessionList *fHostSessionList;
+ nsTArray<nsMsgKey> fCopyResponseKeyArray;
+};
+
+#endif
diff --git a/mailnews/imap/src/nsImapService.cpp b/mailnews/imap/src/nsImapService.cpp
new file mode 100644
index 000000000..60af2084a
--- /dev/null
+++ b/mailnews/imap/src/nsImapService.cpp
@@ -0,0 +1,3400 @@
+/* -*- 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 "nsMsgImapCID.h"
+
+#include "netCore.h"
+
+#include "nsIServiceManager.h"
+#include "nsIComponentManager.h"
+
+#include "nsIIMAPHostSessionList.h"
+#include "nsImapService.h"
+
+#include "nsImapUrl.h"
+#include "nsCOMPtr.h"
+#include "nsIMsgFolder.h"
+#include "nsIMsgImapMailFolder.h"
+#include "nsIImapIncomingServer.h"
+#include "nsIImapServerSink.h"
+#include "nsIImapMockChannel.h"
+#include "nsImapUtils.h"
+#include "nsIDocShell.h"
+#include "nsIDocShellLoadInfo.h"
+#include "nsIRDFService.h"
+#include "nsRDFCID.h"
+#include "nsIMsgStatusFeedback.h"
+#include "nsIPrefBranch.h"
+#include "nsIPrefService.h"
+#include "nsILoadGroup.h"
+#include "nsIMsgAccountManager.h"
+#include "nsMsgBaseCID.h"
+#include "nsMsgFolderFlags.h"
+#include "nsISubscribableServer.h"
+#include "nsIDirectoryService.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 "nsILoadContextInfo.h"
+#include "nsIStreamListenerTee.h"
+#include "nsNetCID.h"
+#include "nsMsgI18N.h"
+#include "nsIOutputStream.h"
+#include "nsIInputStream.h"
+#include "nsISeekableStream.h"
+#include "nsICopyMsgStreamListener.h"
+#include "nsIMsgParseMailMsgState.h"
+#include "nsMsgLocalCID.h"
+#include "nsIOutputStream.h"
+#include "nsIDocShell.h"
+#include "nsIDocShellLoadInfo.h"
+#include "nsIMessengerWindowService.h"
+#include "nsIWindowMediator.h"
+#include "nsIPrompt.h"
+#include "nsIWindowWatcher.h"
+#include "nsImapProtocol.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"
+
+#define PREF_MAIL_ROOT_IMAP "mail.root.imap" // old - for backward compatibility only
+#define PREF_MAIL_ROOT_IMAP_REL "mail.root.imap-rel"
+
+static NS_DEFINE_CID(kImapUrlCID, NS_IMAPURL_CID);
+static NS_DEFINE_CID(kCImapMockChannel, NS_IMAPMOCKCHANNEL_CID);
+
+static const char sequenceString[] = "SEQUENCE";
+static const char uidString[] = "UID";
+
+static bool gInitialized = false;
+static int32_t gMIMEOnDemandThreshold = 15000;
+static bool gMIMEOnDemand = false;
+
+NS_IMPL_ISUPPORTS(nsImapService,
+ nsIImapService,
+ nsIMsgMessageService,
+ nsIProtocolHandler,
+ nsIMsgProtocolInfo,
+ nsIMsgMessageFetchPartService,
+ nsIContentHandler)
+
+nsImapService::nsImapService()
+{
+ mPrintingOperation = false;
+ if (!gInitialized)
+ {
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ if (NS_SUCCEEDED(rv) && prefBranch)
+ {
+ prefBranch->GetBoolPref("mail.imap.mime_parts_on_demand", &gMIMEOnDemand);
+ prefBranch->GetIntPref("mail.imap.mime_parts_on_demand_threshold", &gMIMEOnDemandThreshold);
+ }
+
+ // 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 imap utf-7 - leave it that way
+ 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.Append("/select>");
+ urlSpec.Append(hierarchyDelimiter);
+ urlSpec.Append(folderName);
+ rv = mailNewsUrl->SetSpec(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 char *aMessageURI,
+ nsIURI **aURL,
+ nsIMsgWindow *aMsgWindow)
+{
+ nsAutoCString messageURI(aMessageURI);
+
+ if (messageURI.Find(NS_LITERAL_CSTRING("&type=application/x-message-display")) != 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.Append("fetch>UID>");
+ urlSpec.Append(hierarchyDelimiter);
+
+ nsAutoCString folderName;
+ GetFolderName(folder, folderName);
+ urlSpec.Append(folderName);
+ urlSpec.Append(">");
+ urlSpec.Append(msgKey);
+ rv = url->SetSpec(urlSpec);
+ imapUrl->QueryInterface(NS_GET_IID(nsIURI), (void **) aURL);
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP nsImapService::OpenAttachment(const char *aContentType,
+ const char *aFileName,
+ const char *aUrl,
+ const char *aMessageUri,
+ nsISupports *aDisplayConsumer,
+ nsIMsgWindow *aMsgWindow,
+ nsIUrlListener *aUrlListener)
+{
+ // okay this is a little tricky....we may have to fetch the mime part
+ // or it may already be downloaded for us....the only way i can tell to
+ // distinguish the two events is to search for ?section or ?part
+
+ nsAutoCString uri(aMessageUri);
+ nsAutoCString urlString(aUrl);
+ MsgReplaceSubstring(urlString, "/;section", "?section");
+
+ // more stuff i don't understand
+ int32_t sectionPos = urlString.Find("?section");
+ // if we have a section field then we must be dealing with a mime part we need to fetchf
+ if (sectionPos > 0)
+ {
+ uri.Append(Substring(urlString, sectionPos));
+ uri += "&type=";
+ uri += aContentType;
+ uri += "&filename=";
+ uri += aFileName;
+ }
+ else
+ {
+ // try to extract the specific part number out from the url string
+ const char *partStart = PL_strstr(aUrl, "part=");
+ if (!partStart)
+ return NS_ERROR_FAILURE;
+ nsDependentCString part(partStart);
+ uri += "?";
+ uri += Substring(part, 0, part.FindChar('&'));
+ uri += "&type=";
+ uri += aContentType;
+ uri += "&filename=";
+ uri += aFileName;
+ }
+
+ nsCOMPtr<nsIMsgFolder> folder;
+ nsAutoCString msgKey;
+ nsAutoCString uriMimePart;
+ nsAutoCString folderURI;
+ nsMsgKey key;
+
+ nsresult rv = DecomposeImapURI(uri, getter_AddRefs(folder), msgKey);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = nsParseImapMessageURI(uri.get(), folderURI, &key, getter_Copies(uriMimePart));
+ 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(uri, getter_AddRefs(imapUrl), folder, aUrlListener, urlSpec, hierarchyDelimiter);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ urlSpec.Append("/fetch>UID>");
+ urlSpec.Append(hierarchyDelimiter);
+
+ nsCString folderName;
+ GetFolderName(folder, folderName);
+ urlSpec.Append(folderName);
+ urlSpec.Append(">");
+ urlSpec.Append(msgKey);
+ urlSpec.Append(uriMimePart);
+
+ if (!uriMimePart.IsEmpty())
+ {
+ nsCOMPtr<nsIMsgMailNewsUrl> mailUrl (do_QueryInterface(imapUrl));
+ if (mailUrl)
+ {
+ rv = mailUrl->SetSpec(urlSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (aFileName)
+ mailUrl->SetFileName(nsDependentCString(aFileName));
+ }
+ rv = FetchMimePart(imapUrl, nsIImapUrl::nsImapOpenMimePart, folder, imapMessageSink,
+ nullptr, aDisplayConsumer, msgKey, uriMimePart);
+ }
+ } // if we got a message sink
+ } // if we parsed the message uri
+
+ return rv;
+}
+
+NS_IMETHODIMP nsImapService::FetchMimePart(nsIURI *aURI,
+ const char *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));
+
+ 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::DisplayMessage(const char *aMessageURI,
+ nsISupports *aDisplayConsumer,
+ nsIMsgWindow *aMsgWindow,
+ nsIUrlListener *aUrlListener,
+ const char *aCharsetOverride,
+ nsIURI **aURL)
+{
+ 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);
+ if (aURL)
+ NS_IF_ADDREF(*aURL = uri);
+ 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));
+
+ rv = NewChannel(uri, getter_AddRefs(aChannel));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsISupports> aCtxt = do_QueryInterface(uri);
+ // now try to open the channel passing in our display consumer as the listener
+ rv = aChannel->AsyncOpen(aStreamListener, aCtxt);
+ 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<nsIURI> url = do_QueryInterface(imapUrl);
+
+ rv = AddImapFetchToUrl(url, folder, msgKey + mimePart, EmptyCString());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return FetchMimePart(imapUrl, nsIImapUrl::nsImapMsgFetch, folder, imapMessageSink,
+ aURL, aDisplayConsumer, msgKey, mimePart);
+ }
+
+ nsCOMPtr<nsIMsgMailNewsUrl> msgurl (do_QueryInterface(imapUrl));
+ nsCOMPtr<nsIMsgI18NUrl> i18nurl (do_QueryInterface(imapUrl));
+ i18nurl->SetCharsetOverRide(aCharsetOverride);
+
+ uint32_t messageSize;
+ bool useMimePartsOnDemand = gMIMEOnDemand;
+ bool shouldStoreMsgOffline = false;
+ bool hasMsgOffline = false;
+
+ nsCOMPtr<nsIMsgIncomingServer> aMsgIncomingServer;
+
+ if (imapMessageSink)
+ imapMessageSink->GetMessageSizeFromDB(msgKey.get(), &messageSize);
+
+ msgurl->SetMsgWindow(aMsgWindow);
+
+ rv = msgurl->GetServer(getter_AddRefs(aMsgIncomingServer));
+
+ if (NS_SUCCEEDED(rv) && aMsgIncomingServer)
+ {
+ nsCOMPtr<nsIImapIncomingServer> aImapServer(do_QueryInterface(aMsgIncomingServer, &rv));
+ if (NS_SUCCEEDED(rv) && aImapServer)
+ aImapServer->GetMimePartsOnDemand(&useMimePartsOnDemand);
+ }
+
+ nsAutoCString uriStr(aMessageURI);
+ int32_t keySeparator = uriStr.RFindChar('#');
+ if(keySeparator != -1)
+ {
+ int32_t keyEndSeparator = MsgFindCharInSet(uriStr, "/?&", keySeparator);
+ int32_t mpodFetchPos = MsgFind(uriStr, "fetchCompleteMessage=true", false, keyEndSeparator);
+ if (mpodFetchPos != -1)
+ useMimePartsOnDemand = false;
+ }
+
+ if (folder)
+ {
+ folder->ShouldStoreMsgOffline(key, &shouldStoreMsgOffline);
+ folder->HasMsgOffline(key, &hasMsgOffline);
+ }
+ imapUrl->SetStoreResultsOffline(shouldStoreMsgOffline);
+ imapUrl->SetFetchPartsOnDemand(
+ useMimePartsOnDemand && messageSize >= (uint32_t) gMIMEOnDemandThreshold);
+
+ 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 tha \Seen flag on the msg,
+ // but a FETCH (BODY.PEEK[]) won't.
+ bool forcePeek = false;
+ if (NS_SUCCEEDED(rv) && prefBranch)
+ {
+ 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));
+ }
+
+ rv = FetchMessage(imapUrl, forcePeek ? nsIImapUrl::nsImapMsgFetchPeek : nsIImapUrl::nsImapMsgFetch,
+ folder, imapMessageSink, aMsgWindow, aDisplayConsumer, msgKey, false,
+ (mPrintingOperation) ? NS_LITERAL_CSTRING("print") : NS_LITERAL_CSTRING(""), aURL);
+ }
+ }
+ 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(nsCString(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);
+
+ // rhp: If we are displaying this message for the purpose of printing, we
+ // need to append the header=print option.
+ //
+ if (mPrintingOperation)
+ urlSpec.Append("?header=print");
+
+ rv = url->SetSpec(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)
+ {
+ nsCOMPtr<nsIDocShellLoadInfo> loadInfo;
+ // 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.
+ if (aImapAction == nsImapUrl::nsImapOpenMimePart)
+ {
+ docShell->CreateLoadInfo(getter_AddRefs(loadInfo));
+ loadInfo->SetLoadType(nsIDocShellLoadInfo::loadLink);
+ }
+
+ rv = docShell->LoadURI(url, loadInfo, nsIWebNavigation::LOAD_FLAGS_NONE, 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));
+
+ rv = NewChannel(url, 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);
+
+ nsCOMPtr<nsISupports> aCtxt = do_QueryInterface(url);
+ // now try to open the channel passing in our display consumer as the listener
+ rv = aChannel->AsyncOpen(aStreamListener, aCtxt);
+ }
+ 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;
+}
+
+//
+// rhp: Right now, this is the same as simple DisplayMessage, but it will change
+// to support print rendering.
+//
+NS_IMETHODIMP nsImapService::DisplayMessageForPrinting(const char *aMessageURI,
+ nsISupports *aDisplayConsumer,
+ nsIMsgWindow *aMsgWindow,
+ nsIUrlListener *aUrlListener,
+ nsIURI **aURL)
+{
+ mPrintingOperation = true;
+ nsresult rv = DisplayMessage(aMessageURI, aDisplayConsumer, aMsgWindow, aUrlListener, nullptr, aURL);
+ mPrintingOperation = false;
+ return rv;
+}
+
+NS_IMETHODIMP nsImapService::CopyMessage(const char *aSrcMailboxURI,
+ nsIStreamListener *aMailboxCopy,
+ bool moveMessage,
+ nsIUrlListener *aUrlListener,
+ nsIMsgWindow *aMsgWindow,
+ nsIURI **aURL)
+{
+ NS_ENSURE_ARG_POINTER(aSrcMailboxURI);
+ NS_ENSURE_ARG_POINTER(aMailboxCopy);
+
+ nsresult rv;
+ nsCOMPtr<nsISupports> streamSupport;
+ streamSupport = do_QueryInterface(aMailboxCopy, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgFolder> folder;
+ nsAutoCString msgKey;
+ rv = DecomposeImapURI(nsDependentCString(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(nsDependentCString(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;
+ rv = FetchMessage(imapUrl,imapAction, folder, imapMessageSink,aMsgWindow,
+ streamSupport, msgKey, false, EmptyCString(), aURL);
+ } // if we got an imap message sink
+ } // if we decomposed the imap message
+ return rv;
+}
+
+NS_IMETHODIMP nsImapService::CopyMessages(uint32_t aNumKeys,
+ nsMsgKey*aKeys,
+ nsIMsgFolder *srcFolder,
+ nsIStreamListener *aMailboxCopy,
+ bool moveMessage,
+ nsIUrlListener *aUrlListener,
+ nsIMsgWindow *aMsgWindow,
+ nsIURI **aURL)
+{
+ NS_ENSURE_ARG_POINTER(aMailboxCopy);
+ NS_ENSURE_ARG_POINTER(aKeys);
+
+ nsresult rv;
+ nsCOMPtr<nsISupports> streamSupport = do_QueryInterface(aMailboxCopy, &rv);
+ if (!streamSupport || NS_FAILED(rv))
+ return rv;
+
+ nsCOMPtr<nsIMsgFolder> folder = srcFolder;
+ if (NS_SUCCEEDED(rv))
+ {
+ 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;
+ AllocateImapUidString(aKeys, aNumKeys, 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,
+ streamSupport, messageIds, false, EmptyCString(), aURL);
+ // ### end of copy operation should know how to do the delete.if this is a move
+
+ } // if we got an imap message sink
+ } // if we decomposed the imap message
+ return rv;
+}
+
+NS_IMETHODIMP nsImapService::Search(nsIMsgSearchSession *aSearchSession,
+ nsIMsgWindow *aMsgWindow,
+ nsIMsgFolder *aMsgFolder,
+ const char *aSearchUri)
+{
+ NS_ENSURE_ARG_POINTER(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.Append("/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(nsDependentCString(aSearchUri), nsINetUtil::ESCAPE_XALPHAS, escapedSearchUri);
+ urlSpec.Append(escapedSearchUri);
+ rv = mailNewsUrl->SetSpec(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(PromiseFlatCString(aMessageURI).get(),
+ folderURI, aMsgKey, nullptr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr <nsIRDFService> rdf = do_GetService("@mozilla.org/rdf/rdf-service;1",&rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIRDFResource> res;
+ rv = rdf->GetResource(folderURI, getter_AddRefs(res));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgFolder> msgFolder = do_QueryInterface(res);
+ NS_ENSURE_TRUE(msgFolder, NS_ERROR_FAILURE);
+
+ msgFolder.swap(*aFolder);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapService::SaveMessageToDisk(const char *aMessageURI,
+ nsIFile *aFile,
+ bool aAddDummyEnvelope,
+ nsIUrlListener *aUrlListener,
+ nsIURI **aURL,
+ bool canonicalLineEnding,
+ nsIMsgWindow *aMsgWindow)
+{
+ nsCOMPtr<nsIMsgFolder> folder;
+ nsCOMPtr<nsIImapUrl> imapUrl;
+ nsAutoCString msgKey;
+
+ nsresult rv = DecomposeImapURI(nsDependentCString(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(nsDependentCString(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, EmptyCString(), 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(nsIURI *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.Append("fetch>UID>");
+ urlSpec.Append(hierarchyDelimiter);
+
+ nsAutoCString folderName;
+ GetFolderName(aImapMailFolder, folderName);
+ urlSpec.Append(folderName);
+
+ urlSpec.Append(">");
+ urlSpec.Append(aMessageIdentifierList);
+
+ if (!aAdditionalHeader.IsEmpty())
+ {
+ urlSpec.Append("?header=");
+ urlSpec.Append(aAdditionalHeader);
+ }
+
+ return aUrl->SetSpec(urlSpec);
+}
+
+NS_IMETHODIMP nsImapService::FetchMessage(nsIImapUrl *aImapUrl,
+ nsImapAction aImapAction,
+ nsIMsgFolder *aImapMailFolder,
+ nsIImapMessageSink *aImapMessage,
+ nsIMsgWindow *aMsgWindow,
+ nsISupports *aDisplayConsumer,
+ const nsACString &messageIdentifierList,
+ bool aConvertDataToText,
+ const nsACString &aAdditionalHeader,
+ nsIURI **aURL)
+{
+ NS_ENSURE_ARG_POINTER(aImapUrl);
+ NS_ENSURE_ARG_POINTER(aImapMailFolder);
+ NS_ENSURE_ARG_POINTER(aImapMessage);
+
+ nsresult rv;
+ nsCOMPtr<nsIURI> url = do_QueryInterface(aImapUrl);
+
+ rv = AddImapFetchToUrl(url, aImapMailFolder, messageIdentifierList, aAdditionalHeader);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (WeAreOffline())
+ {
+ bool msgIsInCache = false;
+ nsCOMPtr<nsIMsgMailNewsUrl> msgUrl(do_QueryInterface(aImapUrl));
+ msgUrl->GetMsgIsInLocalCache(&msgIsInCache);
+ if (!msgIsInCache)
+ IsMsgInMemCache(url, aImapMailFolder, &msgIsInCache);
+
+ // Display the "offline" message if we didn't find it in the memory cache either
+ if (!msgIsInCache)
+ {
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = aImapMailFolder->GetServer(getter_AddRefs(server));
+ if (server && aDisplayConsumer)
+ rv = server->DisplayOfflineMsg(aMsgWindow);
+ return rv;
+ }
+ }
+
+ if (aURL)
+ NS_IF_ADDREF(*aURL = url);
+
+ 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");
+ rv = docShell->LoadURI(url, nullptr, nsIWebNavigation::LOAD_FLAGS_NONE, 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));
+
+ rv = NewChannel(url, 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.
+ }
+
+ nsCOMPtr<nsISupports> aCtxt = do_QueryInterface(url);
+ // now try to open the channel passing in our display consumer as the listener
+ rv = channel->AsyncOpen(streamListener, aCtxt);
+ }
+ 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 char *aMessageURI,
+ nsISupports *aConsumer,
+ nsIMsgWindow *aMsgWindow,
+ nsIUrlListener *aUrlListener,
+ bool aConvertData,
+ const nsACString &aAdditionalHeader,
+ bool aLocalOnly,
+ nsIURI **aURL)
+{
+ NS_ENSURE_ARG_POINTER(aMessageURI);
+
+ nsCOMPtr<nsIMsgFolder> folder;
+ nsAutoCString msgKey;
+ nsAutoCString mimePart;
+ nsAutoCString folderURI;
+ nsMsgKey key;
+
+ nsresult rv = DecomposeImapURI(nsDependentCString(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));
+ 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(nsDependentCString(aMessageURI), getter_AddRefs(imapUrl),
+ folder, aUrlListener, urlSpec, hierarchyDelimiter);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIMsgMailNewsUrl> msgurl (do_QueryInterface(imapUrl));
+ nsCOMPtr<nsIURI> url(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...
+
+ uint32_t messageSize = 0;
+ imapMessageSink->GetMessageSizeFromDB(msgKey.get(), &messageSize);
+ nsAutoCString additionalHeader(aAdditionalHeader);
+ bool fetchOnDemand =
+ additionalHeader.Find("&fetchCompleteMessage=false") != kNotFound &&
+ messageSize > (uint32_t) gMIMEOnDemandThreshold;
+ imapUrl->SetFetchPartsOnDemand(fetchOnDemand);
+
+ // We need to add the fetch command here for the cache lookup to behave correctly
+ rv = AddImapFetchToUrl(url, folder, msgKey, additionalHeader);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgIncomingServer> aMsgIncomingServer;
+
+ msgurl->SetMsgWindow(aMsgWindow);
+ rv = msgurl->GetServer(getter_AddRefs(aMsgIncomingServer));
+
+ // Try to check if the message is offline
+ bool hasMsgOffline = false;
+ folder->HasMsgOffline(key, &hasMsgOffline);
+ msgurl->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(url, 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 char *aMessageURI,
+ nsIStreamListener *aConsumer,
+ nsIUrlListener *aUrlListener,
+ bool aLocalOnly,
+ nsIURI **aURL)
+{
+ NS_ENSURE_ARG_POINTER(aMessageURI);
+ NS_ENSURE_ARG_POINTER(aConsumer);
+ nsCOMPtr<nsIMsgFolder> folder;
+ nsAutoCString msgKey;
+ nsAutoCString folderURI;
+ nsCString mimePart;
+ nsMsgKey key;
+
+ nsresult rv = DecomposeImapURI(nsDependentCString(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));
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr<nsIInputStream> inputStream;
+ bool hasMsgOffline = false;
+ folder->HasMsgOffline(key, &hasMsgOffline);
+ if (hasMsgOffline)
+ {
+ int64_t messageOffset;
+ uint32_t messageSize;
+ folder->GetOfflineFileStream(key, &messageOffset, &messageSize, getter_AddRefs(inputStream));
+ if (inputStream)
+ return MsgStreamMsgHeaders(inputStream, aConsumer);
+ }
+ }
+
+ if (aLocalOnly)
+ return NS_ERROR_FAILURE;
+ return rv;
+}
+
+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;
+ aUrl->Clone(getter_AddRefs(newUri));
+ newUri->SetSpec(urlSpec);
+ 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(PromiseFlatCString(aImapURI).get());
+
+ 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->SetSpec(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<nsIURI> uri = do_QueryInterface(imapUrl);
+
+ rv = imapUrl->SetImapAction(nsIImapUrl::nsImapMsgFetch);
+ rv = SetImapUrlSink(aImapMailFolder, imapUrl);
+
+ if (NS_SUCCEEDED(rv))
+ {
+ urlSpec.Append("/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 = uri->SetSpec(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<nsIURI> uri = do_QueryInterface(imapUrl);
+
+ urlSpec.Append("/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 = uri->SetSpec(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<nsIURI> uri = do_QueryInterface(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 = uri->SetSpec(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<nsIURI> uri = do_QueryInterface(imapUrl);
+
+ nsCOMPtr<nsIMsgMailNewsUrl> mailNewsUrl = do_QueryInterface(imapUrl);
+ mailNewsUrl->SetSuppressErrorMsgs(true);
+ mailNewsUrl->SetMsgWindow(aMsgWindow);
+ rv = SetImapUrlSink(aFolder, imapUrl);
+ urlSpec.Append("/verifyLogon");
+ rv = uri->SetSpec(urlSpec);
+ if (NS_SUCCEEDED(rv))
+ rv = GetImapConnectionAndLoadUrl(imapUrl, nullptr, nullptr);
+ if (aURL)
+ uri.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,
+ nsIURI **aURL)
+{
+ NS_ENSURE_ARG_POINTER(aImapMailFolder);
+
+ return FolderCommand(aImapMailFolder, aUrlListener,
+ "/Expunge>", nsIImapUrl::nsImapExpungeFolder, aMsgWindow, aURL);
+}
+
+/* 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<nsIURI> uri = do_QueryInterface(imapUrl);
+ if (NS_SUCCEEDED(rv))
+ {
+ urlSpec.Append("/Biff>");
+ urlSpec.Append(hierarchyDelimiter);
+
+ nsCString folderName;
+ GetFolderName(aImapMailFolder, folderName);
+ urlSpec.Append(folderName);
+ urlSpec.Append(">");
+ urlSpec.AppendInt(uidHighWater);
+ rv = uri->SetSpec(urlSpec);
+ if (NS_SUCCEEDED(rv))
+ rv = GetImapConnectionAndLoadUrl(imapUrl, nullptr, aURL);
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapService::DeleteFolder(nsIMsgFolder *aImapMailFolder,
+ nsIUrlListener *aUrlListener,
+ nsIMsgWindow *aMsgWindow,
+ nsIURI **aURL)
+{
+ 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, aURL);
+}
+
+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<nsIURI> uri = do_QueryInterface(imapUrl);
+
+ urlSpec.Append("/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 = uri->SetSpec(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,
+ nsIURI **aURL)
+{
+ NS_ENSURE_ARG_POINTER(aImapMailFolder);
+
+ return FolderCommand(aImapMailFolder, aUrlListener,
+ "/deleteallmsgs>", nsIImapUrl::nsImapSelectNoopFolder, nullptr, aURL);
+}
+
+NS_IMETHODIMP nsImapService::AddMessageFlags(nsIMsgFolder *aImapMailFolder,
+ nsIUrlListener *aUrlListener,
+ nsIURI **aURL,
+ const nsACString &messageIdentifierList,
+ imapMessageFlagsType flags,
+ bool messageIdsAreUID)
+{
+ NS_ENSURE_ARG_POINTER(aImapMailFolder);
+
+ return DiddleFlags(aImapMailFolder, aUrlListener, aURL, messageIdentifierList,
+ "addmsgflags", flags, messageIdsAreUID);
+}
+
+NS_IMETHODIMP nsImapService::SubtractMessageFlags(nsIMsgFolder *aImapMailFolder,
+ nsIUrlListener *aUrlListener,
+ nsIURI **aURL,
+ const nsACString &messageIdentifierList,
+ imapMessageFlagsType flags,
+ bool messageIdsAreUID)
+{
+ NS_ENSURE_ARG_POINTER(aImapMailFolder);
+
+ return DiddleFlags(aImapMailFolder, aUrlListener, aURL, 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<nsIURI> uri = 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 = uri->SetSpec(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,
+ nsIURI **aURL)
+{
+ 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<nsIURI> uri = do_QueryInterface(imapUrl);
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsurl = do_QueryInterface(imapUrl);
+ if (mailnewsurl)
+ mailnewsurl->SetMsgWindow(aMsgWindow);
+ urlSpec.Append("/discoverallboxes");
+ nsCOMPtr <nsIURI> url = do_QueryInterface(imapUrl, &rv);
+ rv = uri->SetSpec(urlSpec);
+ if (NS_SUCCEEDED(rv))
+ rv = GetImapConnectionAndLoadUrl(imapUrl, nullptr, aURL);
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapService::DiscoverAllAndSubscribedFolders(nsIMsgFolder *aImapMailFolder,
+ nsIUrlListener *aUrlListener,
+ nsIURI **aURL)
+{
+ 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<nsIURI> uri = do_QueryInterface(aImapUrl);
+ urlSpec.Append("/discoverallandsubscribedboxes");
+ rv = uri->SetSpec(urlSpec);
+ if (NS_SUCCEEDED(rv))
+ rv = GetImapConnectionAndLoadUrl(aImapUrl, nullptr, aURL);
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapService::DiscoverChildren(nsIMsgFolder *aImapMailFolder,
+ nsIUrlListener *aUrlListener,
+ const nsACString &folderPath,
+ nsIURI **aURL)
+{
+ 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<nsIURI> uri = do_QueryInterface(aImapUrl);
+ urlSpec.Append("/discoverchildren>");
+ urlSpec.Append(hierarchyDelimiter);
+ urlSpec.Append(folderPath);
+ rv = uri->SetSpec(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, aURL);
+ }
+ 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> msgurl (do_QueryInterface(imapUrl));
+
+ msgurl->SetMsgWindow(aMsgWindow);
+ nsCOMPtr<nsIURI> uri = do_QueryInterface(imapUrl);
+
+ if (isMove)
+ urlSpec.Append("/onlinemove>");
+ else
+ urlSpec.Append("/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 = uri->SetSpec(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;
+ rv = destDB->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.get());
+ nsCOMPtr <nsIOutputStream> offlineStore;
+ 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(offlineStore));
+
+ if (NS_SUCCEEDED(rv) && offlineStore)
+ {
+ int64_t curOfflineStorePos = 0;
+ nsCOMPtr <nsISeekableStream> seekable = do_QueryInterface(offlineStore);
+ if (seekable)
+ seekable->Tell(&curOfflineStorePos);
+ else
+ {
+ NS_ERROR("needs to be a random store!");
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr <nsIInputStream> inputStream;
+ nsCOMPtr <nsIMsgParseMailMsgState> msgParser = do_CreateInstance(NS_PARSEMAILMSGSTATE_CONTRACTID, &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 = offlineStore->Write(newLine, numBytesInLine, &bytesWritten);
+ NS_Free(newLine);
+ }
+ } while (newLine);
+ msgParser->FinishHeader();
+
+ nsCOMPtr<nsIMsgDBHdr> fakeHdr;
+ msgParser->GetNewMsgHdr(getter_AddRefs(fakeHdr));
+ if (fakeHdr)
+ {
+ if (NS_SUCCEEDED(rv) && fakeHdr)
+ {
+ uint32_t resultFlags;
+ fakeHdr->SetMessageOffset(curOfflineStorePos);
+ fakeHdr->OrFlags(nsMsgMessageFlags::Offline | nsMsgMessageFlags::Read, &resultFlags);
+ fakeHdr->SetOfflineMessageSize(fileSize);
+ destDB->AddNewHdrToDB(fakeHdr, true /* notify */);
+ aDstFolder->SetFlag(nsMsgFolderFlags::OfflineEvents);
+ if (msgStore)
+ msgStore->FinishNewMessage(offlineStore, fakeHdr);
+ }
+ }
+ // tell the listener we're done.
+ inputStream->Close();
+ inputStream = nullptr;
+ aListener->OnStopRunningUrl(aUrl, NS_OK);
+ }
+ offlineStore->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,
+ nsIURI **aURL,
+ 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<nsIURI> uri = do_QueryInterface(imapUrl);
+
+ if (inSelectedState)
+ urlSpec.Append("/appenddraftfromfile>");
+ else
+ urlSpec.Append("/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 = uri->SetSpec(urlSpec);
+ if (WeAreOffline())
+ {
+ // handle offline append to drafts or templates folder here.
+ return OfflineAppendFromFile(aFile, uri, aDstFolder, messageId, inSelectedState, aListener, aURL, aCopyState);
+ }
+ if (NS_SUCCEEDED(rv))
+ rv = GetImapConnectionAndLoadUrl(imapUrl, nullptr, aURL);
+ }
+ 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)
+ {
+ nsCOMPtr<nsIURI> msgUrlUri = do_QueryInterface(msgUrl);
+ msgUrlUri.swap(*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,
+ nsIURI **url)
+{
+ 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<nsIURI> uri = do_QueryInterface(imapUrl);
+ GetFolderName(srcFolder, folderName);
+ urlSpec.Append("/movefolderhierarchy>");
+ urlSpec.Append(hierarchyDelimiter);
+ urlSpec.Append(folderName);
+ urlSpec.Append('>');
+ GetFolderName(dstFolder, folderName);
+ if (!folderName.IsEmpty())
+ {
+ urlSpec.Append(hierarchyDelimiter);
+ urlSpec.Append(folderName);
+ }
+ rv = uri->SetSpec(urlSpec);
+ if (NS_SUCCEEDED(rv))
+ {
+ GetFolderName(srcFolder, folderName);
+ rv = GetImapConnectionAndLoadUrl(imapUrl, nullptr, url);
+ }
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapService::RenameLeaf(nsIMsgFolder *srcFolder,
+ const nsAString &newLeafName,
+ nsIUrlListener *urlListener,
+ nsIMsgWindow *msgWindow,
+ nsIURI **url)
+{
+ 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<nsIURI> uri = do_QueryInterface(imapUrl);
+ nsCOMPtr<nsIMsgMailNewsUrl> mailNewsUrl = do_QueryInterface(imapUrl);
+ if (mailNewsUrl)
+ mailNewsUrl->SetMsgWindow(msgWindow);
+ nsCString folderName;
+ GetFolderName(srcFolder, folderName);
+ urlSpec.Append("/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;
+ CopyUTF16toMUTF7(PromiseFlatString(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 = uri->SetSpec(urlSpec);
+ if (NS_SUCCEEDED(rv))
+ rv = GetImapConnectionAndLoadUrl(imapUrl, nullptr, url);
+ } // if (NS_SUCCEEDED(rv))
+ } // if (NS_SUCCEEDED(rv) && imapUrl)
+ 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<nsIURI> uri = do_QueryInterface(imapUrl);
+
+ nsCString folderName;
+ GetFolderName(parent, folderName);
+ urlSpec.Append("/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;
+ rv = CopyUTF16toMUTF7(PromiseFlatString(newFolderName), utfNewName);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCString escapedFolderName;
+ MsgEscapeString(utfNewName, nsINetUtil::ESCAPE_URL_PATH, escapedFolderName);
+ urlSpec.Append(escapedFolderName);
+
+ rv = uri->SetSpec(urlSpec);
+ if (NS_SUCCEEDED(rv))
+ rv = GetImapConnectionAndLoadUrl(imapUrl, nullptr, url);
+ } // if (NS_SUCCEEDED(rv))
+ } // if (NS_SUCCEEDED(rv) && imapUrl)
+ return rv;
+}
+
+NS_IMETHODIMP nsImapService::EnsureFolderExists(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<nsIURI> uri = do_QueryInterface(imapUrl);
+
+ nsCString folderName;
+ GetFolderName(parent, folderName);
+ urlSpec.Append("/ensureExists>");
+ urlSpec.Append(hierarchyDelimiter);
+ if (!folderName.IsEmpty())
+ {
+ urlSpec.Append(folderName);
+ urlSpec.Append(hierarchyDelimiter);
+ }
+ nsAutoCString utfNewName;
+ CopyUTF16toMUTF7(PromiseFlatString(newFolderName), utfNewName);
+ nsCString escapedFolderName;
+ MsgEscapeString(utfNewName, nsINetUtil::ESCAPE_URL_PATH, escapedFolderName);
+ urlSpec.Append(escapedFolderName);
+
+ rv = uri->SetSpec(urlSpec);
+ if (NS_SUCCEEDED(rv))
+ rv = GetImapConnectionAndLoadUrl(imapUrl, nullptr, url);
+ } // if (NS_SUCCEEDED(rv))
+ } // if (NS_SUCCEEDED(rv) && imapUrl)
+ return rv;
+}
+
+NS_IMETHODIMP nsImapService::ListFolder(nsIMsgFolder *aImapMailFolder,
+ nsIUrlListener *aUrlListener,
+ nsIURI **aURL)
+{
+ NS_ENSURE_ARG_POINTER(aImapMailFolder);
+
+ return FolderCommand(aImapMailFolder, aUrlListener,
+ "/listfolder>", nsIImapUrl::nsImapListFolder, nullptr, aURL);
+}
+
+NS_IMETHODIMP nsImapService::GetScheme(nsACString &aScheme)
+{
+ aScheme.Assign("imap");
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapService::GetDefaultPort(int32_t *aDefaultPort)
+{
+ NS_ENSURE_ARG_POINTER(aDefaultPort);
+ *aDefaultPort = nsIImapUrl::DEFAULT_IMAP_PORT;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapService::GetProtocolFlags(uint32_t *result)
+{
+ *result = URI_STD | URI_FORBIDS_AUTOMATIC_DOCUMENT_REPLACEMENT |
+ URI_DANGEROUS_TO_LOAD | ALLOWS_PROXY | URI_FORBIDS_COOKIE_ACCESS
+#ifdef IS_ORIGIN_IS_FULL_SPEC_DEFINED
+ | ORIGIN_IS_FULL_SPEC
+#endif
+ ;
+ 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)
+{
+ nsresult rv = NS_OK;
+
+ // Return Secure IMAP Port if secure option chosen i.e., if isSecure is TRUE
+ if (isSecure)
+ *aDefaultPort = nsIImapUrl::DEFAULT_IMAPS_PORT;
+ else
+ rv = GetDefaultPort(aDefaultPort);
+
+ return rv;
+}
+
+// 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(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = accountManager->FindServerByURI(mailnewsUrl, false, 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;
+ nsCOMPtr<nsIURL> url = do_CreateInstance(NS_STANDARDURL_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mailnewsUrl->GetSpec(turl);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = url->SetSpec(turl);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ url->SetUserPass(EmptyCString());
+ rv = accountManager->FindServerByURI(url, false, 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;
+}
+
+NS_IMETHODIMP 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->SetSpec(newSpec);
+ }
+ else
+ {
+ rv = mailnewsUrl->SetSpec(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));
+ if (rootFolder && !folderName.IsEmpty())
+ {
+ 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);
+
+ nsCOMPtr<nsIMsgFolder> msgFolder = do_QueryInterface(folder);
+ (void) SetImapUrlSink(msgFolder, aImapUrl);
+
+ nsCString messageIdString;
+ aImapUrl->GetListOfMessageIds(messageIdString);
+ if (!messageIdString.IsEmpty())
+ {
+ bool useLocalCache = false;
+ msgFolder->HasMsgOffline(strtoul(messageIdString.get(), nullptr, 10),
+ &useLocalCache);
+ mailnewsUrl->SetMsgIsInLocalCache(useLocalCache);
+ }
+ }
+ }
+
+ // if we are fetching a part, be sure to enable fetch parts on demand
+ bool mimePartSelectorDetected = false;
+ aImapUrl->GetMimePartSelectorDetected(&mimePartSelectorDetected);
+ if (mimePartSelectorDetected)
+ aImapUrl->SetFetchPartsOnDemand(true);
+
+ // we got an imap url, so be sure to return it...
+ nsCOMPtr<nsIURI> imapUri = do_QueryInterface(aImapUrl);
+
+ imapUri.swap(*aRetVal);
+
+ return rv;
+}
+
+NS_IMETHODIMP nsImapService::NewChannel(nsIURI *aURI, nsIChannel **aRetVal)
+{
+ return NewChannel2(aURI, nullptr, aRetVal);
+}
+
+NS_IMETHODIMP nsImapService::NewChannel2(nsIURI *aURI,
+ nsILoadInfo* aLoadInfo,
+ nsIChannel **aRetVal)
+{
+ NS_ENSURE_ARG_POINTER(aURI);
+ NS_ENSURE_ARG_POINTER(aRetVal);
+ *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);
+
+ 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);
+ }
+ }
+ imapUrl->SetMockChannel(channel); // the imap url holds a weak reference so we can pass the channel into the imap protocol when we actually run the url
+
+ bool externalLinkUrl;
+ imapUrl->GetExternalLinkUrl(&externalLinkUrl);
+ 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> aFolder;
+ // 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));
+ aFolder = do_QueryInterface(subFolder);
+ }
+ nsCOMPtr <nsIMsgFolder> parent;
+ if (aFolder)
+ aFolder->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));
+ aFolder = do_QueryInterface(subFolder);
+ if (aFolder)
+ aFolder->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 from mod-utf7 to unicode
+ nsAutoString unescapedName;
+ if (NS_FAILED(CopyMUTF7toUTF16(fullFolderName, unescapedName)))
+ CopyASCIItoUTF16(fullFolderName, unescapedName);
+ const char16_t *formatStrings[1] = { unescapedName.get() };
+
+ rv = bundle->FormatStringFromName(
+ u"imapSubscribePrompt",
+ formatStrings, 1, getter_Copies(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 :-(
+ // It's already in mod-utf7.
+ nsAutoString unicodeName;
+ CopyASCIItoUTF16(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(NS_MSGMAILSESSION_CONTRACTID, &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;
+ nsCOMPtr<nsIImapServerSink> serverSink;
+
+ mailnewsUrl->GetFolder(getter_AddRefs(imapFolder));
+ imapUrl->GetImapServerSink(getter_AddRefs(serverSink));
+ // need to see if this is a link click - one way is to check if the url is set up correctly
+ // if not, it's probably a url click. We need a better way of doing this.
+ if (!imapFolder)
+ {
+ nsCOMPtr<nsIMsgMailSession> mailSession = do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr <nsIMsgWindow> msgWindow;
+ rv = mailSession->GetTopmostMsgWindow(getter_AddRefs(msgWindow));
+ if (NS_SUCCEEDED(rv) && msgWindow)
+ {
+ nsCString uri;
+ rootFolder->GetURI(uri);
+ uri.Append('/');
+ uri.Append(fullFolderName);
+ nsCOMPtr<nsIMsgWindowCommands> windowCommands;
+ msgWindow->GetWindowCommands(getter_AddRefs(windowCommands));
+ if (windowCommands)
+ windowCommands->SelectFolder(uri);
+ // error out this channel, so it'll stop trying to run the url.
+ *aRetVal = nullptr;
+ rv = NS_ERROR_FAILURE;
+ }
+ else
+ {
+ // make sure the imap action is selectFolder, so the content type
+ // will be x-application-imapfolder, so ::HandleContent will
+ // know to open a new 3 pane window.
+ imapUrl->SetImapAction(nsIImapUrl::nsImapSelectFolder);
+ }
+ }
+ }
+ }
+ if (NS_SUCCEEDED(rv))
+ NS_IF_ADDREF(*aRetVal = channel);
+ 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.swap(*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.Append("INBOX");
+ else
+ changedStr.Append(tokenStr);
+
+ if (slashPos > 0 )
+ changedStr.Append(remStr);
+
+ rv = rootMsgFolder->FindSubFolder(changedStr, getter_AddRefs(msgFolder));
+ }
+ return DiscoverChildren(msgFolder, listener, folderPath, nullptr);
+}
+
+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, nullptr);
+}
+
+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<nsIURI> uri = do_QueryInterface(imapUrl);
+ urlSpec.Append(command);
+ urlSpec.Append(hierarchyDelimiter);
+ nsAutoCString utfFolderName;
+ rv = CopyUTF16toMUTF7(PromiseFlatString(folderName), utfFolderName);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCString escapedFolderName;
+ MsgEscapeString(utfFolderName, nsINetUtil::ESCAPE_URL_PATH, escapedFolderName);
+ urlSpec.Append(escapedFolderName);
+ rv = uri->SetSpec(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->SetSpec(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.Append("/customFetch>UID>");
+ urlSpec.Append(hierarchyDelimiter);
+ urlSpec.Append(folderName);
+ urlSpec.Append(">");
+ urlSpec.Append(uids);
+ urlSpec.Append(">");
+ urlSpec.Append(aAttribute);
+ rv = mailNewsUrl->SetSpec(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.Append("/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->SetSpec(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, EmptyCString(), 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 char *uri, nsIMsgDBHdr **aRetVal)
+{
+ NS_ENSURE_ARG_POINTER(uri);
+ NS_ENSURE_ARG_POINTER(aRetVal);
+
+ nsCOMPtr<nsIMsgFolder> folder;
+ nsMsgKey msgKey;
+ nsresult rv = DecomposeImapURI(nsDependentCString(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(aMsgWindow, aListener, nullptr);
+ if (goOnline)
+ {
+ 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)
+{
+ nsImapOfflineDownloader *downloadForOffline = new nsImapOfflineDownloader(aMsgWindow, aListener);
+ if (downloadForOffline)
+ {
+ // hold reference to this so it won't get deleted out from under itself.
+ NS_ADDREF(downloadForOffline);
+ nsresult rv = downloadForOffline->ProcessNextOperation();
+ NS_RELEASE(downloadForOffline);
+ 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::NeckoOriginAttributes());
+
+ 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(NS_MESSENGERWINDOWSERVICE_CONTRACTID,&rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = messengerWindowService->OpenMessengerWindowWithUri("mail:3pane", unescapedUriStr.get(), 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/mailnews/imap/src/nsImapService.h b/mailnews/imap/src/nsImapService.h
new file mode 100644
index 000000000..dc6f1921a
--- /dev/null
+++ b/mailnews/imap/src/nsImapService.h
@@ -0,0 +1,123 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsImapService_h___
+#define nsImapService_h___
+
+#include "nsIImapService.h"
+#include "nsIMsgMessageService.h"
+#include "nsCOMPtr.h"
+#include "nsIFile.h"
+#include "nsIProtocolHandler.h"
+#include "nsIMsgProtocolInfo.h"
+#include "nsIContentHandler.h"
+#include "nsICacheStorage.h"
+
+class nsIImapHostSessionList;
+class nsCString;
+class nsIImapUrl;
+class nsIMsgFolder;
+class nsIMsgStatusFeedback;
+class nsIMsgIncomingServer;
+
+class nsImapService : public nsIImapService,
+ public nsIMsgMessageService,
+ public nsIMsgMessageFetchPartService,
+ public nsIProtocolHandler,
+ public nsIMsgProtocolInfo,
+ public nsIContentHandler
+{
+public:
+ nsImapService();
+
+ 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);
+
+ 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);
+
+ 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;
+ bool mPrintingOperation; // Flag for printing operations
+};
+
+#endif /* nsImapService_h___ */
diff --git a/mailnews/imap/src/nsImapStringBundle.cpp b/mailnews/imap/src/nsImapStringBundle.cpp
new file mode 100644
index 000000000..124b7f0b6
--- /dev/null
+++ b/mailnews/imap/src/nsImapStringBundle.cpp
@@ -0,0 +1,42 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include "prprf.h"
+#include "prmem.h"
+#include "nsCOMPtr.h"
+#include "nsStringGlue.h"
+#include "nsIStringBundle.h"
+#include "nsImapStringBundle.h"
+#include "nsIServiceManager.h"
+#include "nsIURI.h"
+#include "nsServiceManagerUtils.h"
+#include "mozilla/Services.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)
+ rv = sBundle->GetStringFromName(NS_ConvertASCIItoUTF16(stringName).get(),
+ aString);
+ return rv;
+}
+
+nsresult
+IMAPGetStringBundle(nsIStringBundle **aBundle)
+{
+ nsresult rv=NS_OK;
+ nsCOMPtr<nsIStringBundleService> stringService =
+ mozilla::services::GetStringBundleService();
+ if (!stringService) return NS_ERROR_NULL_POINTER;
+ nsCOMPtr<nsIStringBundle> stringBundle;
+ rv = stringService->CreateBundle(IMAP_MSGS_URL, getter_AddRefs(stringBundle));
+ *aBundle = stringBundle;
+ NS_IF_ADDREF(*aBundle);
+ return rv;
+}
diff --git a/mailnews/imap/src/nsImapStringBundle.h b/mailnews/imap/src/nsImapStringBundle.h
new file mode 100644
index 000000000..a5333f185
--- /dev/null
+++ b/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/mailnews/imap/src/nsImapUndoTxn.cpp b/mailnews/imap/src/nsImapUndoTxn.cpp
new file mode 100644
index 000000000..8d51dab35
--- /dev/null
+++ b/mailnews/imap/src/nsImapUndoTxn.cpp
@@ -0,0 +1,751 @@
+/* -*- 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 "msgCore.h" // for precompiled headers
+#include "nsMsgImapCID.h"
+#include "nsIMsgHdr.h"
+#include "nsImapUndoTxn.h"
+#include "nsIIMAPHostSessionList.h"
+#include "nsIMsgIncomingServer.h"
+#include "nsImapMailFolder.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;
+ m_dupKeyArray = *srcKeyArray;
+ 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 (MsgLowerCaseEqualsLiteral(protocolType, "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(NS_IMAPSERVICE_CONTRACTID, &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
+ rv = imapService->LiteSelectFolder(srcFolder, srcListener, nullptr, nullptr);
+ 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, nullptr,
+ m_srcMsgIdString,
+ kImapMsgDeletedFlag,
+ m_idsAreUids);
+ else
+ rv = imapService->AddMessageFlags(srcFolder,
+ srcListener, nullptr,
+ m_srcMsgIdString,
+ kImapMsgDeletedFlag,
+ m_idsAreUids);
+ if (NS_FAILED(rv))
+ return rv;
+
+ finishInOnStopRunningUrl = true;
+ if (deleteModel != nsMsgImapDeleteModels::IMAPDelete)
+ rv = imapService->GetHeaders(srcFolder, srcListener, nullptr,
+ 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.
+ rv = imapService->LiteSelectFolder(dstFolder,
+ dstListener, nullptr, nullptr);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = imapService->AddMessageFlags(dstFolder, dstListener,
+ nullptr, m_dstMsgIdString,
+ kImapMsgDeletedFlag, m_idsAreUids);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsImapMoveCopyMsgTxn::RedoTransaction(void)
+{
+ nsresult rv;
+ nsCOMPtr<nsIImapService> imapService = do_GetService(NS_IMAPSERVICE_CONTRACTID, &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.
+ rv = imapService->LiteSelectFolder(srcFolder, srcListener, nullptr, nullptr);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (deletedMsgs)
+ {
+ rv = imapService->SubtractMessageFlags(srcFolder,
+ srcListener, nullptr,
+ m_srcMsgIdString,
+ kImapMsgDeletedFlag,
+ m_idsAreUids);
+ }
+ else
+ {
+ rv = imapService->AddMessageFlags(srcFolder,
+ srcListener, nullptr, 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
+ rv = imapService->LiteSelectFolder(dstFolder, dstListener, nullptr, nullptr);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = imapService->SubtractMessageFlags(dstFolder,
+ dstListener, nullptr,
+ 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;
+ 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\n");
+ rv = srcDB->CopyHdrFromExistingHdr(m_srcKeyArray[i],
+ oldHdr,true,
+ getter_AddRefs(newHdr));
+ NS_ASSERTION(newHdr, "fatal ... cannot create new header\n");
+
+ 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
+ }
+ else
+ {
+ rv = NS_ERROR_FAILURE;
+ }
+ return rv;
+}
+
+
+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.Length(), m_srcKeyArray.Elements(), nullptr);
+ srcDB->SetSummaryValid(true);
+ }
+ return NS_OK; // always return NS_OK
+ }
+ else
+ {
+ rv = NS_ERROR_FAILURE;
+ }
+ return rv;
+}
+
+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(NS_IMAPSERVICE_CONTRACTID, &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.
+ rv = imapService->LiteSelectFolder(dstFolder, dstListener, nullptr, nullptr);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = imapService->AddMessageFlags(dstFolder, dstListener,
+ nullptr, 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.Elements(), dstKeys.Length(), 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);
+ rv = srcDB->GetOfflineOpForKey(hdrKey, false, getter_AddRefs(op));
+ bool offlineOpPlayedBack = true;
+ if (NS_SUCCEEDED(rv) && op)
+ {
+ op->GetPlayingBack(&offlineOpPlayedBack);
+ srcDB->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:
+ for (int32_t i = 0; i < m_srcHdrs.Count(); i++)
+ {
+ nsMsgKey hdrKey;
+ m_srcHdrs[i]->GetMessageKey(&hdrKey);
+ rv = srcDB->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.get()); // offline move
+ if (m_opType == nsIMsgOfflineImapOperation::kMsgCopy)
+ {
+ op->SetOperation(nsIMsgOfflineImapOperation::kMsgMoved);
+ op->AddMessageCopyOperation(folderURI.get()); // 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);
+ 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 = destDB->GetOfflineOpForKey(msgKey, true, getter_AddRefs(op));
+ if (NS_SUCCEEDED(rv) && op)
+ {
+ nsCString folderURI;
+ srcFolder->GetURI(folderURI);
+ op->SetSourceFolderURI(folderURI.get());
+ }
+ }
+ 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:
+ for (int32_t i = 0; i < m_srcHdrs.Count(); i++)
+ {
+ nsMsgKey msgKey;
+ m_srcHdrs[i]->GetMessageKey(&msgKey);
+ rv = srcDB->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/mailnews/imap/src/nsImapUndoTxn.h b/mailnews/imap/src/nsImapUndoTxn.h
new file mode 100644
index 000000000..cc910d768
--- /dev/null
+++ b/mailnews/imap/src/nsImapUndoTxn.h
@@ -0,0 +1,92 @@
+/* -*- 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 "nsImapCore.h"
+#include "nsIImapService.h"
+#include "nsIImapIncomingServer.h"
+#include "nsIUrlListener.h"
+#include "nsMsgTxn.h"
+#include "MailNewsTypes.h"
+#include "nsTArray.h"
+#include "nsIMsgOfflineImapOperation.h"
+#include "nsCOMPtr.h"
+#include "nsWeakReference.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/mailnews/imap/src/nsImapUrl.cpp b/mailnews/imap/src/nsImapUrl.cpp
new file mode 100644
index 000000000..8fd9f2f65
--- /dev/null
+++ b/mailnews/imap/src/nsImapUrl.cpp
@@ -0,0 +1,1561 @@
+/* -*- 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 "nsMsgImapCID.h"
+
+#include "nsIURL.h"
+#include "nsImapUrl.h"
+#include "nsIIMAPHostSessionList.h"
+#include "nsThreadUtils.h"
+#include "nsStringGlue.h"
+#include "prmem.h"
+#include "plstr.h"
+#include "prprf.h"
+#include "nsMemory.h"
+#include "nsCOMPtr.h"
+#include "nsIImapIncomingServer.h"
+#include "nsMsgBaseCID.h"
+#include "nsImapUtils.h"
+#include "nsIMAPNamespace.h"
+#include "nsICacheEntry.h"
+#include "nsIMsgFolder.h"
+#include "nsIDocShell.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsMsgUtils.h"
+#include "nsIMsgHdr.h"
+#include "nsIProgressEventSink.h"
+#include "nsServiceManagerUtils.h"
+
+using namespace mozilla;
+
+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_searchCriteriaString = nullptr;
+ m_idsAreUids = false;
+ m_mimePartSelectorDetected = false;
+ m_allowContentChange = true; // assume we can do MPOD.
+ m_fetchPartsOnDemand = false; // but assume we're not doing it :-)
+ 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_contentModified = IMAP_CONTENT_NOT_MODIFIED;
+ m_validUrl = true; // assume the best.
+ m_flags = 0;
+ m_extraStatus = ImapStatusNone;
+ m_onlineSubDirSeparator = '/';
+
+ // ** 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);
+ NS_IF_ADDREF(*aMsgFolder = folder);
+ 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);
+ *aImapMailFolderSink = folderSink;
+ NS_IF_ADDREF(*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);
+ *aImapMessageSink = messageSink;
+ NS_IF_ADDREF(*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);
+ *aImapServerSink = serverSink;
+ NS_IF_ADDREF(*aImapServerSink);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapUrl::SetImapServerSink(nsIImapServerSink * aImapServerSink)
+{
+ nsresult rv;
+ m_imapServerSink = do_GetWeakReference(aImapServerSink, &rv);
+ return rv;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////////
+// End nsIImapUrl specific support
+////////////////////////////////////////////////////////////////////////////////////
+
+NS_IMETHODIMP nsImapUrl::SetSpec(const nsACString &aSpec)
+{
+ nsresult rv = nsMsgMailNewsUrl::SetSpec(aSpec);
+ if (NS_SUCCEEDED(rv))
+ {
+ m_validUrl = true; // assume the best.
+ rv = ParseUrl();
+ }
+ return rv;
+}
+
+NS_IMETHODIMP 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 = GetPath(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....
+
+ // unforunately 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;
+ }
+ else
+ {
+ 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
+ MsgReplaceChar(onlineDirWithDelimiter, '/', 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)
+ NS_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)
+ {
+ NS_Free(rv);
+ rv = onlineNameAdded;
+ }
+
+ if (aAllocatedPath)
+ *aAllocatedPath = rv;
+ else
+ NS_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 */ nsresult nsImapUrl::UnescapeSlashes(char *sourcePath)
+{
+ char *src = sourcePath;
+ char *dst = sourcePath;
+
+ while (*src)
+ {
+ if (*src == '^')
+ {
+ if (*(src + 1) == '^')
+ {
+ *dst++ = '^';
+ src++; // skip over second '^'
+ }
+ else
+ *dst++ = '/';
+ src++;
+ }
+ else
+ *dst++ = *src++;
+ }
+
+ *dst = 0;
+ 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.
+// The caller has already converted m-utf-7 to 8 bit ascii, which is a problem.
+// this method is only called from the imap thread
+NS_IMETHODIMP nsImapUrl::AllocateCanonicalPath(const char *serverPath, char onlineDelimiter, char **allocatedPath )
+{
+ nsresult rv = NS_ERROR_NULL_POINTER;
+ char delimiterToUse = onlineDelimiter;
+ char *serverKey = nullptr;
+ nsString aString;
+ char *currentPath = (char *) serverPath;
+ nsAutoCString onlineDir;
+ nsCOMPtr<nsIMsgIncomingServer> server;
+
+ nsCOMPtr<nsIImapHostSessionList> hostSessionList =
+ do_GetService(kCImapHostSessionListCID, &rv);
+
+ *allocatedPath = nullptr;
+
+ if (onlineDelimiter == kOnlineHierarchySeparatorUnknown ||
+ onlineDelimiter == 0)
+ GetOnlineSubDirSeparator(&delimiterToUse);
+
+ NS_ASSERTION (serverPath, "Oops... null serverPath");
+
+ if (!serverPath || NS_FAILED(rv))
+ goto done;
+
+ hostSessionList->GetOnlineDirForHost(m_serverKey.get(), aString);
+ // 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
+ LossyCopyUTF16toASCII(aString, onlineDir);
+
+ if (currentPath && !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
+ MsgReplaceChar(onlineDir, '/', 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");
+ }
+ }
+
+
+ if (!currentPath)
+ goto done;
+
+ rv = ConvertToCanonicalFormat(currentPath, delimiterToUse, allocatedPath);
+
+done:
+ PR_Free(serverKey);
+ return rv;
+}
+
+// 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;
+}
+
+// for enabling or disabling mime parts on demand. Setting this to true says we
+// can use mime parts on demand, if we chose.
+NS_IMETHODIMP nsImapUrl::SetAllowContentChange(bool allowContentChange)
+{
+ m_allowContentChange = allowContentChange;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapUrl::SetContentModified(nsImapContentModifiedType contentModified)
+{
+ m_contentModified = contentModified;
+ nsCOMPtr<nsICacheEntry> cacheEntry;
+ nsresult res = GetMemCacheEntry(getter_AddRefs(cacheEntry));
+ if (NS_SUCCEEDED(res) && cacheEntry)
+ {
+ const char *contentModifiedAnnotation = "";
+ switch (m_contentModified)
+ {
+ case IMAP_CONTENT_NOT_MODIFIED:
+ contentModifiedAnnotation = "Not Modified";
+ break;
+ case IMAP_CONTENT_MODIFIED_VIEW_INLINE:
+ contentModifiedAnnotation = "Modified View Inline";
+ break;
+ case IMAP_CONTENT_MODIFIED_VIEW_AS_LINKS:
+ contentModifiedAnnotation = "Modified View As Link";
+ break;
+ case IMAP_CONTENT_FORCE_CONTENT_NOT_MODIFIED:
+ contentModifiedAnnotation = "Force Content Not Modified";
+ break;
+ }
+ cacheEntry->SetMetaDataElement("ContentModified", contentModifiedAnnotation);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapUrl::GetContentModified(nsImapContentModifiedType *contentModified)
+{
+ if (!contentModified) return NS_ERROR_NULL_POINTER;
+
+ *contentModified = m_contentModified;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapUrl::SetFetchPartsOnDemand(bool fetchPartsOnDemand)
+{
+ m_fetchPartsOnDemand = fetchPartsOnDemand;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapUrl::GetFetchPartsOnDemand(bool *fetchPartsOnDemand)
+{
+ if (!fetchPartsOnDemand) return NS_ERROR_NULL_POINTER;
+
+ *fetchPartsOnDemand = m_fetchPartsOnDemand;
+ return NS_OK;
+}
+
+
+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);
+ *copyState = m_copyState;
+ NS_IF_ADDREF(*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);
+ NS_WARNING_ASSERTION(NS_IsMainThread(), "should only access mock channel on ui thread");
+ *aChannel = nullptr;
+ nsCOMPtr<nsIImapMockChannel> channel(do_QueryReferent(m_channelWeakPtr));
+ channel.swap(*aChannel);
+ return *aChannel ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP nsImapUrl::SetMockChannel(nsIImapMockChannel * aChannel)
+{
+ NS_WARNING_ASSERTION(NS_IsMainThread(), "should only access mock channel on ui thread");
+ m_channelWeakPtr = do_GetWeakReference(aChannel);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapUrl::GetAllowContentChange(bool *result)
+{
+ NS_ENSURE_ARG_POINTER(result);
+ *result = m_allowContentChange;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapUrl::CloneInternal(uint32_t aRefHandlingMode,
+ const nsACString& newRef,
+ nsIURI** _retval)
+{
+ nsresult rv =
+ nsMsgMailNewsUrl::CloneInternal(aRefHandlingMode, newRef, _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.get());
+ return rv;
+}
+
+NS_IMETHODIMP nsImapUrl::GetPrincipalSpec(nsACString& aPrincipalSpec)
+{
+ // URLs look like this:
+ // imap://user@domain@server:port/fetch>UID>folder>nn
+ // We simply strip any query part beginning with ? & or /;
+ // Normalised 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 /;
+ int32_t ind = spec.Find("/;");
+ if (ind != kNotFound)
+ spec.SetLength(ind);
+
+ ind = spec.FindChar('?');
+ if (ind != kNotFound)
+ spec.SetLength(ind);
+
+ aPrincipalSpec.Assign(spec);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapUrl::SetUri(const char * aURI)
+{
+ mURI= aURI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapUrl::GetUri(char** aURI)
+{
+ nsresult rv = NS_OK;
+ if (!mURI.IsEmpty())
+ *aURI = ToNewCString(mURI);
+ else
+ {
+ *aURI = nullptr;
+ 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);
+ nsAutoCString uriStr;
+ rv = nsBuildImapMessageURI(baseMessageURI.get(), key, uriStr);
+ *aURI = ToNewCString(uriStr);
+ }
+ 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(char ** aSpec)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsImapUrl::SetOriginalSpec(const char *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 we're asking for just the body, don't download the whole message. see
+ // nsMsgQuote::QuoteMessage() for the "header=" settings when replying to msgs.
+ if (!m_fetchPartsOnDemand)
+ m_fetchPartsOnDemand = (PL_strstr(m_listOfMessageIds, "?header=quotebody") != 0 ||
+ PL_strstr(m_listOfMessageIds, "?header=only") != 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(getter_Copies(uri));
+ NS_ENSURE_TRUE(!uri.IsEmpty(), NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsIMsgDBHdr> msg;
+ GetMsgDBHdrFromURI(uri.get(), 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::GetFolderCharset(char ** aCharacterSet)
+{
+ nsCOMPtr<nsIMsgFolder> folder;
+ nsresult rv = GetMsgFolder(getter_AddRefs(folder));
+ NS_ENSURE_SUCCESS(rv,rv);
+ nsCString tmpStr;
+ folder->GetCharset(tmpStr);
+ *aCharacterSet = ToNewCString(tmpStr);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapUrl::GetFolderCharsetOverride(bool * aCharacterSetOverride)
+{
+ nsCOMPtr<nsIMsgFolder> folder;
+ nsresult rv = GetMsgFolder(getter_AddRefs(folder));
+ NS_ENSURE_SUCCESS(rv,rv);
+ NS_ENSURE_TRUE(folder, NS_ERROR_FAILURE);
+ folder->GetCharsetOverride(aCharacterSetOverride);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapUrl::GetCharsetOverRide(char ** aCharacterSet)
+{
+ if (!mCharsetOverride.IsEmpty())
+ *aCharacterSet = ToNewCString(mCharsetOverride);
+ else
+ *aCharacterSet = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapUrl::SetCharsetOverRide(const char * aCharacterSet)
+{
+ mCharsetOverride = aCharacterSet;
+ 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(getter_Copies(uri));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return GetMsgDBHdrFromURI(uri.get(), aMsgHdr);
+}
+
+NS_IMETHODIMP nsImapUrl::SetMessageHeader(nsIMsgDBHdr *aMsgHdr)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
diff --git a/mailnews/imap/src/nsImapUrl.h b/mailnews/imap/src/nsImapUrl.h
new file mode 100644
index 000000000..d1925d87a
--- /dev/null
+++ b/mailnews/imap/src/nsImapUrl.h
@@ -0,0 +1,133 @@
+/* -*- 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 "nsIImapMockChannel.h"
+#include "nsCOMPtr.h"
+#include "nsMsgMailNewsUrl.h"
+#include "nsIMsgIncomingServer.h"
+#include "nsIImapMailFolderSink.h"
+#include "nsIImapServerSink.h"
+#include "nsIImapMessageSink.h"
+
+#include "nsWeakPtr.h"
+#include "nsIFile.h"
+#include "mozilla/Mutex.h"
+
+class nsImapUrl : public nsIImapUrl, public nsMsgMailNewsUrl, public nsIMsgMessageUrl, public nsIMsgI18NUrl
+{
+public:
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+ // nsIURI override
+ NS_IMETHOD SetSpec(const nsACString &aSpec) override;
+ NS_IMETHOD SetQuery(const nsACString &aQuery) override;
+ NS_IMETHOD CloneInternal(uint32_t aRefHandlingMode,
+ const nsACString& newRef, 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(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_allowContentChange; // if false, we can't use Mime parts on demand
+ bool m_fetchPartsOnDemand; // if true, we should fetch leave parts on server.
+ 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.
+ nsCString mCharsetOverride; // used by nsIMsgI18NUrl...
+ mozilla::Mutex mLock;
+};
+
+#endif /* nsImapUrl_h___ */
diff --git a/mailnews/imap/src/nsImapUtils.cpp b/mailnews/imap/src/nsImapUtils.cpp
new file mode 100644
index 000000000..b213d33f8
--- /dev/null
+++ b/mailnews/imap/src/nsImapUtils.cpp
@@ -0,0 +1,373 @@
+/* -*- 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 "nsIServiceManager.h"
+#include "prsystem.h"
+#include "prprf.h"
+#include "nsNetCID.h"
+
+// stuff for temporary root folder hack
+#include "nsIMsgAccountManager.h"
+#include "nsIMsgIncomingServer.h"
+#include "nsIImapIncomingServer.h"
+#include "nsMsgBaseCID.h"
+#include "nsImapCore.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 char* uri, nsCString& folderURI, uint32_t *key, char **part)
+{
+ if(!key)
+ return NS_ERROR_NULL_POINTER;
+
+ nsAutoCString uriStr(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, NS_LITERAL_CSTRING("imap-message")))
+ folderEnd = uriStr.Find("imap://");
+
+ int32_t keySeparator = MsgRFindChar(uriStr, '#', 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 = MsgFind(uriStr, "part=", false, keyEndSeparator);
+ if (partPos != -1)
+ {
+ *part = ToNewCString(Substring(uriStr, keyEndSeparator));
+ }
+ }
+ }
+ return NS_OK;
+}
+
+nsresult nsBuildImapMessageURI(const char *baseURI, uint32_t key, nsCString& 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 = aUnicharPathName;
+ 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(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/mailnews/imap/src/nsImapUtils.h b/mailnews/imap/src/nsImapUtils.h
new file mode 100644
index 000000000..b24a38a00
--- /dev/null
+++ b/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 "nsStringGlue.h"
+#include "nsIMsgIncomingServer.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 char* uri, nsCString& folderURI, uint32_t *key, char **part);
+
+extern nsresult
+nsBuildImapMessageURI(const char *baseURI, uint32_t key, nsCString& uri);
+
+extern nsresult
+nsCreateImapBaseMessageURI(const nsACString& baseURI, nsCString& baseMessageURI);
+
+void AllocateImapUidString(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/mailnews/imap/src/nsSyncRunnableHelpers.cpp b/mailnews/imap/src/nsSyncRunnableHelpers.cpp
new file mode 100644
index 000000000..2928df8eb
--- /dev/null
+++ b/mailnews/imap/src/nsSyncRunnableHelpers.cpp
@@ -0,0 +1,601 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.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 "nsIMsgMailNewsUrl.h"
+#include "nsIMsgWindow.h"
+#include "nsImapMailFolder.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()
+ : 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_SYNCRUNNABLEMETHOD5(StreamListener, OnDataAvailable,
+ nsIRequest *, nsISupports *, nsIInputStream *, uint64_t, uint32_t)
+
+NS_SYNCRUNNABLEMETHOD2(StreamListener, OnStartRequest,
+ nsIRequest *, nsISupports *)
+
+NS_SYNCRUNNABLEMETHOD3(StreamListener, OnStopRequest,
+ nsIRequest *, nsISupports *, 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_SYNCRUNNABLEMETHOD3(ImapMailFolderSink, SetFolderQuotaData, const nsACString &, uint32_t, uint32_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_SYNCRUNNABLEMETHOD4(ImapMailFolderSink, GetMsgHdrsToDownload, bool *, int32_t *, uint32_t *, 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_SYNCRUNNABLEMETHOD4(ImapMailFolderSink, PercentProgress, nsIImapProtocol*, const char16_t *, 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_SYNCRUNNABLEMETHOD2(ImapMessageSink, SetContentModified, nsIImapUrl *, nsImapContentModifiedType)
+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, nsACString &)
+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, nsACString &)
+NS_SYNCRUNNABLEMETHOD1(ImapServerSink, RemoveServerConnection, nsIImapProtocol *)
+NS_SYNCRUNNABLEMETHOD1(ImapServerSink, GetServerShuttingDown, bool *)
+NS_SYNCRUNNABLEMETHOD1(ImapServerSink, ResetServerConnection, const nsACString &)
+NS_SYNCRUNNABLEMETHOD1(ImapServerSink, SetServerDoingLsub, bool)
+NS_SYNCRUNNABLEMETHOD1(ImapServerSink, SetServerForceSelect, const nsACString &)
+
+#ifdef MOZ_MAILNEWS_OAUTH2
+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(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(this, &OAuth2ThreadHelper::Init);
+ NS_DispatchToMainThread(runInit);
+ 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(this, &OAuth2ThreadHelper::Connect);
+ NS_DispatchToMainThread(runInit);
+ mMonitor.Wait();
+
+ // 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 &aAccessToken)
+{
+ 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");
+ mOAuth2Support->BuildXOAuth2String(mOAuth2String);
+ 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
+#endif \ No newline at end of file
diff --git a/mailnews/imap/src/nsSyncRunnableHelpers.h b/mailnews/imap/src/nsSyncRunnableHelpers.h
new file mode 100644
index 000000000..273810a70
--- /dev/null
+++ b/mailnews/imap/src/nsSyncRunnableHelpers.h
@@ -0,0 +1,151 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsSyncRunnableHelpers_h
+#define nsSyncRunnableHelpers_h
+
+#include "nsThreadUtils.h"
+#include "nsProxyRelease.h"
+
+#ifdef MOZ_MAILNEWS_OAUTH2
+#include "mozilla/Monitor.h"
+#include "msgIOAuth2Module.h"
+#endif
+
+#include "nsIStreamListener.h"
+#include "nsIInterfaceRequestor.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)
+ { }
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+
+private:
+ ~StreamListenerProxy() {
+ NS_ReleaseOnMainThread(mReceiver.forget());
+ }
+ nsCOMPtr<nsIStreamListener> mReceiver;
+};
+
+class ImapMailFolderSinkProxy final : public nsIImapMailFolderSink
+{
+public:
+ explicit ImapMailFolderSinkProxy(nsIImapMailFolderSink* receiver)
+ : mReceiver(receiver)
+ {
+ NS_ASSERTION(receiver, "Don't allow receiver is nullptr");
+ }
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIIMAPMAILFOLDERSINK
+
+private:
+ ~ImapMailFolderSinkProxy() {
+ NS_ReleaseOnMainThread(mReceiver.forget());
+ }
+ nsCOMPtr<nsIImapMailFolderSink> mReceiver;
+};
+
+class ImapServerSinkProxy final : public nsIImapServerSink
+{
+public:
+ explicit ImapServerSinkProxy(nsIImapServerSink* receiver)
+ : mReceiver(receiver)
+ { }
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIIMAPSERVERSINK
+
+private:
+ ~ImapServerSinkProxy() {
+ NS_ReleaseOnMainThread(mReceiver.forget());
+ }
+ nsCOMPtr<nsIImapServerSink> mReceiver;
+};
+
+
+class ImapMessageSinkProxy final : public nsIImapMessageSink
+{
+public:
+ explicit ImapMessageSinkProxy(nsIImapMessageSink* receiver)
+ : mReceiver(receiver)
+ { }
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIIMAPMESSAGESINK
+
+private:
+ ~ImapMessageSinkProxy() {
+ NS_ReleaseOnMainThread(mReceiver.forget());
+ }
+ nsCOMPtr<nsIImapMessageSink> mReceiver;
+};
+
+class ImapProtocolSinkProxy final : public nsIImapProtocolSink
+{
+public:
+ explicit ImapProtocolSinkProxy(nsIImapProtocolSink* receiver)
+ : mReceiver(receiver)
+ { }
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIIMAPPROTOCOLSINK
+
+private:
+ ~ImapProtocolSinkProxy() {
+ NS_ReleaseOnMainThread(mReceiver.forget());
+ }
+ nsCOMPtr<nsIImapProtocolSink> mReceiver;
+};
+
+#ifdef MOZ_MAILNEWS_OAUTH2
+class msgIOAuth2Module;
+class nsIMsgIncomingServer;
+class nsIVariant;
+class nsIWritableVariant;
+
+namespace mozilla {
+namespace mailnews {
+
+class OAuth2ThreadHelper final : public msgIOAuth2ModuleListener
+{
+public:
+ 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
+
+#endif // nsSyncRunnableHelpers_h