From 302bf1b523012e11b60425d6eee1221ebc2724eb Mon Sep 17 00:00:00 2001 From: "Matt A. Tobin" Date: Sun, 3 Nov 2019 00:17:46 -0400 Subject: Issue #1258 - Part 1: Import mailnews, ldap, and mork from comm-esr52.9.1 --- mailnews/imap/src/moz.build | 33 + mailnews/imap/src/nsAutoSyncManager.cpp | 1412 +++ mailnews/imap/src/nsAutoSyncManager.h | 265 + mailnews/imap/src/nsAutoSyncState.cpp | 765 ++ mailnews/imap/src/nsAutoSyncState.h | 107 + mailnews/imap/src/nsIMAPBodyShell.cpp | 1333 +++ mailnews/imap/src/nsIMAPBodyShell.h | 361 + mailnews/imap/src/nsIMAPGenericParser.cpp | 484 + mailnews/imap/src/nsIMAPGenericParser.h | 76 + mailnews/imap/src/nsIMAPHostSessionList.cpp | 701 ++ mailnews/imap/src/nsIMAPHostSessionList.h | 135 + mailnews/imap/src/nsIMAPNamespace.cpp | 650 ++ mailnews/imap/src/nsIMAPNamespace.h | 87 + mailnews/imap/src/nsImapCore.h | 188 + mailnews/imap/src/nsImapFlagAndUidState.cpp | 321 + mailnews/imap/src/nsImapFlagAndUidState.h | 55 + mailnews/imap/src/nsImapIncomingServer.cpp | 3382 +++++++ mailnews/imap/src/nsImapIncomingServer.h | 137 + mailnews/imap/src/nsImapMailFolder.cpp | 9868 ++++++++++++++++++++ mailnews/imap/src/nsImapMailFolder.h | 550 ++ mailnews/imap/src/nsImapOfflineSync.cpp | 1292 +++ mailnews/imap/src/nsImapOfflineSync.h | 92 + mailnews/imap/src/nsImapProtocol.cpp | 10046 +++++++++++++++++++++ mailnews/imap/src/nsImapProtocol.h | 764 ++ mailnews/imap/src/nsImapSearchResults.cpp | 92 + mailnews/imap/src/nsImapSearchResults.h | 42 + mailnews/imap/src/nsImapServerResponseParser.cpp | 3360 +++++++ mailnews/imap/src/nsImapServerResponseParser.h | 269 + mailnews/imap/src/nsImapService.cpp | 3400 +++++++ mailnews/imap/src/nsImapService.h | 123 + mailnews/imap/src/nsImapStringBundle.cpp | 42 + mailnews/imap/src/nsImapStringBundle.h | 17 + mailnews/imap/src/nsImapUndoTxn.cpp | 751 ++ mailnews/imap/src/nsImapUndoTxn.h | 92 + mailnews/imap/src/nsImapUrl.cpp | 1563 ++++ mailnews/imap/src/nsImapUrl.h | 133 + mailnews/imap/src/nsImapUtils.cpp | 373 + mailnews/imap/src/nsImapUtils.h | 77 + mailnews/imap/src/nsSyncRunnableHelpers.cpp | 600 ++ mailnews/imap/src/nsSyncRunnableHelpers.h | 146 + 40 files changed, 44184 insertions(+) create mode 100644 mailnews/imap/src/moz.build create mode 100644 mailnews/imap/src/nsAutoSyncManager.cpp create mode 100644 mailnews/imap/src/nsAutoSyncManager.h create mode 100644 mailnews/imap/src/nsAutoSyncState.cpp create mode 100644 mailnews/imap/src/nsAutoSyncState.h create mode 100644 mailnews/imap/src/nsIMAPBodyShell.cpp create mode 100644 mailnews/imap/src/nsIMAPBodyShell.h create mode 100644 mailnews/imap/src/nsIMAPGenericParser.cpp create mode 100644 mailnews/imap/src/nsIMAPGenericParser.h create mode 100644 mailnews/imap/src/nsIMAPHostSessionList.cpp create mode 100644 mailnews/imap/src/nsIMAPHostSessionList.h create mode 100644 mailnews/imap/src/nsIMAPNamespace.cpp create mode 100644 mailnews/imap/src/nsIMAPNamespace.h create mode 100644 mailnews/imap/src/nsImapCore.h create mode 100644 mailnews/imap/src/nsImapFlagAndUidState.cpp create mode 100644 mailnews/imap/src/nsImapFlagAndUidState.h create mode 100644 mailnews/imap/src/nsImapIncomingServer.cpp create mode 100644 mailnews/imap/src/nsImapIncomingServer.h create mode 100644 mailnews/imap/src/nsImapMailFolder.cpp create mode 100644 mailnews/imap/src/nsImapMailFolder.h create mode 100644 mailnews/imap/src/nsImapOfflineSync.cpp create mode 100644 mailnews/imap/src/nsImapOfflineSync.h create mode 100644 mailnews/imap/src/nsImapProtocol.cpp create mode 100644 mailnews/imap/src/nsImapProtocol.h create mode 100644 mailnews/imap/src/nsImapSearchResults.cpp create mode 100644 mailnews/imap/src/nsImapSearchResults.h create mode 100644 mailnews/imap/src/nsImapServerResponseParser.cpp create mode 100644 mailnews/imap/src/nsImapServerResponseParser.h create mode 100644 mailnews/imap/src/nsImapService.cpp create mode 100644 mailnews/imap/src/nsImapService.h create mode 100644 mailnews/imap/src/nsImapStringBundle.cpp create mode 100644 mailnews/imap/src/nsImapStringBundle.h create mode 100644 mailnews/imap/src/nsImapUndoTxn.cpp create mode 100644 mailnews/imap/src/nsImapUndoTxn.h create mode 100644 mailnews/imap/src/nsImapUrl.cpp create mode 100644 mailnews/imap/src/nsImapUrl.h create mode 100644 mailnews/imap/src/nsImapUtils.cpp create mode 100644 mailnews/imap/src/nsImapUtils.h create mode 100644 mailnews/imap/src/nsSyncRunnableHelpers.cpp create mode 100644 mailnews/imap/src/nsSyncRunnableHelpers.h (limited to 'mailnews/imap/src') 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 server; + + nsresult rv = aFolder->GetServer(getter_AddRefs(server)); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr 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 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 parent; + aFolder->GetParent(getter_AddRefs(parent)); + if (!parent) + *aDecision = true; + } + return NS_OK; +} + +#define NOTIFY_LISTENERS_STATIC(obj_, propertyfunc_, params_) \ + PR_BEGIN_MACRO \ + nsTObserverArray >::ForwardIterator iter(obj_->mListeners); \ + nsCOMPtr 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 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(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 autoSyncStateObj(autoSyncMgr->mDiscoveryQ[0]); + if (autoSyncStateObj) + { + uint32_t leftToProcess; + nsresult rv = autoSyncStateObj->ProcessExistingHeaders(kNumberOfHeadersToProcess, &leftToProcess); + + nsCOMPtr 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 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 folder; + autoSyncStateObj->GetOwnerFolder(getter_AddRefs(folder)); + if (folder) + { + nsCOMPtr 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 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 &aQueue, + nsCOMArray &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 &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 &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 &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 &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 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 chainedQ; + nsCOMArray *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 foldersToBeRemoved; + + // process folders in the priority queue + int32_t elemCount = queue->Count(); + for (int32_t idx = 0; idx < elemCount; idx++) + { + nsCOMPtr 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 autoSyncStateObj(foldersToBeRemoved[idx]); + if (!autoSyncStateObj) + continue; + + nsCOMPtr 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 accountManager = do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv,rv); + + nsCOMPtr 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 account(do_QueryElementAt(accounts, i, &rv)); + if (!account) + continue; + + nsCOMPtr 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 rootFolder; + nsCOMPtr 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 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 imapFolder = do_QueryInterface(folder, &rv); + if (NS_FAILED(rv)) + continue; + + nsCOMPtr 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 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 folStrategy; + GetFolderStrategy(getter_AddRefs(folStrategy)); + + if (mPriorityQ.Count() <= 0) + { + // make sure that we don't insert a folder excluded by the given strategy + nsCOMPtr 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 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 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 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 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 folder; + nsresult rv = aAutoSyncStateObj->GetOwnerFolder(getter_AddRefs(folder)); + if (NS_FAILED(rv)) + return kDefaultUpdateInterval; + + nsCOMPtr 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 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 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 folStrategy; + nsCOMPtr 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 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 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 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 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 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 &aQueue, + nsCOMArray &aChainedQ); + static + nsIAutoSyncState* SearchQForSibling(const nsCOMArray &aQueue, + nsIAutoSyncState *aAutoSyncStateObj, int32_t aStartIdx, int32_t *aIndex = nullptr); + static + bool DoesQContainAnySiblingOf(const nsCOMArray &aQueue, + nsIAutoSyncState *aAutoSyncStateObj, const int32_t aState, + int32_t *aIndex = nullptr); + static + nsIAutoSyncState* GetNextSibling(const nsCOMArray &aQueue, + nsIAutoSyncState *aAutoSyncStateObj, int32_t *aIndex = nullptr); + static + nsIAutoSyncState* GetHighestPrioSibling(const nsCOMArray &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 mMsgStrategyImpl; + nsCOMPtr mFolderStrategyImpl; + // contains the folders that will be downloaded on background + nsCOMArray 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 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 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 mIdleService; + nsCOMPtr mTimer; + nsTObserverArray > 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 hdrA; + nsCOMPtr 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 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 hdrA; + nsCOMPtr 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 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(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 &aMsgKeyList) +{ + nsresult rv = NS_OK; + if (!aMsgKeyList.IsEmpty()) + { + nsCOMPtr folder = do_QueryReferent(mOwnerFolder, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr database; + rv = folder->GetMsgDatabase(getter_AddRefs(database)); + if (!database) + return NS_ERROR_FAILURE; + + nsCOMPtr autoSyncMgr = do_GetService(NS_AUTOSYNCMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv,rv); + + nsCOMPtr 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 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 &aQueue) +{ + nsresult rv; + nsCOMPtr folder = do_QueryReferent(mOwnerFolder, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr database; + rv = folder->GetMsgDatabase(getter_AddRefs(database)); + if (!database) + return NS_ERROR_FAILURE; + + nsCOMPtr autoSyncMgr = do_GetService(NS_AUTOSYNCMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr 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 &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 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 folder = do_QueryReferent(mOwnerFolder, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr database; + folder->GetMsgDatabase(getter_AddRefs(database)); + + nsCOMPtr 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 autoSyncMgr = do_GetService(NS_AUTOSYNCMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + uint32_t msgCount = mDownloadQ.Length(); + uint32_t idx = mOffset; + + nsCOMPtr 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 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 folder = do_QueryReferent(mOwnerFolder, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr 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 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 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 &aMsgKeyList) +{ + SetLastUpdateTime(PR_Now()); + if (!aMsgKeyList.IsEmpty()) + PlaceIntoDownloadQ(aMsgKeyList); +} + +NS_IMETHODIMP nsAutoSyncState::UpdateFolder() +{ + nsresult rv; + nsCOMPtr autoSyncMgr = do_GetService(NS_AUTOSYNCMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr autoSyncMgrListener = do_QueryInterface(autoSyncMgr, &rv); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr 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 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 ownerFolder = do_QueryReferent(mOwnerFolder, &rv); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr autoSyncMgr = do_GetService(NS_AUTOSYNCMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr autoSyncMgrListener = do_QueryInterface(autoSyncMgr, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + if (mSyncState == stStatusIssued) + { + nsCOMPtr 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 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 session = + do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv); + if (NS_SUCCEEDED(rv) && session) + { + nsCOMPtr 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 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 folderA, folderB; + + rv = GetOwnerFolder(getter_AddRefs(folderA)); + NS_ENSURE_SUCCESS(rv,rv); + + rv = aAnotherStateObj->GetOwnerFolder(getter_AddRefs(folderB)); + NS_ENSURE_SUCCESS(rv,rv); + + nsCOMPtr 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 imapService = do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv,rv); + + nsAutoCString messageIds; + nsTArray 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 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& q, uint32_t toOffset) +{ + nsCOMPtr ownerFolder = do_QueryReferent(mOwnerFolder); + if (ownerFolder) + { + nsCOMPtr database; + ownerFolder->GetMsgDatabase(getter_AddRefs(database)); + + uint32_t x = q.Length(); + while (x > toOffset && database) + { + x--; + nsCOMPtr 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 ownerFolder = do_QueryReferent(mOwnerFolder); + if (ownerFolder) + { + nsCOMPtr database; + ownerFolder->GetMsgDatabase(getter_AddRefs(database)); + + uint32_t x; + q->GetLength(&x); + while (x > toOffset && database) + { + x--; + nsCOMPtr 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 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..c28c10d60 --- /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 + + nsAutoSyncState(nsImapMailFolder *aOwnerFolder, PRTime aLastSyncTime = 0UL); + + /// Called by owner folder when new headers are fetched from the server + void OnNewHeaderFetchCompleted(const nsTArray &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 &aMsgKeyList); + nsresult SortQueueBasedOnStrategy(nsTArray &aQueue); + nsresult SortSubQueueBasedOnStrategy(nsTArray &aQueue, + uint32_t aStartingOffset); + + void LogOwnerFolderName(const char *s); + void LogQWithSize(nsTArray& 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 mDownloadSet; + nsTArray mDownloadQ; + nsTArray 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(); + 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(); +} + +/* 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 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 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 *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 *m_shellList; // For maintenance + // For quick lookup based on UID + nsRefPtrHashtable 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 { +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 = +// 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 ::= / +// "\" quoted_specials +// TEXT_CHAR ::= +// 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 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 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 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..d46352373 --- /dev/null +++ b/mailnews/imap/src/nsIMAPNamespace.cpp @@ -0,0 +1,650 @@ +/* -*- 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" + +//////////////////// 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 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 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 m_NamespaceList; + +}; + + +#endif diff --git a/mailnews/imap/src/nsImapCore.h b/mailnews/imap/src/nsImapCore.h new file mode 100644 index 000000000..5fd0e1c1f --- /dev/null +++ b/mailnews/imap/src/nsImapCore.h @@ -0,0 +1,188 @@ +/* -*- 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 */ +const eIMAPCapabilityFlag kHasXOAuth2Capability = 0x800000000LL; /* AUTH XOAUTH2 extension */ + + +// 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..6bf5f3fbe --- /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 + 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 fUids; + nsTArray fFlags; + // Hash table, mapping uids to extra flags + nsDataHashtable m_customFlagsHash; + // Hash table, mapping UID+customAttributeName to customAttributeValue. + nsDataHashtable 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 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 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 accountManager = + do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv,rv); + + nsCOMPtr 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 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 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 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 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 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 aProtocol; + + nsresult rv = GetImapConnection(aImapUrl, getter_AddRefs(aProtocol)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr 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 protocolInstance; + nsImapProtocol::LogImapUrl("creating protocol instance to retry queued url", aImapUrl); + nsCOMPtr thread(do_GetCurrentThread()); + rv = GetImapConnection(aImapUrl, getter_AddRefs(protocolInstance)); + if (NS_SUCCEEDED(rv) && protocolInstance) + { + nsCOMPtr 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 protocolInstance ; + + MutexAutoLock mon(mLock); + int32_t cnt = m_urlQueue.Count(); + + while (cnt > 0 && !urlRun && keepGoing) + { + nsCOMPtr aImapUrl(m_urlQueue[0]); + nsCOMPtr 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 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 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 aMailNewsUrl(do_QueryInterface(aImapUrl, &rv)); + + if (aMailNewsUrl && aImapUrl) + { + nsCOMPtr mockChannel; + + if (NS_SUCCEEDED(aImapUrl->GetMockChannel(getter_AddRefs(mockChannel))) && mockChannel) + { + nsresult requestStatus; + nsCOMPtr 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 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 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 connection; + nsCOMPtr 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 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 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 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 connection; + bool isBusy = false, isInbox = false; + nsCString inFolderName; + nsCString connectionFolderName; + nsCOMPtr 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 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 rootMsgFolder; + rv = GetRootFolder(getter_AddRefs(rootMsgFolder)); + NS_ENSURE_SUCCESS(rv, rv); + + if (!rootMsgFolder) return NS_ERROR_FAILURE; + + nsCOMPtr imapService = do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr 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 imapService = do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr 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 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 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 hostFolder; + nsCOMPtr aFolder; + bool explicitlyVerify = false; + + *aNewFolder = false; + nsCOMPtr 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 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 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 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 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 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 rootFolder; + nsresult rv = GetRootFolder(getter_AddRefs(rootFolder)); + if(NS_SUCCEEDED(rv) && rootFolder) + { + nsCOMPtr imapRoot = do_QueryInterface(rootFolder); + if (imapRoot) + { + nsCOMPtr 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 rootFolder; + nsresult rv = GetRootFolder(getter_AddRefs(rootFolder)); + if(NS_SUCCEEDED(rv) && rootFolder) + { + nsCOMPtr imapRoot = do_QueryInterface(rootFolder); + if (imapRoot) + { + nsCOMPtr foundFolder; + rv = imapRoot->FindOnlineSubFolder(folderPath, getter_AddRefs(foundFolder)); + if (NS_SUCCEEDED(rv) && foundFolder) + { + nsCOMPtr 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 rootFolder; + nsresult rv = GetRootFolder(getter_AddRefs(rootFolder)); + if(NS_SUCCEEDED(rv) && rootFolder) + { + nsCOMPtr imapRoot = do_QueryInterface(rootFolder); + if (imapRoot) + { + nsCOMPtr 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 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 rdf(do_GetService(kRDFServiceCID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr res; + rv = rdf->GetResource(uriString, getter_AddRefs(res)); + if (NS_SUCCEEDED(rv)) + { + nsCOMPtr 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 me; + rv = GetFolder(oldName, getter_AddRefs(me)); + if (NS_FAILED(rv)) + return rv; + + nsCOMPtr 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 folder; + folder = do_QueryInterface(me, &rv); + if (NS_SUCCEEDED(rv)) + { + folder->RenameLocal(tmpNewName, parent); + nsCOMPtr parentImapFolder = do_QueryInterface(parent); + + if (parentImapFolder) + parentImapFolder->RenameClient(msgWindow, me, oldName, tmpNewName); + + nsCOMPtr 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 folderRenameAtom; + folderRenameAtom = MsgGetAtom("RenameCompleted"); + newFolder->NotifyFolderEvent(folderRenameAtom); + } + } + } + } + return rv; +} + +NS_IMETHODIMP nsImapIncomingServer::FolderIsNoSelect(const nsACString& aFolderName, bool *result) +{ + NS_ENSURE_ARG_POINTER(result); + nsCOMPtr 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 rootFolder; + nsresult rv = GetRootFolder(getter_AddRefs(rootFolder)); + if(NS_SUCCEEDED(rv) && rootFolder) + { + nsCOMPtr imapRoot = do_QueryInterface(rootFolder); + if (imapRoot) + { + nsCOMPtr 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 rootFolder; + nsresult rv = GetRootFolder(getter_AddRefs(rootFolder)); + if (NS_SUCCEEDED(rv) && rootFolder) + { + nsCOMPtr folder; + rv = rootFolder->FindSubFolder(folderName, getter_AddRefs(folder)); + if (NS_SUCCEEDED(rv) && folder) + { + nsCOMPtr imapFolder = do_QueryInterface(folder); + if (imapFolder) + imapFolder->GetVerifiedAsOnlineFolder(aResult); + } + } + return rv; +} + +NS_IMETHODIMP nsImapIncomingServer::DiscoveryDone() +{ + if (mDoingSubscribeDialog) + return NS_OK; + + nsCOMPtr 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 rdf(do_GetService("@mozilla.org/rdf/rdf-service;1", + &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr accountMgr = do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr 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 res; + if (!folderUri.IsEmpty() && NS_SUCCEEDED(rdf->GetResource(folderUri, getter_AddRefs(res)))) + { + nsCOMPtr folder(do_QueryInterface(res, &rv)); + if (NS_SUCCEEDED(rv)) + rv = folder->SetFlag(nsMsgFolderFlags::Templates); + } + } + + nsCOMPtr 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 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 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 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 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 currentImapFolder(unverifiedFolders[k]); + nsCOMPtr 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 res; + nsCOMPtr folder; + nsCOMPtr rootMsgFolder; + nsresult rv = GetRootFolder(getter_AddRefs(rootMsgFolder)); + NS_ENSURE_SUCCESS(rv, false); + nsCOMPtr 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 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 prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv); + if (NS_SUCCEEDED(rv)) + prefBranch->GetBoolPref("mail.imap.auto_unsubscribe_from_noselect_folders", &autoUnsubscribeFromNoSelectFolders); + + nsCOMPtr subFolders; + + rv = curFolder->GetSubFolders(getter_AddRefs(subFolders)); + if(NS_SUCCEEDED(rv)) + { + bool moreFolders; + + while (NS_SUCCEEDED(subFolders->HasMoreElements(&moreFolders)) && moreFolders) + { + nsCOMPtr child; + rv = subFolders->GetNext(getter_AddRefs(child)); + if (NS_SUCCEEDED(rv) && child) + { + bool childVerified = false; + nsCOMPtr childImapFolder = do_QueryInterface(child, &rv); + if (NS_SUCCEEDED(rv) && childImapFolder) + { + uint32_t flags; + + nsCOMPtr 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 parent; + rv = curFolder->GetParent(getter_AddRefs(parent)); + + if (NS_SUCCEEDED(rv) && parent) + { + nsCOMPtr imapParent = do_QueryInterface(parent); + if (imapParent) + imapParent->RemoveSubFolder(curFolder); + } + + return rv; +} + +bool nsImapIncomingServer::NoDescendentsAreVerified(nsIMsgFolder *parentFolder) +{ + bool nobodyIsVerified = true; + nsCOMPtr subFolders; + nsresult rv = parentFolder->GetSubFolders(getter_AddRefs(subFolders)); + if(NS_SUCCEEDED(rv)) + { + bool moreFolders; + while (NS_SUCCEEDED(subFolders->HasMoreElements(&moreFolders)) && + moreFolders && nobodyIsVerified) + { + nsCOMPtr child; + rv = subFolders->GetNext(getter_AddRefs(child)); + if (NS_SUCCEEDED(rv) && child) + { + bool childVerified = false; + nsCOMPtr childImapFolder = do_QueryInterface(child, &rv); + if (NS_SUCCEEDED(rv) && childImapFolder) + { + nsCOMPtr childFolder = do_QueryInterface(child, &rv); + rv = childImapFolder->GetVerifiedAsOnlineFolder(&childVerified); + nobodyIsVerified = !childVerified && NoDescendentsAreVerified(childFolder); + } + } + } + } + return nobodyIsVerified; +} + + +bool nsImapIncomingServer::AllDescendentsAreNoSelect(nsIMsgFolder *parentFolder) +{ + bool allDescendentsAreNoSelect = true; + nsCOMPtr subFolders; + nsresult rv = parentFolder->GetSubFolders(getter_AddRefs(subFolders)); + if(NS_SUCCEEDED(rv)) + { + bool moreFolders; + while (NS_SUCCEEDED(subFolders->HasMoreElements(&moreFolders)) && + moreFolders && allDescendentsAreNoSelect) + { + nsCOMPtr child; + rv = subFolders->GetNext(getter_AddRefs(child)); + if (NS_SUCCEEDED(rv) && child) + { + bool childIsNoSelect = false; + nsCOMPtr childImapFolder = do_QueryInterface(child, &rv); + if (NS_SUCCEEDED(rv) && childImapFolder) + { + uint32_t flags; + nsCOMPtr 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 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 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 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 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 rootFolder; + rv = GetRootFolder(getter_AddRefs(rootFolder)); + NS_ENSURE_SUCCESS(rv, rv); + return ResetFoldersToUnverified(rootFolder); + } + else + { + nsCOMPtr subFolders; + nsCOMPtr 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 child; + rv = subFolders->GetNext(getter_AddRefs(child)); + if (NS_SUCCEEDED(rv) && child) + { + nsCOMPtr childFolder = do_QueryInterface(child, &rv); + if (NS_SUCCEEDED(rv) && childFolder) + { + rv = ResetFoldersToUnverified(childFolder); + if (NS_FAILED(rv)) + break; + } + } + } + } + return rv; +} + +void +nsImapIncomingServer::GetUnverifiedFolders(nsCOMArray &aFoldersArray) +{ + nsCOMPtr rootFolder; + if (NS_FAILED(GetRootFolder(getter_AddRefs(rootFolder))) || !rootFolder) + return; + + nsCOMPtr 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 &aFoldersArray) +{ + nsCOMPtr 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 subFolders; + if (NS_SUCCEEDED(parentFolder->GetSubFolders(getter_AddRefs(subFolders)))) + { + bool moreFolders; + + while (NS_SUCCEEDED(subFolders->HasMoreElements(&moreFolders)) && moreFolders) + { + nsCOMPtr child; + subFolders->GetNext(getter_AddRefs(child)); + if (child) + { + nsCOMPtr 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 asyncPrompter = + do_GetService(NS_MSGASYNCPROMPTER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr 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 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 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 rootFolder; + nsresult rv = GetRootFolder(getter_AddRefs(rootFolder)); + if (NS_SUCCEEDED(rv) && rootFolder) + { + nsCOMPtr 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 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 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 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 msgWindow; + nsCOMPtr 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 msgFolder; + nsCOMPtr mailUrl = do_QueryInterface(imapUrl); + mailUrl->GetFolder(getter_AddRefs(msgFolder)); + if (msgFolder) + { + nsresult rv; + nsCOMPtr 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 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 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 imapService = do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr 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 msgFolder; + if (rootMsgFolder && !aName.IsEmpty()) + rv = rootMsgFolder->FindSubFolder(folderCName, getter_AddRefs(msgFolder)); + + nsCOMPtr 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 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 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 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:, + * 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 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 rootMsgFolder; + nsresult rv = GetRootMsgFolder(getter_AddRefs(rootMsgFolder)); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr 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 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 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 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 enumerator; + rv = aFolder->GetSubFolders(getter_AddRefs(enumerator)); + if (NS_FAILED(rv)) + return rv; + + bool hasMore; + while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMore)) && hasMore) + { + nsCOMPtr item; + enumerator->GetNext(getter_AddRefs(item)); + + nsCOMPtr 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 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 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 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 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 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 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 rdf = do_GetService("@mozilla.org/rdf/rdf-service;1", &rv); + NS_ENSURE_SUCCESS(rv,rv); + + nsCOMPtr resource; + rv = rdf->GetResource(folderUriWithNamespace, getter_AddRefs(resource)); + NS_ENSURE_SUCCESS(rv,rv); + + nsCOMPtr 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 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 &aFoldersArray); + void GetUnverifiedFolders(nsCOMArray &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 m_connectionCache; + nsCOMArray m_urlQueue; + nsCOMPtr m_stringBundle; + nsCOMArray m_subscribeFolders; // used to keep folder resources around while subscribe UI is up. + nsCOMArray m_foldersToStat; // folders to check for new mail with Status + nsTArray 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 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..12e687360 --- /dev/null +++ b/mailnews/imap/src/nsImapMailFolder.cpp @@ -0,0 +1,9868 @@ +/* -*- 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 +#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 "nsAlgorithm.h" +#include "nsMsgLineBuffer.h" +#include +#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 dirIterator; + rv = srcDir->GetDirectoryEntries(getter_AddRefs(dirIterator)); + if (NS_FAILED(rv)) return rv; + + rv = dirIterator->HasMoreElements(&hasMore); + if (NS_FAILED(rv)) return rv; + + nsCOMPtr dirEntry; + + while (hasMore) + { + nsCOMPtr 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 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 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 msgFolder; + rv = GetChildWithURI(uri, false/*deep*/, true /*case Insensitive*/, getter_AddRefs(msgFolder)); + if (NS_SUCCEEDED(rv) && msgFolder) + return NS_MSG_FOLDER_EXISTS; + + nsCOMPtr res; + rv = rdf->GetResource(uri, getter_AddRefs(res)); + if (NS_FAILED(rv)) + return rv; + + nsCOMPtr folder(do_QueryInterface(res, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr path; + rv = CreateDirectoryForFolder(getter_AddRefs(path)); + NS_ENSURE_SUCCESS(rv, rv); + + folder->GetFlags((uint32_t *)&flags); + + flags |= nsMsgFolderFlags::Mail; + + nsCOMPtr 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 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 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 msgFolder; + rv = GetChildWithURI(uri, false/*deep*/, isInbox /*case Insensitive*/, getter_AddRefs(msgFolder)); + if (NS_SUCCEEDED(rv) && msgFolder) + return NS_MSG_FOLDER_EXISTS; + + nsCOMPtr res; + rv = rdf->GetResource(uri, getter_AddRefs(res)); + if (NS_FAILED(rv)) + return rv; + + nsCOMPtr folder(do_QueryInterface(res, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + folder->SetFilePath(dbPath); + nsCOMPtr 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 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 child; + nsCOMPtr server; + rv = GetServer(getter_AddRefs(server)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr children; + rv = path->GetDirectoryEntries(getter_AddRefs(children)); + bool more = false; + if (children) + children->HasMoreElements(&more); + nsCOMPtr dirEntry; + + while (more) + { + nsCOMPtr 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 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 cacheElement; + nsCOMPtr curFolder = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr 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 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 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 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 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 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 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 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 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 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 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 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 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 action; + rv = filter->GetActionAt(actionIndex, getter_AddRefs(action)); + if (NS_FAILED(rv) || !action) + continue; + + nsCOMPtr 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 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 imapService = do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr url; + rv = imapService->SelectFolder(this, m_urlListener, aMsgWindow, getter_AddRefs(url)); + if (NS_SUCCEEDED(rv)) + { + m_urlRunning = true; + m_updatingFolder = true; + } + if (url) + { + nsCOMPtr 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 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 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 rdf(do_GetService(kRDFServiceCID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr res; + nsCOMPtr 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 mailDBFactory; + nsCOMPtr child; + + nsCOMPtr msgDBService = do_GetService(NS_MSGDB_SERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr unusedDB; + nsCOMPtr 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 folderInfo; + rv = unusedDB->GetDBFolderInfo(getter_AddRefs(folderInfo)); + nsCOMPtr 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 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 folderCreateAtom; + if(NS_SUCCEEDED(rv) && child) + { + NotifyItemAdded(child); + folderCreateAtom = MsgGetAtom("FolderCreateCompleted"); + child->NotifyFolderEvent(folderCreateAtom); + nsCOMPtr 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 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 folders(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv)); + NS_ENSURE_TRUE(folders, rv); + nsCOMPtr 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 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 rdf(do_GetService(kRDFServiceCID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr 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 imapService = do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr 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 parent; + do + { + GetParent(getter_AddRefs(parent)); + if (parent) + { + nsCOMPtr 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 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 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 imapServer; + nsresult rv = GetImapIncomingServer(getter_AddRefs(imapServer)); + NS_ENSURE_SUCCESS(rv, rv); + imapServer->GetAutoSyncMaxAgeDays(&numDaysToKeepOfflineMsgs); + + nsCOMPtr holdDBOpen; + if (numDaysToKeepOfflineMsgs > 0) + { + bool dbWasCached = mDatabase != nullptr; + rv = GetDatabase(); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr hdrs; + rv = mDatabase->EnumerateMessages(getter_AddRefs(hdrs)); + NS_ENSURE_SUCCESS(rv, rv); + bool hasMore = false; + + PRTime cutOffDay = + MsgConvertAgeInDaysToCutoffDate(numDaysToKeepOfflineMsgs); + + nsCOMPtr 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 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 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 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 folderArray, offlineFolderArray; + + nsCOMPtr rootFolder; + nsCOMPtr 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 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 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 imapService = do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv,rv); + + nsCOMPtr uri; + rv = imapService->UpdateFolderStatus(this, aListener, getter_AddRefs(uri)); + if (uri && !aMsgWindow) + { + nsCOMPtr 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 trashFolder; + nsresult rv = GetTrashFolder(getter_AddRefs(trashFolder)); + if (NS_SUCCEEDED(rv)) + { + nsCOMPtr 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 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. + + nsCOMPtr trashDB; + if (WeAreOffline()) + { + nsCOMPtr trashDB; + rv = trashFolder->GetMsgDatabase(getter_AddRefs(trashDB)); + if (trashDB) + { + nsMsgKey fakeKey; + trashDB->GetNextFakeOfflineMsgKey(&fakeKey); + + nsCOMPtr op; + rv = trashDB->GetOfflineOpForKey(fakeKey, true, getter_AddRefs(op)); + trashFolder->SetFlag(nsMsgFolderFlags::OfflineEvents); + op->SetOperation(nsIMsgOfflineImapOperation::kDeleteAllMsgs); + } + return rv; + } + nsCOMPtr transferInfo; + rv = trashFolder->GetDBTransferInfo(getter_AddRefs(transferInfo)); + rv = trashFolder->Delete(); // delete summary spec + trashFolder->SetDBTransferInfo(transferInfo); + + trashFolder->SetSizeOnDisk(0); + nsCOMPtr imapService = do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + if (aListener) + rv = imapService->DeleteAllMessages(trashFolder, aListener, nullptr); + else + { + nsCOMPtr 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 enumerator; + nsCOMPtr item; + nsCOMArray 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 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); + } + } + + // The trash folder has effectively been deleted + nsCOMPtr notifier(do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID)); + if (notifier) + notifier->NotifyFolderDeleted(trashFolder); + + return NS_OK; + } + return rv; +} + +NS_IMETHODIMP nsImapMailFolder::Delete() +{ + nsresult rv; + if (!mDatabase) + { + // Check if anyone has this db open. If so, do a force closed. + nsCOMPtr msgDBService = do_GetService(NS_MSGDB_SERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + msgDBService->CachedDBForFolder(this, getter_AddRefs(mDatabase)); + } + if (mDatabase) + { + mDatabase->ForceClosed(); + mDatabase = nullptr; + } + + nsCOMPtr path; + rv = GetFilePath(getter_AddRefs(path)); + if (NS_SUCCEEDED(rv)) + { + nsCOMPtr summaryLocation; + rv = GetSummaryFileLocation(path, getter_AddRefs(summaryLocation)); + if (NS_SUCCEEDED(rv)) + { + bool exists = false; + rv = summaryLocation->Exists(&exists); + if (NS_SUCCEEDED(rv) && exists) + { + rv = summaryLocation->Remove(false); + if (NS_FAILED(rv)) + NS_WARNING("failed to remove imap summary file"); + } + } + } + if (mPath) + mPath->Remove(false); + // 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 docShell; + if (msgWindow) + msgWindow->GetRootDocShell(getter_AddRefs(docShell)); + if (docShell) + { + nsCOMPtr 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 dialog(do_GetInterface(docShell)); + // setting up the dialog title + nsCOMPtr 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 incomingImapServer; + GetImapIncomingServer(getter_AddRefs(incomingImapServer)); + if (incomingImapServer) + RecursiveCloseActiveConnections(incomingImapServer); + + nsCOMPtr 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 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 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 oldPathFile; + rv = GetFilePath(getter_AddRefs(oldPathFile)); + if (NS_FAILED(rv)) return rv; + + nsCOMPtr parentPathFile; + rv = parent->GetFilePath(getter_AddRefs(parentPathFile)); + NS_ENSURE_SUCCESS(rv,rv); + + bool isDirectory = false; + parentPathFile->IsDirectory(&isDirectory); + if (!isDirectory) + AddDirectorySeparator(parentPathFile); + + nsCOMPtr dirFile; + + int32_t count = mSubFolders.Count(); + if (count > 0) + rv = CreateDirectoryForFolder(getter_AddRefs(dirFile)); + + nsCOMPtr 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 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 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 server; + if (NS_SUCCEEDED(GetServer(getter_AddRefs(server))) && server) + { + nsCOMPtr 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 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 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 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 db; + nsCOMPtr 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& 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 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 *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 res; + nsAutoCString uri; + bool deleteImmediatelyNoTrash = false; + nsAutoCString messageIds; + nsTArray srcKeyArray; + bool deleteMsgs = true; //used for toggling delete status - default is true + nsMsgImapDeleteModel deleteModel = nsMsgImapDeleteModels::MoveToTrash; + imapMessageFlagsType messageFlags = kImapMsgDeletedFlag; + + nsCOMPtr 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 rootFolder; + nsCOMPtr 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 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 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 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 urlListener = do_QueryInterface(listener); + if (deleteMsgs) + messageFlags |= kImapMsgSeenFlag; + rv = StoreImapFlags(messageFlags, deleteMsgs, srcKeyArray.Elements(), + srcKeyArray.Length(), urlListener); + + if (NS_SUCCEEDED(rv)) + { + if (mDatabase) + { + nsCOMPtr 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 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 srcFolder; + nsCOMPtrsrcSupport; + uint32_t count = 0; + rv = messages->GetLength(&count); + + rv = QueryInterface(NS_GET_IID(nsIMsgFolder), getter_AddRefs(srcFolder)); + nsCOMPtr 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 parent; + nsCOMPtr 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 curFolder; + nsCOMPtr urlListener; + nsCOMPtr 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 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 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 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 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 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 docShell; + msgWindow->GetRootDocShell(getter_AddRefs(docShell)); + nsCOMPtr 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 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 rootFolder; + nsresult rv = GetRootFolder(getter_AddRefs(rootFolder)); + if(NS_SUCCEEDED(rv) && rootFolder) + { + nsCOMPtr imapServer; + rv = GetImapIncomingServer(getter_AddRefs(imapServer)); + NS_ENSURE_SUCCESS(rv, rv); + bool performingBiff = false; + nsCOMPtr 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 inbox; + rv = rootFolder->GetFolderWithFlags(nsMsgFolderFlags::Inbox, + getter_AddRefs(inbox)); + if (inbox) + { + nsCOMPtr 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 *keysOfMessagesToDownload) +{ + NS_ENSURE_ARG(keysOfMessagesToDownload); + NS_ENSURE_TRUE(mDatabase, NS_ERROR_FAILURE); + + nsCOMPtr enumerator; + nsresult rv = mDatabase->EnumerateMessages(getter_AddRefs(enumerator)); + if (NS_SUCCEEDED(rv) && enumerator) + { + bool hasMore; + while (NS_SUCCEEDED(rv = enumerator->HasMoreElements(&hasMore)) && hasMore) + { + nsCOMPtr supports; + rv = enumerator->GetNext(getter_AddRefs(supports)); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr 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 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 existingKeys; + nsTArray keysToDelete; + uint32_t numNewUnread; + nsCOMPtr 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 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 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 pathFile; + rv = GetFilePath(getter_AddRefs(pathFile)); + if (NS_FAILED(rv)) return rv; + + nsCOMPtr msgDBService = do_GetService(NS_MSGDB_SERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr 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 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 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 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 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 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 headerInfo; + nsCOMPtr 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 msgHdr; + headerInfo->GetMsgHdrs(&msgHdrs); + // create an input stream based on the hdr string. + nsCOMPtr 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 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 msgWindow; + nsCOMPtr msgUrl; + if (imapUrl) + { + msgUrl = do_QueryInterface(imapUrl, &rv); + NS_ENSURE_SUCCESS(rv, rv); + msgUrl->GetMsgWindow(getter_AddRefs(msgWindow)); + } + + nsCOMPtr server; + rv = GetServer(getter_AddRefs(server)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr 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 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 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 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 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 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 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 urlListener; + m_copyState->m_msgFileStream->Close(); + // m_tmpFile can be stale because we wrote to it + nsCOMPtr tmpFile; + m_copyState->m_tmpFile->Clone(getter_AddRefs(tmpFile)); + m_copyState->m_tmpFile = tmpFile; + nsCOMPtr imapService = do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv,rv); + + rv = QueryInterface(NS_GET_IID(nsIUrlListener), getter_AddRefs(urlListener)); + nsCOMPtr 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 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 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 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 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 messageArray(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv)); + NS_ENSURE_TRUE(messageArray, rv); + messageArray->AppendElement(msgHdr, false); + + nsCOMPtr dstFolder; + rv = GetExistingFolder(actionTargetFolderUri, getter_AddRefs(dstFolder)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr 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 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(filterPriority)); + } + break; + case nsMsgFilterAction::Label: + { + nsMsgLabelValue filterLabel; + filterAction->GetLabel(&filterLabel); + mDatabase->SetUint32PropertyByHdr(msgHdr, "label", + static_cast(filterLabel)); + StoreImapFlags((filterLabel << 9), true, &msgKey, 1, nullptr); + } + break; + case nsMsgFilterAction::AddTag: + { + nsCString keyword; + filterAction->GetStrValue(keyword); + nsCOMPtr 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 *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 server; + rv = GetServer(getter_AddRefs(server)); + NS_ENSURE_SUCCESS(rv, rv); + if (!forwardTo.IsEmpty()) + { + nsCOMPtr 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 server; + GetServer(getter_AddRefs(server)); + NS_ENSURE_SUCCESS(rv, rv); + if (!replyTemplateUri.IsEmpty()) + { + nsCOMPtr 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 customAction; + rv = filterAction->GetCustomAction(getter_AddRefs(customAction)); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString value; + filterAction->GetStrValue(value); + + nsCOMPtr 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 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 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 imapFolder = do_QueryInterface(aDstFolder); + if (imapFolder) + { + nsImapMailFolder *destImapFolder = static_cast(aDstFolder); + nsCOMPtr messages(do_CreateInstance(NS_ARRAY_CONTRACTID)); + nsCOMPtr 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 offlineOps; + if (NS_SUCCEEDED(dstFolderDB->ListAllOfflineOpIds(&offlineOps))) + { + nsCString srcFolderUri; + GetURI(srcFolderUri); + nsCOMPtr 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 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 imapService = do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr resultUrl; + nsAutoCString uids; + AllocateUidStringFromKeys(aMsgKeys, aNumKeys, uids); + rv = imapService->OnlineMessageCopy(this, uids, aDstFolder, + true, isMove, aUrlListener, + getter_AddRefs(resultUrl), nullptr, aWindow); + if (resultUrl) + { + nsCOMPtr mailnewsUrl = do_QueryInterface(resultUrl, &rv); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr 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 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 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 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 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 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 extProtService = do_GetService(NS_EXTERNALPROTOCOLSERVICE_CONTRACTID); + if (extProtService) + { + nsAutoCString scheme; + nsCOMPtr 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 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 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 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 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 rdf(do_GetService(kRDFServiceCID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr res; + rv = rdf->GetResource(destFolderUri, getter_AddRefs(res)); + if (NS_FAILED(rv)) + return rv; + + nsCOMPtr 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 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 pairs here in the imap code. + // nsTArray *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 &existingKeys, + nsTArray &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 hdrs; + nsresult rv = GetMessages(getter_AddRefs(hdrs)); + NS_ENSURE_SUCCESS_VOID(rv); + bool hasMore = false; + nsCOMPtr pHeader; + while (NS_SUCCEEDED(rv = hdrs->HasMoreElements(&hasMore)) && hasMore) + { + nsCOMPtr 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 &existingKeys, nsTArray &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 session = do_GetService(NS_MSGMAILSESSION_CONTRACTID); + bool folderOpen = false; + if (session) + session->IsFolderOpenInWindow(this, &folderOpen); + + int32_t hdrChunkSize = 200; + if (folderOpen) + { + nsresult rv; + nsCOMPtr 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 srcKeyArray; + nsresult rv = BuildIdsAndKeyArray(messages, messageIds, srcKeyArray); + if (NS_FAILED(rv) || messageIds.IsEmpty()) return rv; + + nsCOMPtr imapService = do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv,rv); + + rv = AcquireSemaphore(static_cast(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 runningURI; + bool noSelect; + GetFlag(nsMsgFolderFlags::ImapNoselect, &noSelect); + + if (!noSelect) + { + nsAutoCString messageIdsToDownload; + nsTArray msgsToDownload; + + GetDatabase(); + m_downloadingFolderForOfflineUse = true; + + rv = AcquireSemaphore(static_cast(this)); + if (NS_FAILED(rv)) + { + m_downloadingFolderForOfflineUse = false; + ThrowAlertMsg("operationFailedFolderBusy", msgWindow); + return rv; + } + nsCOMPtr 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 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 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(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 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 newMsgHdr; + GetMessageHeader(uidOfMessage, getter_AddRefs(newMsgHdr)); + GetMoveCoalescer(); + nsCOMPtr msgWindow; + if (imapUrl) + { + nsresult rv; + nsCOMPtr 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 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 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 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 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 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 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 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 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 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 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 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 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 &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 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 copyState; + runningUrl->GetCopyState(getter_AddRefs(copyState)); + if (copyState) + { + nsCOMPtr 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 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 session = + do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + if (aUrl) + { + nsCOMPtr 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(this), &hasSemaphore); + if (hasSemaphore) + ReleaseSemaphore(static_cast(this)); + if (downloadingForOfflineUse) + { + endedOfflineDownload = true; + EndOfflineDownload(); + } + nsCOMPtr msgWindow; + nsCOMPtr 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(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 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 srcFolder = do_QueryInterface(m_copyState->m_srcSupport, &rv); + if (m_copyState->m_isMove && !m_copyState->m_isCrossServerOp) + { + if (NS_SUCCEEDED(aExitCode)) + { + nsCOMPtr srcDB; + if (srcFolder) + rv = srcFolder->GetMsgDatabase(getter_AddRefs(srcDB)); + if (NS_SUCCEEDED(rv) && srcDB) + { + RefPtr msgTxn; + nsTArray 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 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 txnMgr; + m_copyState->m_msgWindow->GetTransactionManager(getter_AddRefs(txnMgr)); + if (txnMgr) + { + mozilla::DebugOnly 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 db; + rv = GetMsgDatabase(getter_AddRefs(db)); + if (NS_SUCCEEDED(rv) && db) + { + nsTArray 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 db; + rv = GetMsgDatabase(getter_AddRefs(db)); + if (NS_SUCCEEDED(rv) && db) + { + nsTArray 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 offlineStore; + (void) GetMsgStore(getter_AddRefs(offlineStore)); + if (offlineStore) + offlineStore->GetSupportsCompaction(&supportsCompaction); + + nsCOMPtr 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 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 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 copyService = do_GetService(NS_MSGCOPYSERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr srcFolder = do_QueryInterface(m_copyState->m_srcSupport); + if (srcFolder) + { + nsCOMPtr 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 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 parent; + rv = GetParent(getter_AddRefs(parent)); + if (NS_SUCCEEDED(rv) && parent) + { + nsCOMPtr 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 folderCreateAtom; + folderCreateAtom = MsgGetAtom("FolderCreateFailed"); + NotifyFolderEvent(folderCreateAtom); + } + break; + case nsIImapUrl::nsImapSubscribe: + if (NS_SUCCEEDED(aExitCode) && msgWindow) + { + nsCString canonicalFolderName; + imapUrl->CreateCanonicalSourceFolderPathString(getter_Copies(canonicalFolderName)); + nsCOMPtr rootFolder; + nsresult rv = GetRootFolder(getter_AddRefs(rootFolder)); + if(NS_SUCCEEDED(rv) && rootFolder) + { + nsCOMPtr imapRoot = do_QueryInterface(rootFolder); + if (imapRoot) + { + nsCOMPtr foundFolder; + rv = imapRoot->FindOnlineSubFolder(canonicalFolderName, getter_AddRefs(foundFolder)); + if (NS_SUCCEEDED(rv) && foundFolder) + { + nsCString uri; + nsCOMPtr msgFolder = do_QueryInterface(foundFolder); + if (msgFolder) + { + msgFolder->GetURI(uri); + nsCOMPtr 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 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 msgTxn; + nsCOMPtr copyState; + + if (aUrl) + aUrl->GetCopyState(getter_AddRefs(copyState)); + + if (copyState) + { + nsCOMPtr 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 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 imapUrl (do_QueryInterface(aUrl)); + nsCOMPtr copyState; + NS_ENSURE_TRUE(imapUrl, NS_ERROR_FAILURE); + + imapUrl->GetCopyState(getter_AddRefs(copyState)); + if (copyState) + { + nsCOMPtr listener = do_QueryInterface(copyState); + if (listener) + listener->StartMessage(); + } + return NS_OK; +} + +NS_IMETHODIMP +nsImapMailFolder::EndMessage(nsIMsgMailNewsUrl * aUrl, nsMsgKey uidOfMessage) +{ + nsCOMPtr imapUrl (do_QueryInterface(aUrl)); + nsCOMPtr copyState; + NS_ENSURE_TRUE(imapUrl, NS_ERROR_FAILURE); + imapUrl->GetCopyState(getter_AddRefs(copyState)); + if (copyState) + { + nsCOMPtr 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 ..." + // expect search results in the form of "* SEARCH ..." + nsCString tokenString(searchHitLine); + char *currentPosition = PL_strcasestr(tokenString.get(), "SEARCH"); + if (currentPosition) + { + currentPosition += strlen("SEARCH"); + bool shownUpdateAlert = false; + char *hitUidToken = NS_strtok(WHITESPACE, ¤tPosition); + while (hitUidToken) + { + long naturalLong; // %l is 64 bits on OSF1 + sscanf(hitUidToken, "%ld", &naturalLong); + nsMsgKey hitUid = (nsMsgKey) naturalLong; + + nsCOMPtr hitHeader; + rv = mDatabase->GetMsgHdrForKey(hitUid, getter_AddRefs(hitHeader)); + if (NS_SUCCEEDED(rv) && hitHeader) + { + nsCOMPtr searchSession; + nsCOMPtr 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, ¤tPosition); + } +} + return NS_OK; +} + +NS_IMETHODIMP +nsImapMailFolder::SetAppendMsgUid(nsMsgKey aKey, + nsIImapUrl * aUrl) +{ + nsresult rv; + nsCOMPtr copyState; + if (aUrl) + aUrl->GetCopyState(getter_AddRefs(copyState)); + if (copyState) + { + nsCOMPtr mailCopyState = do_QueryInterface(copyState, &rv); + if (NS_FAILED(rv)) return rv; + + if (mailCopyState->m_undoMsgTxn) // CopyMessages() + { + RefPtr 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 copyState; + + if (aUrl) + aUrl->GetCopyState(getter_AddRefs(copyState)); + if (copyState) + { + nsCOMPtr 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 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 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 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 runningUri; + aProtocol->GetRunningUrl(getter_AddRefs(runningUri)); + if (runningUri) + { + nsCOMPtr 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 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 db; + nsCOMPtr 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 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 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 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 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 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 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 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: + AdoptUTF8StringEnumerator(nsTArray* array) : + mStrings(array), mIndex(0) + {} + + NS_DECL_ISUPPORTS + NS_DECL_NSIUTF8STRINGENUMERATOR +private: + ~AdoptUTF8StringEnumerator() + { + delete mStrings; + } + + nsTArray* 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* resultArray = new nsTArray; + 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 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 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 mockChannel; + aImapUrl->GetMockChannel(getter_AddRefs(mockChannel)); + if (mockChannel) + { + nsCOMPtr progressSink; + mockChannel->GetProgressEventSink(getter_AddRefs(progressSink)); + if (progressSink) + { + nsCOMPtr 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 server; + nsresult rv = GetServer(getter_AddRefs(server)); + if (NS_SUCCEEDED(rv) && server) + { + nsCOMPtr serverSink = do_QueryInterface(server); + if (serverSink) + serverSink->GetImapStringByName(aMsgName, progressMsg); + } + if (progressMsg.IsEmpty()) + IMAPGetStringByName(aMsgName, getter_Copies(progressMsg)); + + if (aProtocol && !progressMsg.IsEmpty()) + { + nsCOMPtr 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 imapUrl; + aProtocol->GetRunningImapURL(getter_AddRefs(imapUrl)); + if (imapUrl) + { + nsCOMPtr mockChannel; + imapUrl->GetMockChannel(getter_AddRefs(mockChannel)); + if (mockChannel) + { + nsCOMPtr progressSink; + mockChannel->GetProgressEventSink(getter_AddRefs(progressSink)); + if (progressSink) + { + nsCOMPtr 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 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 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 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 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 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 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 srcKeyArray; + rv = BuildIdsAndKeyArray(messages, messageIds, srcKeyArray); + + RefPtr 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 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 returnOp; + nsOfflineImapOperationType opType; + op->GetOperation(&opType); + NS_ASSERTION(opType & nsIMsgOfflineImapOperation::kMoveResult, "not an offline move op"); + + nsCString sourceFolderURI; + op->GetSourceFolderURI(getter_Copies(sourceFolderURI)); + + nsCOMPtr res; + nsresult rv; + nsCOMPtr rdf(do_GetService(kRDFServiceCID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + rv = rdf->GetResource(sourceFolderURI, getter_AddRefs(res)); + if (NS_SUCCEEDED(rv)) + { + nsCOMPtr sourceFolder(do_QueryInterface(res, &rv)); + if (NS_SUCCEEDED(rv) && sourceFolder) + { + if (sourceFolder) + { + nsCOMPtr 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 returnOp; + nsCString sourceFolderURI; + op->GetSourceFolderURI(getter_Copies(sourceFolderURI)); + + nsCOMPtr res; + nsresult rv; + + nsCOMPtr rdf(do_GetService(kRDFServiceCID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + rv = rdf->GetResource(sourceFolderURI, getter_AddRefs(res)); + if (NS_SUCCEEDED(rv)) + { + nsCOMPtr sourceFolder(do_QueryInterface(res, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr 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 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 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 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 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 sourceMailDB; + nsCOMPtr srcDbFolderInfo; + srcFolder->GetDBFolderInfoAndDB(getter_AddRefs(srcDbFolderInfo), getter_AddRefs(sourceMailDB)); + bool deleteToTrash = false; + bool deleteImmediately = false; + uint32_t srcCount; + messages->GetLength(&srcCount); + nsCOMPtr imapServer; + rv = GetImapIncomingServer(getter_AddRefs(imapServer)); + nsCOMPtr msgHdrsCopied(do_CreateInstance(NS_ARRAY_CONTRACTID)); + nsCOMPtr 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 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 txnMgr; + if (msgWindow) + msgWindow->GetTransactionManager(getter_AddRefs(txnMgr)); + if (txnMgr) + txnMgr->BeginBatch(nullptr); + nsCOMPtr 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 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 inputStream; + bool reusable = false; + nsCOMPtr outputStream; + nsTArray addedKeys; + nsTArray srcKeyArray; + nsCOMArray addedHdrs; + nsCOMArray 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 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 sourceOp; + rv = sourceMailDB->GetOfflineOpForKey(originalKey, true, getter_AddRefs(sourceOp)); + if (NS_SUCCEEDED(rv) && sourceOp) + { + srcFolder->SetFlag(nsMsgFolderFlags::OfflineEvents); + nsCOMPtr 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 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 mailHdr; + rv = sourceMailDB->GetMsgHdrForKey(originalKey, getter_AddRefs(mailHdr)); + if (NS_SUCCEEDED(rv) && mailHdr) + { + bool successfulCopy = false; + nsMsgKey srcDBhighWaterMark; + srcDbFolderInfo->GetHighWater(&srcDBhighWaterMark); + + nsCOMPtr 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 offlineStore; + (void) GetMsgStore(getter_AddRefs(offlineStore)); + if (offlineStore) + offlineStore->FinishNewMessage(outputStream, newMailHdr); + } + else + database->MarkOffline(fakeBase + sourceKeyIndex, false, nullptr); + + nsCOMPtr 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 addHdrMsgTxn = new + nsImapOfflineTxn(this, &addedKeys, nullptr, this, isMove, nsIMsgOfflineImapOperation::kAddedHeader, + addedHdrs); + if (addHdrMsgTxn && txnMgr) + txnMgr->DoTransaction(addHdrMsgTxn); + RefPtr undoMsgTxn = new + nsImapOfflineTxn(srcFolder, &srcKeyArray, messageIds.get(), this, + isMove, moveCopyOpType, srcMsgs); + if (undoMsgTxn) + { + if (isMove) + { + undoMsgTxn->SetTransactionType(nsIMessenger::eMoveMsg); + nsCOMPtr 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(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 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 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 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 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 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 srcServer; + nsCOMPtr dstServer; + nsCOMPtr 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(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 keyArray(numMsgs); + for (uint32_t i = 0; i < numMsgs; i++) + { + nsCOMPtr aMessage = do_QueryElementAt(messages, i, &rv); + if (NS_SUCCEEDED(rv) && aMessage) + { + nsMsgKey key; + aMessage->GetMessageKey(&key); + keyArray.AppendElement(key); + } + } + keyArray.Sort(); + + nsCOMPtr 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 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 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 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 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 m_newDestFolder; + nsCOMPtr m_origSrcFolder; + nsCOMPtr m_curDestParent; + nsCOMPtr m_curSrcFolder; + bool m_isMoveFolder; + nsCOMPtr m_copySrvcListener; + nsCOMPtr m_msgWindow; + int32_t m_childIndex; + nsCOMArray m_srcChildFolders; + nsCOMArray 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 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 imapUrl = do_QueryInterface(aUrl); + if (imapUrl) + { + nsImapAction imapAction = nsIImapUrl::nsImapTest; + imapUrl->GetImapAction(&imapAction); + + switch(imapAction) + { + case nsIImapUrl::nsImapEnsureExistsFolder: + { + nsCOMPtr 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(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 enumerator; + rv = m_curSrcFolder->GetSubFolders(getter_AddRefs(enumerator)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr item; + bool hasMore = false; + uint32_t childIndex = 0; + while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMore)) && hasMore) + { + rv = enumerator->GetNext(getter_AddRefs(item)); + nsCOMPtr 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 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 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 newMsgFolder; + nsString folderName; + srcFolder->GetName(folderName); + + nsAutoString safeFolderName(folderName); + NS_MsgHashIfNecessary(safeFolderName); + + srcFolder->ForceDBClosed(); + + nsCOMPtr oldPathFile; + rv = srcFolder->GetFilePath(getter_AddRefs(oldPathFile)); + NS_ENSURE_SUCCESS(rv,rv); + + nsCOMPtr summaryFile; + GetSummaryFileLocation(oldPathFile, getter_AddRefs(summaryFile)); + + nsCOMPtr 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 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 srcDB; // we need to force closed the source db + srcFolder->Delete(); + + nsCOMPtr parentPathFile; + rv = msgParent->GetFilePath(getter_AddRefs(parentPathFile)); + NS_ENSURE_SUCCESS(rv,rv); + + AddDirectorySeparator(parentPathFile); + nsCOMPtr 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 imapService = do_GetService (NS_IMAPSERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr 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 urlListener; + nsCOMPtr messages(do_CreateInstance(NS_ARRAY_CONTRACTID)); + nsCOMPtr srcSupport = do_QueryInterface(file, &rv); + + if (!messages) + return OnCopyCompleted(srcSupport, rv); + + nsCOMPtr 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 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 copyStreamListener = do_CreateInstance(NS_COPYMESSAGESTREAMLISTENER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv,rv); + + nsCOMPtr copyListener(do_QueryInterface(dstFolder, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr 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 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 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 bundle; + rv = IMAPGetStringBundle(getter_AddRefs(bundle)); + NS_ENSURE_SUCCESS(rv, rv); + rv = bundle->FormatStringFromName( + u"imapCopyingMessageOf2", + formatStrings, 3, getter_Copies(progressText)); + nsCOMPtr 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 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 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 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 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 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 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 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 inputStream; + nsCOMPtr 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. + 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 msgStore; + GetMsgStore(getter_AddRefs(msgStore)); + if (msgStore) + msgStore->FinishNewMessage(offlineStore, fakeHdr); + } + + nsCOMPtr 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 + notifier(do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID)); + if (notifier) + notifier->NotifyMsgsClassified(messages, false, false); + inputStream->Close(); + inputStream = nullptr; + delete inputStreamBuffer; + } + 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 srcFile(do_QueryInterface(srcSupport)); + if (srcFile) + (void) CopyFileToOfflineStore(srcFile, m_copyState->m_appendUID); + } + m_copyState = nullptr; + nsresult result; + nsCOMPtr 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 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 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 enumerator; + GetSubFolders(getter_AddRefs(enumerator)); + if (!enumerator) + return NS_OK; + + nsresult rv; + bool hasMore; + while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMore)) && hasMore) + { + nsCOMPtr item; + rv = enumerator->GetNext(getter_AddRefs(item)); + if (NS_FAILED(rv)) + break; + + nsCOMPtr 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 enumerator; + GetSubFolders(getter_AddRefs(enumerator)); + if (!enumerator) + return NS_OK; + + bool hasMore; + while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMore)) && hasMore) + { + nsCOMPtr item; + rv = enumerator->GetNext(getter_AddRefs(item)); + if (NS_FAILED(rv)) + break; + + nsCOMPtr 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 imapServer; + rv = GetImapIncomingServer(getter_AddRefs(imapServer)); + NS_ENSURE_SUCCESS(rv, rv); + rv = imapServer->GetUsingSubscription(&usingSubscription); + if (NS_SUCCEEDED(rv) && !usingSubscription) + { + nsCOMPtr 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 pathFile; + rv = GetFilePath(getter_AddRefs(pathFile)); + if (NS_FAILED(rv)) return rv; + + nsCOMPtr 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 mailDBFactory; + nsCOMPtr child; + nsCOMPtr imapFolder; + + nsCOMPtr msgDBService = do_GetService(NS_MSGDB_SERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr unusedDB; + nsCOMPtr 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 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 msgParent; + msgFolder->GetParent(getter_AddRefs(msgParent)); + msgFolder->SetParent(nullptr); + // Reset online status now that the folder is renamed. + nsCOMPtr oldImapFolder = do_QueryInterface(msgFolder); + if (oldImapFolder) + oldImapFolder->SetVerifiedAsOnlineFolder(false); + nsCOMPtr 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 enumerator; + nsresult rv = oldFolder->GetSubFolders(getter_AddRefs(enumerator)); + NS_ENSURE_SUCCESS(rv, rv); + + bool hasMore; + while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMore)) && hasMore) + { + nsCOMPtr item; + if (NS_FAILED(enumerator->GetNext(getter_AddRefs(item)))) + continue; + + nsCOMPtr msgFolder(do_QueryInterface(item, &rv)); + if (NS_FAILED(rv)) + return rv; + + nsCOMPtr 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 oldPathFile; + rv = msgFolder->GetFilePath(getter_AddRefs(oldPathFile)); + if (NS_FAILED(rv)) return rv; + + nsCOMPtr 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 newPathFile = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + newPathFile->InitWithFile(newParentPathFile); + + nsCOMPtr dbFilePath = newPathFile; + + nsCOMPtr 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 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 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 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 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 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 *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 *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 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 server; + nsresult rv = GetServer(getter_AddRefs(server)); + NS_ENSURE_SUCCESS(rv, rv); + + if (aMsgURI) // not end of batch + { + nsCOMPtr 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 *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 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 filterList; + nsresult rv = GetFilterList(nullptr, getter_AddRefs(filterList)); + NS_ENSURE_SUCCESS(rv,rv); + + rv = filterList->GetShouldDownloadAllHeaders(aResult); + if (*aResult) + return rv; + } + nsCOMPtr filterPlugin; + nsCOMPtr 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 server; + nsCOMPtr 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 keysToFetchFromServer; + + *aAsyncResults = false; + nsresult rv = NS_OK; + + nsCOMPtr imapService = do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr msgService = do_QueryInterface(imapService, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + for (uint32_t i = 0; i < aNumKeys; i++) + { + nsCOMPtr 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 url; + nsCOMPtr 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 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 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 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, @ + if (delegateOtherUsersFolders) + { + nsCOMPtr server = do_QueryReferent(mServer, &rv); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr accountManager = do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr ourIdentity; + nsCOMPtr retIdentity; + nsCOMPtr 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 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 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 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 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(aClosure); + + NS_ASSERTION(request->SrcFolder->m_pendingPlaybackReq == request, "wrong playback request pointer"); + + RefPtr offlineSync = new nsImapOfflineSync(request->MsgWindow, nullptr, request->SrcFolder, true); + if (offlineSync) + { + mozilla::DebugOnly 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 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 subMsgFolder; + nsresult rv = GetDatabase(); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr 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 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 labelNames; + hdr->GetStringProperty("X-GM-LABELS", getter_Copies(labels)); + ParseString(labels, ' ', labelNames); + nsCOMPtr rootFolder; + nsCOMPtr subFolder; + for (uint32_t i = 0; i < labelNames.Length(); i++) + { + rv = GetRootFolder(getter_AddRefs(rootFolder)); + if (NS_SUCCEEDED(rv) && (rootFolder)) + { + nsCOMPtr 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 db; + subMsgFolder->GetMsgDatabase(getter_AddRefs(db)); + if (db) + { + nsCOMPtr 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 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 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 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 offlineStore; + (void) GetMsgStore(getter_AddRefs(offlineStore)); + + if (offlineStore) + { + bool supportsCompaction; + offlineStore->GetSupportsCompaction(&supportsCompaction); + if (!supportsCompaction) + offlineStore->DeleteMessages(aMessages); + } +} + +void nsImapMailFolder::DeleteStoreMessages(nsTArray &aMessages) +{ + DeleteStoreMessages(aMessages, this); +} + +void nsImapMailFolder::DeleteStoreMessages(nsTArray &aMessages, + nsIMsgFolder* aFolder) +{ + // Delete messages for pluggable stores that do not support compaction. + NS_ASSERTION(aFolder, "Missing Source Folder"); + nsCOMPtr offlineStore; + (void) aFolder->GetMsgStore(getter_AddRefs(offlineStore)); + if (offlineStore) + { + bool supportsCompaction; + offlineStore->GetSupportsCompaction(&supportsCompaction); + if (!supportsCompaction) + { + nsCOMPtr db; + aFolder->GetMsgDatabase(getter_AddRefs(db)); + nsresult rv = NS_ERROR_FAILURE; + nsCOMPtr 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..b2a9430b3 --- /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 m_srcSupport; // source file spec or folder + nsCOMPtr m_messages; // array of source messages + RefPtr m_undoMsgTxn; // undo object with this copy operation + nsCOMPtr m_message; // current message to be copied + nsCOMPtr m_listener; // listener of this copy + // operation + nsCOMPtr m_tmpFile; // temp file spec for copy operation + nsCOMPtr m_msgWindow; // msg window for copy operation + + nsCOMPtr 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 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: + 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 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 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& 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 &existingKeys, nsTArray + &keysToFetch, uint32_t &numNewUnread, nsIImapFlagAndUidState *flagState); + void FindKeysToDelete(const nsTArray &existingKeys, nsTArray + &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 *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 &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 *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 m_msgParser; + nsCOMPtr m_filterList; + nsCOMPtr 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 m_junkMessagesToMarkAsRead; + /// list of keys to be moved to the junk folder + nsTArray mSpamKeysToMove; + /// the junk destination folder + nsCOMPtr 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 m_urlListener; + bool m_urlRunning; + + // undo move/copy transaction support + RefPtr m_pendingUndoTxn; + RefPtr 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 m_autoSyncStateObj; + + // Quota support + nsCString m_folderQuotaRoot; + uint32_t m_folderQuotaUsedKB; + uint32_t m_folderQuotaMaxKB; + + // Pseudo-Offline Playback support + nsPlaybackRequest *m_pendingPlaybackReq; + nsCOMPtr m_playbackTimer; + nsTArray > m_pendingOfflineMoves; + // hash table of mapping between messageids and message keys + // for pseudo hdrs. + nsDataHashtable m_pseudoHdrs; + + nsTArray 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 &aMessages); + static void DeleteStoreMessages(nsTArray &aMessages, nsIMsgFolder* aFolder); +}; +#endif diff --git a/mailnews/imap/src/nsImapOfflineSync.cpp b/mailnews/imap/src/nsImapOfflineSync.cpp new file mode 100644 index 000000000..df31bd299 --- /dev/null +++ b/mailnews/imap/src/nsImapOfflineSync.cpp @@ -0,0 +1,1292 @@ +/* -*- 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 "nsAlgorithm.h" +#include "nsArrayUtils.h" +#include + +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 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 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 rootFolder; + + while (serverIndex < numServers) + { + nsCOMPtr server(do_QueryElementAt(m_allServers, serverIndex)); + serverIndex++; + + nsCOMPtr 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 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 imapFolder; + while (!imapFolder && AdvanceToNextFolder()) + { + imapFolder = do_QueryInterface(m_currentFolder); + } +} + +void nsImapOfflineSync::ProcessFlagOperation(nsIMsgOfflineImapOperation *op) +{ + nsCOMPtr currentOp = op; + nsTArray 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 imapFolder = do_QueryInterface(m_currentFolder); + nsCOMPtr uriToSetFlags; + if (imapFolder) + { + rv = imapFolder->SetImapFlags(uids.get(), matchingFlags, getter_AddRefs(uriToSetFlags)); + if (NS_SUCCEEDED(rv) && uriToSetFlags) + { + nsCOMPtr mailnewsUrl = do_QueryInterface(uriToSetFlags); + if (mailnewsUrl) + mailnewsUrl->RegisterListener(this); + } + } + } + } + else + ProcessNextOperation(); +} + +void nsImapOfflineSync::ProcessKeywordOperation(nsIMsgOfflineImapOperation *op) +{ + nsCOMPtr currentOp = op; + nsTArray 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 imapFolder = do_QueryInterface(m_currentFolder); + nsCOMPtr 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 mailnewsUrl = do_QueryInterface(uriToStoreCustomKeywords); + if (mailnewsUrl) + mailnewsUrl->RegisterListener(this); + } + } + } + } + else + ProcessNextOperation(); +} + +void +nsImapOfflineSync::ProcessAppendMsgOperation(nsIMsgOfflineImapOperation *currentOp, int32_t opType) +{ + nsCOMPtr 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 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 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 rdf(do_GetService(kRDFServiceCID, &rv)); + nsCOMPtr res; + if (NS_FAILED(rv)) return ; // ### return error code. + rv = rdf->GetResource(moveDestination, getter_AddRefs(res)); + if (NS_SUCCEEDED(rv)) + { + nsCOMPtr destFolder(do_QueryInterface(res, &rv)); + if (NS_SUCCEEDED(rv) && destFolder) + { + nsCOMPtr offlineStoreInputStream; + bool reusable; + rv = destFolder->GetMsgInputStream( + mailHdr, &reusable, getter_AddRefs(offlineStoreInputStream)); + if (NS_SUCCEEDED(rv) && offlineStoreInputStream) + { + nsCOMPtr 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 cloneTmpFile; + // clone the tmp file to defeat nsIFile's stat/size caching. + tmpFile->Clone(getter_AddRefs(cloneTmpFile)); + m_curTempFile = do_QueryInterface(cloneTmpFile); + nsCOMPtr 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 matchingFlagKeys; + uint32_t currentKeyIndex = m_KeyIndex; + nsCString moveDestination; + op->GetDestinationFolderURI(getter_Copies(moveDestination)); + bool moveMatches = true; + nsCOMPtr 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 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 imapFolder = do_QueryInterface(m_currentFolder); + if (imapFolder && DestFolderOnSameServer(destFolder)) + { + imapFolder->ReplayOfflineMoveCopy(matchingFlagKeys.Elements(), matchingFlagKeys.Length(), true, destFolder, + this, m_window); + } + else + { + nsresult rv; + nsCOMPtr messages(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv)); + if (NS_SUCCEEDED(rv)) + { + for (uint32_t keyIndex = 0; keyIndex < matchingFlagKeys.Length(); keyIndex++) + { + nsCOMPtr 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 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 srcServer; + nsCOMPtr 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 currentOp = aCurrentOp; + + nsTArray 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 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 imapFolder = do_QueryInterface(m_currentFolder); + if (imapFolder && DestFolderOnSameServer(destFolder)) + { + rv = imapFolder->ReplayOfflineMoveCopy(matchingFlagKeys.Elements(), matchingFlagKeys.Length(), false, destFolder, + this, m_window); + } + else + { + nsCOMPtr messages(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv)); + if (messages && NS_SUCCEEDED(rv)) + { + for (uint32_t keyIndex = 0; keyIndex < matchingFlagKeys.Length(); keyIndex++) + { + nsCOMPtr mailHdr = nullptr; + rv = m_currentFolder->GetMessageHeader(matchingFlagKeys.ElementAt(keyIndex), getter_AddRefs(mailHdr)); + if (NS_SUCCEEDED(rv) && mailHdr) + messages->AppendElement(mailHdr, false); + } + nsCOMPtr 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 parent; + folder->GetParent(getter_AddRefs(parent)); + + nsCOMPtr imapFolder = do_QueryInterface(parent); + nsCOMPtr 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 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 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 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 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 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 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 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 imapFolder(do_QueryInterface(m_singleFolderToUpdate)); + if (imapFolder) + { + nsCOMPtr 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 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 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 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 accountManager = + do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv); + if (NS_FAILED(rv)) return rv; + + nsCOMPtr servers; + rv = accountManager->GetAllServers(getter_AddRefs(servers)); + if (NS_FAILED(rv)) return rv; + } + if (!m_mailboxupdatesFinished) + { + if (AdvanceToNextServer()) + { + nsCOMPtr rootMsgFolder; + m_currentServer->GetRootFolder(getter_AddRefs(rootMsgFolder)); + nsCOMPtr inbox; + if (rootMsgFolder) + { + rootMsgFolder->GetFolderWithFlags(nsMsgFolderFlags::Inbox, + getter_AddRefs(inbox)); + if (inbox) + { + nsCOMPtr offlineImapFolder; + nsCOMPtr 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 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 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 m_currentFolder; + nsCOMPtr m_singleFolderToUpdate; + nsCOMPtr m_window; + nsCOMPtr m_allServers; + nsCOMPtr m_allFolders; + nsCOMPtr m_currentServer; + nsCOMPtr m_serverEnumerator; + nsCOMPtr m_curTempFile; + + nsTArray m_CurrentKeys; + nsCOMArray m_currentOpsToClear; + uint32_t m_KeyIndex; + nsCOMPtr m_currentDB; + nsCOMPtr 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..2a3d1e9ff --- /dev/null +++ b/mailnews/imap/src/nsImapProtocol.cpp @@ -0,0 +1,10046 @@ +/* -*- 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" +#include +// 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 "nsAlgorithm.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 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 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 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 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; // no ref count...host session list has life time > connection + m_parser.SetHostSessionList(aHostSessionList); + m_parser.SetFlagState(m_flagState); + + // Initialize the empty mime part string on the main thread. + nsCOMPtr 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); + delete m_inputStreamBuffer; + + // **** 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 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 aImapMailFolderSink; + (void) m_runningUrl->GetImapMailFolderSink(getter_AddRefs(aImapMailFolderSink)); + if (aImapMailFolderSink) + { + m_imapMailFolderSink = new ImapMailFolderSinkProxy(aImapMailFolderSink); + } + } + + if (!m_imapMessageSink) + { + nsCOMPtr 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 aImapServerSink; + res = m_runningUrl->GetImapServerSink(getter_AddRefs(aImapServerSink)); + if (aImapServerSink) { + m_imapServerSink = new ImapServerSinkProxy(aImapServerSink); + } else { + return NS_ERROR_ILLEGAL_VALUE; + } + } + if (!m_imapProtocolSink) + { + nsCOMPtr 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 callbacks; + aChannel->GetNotificationCallbacks(getter_AddRefs(callbacks)); + + nsCOMPtr loadGroup; + aChannel->GetLoadGroup(getter_AddRefs(loadGroup)); + + nsCOMPtr 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); + if (NS_FAILED(rv)) return rv; + nsCOMPtr mailnewsUrl = do_QueryInterface(m_runningUrl); + nsCOMPtr server = do_QueryReferent(m_server); + if (!server) + { + rv = mailnewsUrl->GetServer(getter_AddRefs(server)); + NS_ENSURE_SUCCESS(rv, rv); + m_server = do_GetWeakReference(server); + } + nsCOMPtr folder; + mailnewsUrl->GetFolder(getter_AddRefs(folder)); + mFolderLastModSeq = 0; + mFolderTotalMsgCount = 0; + mFolderHighestUID = 0; + m_uidValidity = kUidUnknown; + if (folder) + { + nsCOMPtr folderDB; + nsCOMPtr 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 imapServer = do_QueryInterface(server); + nsCOMPtr aRealStreamListener = do_QueryInterface(aConsumer); + m_runningUrl->GetMockChannel(getter_AddRefs(m_mockChannel)); + imapServer->GetIsGMailServer(&m_isGmailServer); + if (!m_mockChannel) + { + + nsCOMPtr 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 channel; + rv = NS_NewChannel(getter_AddRefs(channel), + aURL, + nullPrincipal, + nsILoadInfo::SEC_NORMAL, + 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 loadGroup; + mailnewsUrl->GetLoadGroup(getter_AddRefs(loadGroup)); // get the message pane load group + nsCOMPtr 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 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 msgWindow; + GetMsgWindow(getter_AddRefs(msgWindow)); + if (!msgWindow) + GetTopmostMsgWindow(getter_AddRefs(msgWindow)); + if (msgWindow) + { + nsCOMPtr docShell; + msgWindow->GetMessageWindowDocShell(getter_AddRefs(docShell)); + nsCOMPtr ir(do_QueryInterface(docShell)); + nsCOMPtr interfaceRequestor; + msgWindow->GetNotificationCallbacks(getter_AddRefs(interfaceRequestor)); + nsCOMPtr 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); + InitPrefAuthMethods(authMethod, server); + (void) server->GetSocketType(&m_socketType); + bool shuttingDown; + (void) imapServer->GetShuttingDown(&shuttingDown); + if (!shuttingDown) + (void) imapServer->GetUseIdle(&m_useIdle); + else + m_useIdle = false; + imapServer->GetFetchByChunks(&m_fetchByChunks); + imapServer->GetSendID(&m_sendID); + + nsAutoString trashFolderName; + if (NS_SUCCEEDED(imapServer->GetTrashFolderName(trashFolderName))) + CopyUTF16toMUTF7(trashFolderName, m_trashFolderName); + + nsCOMPtr 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 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 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 securityInfo; + m_transport->GetSecurityInfo(getter_AddRefs(securityInfo)); + m_mockChannel->SetSecurityInfo(securityInfo); + + SetSecurityCallbacksFromChannel(m_transport, m_mockChannel); + + nsCOMPtr sink = do_QueryInterface(m_mockChannel); + if (sink) { + nsCOMPtr 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 mailnewsUrl = do_QueryInterface(m_runningUrl); + if (mailnewsUrl) + { + nsCOMPtr 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 mailnewsurl; + nsCOMPtr 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: + nsImapThreadShutdownEvent(nsIThread *thread) : mThread(thread) { + } + NS_IMETHOD Run() { + mThread->Shutdown(); + return NS_OK; + } +private: + nsCOMPtr 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 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 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 me_server = do_QueryReferent(m_server); + if (me_server) + { + nsresult result; + nsCOMPtr + 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 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 me_server = do_QueryReferent(m_server); + if (me_server) + { + nsresult rv; + nsCOMPtr + 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 runningImapURL; + + rv = GetRunningImapURL(getter_AddRefs(runningImapURL)); + if (NS_SUCCEEDED(rv) && runningImapURL) + { + nsCOMPtr runningImapFolder; + nsCOMPtr msgWindow; + nsCOMPtr 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 + } + 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 imapUrl = do_QueryInterface(url); + imapUrl->GetMsgLoadingFromCache(&readingFromMemCache); + if (!readingFromMemCache) + { + nsCOMPtr 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 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 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 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 secInfo; + nsCOMPtr strans = do_QueryInterface(m_transport, &rv); + if (NS_FAILED(rv)) + return false; + + rv = strans->GetSecurityInfo(getter_AddRefs(secInfo)); + + if (NS_SUCCEEDED(rv) && secInfo) + { + nsCOMPtr 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(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 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 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 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 kungFuGripImapUrl = m_runningUrl; + nsCOMPtr 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 folder; + mUrl->GetFolder(getter_AddRefs(folder)); + NS_ENSURE_TRUE(folder, NS_OK); + nsCOMPtr 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 mUrl; + nsCOMPtr mProtocol; +}; + + +bool nsImapProtocol::TryToRunUrlLocally(nsIURI *aURL, nsISupports *aConsumer) +{ + nsresult rv; + nsCOMPtr imapUrl(do_QueryInterface(aURL, &rv)); + NS_ENSURE_SUCCESS(rv, false); + nsCOMPtr mailnewsUrl = do_QueryInterface(aURL); + nsCString messageIdString; + imapUrl->GetListOfMessageIds(messageIdString); + bool useLocalCache = false; + if (!messageIdString.IsEmpty() && !HandlingMultipleMessages(messageIdString)) + { + nsImapAction action; + imapUrl->GetImapAction(&action); + nsCOMPtr 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 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 mockChannel; + imapUrl->GetMockChannel(getter_AddRefs(mockChannel)); + if (!mockChannel) + return false; + + nsImapMockChannel *imapChannel = static_cast(mockChannel.get()); + if (!imapChannel) + return false; + + nsCOMPtr 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 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 msgUrl = do_QueryInterface(aImapUrl); + nsCOMPtr 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 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 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 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 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 file; + bool addDummyEnvelope = true; + nsCOMPtr 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 copyState; + if (m_runningUrl) + { + m_runningUrl->GetCopyState(getter_AddRefs(copyState)); + if (copyState) // only need this notification during copy + { + nsCOMPtr 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 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_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 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 copyState; + m_runningUrl->GetCopyState(getter_AddRefs(copyState)); + if (copyState) // only need this notification during copy + { + nsCOMPtr 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 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) + { + char *id = (char *)PR_CALLOC(strlen(messageId) + 1); + char *folderName; + uint32_t size; + + PL_strcpy(id, messageId); + + nsIMAPNamespace *nsForMailbox = nullptr; + m_hostSessionList->GetNamespaceForMailboxForHost(GetImapServerKey(), folderFromParser, + nsForMailbox); + + + if (nsForMailbox) + m_runningUrl->AllocateCanonicalPath( + folderFromParser, nsForMailbox->GetDelimiter(), + &folderName); + else + m_runningUrl->AllocateCanonicalPath( + folderFromParser,kOnlineHierarchySeparatorUnknown, + &folderName); + + if (id && folderName) + { + if (m_imapMessageSink) + m_imapMessageSink->GetMessageSizeFromDB(id, &size); + } + PR_FREEIF(id); + PR_FREEIF(folderName); + + uint32_t rv = 0; + if (!DeathSignalReceived()) + rv = size; + return rv; + } + return 0; +} + +// 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 ,, or : + 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 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 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(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 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 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 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) +{ + if (m_imapServerSink && aServerEvent) + { + nsCOMPtr 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 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 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(); + if (!gUseLiteralPlus) + { + eIMAPCapabilityFlags capabilityFlag = GetServerStateParser().GetCapabilityFlag(); + if (capabilityFlag & kLiteralPlusCapability) + { + GetServerStateParser().SetCapabilityFlag(capabilityFlag & ~kLiteralPlusCapability); + } + } +} + +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 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 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; iAppend('\\'); + } + resultStr->Append(strToEscape[i]); + } + } +} + +void nsImapProtocol::InitPrefAuthMethods(int32_t authMethodPrefValue, + nsIMsgIncomingServer *aServer) +{ + // 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 | + kHasAuthExternalCapability | kHasXOAuth2Capability; + break; + case nsMsgAuthMethod::OAuth2: + m_prefAuthMethods = kHasXOAuth2Capability; + break; + } + + 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; + + 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)); + 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)); + + if (kHasAuthExternalCapability & availCaps) + m_currentAuthMethod = kHasAuthExternalCapability; + else if (kHasAuthGssApiCapability & availCaps) + m_currentAuthMethod = kHasAuthGssApiCapability; + else if (kHasCRAMCapability & availCaps) + m_currentAuthMethod = kHasCRAMCapability; + else if (kHasAuthNTLMCapability & availCaps) + m_currentAuthMethod = kHasAuthNTLMCapability; + else if (kHasAuthMSNCapability & availCaps) + m_currentAuthMethod = kHasAuthMSNCapability; + else if (kHasXOAuth2Capability & availCaps) + m_currentAuthMethod = kHasXOAuth2Capability; + else if (kHasAuthPlainCapability & availCaps) + m_currentAuthMethod = kHasAuthPlainCapability; + else if (kHasAuthLoginCapability & availCaps) + m_currentAuthMethod = kHasAuthLoginCapability; + else if (kHasAuthOldLoginCapability & availCaps) + m_currentAuthMethod = kHasAuthOldLoginCapability; + else + { + MOZ_LOG(IMAP, LogLevel::Debug, ("no remaining auth method")); + m_currentAuthMethod = kCapabilityUndefined; + return NS_ERROR_FAILURE; + } + MOZ_LOG(IMAP, LogLevel::Debug, ("trying auth method 0x%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 "userNamepassword" TODO nsAutoCString + int len = 1; // count for first char + memset(plainstr, 0, 512); + PR_snprintf(&plainstr[1], 510, "%s", userName); + len += PL_strlen(userName); + len++; // count for second 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(); + } + 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(); + } + else if (flag & kHasAuthNoneCapability) + { + // TODO What to do? "login " 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 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; + bool hasLiteralPlus = (GetServerStateParser().GetCapabilityFlag() & + kLiteralPlusCapability); + + nsCOMPtr 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); + if (hasLiteralPlus) + command.Append("+}" CRLF); + else + command.Append("}" CRLF); + + rv = SendData(command.get()); + if (NS_FAILED(rv)) goto done; + + if (!hasLiteralPlus) + ParseIMAPandCheckForNewMail(); + + 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 msgFolder; + + m_runningUrl->GetOnlineSubDirSeparator(&onlineSubDirDelimiter); + nsCOMPtr mailnewsUrl = do_QueryInterface(m_runningUrl); + mailnewsUrl->GetFolder(getter_AddRefs(msgFolder)); + if (msgFolder) + { + nsCOMPtr 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(); + + 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(); + + 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; iElementAt(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 *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 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)) + { + m_idle = true; + // we'll just get back a continuation char at first. + // + idling... + ParseIMAPandCheckForNewMail(); + // 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 asyncInputStream = do_QueryInterface(m_inputStream); + if (asyncInputStream) + asyncInputStream->AsyncWait(this, 0, 0, nullptr); + } +} + +// 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 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 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 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 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) + nsAutoCString pwd; // GetPasswordWithUI truncates the password on Cancel + rv = m_imapServerSink->AsyncGetPassword(this, + newPasswordRequested, + password); + if (password.IsEmpty()) + { + PRIntervalTime sleepTime = kImapSleepTime; + m_passwordStatus = NS_OK; + ReentrantMonitorAutoEnter mon(m_passwordReadyMonitor); + while (m_password.IsEmpty() && !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 imapServer = do_QueryReferent(m_server, &rv); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr 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. + ReentrantMonitorAutoEnter passwordMon(m_passwordReadyMonitor); + passwordMon.Notify(); + return rv; +} + +NS_IMETHODIMP +nsImapProtocol::OnPromptAuthAvailable() +{ + nsresult rv; + nsCOMPtr 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. + 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 && + m_currentAuthMethod != kHasXOAuth2Capability && + m_currentAuthMethod != kHasAuthNoneCapability) + { + rv = GetPassword(password, newPasswordRequested); + newPasswordRequested = false; + if (rv == NS_MSG_PASSWORD_PROMPT_CANCELLED || NS_FAILED(rv)) + { + MOZ_LOG(IMAP, LogLevel::Error, ("IMAP: password prompt failed or user canceled it")); + break; + } + MOZ_LOG(IMAP, LogLevel::Debug, ("got new password")); + } + + bool lastReportingErrors = GetServerStateParser().GetReportingErrors(); + GetServerStateParser().SetReportingErrors(false); // turn off errors - we'll put up our own. + + rv = AuthLogin(userName.get(), password, m_currentAuthMethod); + + GetServerStateParser().SetReportingErrors(lastReportingErrors); // restore error reports + loginSucceeded = NS_SUCCEEDED(rv); + + if (!loginSucceeded) + { + MOZ_LOG(IMAP, LogLevel::Debug, ("authlogin failed")); + MarkAuthMethodAsFailed(m_currentAuthMethod); + rv = ChooseAuthMethod(); // change m_currentAuthMethod to try other one next round + + if (NS_FAILED(rv)) // all methods failed + { + if (m_prefAuthMethods == kHasAuthGssApiCapability) + { + // GSSAPI failed, and it's the only available method, + // and it's password-less, so nothing left to do. + AlertUserEventUsingName("imapAuthGssapiFailed"); + break; + } + + if (m_prefAuthMethods & kHasXOAuth2Capability) + { + // OAuth2 failed. 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; + } + + // 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 mChannelToUse; + nsCOMPtr 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 loadGroup; + mChannelToUse->GetLoadGroup(getter_AddRefs(loadGroup)); + nsCOMPtr 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 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 imapUrl = do_QueryInterface(m_url, &rv); + nsCOMPtr imapProtocol = do_QueryReferent(mProtocol); + if (imapUrl) + { + nsCOMPtr folderSink; + rv = imapUrl->GetImapMailFolderSink(getter_AddRefs(folderSink)); + if (folderSink) + { + nsCOMPtr 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 mailnewsUrl = do_QueryInterface(m_url); + if (mailnewsUrl) + { + nsCOMPtr cacheEntry; + mailnewsUrl->GetMemCacheEntry(getter_AddRefs(cacheEntry)); + if (cacheEntry) + cacheEntry->MarkValid(); + // remove the channel from the load group + nsCOMPtr 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 imapUrl = do_QueryInterface(m_url, &rv); + if (imapUrl) + { + nsCOMPtr folderSink; + rv = imapUrl->GetImapMailFolderSink(getter_AddRefs(folderSink)); + if (folderSink) + { + nsCOMPtr 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 mailnewsUrl = do_QueryInterface(m_url); + if (mailnewsUrl && !mProgressEventSink) + { + nsCOMPtr 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 imapUrl(do_QueryInterface(m_url)); + nsImapAction imapAction; + imapUrl->GetImapAction(&imapAction); + if (imapAction == nsIImapUrl::nsImapMsgFetch) + { + nsCOMPtr msgUrl(do_QueryInterface(m_url)); + if (msgUrl) + { + nsCOMPtr 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 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 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 tee = do_CreateInstance(NS_STREAMLISTENERTEE_CONTRACTID, &rv); + if (NS_SUCCEEDED(rv)) + { + nsCOMPtr 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 imapService = do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr cacheStorage; + rv = imapService->GetCacheStorage(getter_AddRefs(cacheStorage)); + NS_ENSURE_SUCCESS(rv, rv); + + int32_t uidValidity = -1; + nsCacheAccessMode cacheAccess = nsICacheStorage::OPEN_NORMALLY; + + nsCOMPtr imapUrl = do_QueryInterface(m_url, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + bool storeResultsOffline; + nsCOMPtr 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 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 msgUrl(do_QueryInterface(m_url)); + if (msgUrl) + { + nsCOMPtr 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 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 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 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 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 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: + nsReadFromImapConnectionFailure(nsImapMockChannel *aChannel) + : mImapMockChannel(aChannel) + {} + + NS_IMETHOD Run() + { + if (mImapMockChannel) { + mImapMockChannel->RunOnStopRequestFailure(); + } + return NS_OK; + } +private: + RefPtr 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 imapUrl = do_QueryInterface(m_url); + nsCOMPtr 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 event = new nsReadFromImapConnectionFailure(this); + NS_DispatchToCurrentThread(event); + return NS_MSG_ERROR_MSG_NOT_OFFLINE; + } + + nsCOMPtr 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 server; + rv = mailnewsUrl->GetServer(getter_AddRefs(server)); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr 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 imapUrl = do_QueryInterface(m_url); + nsCOMPtr mailnewsUrl = do_QueryInterface(m_url, &rv); + + bool useLocalCache = false; + mailnewsUrl->GetMsgIsInLocalCache(&useLocalCache); + if (useLocalCache) + { + nsAutoCString messageIdString; + + SetupPartExtractorListener(imapUrl, m_channelListener); + + imapUrl->GetListOfMessageIds(messageIdString); + nsCOMPtr 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 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 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 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 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 converter = do_GetService("@mozilla.org/streamConverters;1"); + if (converter && aConsumer) + { + nsCOMPtr newConsumer; + converter->AsyncConvertData("message/rfc822", "*/*", + aConsumer, static_cast(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 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 imapProtocol = do_QueryReferent(mProtocol); + + // if we aren't reading from the cache and we get canceled...doom our cache entry... + if (m_url) + { + nsCOMPtr 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 mailnewsUrl = do_QueryInterface(m_url); + if (mailnewsUrl) + { + nsCOMPtr 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..0341d87bb --- /dev/null +++ b/mailnews/imap/src/nsImapProtocol.h @@ -0,0 +1,764 @@ +/* -*- 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 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); + + 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 m_runningUrl; // the nsIImapURL that is currently running + 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; + 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 m_transport; + + nsCOMPtr m_channelInputStream; + nsCOMPtr m_channelOutputStream; + nsCOMPtr m_mockChannel; // this is the channel we should forward to people + uint32_t m_bytesToChannel; + bool m_fetchingWholeMessage; + //nsCOMPtr mAsyncReadRequest; // we're going to cancel this when we're done with the conn. + + + // ******* Thread support ******* + nsCOMPtr 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_imapThreadIsRunning; + void ImapThreadMainLoop(void); + nsresult m_connectionStatus; + nsCString m_connectionType; + + bool m_nextUrlReadyToRun; + nsWeakPtr m_server; + + RefPtr m_imapMailFolderSink; + RefPtr m_imapMessageSink; + RefPtr m_imapServerSink; + RefPtr 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); + void InitPrefAuthMethods(int32_t authMethodPrefValue, + nsIMsgIncomingServer *aServer); + nsresult ChooseAuthMethod(); + void MarkAuthMethodAsFailed(eIMAPCapabilityFlags failedAuthMethod); + void ResetAuthMethods(); + + // All of these methods actually issue protocol + void Capability(); // query host for capabilities. + void ID(); // send RFC 2971 app info to server + void 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 mCustomDBHeaders; + nsTArray 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 m_downloadLineCache; + RefPtr m_hdrDownloadCache; + nsCOMPtr m_curHdrInfo; + // mapping between mailboxes and the corresponding folder flags + nsDataHashtable m_standardListMailboxes; + // mapping between special xlist mailboxes and the corresponding folder flags + nsDataHashtable m_specialXListMailboxes; + + + 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 m_listedMailboxList; + nsTArray * m_deletableChildren; + uint32_t m_flagChangeCount; + PRTime m_lastCheckTime; + + bool CheckNeeded(); + + nsString m_emptyMimePartString; + + RefPtr mOAuth2Support; +}; + +// 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 m_url; + + nsCOMPtr m_originalUrl; + nsCOMPtr m_loadGroup; + nsCOMPtr m_loadInfo; + nsCOMPtr m_channelListener; + nsISupports * m_channelContext; + nsresult m_cancelStatus; + nsLoadFlags mLoadFlags; + nsCOMPtr mProgressEventSink; + nsCOMPtr mCallbacks; + nsCOMPtr mOwner; + nsCOMPtr mSecurityInfo; + nsCOMPtr 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::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..b4333417b --- /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 +{ +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: + 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..faa37cc45 --- /dev/null +++ b/mailnews/imap/src/nsImapServerResponseParser.cpp @@ -0,0 +1,3360 @@ +/* -*- 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), + 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); + + 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(); + } + } + + // 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) + { + 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); + } + } + } + } + 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 ::= "=?" "?" "?" + "?=" + ;; 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*] ) + "]" + + +*/ +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 ::= / + "\" quoted_specials + + quoted_specials ::= <"> / "\" +*/ + +bool nsImapServerResponseParser::msg_fetch_quoted() +{ + // *Should* never get a quoted string in response to a chunked download, + // but the RFCs don't forbid it + char *q = CreateQuoted(); + if (q) + { + numberOfCharsInThisChunk = PL_strlen(q); + fServerConnection.HandleMessageDownLoadLine(q, false, q); + PR_Free(q); + } + else + numberOfCharsInThisChunk = 0; + + AdvanceToNextToken(); + bool lastChunk = ((fServerConnection.GetCurFetchSize() == 0) || + (numberOfCharsInThisChunk != fServerConnection.GetCurFetchSize())); + return lastChunk; +} + +/* msg_obsolete ::= "COPY" / ("STORE" SPACE msg_fetch) +;; OBSOLETE untagged data responses */ +void nsImapServerResponseParser::msg_obsolete() +{ + if (!PL_strcasecmp(fNextToken, "COPY")) + AdvanceToNextToken(); + else if (!PL_strcasecmp(fNextToken, "STORE")) + { + AdvanceToNextToken(); + if (ContinueParse()) + msg_fetch(); + } + else + SetSyntaxError(true); +} + +void nsImapServerResponseParser::capability_data() +{ + int32_t endToken = -1; + fCapabilityFlag = kCapabilityDefined | kHasAuthOldLoginCapability; + do { + AdvanceToNextToken(); + if (fNextToken) { + nsCString token(fNextToken); + endToken = token.FindChar(']'); + if (endToken >= 0) + token.SetLength(endToken); + + if(token.Equals("AUTH=LOGIN", nsCaseInsensitiveCStringComparator())) + fCapabilityFlag |= kHasAuthLoginCapability; + else if(token.Equals("AUTH=PLAIN", nsCaseInsensitiveCStringComparator())) + fCapabilityFlag |= kHasAuthPlainCapability; + else if (token.Equals("AUTH=CRAM-MD5", nsCaseInsensitiveCStringComparator())) + fCapabilityFlag |= kHasCRAMCapability; + else if (token.Equals("AUTH=NTLM", nsCaseInsensitiveCStringComparator())) + fCapabilityFlag |= kHasAuthNTLMCapability; + else if (token.Equals("AUTH=GSSAPI", nsCaseInsensitiveCStringComparator())) + fCapabilityFlag |= kHasAuthGssApiCapability; + else if (token.Equals("AUTH=MSN", nsCaseInsensitiveCStringComparator())) + fCapabilityFlag |= kHasAuthMSNCapability; + else if (token.Equals("AUTH=EXTERNAL", nsCaseInsensitiveCStringComparator())) + fCapabilityFlag |= kHasAuthExternalCapability; + else if (token.Equals("AUTH=XOAUTH2", nsCaseInsensitiveCStringComparator())) + fCapabilityFlag |= kHasXOAuth2Capability; + else if (token.Equals("STARTTLS", nsCaseInsensitiveCStringComparator())) + fCapabilityFlag |= kHasStartTLSCapability; + else if (token.Equals("LOGINDISABLED", nsCaseInsensitiveCStringComparator())) + fCapabilityFlag &= ~kHasAuthOldLoginCapability; // remove flag + else if (token.Equals("XSENDER", nsCaseInsensitiveCStringComparator())) + fCapabilityFlag |= kHasXSenderCapability; + else if (token.Equals("IMAP4", nsCaseInsensitiveCStringComparator())) + fCapabilityFlag |= kIMAP4Capability; + else if (token.Equals("IMAP4rev1", nsCaseInsensitiveCStringComparator())) + fCapabilityFlag |= kIMAP4rev1Capability; + else if (Substring(token,0,5).Equals("IMAP4", nsCaseInsensitiveCStringComparator())) + fCapabilityFlag |= kIMAP4other; + else if (token.Equals("X-NO-ATOMIC-RENAME", nsCaseInsensitiveCStringComparator())) + fCapabilityFlag |= kNoHierarchyRename; + else if (token.Equals("X-NON-HIERARCHICAL-RENAME", nsCaseInsensitiveCStringComparator())) + fCapabilityFlag |= kNoHierarchyRename; + else if (token.Equals("NAMESPACE", nsCaseInsensitiveCStringComparator())) + fCapabilityFlag |= kNamespaceCapability; + else if (token.Equals("ID", nsCaseInsensitiveCStringComparator())) + fCapabilityFlag |= kHasIDCapability; + else if (token.Equals("ACL", nsCaseInsensitiveCStringComparator())) + fCapabilityFlag |= kACLCapability; + else if (token.Equals("XSERVERINFO", nsCaseInsensitiveCStringComparator())) + fCapabilityFlag |= kXServerInfoCapability; + else if (token.Equals("UIDPLUS", nsCaseInsensitiveCStringComparator())) + fCapabilityFlag |= kUidplusCapability; + else if (token.Equals("LITERAL+", nsCaseInsensitiveCStringComparator())) + fCapabilityFlag |= kLiteralPlusCapability; + else if (token.Equals("XAOL-OPTION", nsCaseInsensitiveCStringComparator())) + fCapabilityFlag |= kAOLImapCapability; + else if (token.Equals("X-GM-EXT-1", nsCaseInsensitiveCStringComparator())) + fCapabilityFlag |= kGmailImapCapability; + else if (token.Equals("QUOTA", nsCaseInsensitiveCStringComparator())) + fCapabilityFlag |= kQuotaCapability; + else if (token.Equals("LANGUAGE", nsCaseInsensitiveCStringComparator())) + fCapabilityFlag |= kHasLanguageCapability; + else if (token.Equals("IDLE", nsCaseInsensitiveCStringComparator())) + fCapabilityFlag |= kHasIdleCapability; + else if (token.Equals("CONDSTORE", nsCaseInsensitiveCStringComparator())) + fCapabilityFlag |= kHasCondStoreCapability; + else if (token.Equals("ENABLE", nsCaseInsensitiveCStringComparator())) + fCapabilityFlag |= kHasEnableCapability; + else if (token.Equals("LIST-EXTENDED", nsCaseInsensitiveCStringComparator())) + fCapabilityFlag |= kHasListExtendedCapability; + else if (token.Equals("XLIST", nsCaseInsensitiveCStringComparator())) + fCapabilityFlag |= kHasXListCapability; + else if (token.Equals("SPECIAL-USE", nsCaseInsensitiveCStringComparator())) + fCapabilityFlag |= kHasSpecialUseCapability; + else if (token.Equals("COMPRESS=DEFLATE", nsCaseInsensitiveCStringComparator())) + fCapabilityFlag |= kHasCompressDeflateCapability; + else if (token.Equals("MOVE", nsCaseInsensitiveCStringComparator())) + fCapabilityFlag |= kHasMoveCapability; + else if (token.Equals("HIGHESTMODSEQ", nsCaseInsensitiveCStringComparator())) + fCapabilityFlag |= kHasHighestModSeqCapability; + } + } 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 +*/ +// returns true if this is the last chunk and we should close the stream +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())); + +#ifdef DEBUG + if (lastChunk) + MOZ_LOG(IMAP, mozilla::LogLevel::Debug, + ("PARSER: fetch_literal chunk = %d, requested %d, receiving %d", + chunk, fServerConnection.GetCurFetchSize(), + numberOfCharsInThisChunk)); +#endif + + charsReadSoFar = 0; + static bool lastCRLFwasCRCRLF = false; + + while (ContinueParse() && !fServerConnection.DeathSignalReceived() && (charsReadSoFar < numberOfCharsInThisChunk)) + { + AdvanceToNextLine(); + if (ContinueParse()) + { + // When we split CRLF across two chunks, AdvanceToNextLine() turns the LF at the + // beginning of the next chunk into an empty line ending with CRLF, so discard + // that leading CR + bool specialLineEnding = false; + if (lastCRLFwasCRCRLF && (*fCurrentLine == '\r')) + { + char *usableCurrentLine = PL_strdup(fCurrentLine + 1); + PR_Free(fCurrentLine); + fCurrentLine = usableCurrentLine; + specialLineEnding = true; + } + + // This *would* fail on data containing \0, but down below AdvanceToNextLine() in + // nsMsgLineStreamBuffer::ReadNextLine() we replace '\0' with ' ' (blank) because + // who cares about binary transparency, and anyways \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) + { + // The chunk we are receiving doesn't end in CRLF, so the last line includes + // the CRLF that comes after the literal + char *displayEndOfLine = (fCurrentLine + strlen(fCurrentLine) - (charsReadSoFar - numberOfCharsInThisChunk)); + char saveit = *displayEndOfLine; + *displayEndOfLine = 0; + fServerConnection.HandleMessageDownLoadLine(fCurrentLine, specialLineEnding || !lastChunk); + *displayEndOfLine = saveit; + lastCRLFwasCRCRLF = (*(displayEndOfLine - 1) == '\r'); + } + else + { + lastCRLFwasCRCRLF = (*(fCurrentLine + strlen(fCurrentLine) - 1) == '\r'); + fServerConnection.HandleMessageDownLoadLine(fCurrentLine, + specialLineEnding || (!lastChunk && (charsReadSoFar == numberOfCharsInThisChunk)), + fCurrentLine); + } + } + } + + // This would be a good thing to log. + if (lastCRLFwasCRCRLF) + MOZ_LOG(IMAP, mozilla::LogLevel::Info, ("PARSER: CR/LF fell on chunk boundary.")); + + 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 + { + lastCRLFwasCRCRLF = 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: "), msg); + } + else + { + if (!strcmp(fCurrentLine, CRLF)) + fServerConnection.Log("PARSER", "Internal Syntax Error: %s: ", 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..5b46f8a43 --- /dev/null +++ b/mailnews/imap/src/nsImapServerResponseParser.h @@ -0,0 +1,269 @@ +/* -*- 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: + 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 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 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; + + // points to the current body shell, if any + RefPtr m_shell; + + // The connection object + nsImapProtocol &fServerConnection; + + nsIImapHostSessionList *fHostSessionList; + nsTArray fCopyResponseKeyArray; +}; + +#endif diff --git a/mailnews/imap/src/nsImapService.cpp b/mailnews/imap/src/nsImapService.cpp new file mode 100644 index 000000000..5e097311e --- /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 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 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 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 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 imapFolder = do_QueryInterface(aImapMailFolder); + if (imapFolder) + imapFolder->GetCanOpenFolder(&canOpenThisFolder); + + if (!canOpenThisFolder) + return NS_OK; + + nsresult rv; + nsCOMPtr 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 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 folder; + nsAutoCString msgKey; + nsresult rv = DecomposeImapURI(messageURI, getter_AddRefs(folder), msgKey); + if (NS_SUCCEEDED(rv)) + { + nsCOMPtr 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 mailnewsUrl = do_QueryInterface(imapUrl); + bool useLocalCache = false; + folder->HasMsgOffline(strtoul(msgKey.get(), nullptr, 10), &useLocalCache); + mailnewsUrl->SetMsgIsInLocalCache(useLocalCache); + + nsCOMPtr 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 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 imapMessageSink(do_QueryInterface(folder, &rv)); + if (NS_SUCCEEDED(rv)) + { + nsCOMPtr 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 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 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 imapMessageSink(do_QueryInterface(folder, &rv)); + if (NS_SUCCEEDED(rv)) + { + nsCOMPtr imapUrl = do_QueryInterface(aURI); + nsCOMPtr 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 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 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 aStreamListener = do_QueryInterface(aDisplayConsumer, &rv); + if (NS_SUCCEEDED(rv) && aStreamListener) + { + nsCOMPtr aChannel; + nsCOMPtr aLoadGroup; + nsCOMPtr 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 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 imapMessageSink(do_QueryInterface(folder, &rv)); + if (NS_SUCCEEDED(rv)) + { + nsCOMPtr 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 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 msgurl (do_QueryInterface(imapUrl)); + nsCOMPtr i18nurl (do_QueryInterface(imapUrl)); + i18nurl->SetCharsetOverRide(aCharsetOverride); + + uint32_t messageSize; + bool useMimePartsOnDemand = gMIMEOnDemand; + bool shouldStoreMsgOffline = false; + bool hasMsgOffline = false; + + nsCOMPtr aMsgIncomingServer; + + if (imapMessageSink) + imapMessageSink->GetMessageSizeFromDB(msgKey.get(), &messageSize); + + msgurl->SetMsgWindow(aMsgWindow); + + rv = msgurl->GetServer(getter_AddRefs(aMsgIncomingServer)); + + if (NS_SUCCEEDED(rv) && aMsgIncomingServer) + { + nsCOMPtr 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 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 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 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 aMsgIncomingServer; + rv = aImapMailFolder->GetServer(getter_AddRefs(aMsgIncomingServer)); + if (NS_SUCCEEDED(rv) && aMsgIncomingServer) + { + bool interrupted; + nsCOMPtr + 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 docShell(do_QueryInterface(aDisplayConsumer, &rv)); + if (NS_SUCCEEDED(rv) && docShell) + { + nsCOMPtr 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 aStreamListener = do_QueryInterface(aDisplayConsumer, &rv); + if (NS_SUCCEEDED(rv) && aStreamListener) + { + nsCOMPtr aChannel; + nsCOMPtr loadGroup; + nsCOMPtr 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 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 streamSupport; + streamSupport = do_QueryInterface(aMailboxCopy, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr folder; + nsAutoCString msgKey; + rv = DecomposeImapURI(nsDependentCString(aSrcMailboxURI), getter_AddRefs(folder), msgKey); + if (NS_SUCCEEDED(rv)) + { + nsCOMPtr imapMessageSink(do_QueryInterface(folder, &rv)); + if (NS_SUCCEEDED(rv)) + { + nsCOMPtr 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 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 streamSupport = do_QueryInterface(aMailboxCopy, &rv); + if (!streamSupport || NS_FAILED(rv)) + return rv; + + nsCOMPtr folder = srcFolder; + if (NS_SUCCEEDED(rv)) + { + nsCOMPtr 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 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 imapUrl; + nsCOMPtr 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 msgurl (do_QueryInterface(imapUrl)); + + msgurl->SetMsgWindow(aMsgWindow); + msgurl->SetSearchSession(aSearchSession); + rv = SetImapUrlSink(aMsgFolder, imapUrl); + + if (NS_SUCCEEDED(rv)) + { + nsCString folderName; + GetFolderName(aMsgFolder, folderName); + + nsCOMPtr 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 rdf = do_GetService("@mozilla.org/rdf/rdf-service;1",&rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr res; + rv = rdf->GetResource(folderURI, getter_AddRefs(res)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr 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 folder; + nsCOMPtr 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 imapMessageSink(do_QueryInterface(folder, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr msgUrl = do_QueryInterface(imapUrl, &rv); + NS_ENSURE_SUCCESS(rv, rv); + msgUrl->SetMessageFile(aFile); + msgUrl->SetAddDummyEnvelope(aAddDummyEnvelope); + msgUrl->SetCanonicalLineEnding(canonicalLineEnding); + + nsCOMPtr mailnewsUrl = do_QueryInterface(msgUrl); + if (mailnewsUrl) + mailnewsUrl->SetMsgIsInLocalCache(hasMsgOffline); + + nsCOMPtr 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>>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 url = do_QueryInterface(aImapUrl); + + rv = AddImapFetchToUrl(url, aImapMailFolder, messageIdentifierList, aAdditionalHeader); + NS_ENSURE_SUCCESS(rv, rv); + + if (WeAreOffline()) + { + bool msgIsInCache = false; + nsCOMPtr 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 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 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 docShell(do_QueryInterface(aDisplayConsumer, &rv)); + if (aImapMailFolder && docShell) + { + nsCOMPtr aMsgIncomingServer; + rv = aImapMailFolder->GetServer(getter_AddRefs(aMsgIncomingServer)); + if (NS_SUCCEEDED(rv) && aMsgIncomingServer) + { + bool interrupted; + nsCOMPtr + 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 streamListener = do_QueryInterface(aDisplayConsumer, &rv); + nsCOMPtr mailnewsUrl = do_QueryInterface(aImapUrl, &rv); + if (aMsgWindow && mailnewsUrl) + mailnewsUrl->SetMsgWindow(aMsgWindow); + if (NS_SUCCEEDED(rv) && streamListener) + { + nsCOMPtr channel; + nsCOMPtr 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 conversionListener; + nsCOMPtr 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 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 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 imapMessageSink(do_QueryInterface(folder, &rv)); + if (NS_SUCCEEDED(rv)) + { + nsCOMPtr imapUrl; + nsAutoCString urlSpec; + char hierarchyDelimiter = GetHierarchyDelimiter(folder); + rv = CreateStartOfImapUrl(nsDependentCString(aMessageURI), getter_AddRefs(imapUrl), + folder, aUrlListener, urlSpec, hierarchyDelimiter); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr msgurl (do_QueryInterface(imapUrl)); + nsCOMPtr 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 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 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 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 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 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 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 mailnewsUrl = do_QueryInterface(*imapUrl, &rv); + if (NS_SUCCEEDED(rv) && mailnewsUrl && aUrlListener) + mailnewsUrl->RegisterListener(aUrlListener); + nsCOMPtr 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 imapFolder = do_QueryInterface(aImapMailFolder); + if (imapFolder) + imapFolder->GetHierarchyDelimiter(&hierarchyDelimiter); + } + return rv; +} + +/* fetching the headers of RFC822 messages */ +/* imap4://HOST>header>>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 imapUrl; + nsAutoCString urlSpec; + char hierarchyDelimiter = GetHierarchyDelimiter(aImapMailFolder); + + nsresult rv = CreateStartOfImapUrl(EmptyCString(), getter_AddRefs(imapUrl), aImapMailFolder, + aUrlListener, urlSpec, hierarchyDelimiter); + if (NS_SUCCEEDED(rv) && imapUrl) + { + nsCOMPtr 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>>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 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 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 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 uri = do_QueryInterface(imapUrl); + nsCOMPtr 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 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 uri = do_QueryInterface(imapUrl); + + nsCOMPtr 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 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 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 server; + if (NS_SUCCEEDED(aImapMailFolder->GetServer(getter_AddRefs(server))) && server) + { + nsCOMPtr 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 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 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 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 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 incomingServer; + nsCOMPtr imapServerSink; + + rv = aMsgFolder->GetServer(getter_AddRefs(incomingServer)); + if (NS_SUCCEEDED(rv) && incomingServer) + { + imapServerSink = do_QueryInterface(incomingServer); + if (imapServerSink) + aImapUrl->SetImapServerSink(imapServerSink); + } + + nsCOMPtr imapMailFolderSink = do_QueryInterface(aMsgFolder); + if (NS_SUCCEEDED(rv) && imapMailFolderSink) + aImapUrl->SetImapMailFolderSink(imapMailFolderSink); + + nsCOMPtr imapMessageSink = do_QueryInterface(aMsgFolder); + if (NS_SUCCEEDED(rv) && imapMessageSink) + aImapUrl->SetImapMessageSink(imapMessageSink); + + nsCOMPtr 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 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 uri = do_QueryInterface(imapUrl); + nsCOMPtr mailnewsurl = do_QueryInterface(imapUrl); + if (mailnewsurl) + mailnewsurl->SetMsgWindow(aMsgWindow); + urlSpec.Append("/discoverallboxes"); + nsCOMPtr 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 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 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 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 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 srcServer; + nsCOMPtr 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 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 msgurl (do_QueryInterface(imapUrl)); + + msgurl->SetMsgWindow(aMsgWindow); + nsCOMPtr 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 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 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 offlineStore; + nsCOMPtr msgStore; + nsCOMPtr dstServer; + nsCOMPtr 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 seekable = do_QueryInterface(offlineStore); + if (seekable) + seekable->Tell(&curOfflineStorePos); + else + { + NS_ERROR("needs to be a random store!"); + return NS_ERROR_FAILURE; + } + + nsCOMPtr inputStream; + nsCOMPtr 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. + 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 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); + delete inputStreamBuffer; + } + 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 imapUrl; + nsAutoCString urlSpec; + + char hierarchyDelimiter = GetHierarchyDelimiter(aDstFolder); + rv = CreateStartOfImapUrl(EmptyCString(), getter_AddRefs(imapUrl), aDstFolder, aListener, urlSpec, hierarchyDelimiter); + if (NS_SUCCEEDED(rv)) + { + nsCOMPtr 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 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 aMsgIncomingServer; + nsCOMPtr msgUrl = do_QueryInterface(aImapUrl); + nsresult rv = msgUrl->GetServer(getter_AddRefs(aMsgIncomingServer)); + + if (aURL) + { + nsCOMPtr msgUrlUri = do_QueryInterface(msgUrl); + msgUrlUri.swap(*aURL); + } + + if (NS_SUCCEEDED(rv) && aMsgIncomingServer) + { + nsCOMPtr 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 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 mailNewsUrl = do_QueryInterface(imapUrl); + if (mailNewsUrl) + mailNewsUrl->SetMsgWindow(msgWindow); + char hierarchyDelimiter = kOnlineHierarchySeparatorUnknown; + nsCString folderName; + + nsCOMPtr 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 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 uri = do_QueryInterface(imapUrl); + nsCOMPtr 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 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 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 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 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 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 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 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 aImapUrl = do_CreateInstance(kImapUrlCID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + // now extract lots of fun information... + nsCOMPtr 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 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 rootFolder; + server->GetRootFolder(getter_AddRefs(rootFolder)); + if (rootFolder && !folderName.IsEmpty()) + { + nsCOMPtr folder; + nsCOMPtr imapRoot = do_QueryInterface(rootFolder); + nsCOMPtr 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 msgSink = do_QueryInterface(folder); + (void) aImapUrl->SetImapMessageSink(msgSink); + + nsCOMPtr 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 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 imapUrl = do_QueryInterface(aURI, &rv); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr 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 channel = do_CreateInstance(kCImapMockChannel, &rv); + NS_ENSURE_SUCCESS(rv, rv); + channel->SetURI(aURI); + + rv = channel->SetLoadInfo(aLoadInfo); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr msgWindow; + mailnewsUrl->GetMsgWindow(getter_AddRefs(msgWindow)); + if (msgWindow) + { + nsCOMPtr msgDocShell; + msgWindow->GetRootDocShell(getter_AddRefs(msgDocShell)); + if (msgDocShell) + { + nsCOMPtr prevEventSink; + channel->GetProgressEventSink(getter_AddRefs(prevEventSink)); + nsCOMPtr 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 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 aFolder; + // now try to get the folder in question... + nsCOMPtr rootFolder; + server->GetRootFolder(getter_AddRefs(rootFolder)); + nsCOMPtr imapRoot = do_QueryInterface(rootFolder); + nsCOMPtr subFolder; + if (imapRoot) + { + imapRoot->FindOnlineSubFolder(folderName, getter_AddRefs(subFolder)); + aFolder = do_QueryInterface(subFolder); + } + nsCOMPtr 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()) // 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 dialog; + nsCOMPtr wwatch(do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + wwatch->GetNewPrompter(nullptr, getter_AddRefs(dialog)); + + nsString statusString, confirmText; + nsCOMPtr 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 imapServer = do_QueryInterface(server); + if (imapServer) + { + nsCOMPtr 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 imapSubscribeUrl = do_QueryInterface(subscribeURI); + if (imapSubscribeUrl) + imapSubscribeUrl->SetExternalLinkUrl(true); + nsCOMPtr mailnewsUrl = do_QueryInterface(subscribeURI); + if (mailnewsUrl) + { + nsCOMPtr mailSession = do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr msgWindow; + rv = mailSession->GetTopmostMsgWindow(getter_AddRefs(msgWindow)); + if (NS_SUCCEEDED(rv) && msgWindow) + { + mailnewsUrl->SetMsgWindow(msgWindow); + nsCOMPtr 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 imapFolder; + nsCOMPtr 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 mailSession = do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr msgWindow; + rv = mailSession->GetTopmostMsgWindow(getter_AddRefs(msgWindow)); + if (NS_SUCCEEDED(rv) && msgWindow) + { + nsCString uri; + rootFolder->GetURI(uri); + uri.Append('/'); + uri.Append(fullFolderName); + nsCOMPtr 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 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 server = do_QueryInterface(aServer); + if (!server) + return NS_ERROR_FAILURE; + + nsCOMPtr rootMsgFolder; + rv = server->GetRootMsgFolder(getter_AddRefs(rootMsgFolder)); + + NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && rootMsgFolder, NS_ERROR_FAILURE); + + nsCOMPtr 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 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 server = do_QueryInterface(aServer); + if (!server) + return NS_ERROR_FAILURE; + + nsCOMPtr rootMsgFolder; + rv = server->GetRootMsgFolder(getter_AddRefs(rootMsgFolder)); + + NS_ENSURE_SUCCESS(rv, rv); + if (!rootMsgFolder) + return NS_ERROR_FAILURE; + + nsCOMPtr 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 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 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 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 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 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 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 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 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 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 runningURI; + // need to pass in stream listener in order to get the channel created correctly + nsCOMPtr imapMessageSink(do_QueryInterface(aFolder, &rv)); + rv = FetchMessage(imapUrl, nsImapUrl::nsImapMsgDownloadForOffline,aFolder, + imapMessageSink, aMsgWindow, nullptr, messageIds, + false, EmptyCString(), getter_AddRefs(runningURI)); + if (runningURI && aUrlListener) + { + nsCOMPtr msgurl (do_QueryInterface(runningURI)); + nsCOMPtr 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 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 cacheStorageService = + do_GetService("@mozilla.org/netwerk/cache-storage-service;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + RefPtr 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 aChannel = do_QueryInterface(request, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + if (PL_strcasecmp(aContentType, "x-application-imapfolder") == 0) + { + nsCOMPtr uri; + rv = aChannel->GetURI(getter_AddRefs(uri)); + NS_ENSURE_SUCCESS(rv, rv); + + if (uri) + { + request->Cancel(NS_BINDING_ABORTED); + nsCOMPtr 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 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 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 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 stringService = + mozilla::services::GetStringBundleService(); + if (!stringService) return NS_ERROR_NULL_POINTER; + nsCOMPtr 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* 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 srcDB; + rv = srcFolder->GetMsgDatabase(getter_AddRefs(srcDB)); + NS_ENSURE_SUCCESS(rv, rv); + uint32_t i, count = m_srcKeyArray.Length(); + nsCOMPtr srcHdr; + nsCOMPtr 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 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 srcFolder = do_QueryReferent(m_srcFolder, &rv); + if (NS_FAILED(rv) || !srcFolder) + return rv; + nsCOMPtr 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 dstFolder = do_QueryReferent(m_dstFolder, &rv); + if (NS_FAILED(rv) || !dstFolder) + return rv; + + nsCOMPtr 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 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 srcFolder = do_QueryReferent(m_srcFolder, &rv); + if (NS_FAILED(rv) || !srcFolder) + return rv; + nsCOMPtr 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 dstFolder = do_QueryReferent(m_dstFolder, &rv); + if (NS_FAILED(rv) || !dstFolder) return rv; + + nsCOMPtr 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& 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 srcFolder = do_QueryReferent(m_srcFolder, &rv); + if (NS_FAILED(rv) || !srcFolder) return rv; + + nsCOMPtr dstFolder = do_QueryReferent(m_dstFolder, &rv); + if (NS_FAILED(rv) || !dstFolder) return rv; + + nsCOMPtr srcDB; + nsCOMPtr 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 oldHdr; + nsCOMPtr 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 srcDB; + nsCOMPtr 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 server; + if (!aFolder) + return NS_ERROR_NULL_POINTER; + rv = aFolder->GetServer(getter_AddRefs(server)); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr 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 urlListener = do_QueryReferent(m_onStopListener); + if (urlListener) + urlListener->OnStopRunningUrl(aUrl, aExitCode); + + nsCOMPtr imapUrl = do_QueryInterface(aUrl); + if (imapUrl) + { + nsresult rv; + nsCOMPtr imapService = do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + nsImapAction imapAction; + imapUrl->GetImapAction(&imapAction); + nsCOMPtr dstFolder = do_QueryReferent(m_dstFolder, &rv); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr 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 imapDest = do_QueryInterface(dstFolder); + if (imapDest) + imapDest->UpdateFolderWithListener(nullptr, this); + } + } + else if (!m_dstMsgIdString.IsEmpty()) + { + nsCOMPtr 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 dstKeys; + nsCOMPtr destDB; + nsCOMPtr 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* srcKeyArray, + const char *srcMsgIdString, nsIMsgFolder* dstFolder, + bool isMove, nsOfflineImapOperationType opType, + nsCOMArray &srcHdrs) +{ + Init(srcFolder, srcKeyArray, srcMsgIdString, dstFolder, true, + isMove); + + m_opType = opType; + m_flags = 0; + m_addFlags = false; + if (opType == nsIMsgOfflineImapOperation::kDeletedMsg) + { + nsCOMPtr srcDB; + nsCOMPtr folderInfo; + + nsresult rv = srcFolder->GetDBFolderInfoAndDB(getter_AddRefs(folderInfo), getter_AddRefs(srcDB)); + if (NS_SUCCEEDED(rv) && srcDB) + { + nsMsgKey pseudoKey; + nsCOMPtr 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 srcFolder = do_QueryReferent(m_srcFolder, &rv); + if (NS_FAILED(rv) || !srcFolder) + return rv; + nsCOMPtr op; + nsCOMPtr folderInfo; + nsCOMPtr srcDB; + nsCOMPtr 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 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 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 undeletedHdr = m_srcHdrs[i]; + m_srcHdrs[i]->GetMessageKey(&msgKey); + if (undeletedHdr) + { + nsCOMPtr 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 srcFolder = do_QueryReferent(m_srcFolder, &rv); + if (NS_FAILED(rv) || !srcFolder) + return rv; + nsCOMPtr op; + nsCOMPtr folderInfo; + nsCOMPtr srcDB; + nsCOMPtr 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 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 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 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* 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& srcKeyArray); + void GetSrcMsgIds(nsCString &srcMsgIds) {srcMsgIds = m_srcMsgIdString;} + nsresult AddDstKey(nsMsgKey aKey); + nsresult UndoMailboxDelete(); + nsresult RedoMailboxDelete(); + nsresult Init(nsIMsgFolder* srcFolder, nsTArray* srcKeyArray, + const char* srcMsgIdString, nsIMsgFolder* dstFolder, + bool idsAreUids, bool isMove); + +protected: + virtual ~nsImapMoveCopyMsgTxn(); + + nsWeakPtr m_srcFolder; + nsCOMArray m_srcHdrs; + nsTArray m_dupKeyArray; + nsTArray m_srcKeyArray; + nsTArray m_srcMessageIds; + nsCString m_srcMsgIdString; + nsWeakPtr m_dstFolder; + nsCString m_dstMsgIdString; + bool m_idsAreUids; + bool m_isMove; + bool m_srcIsPop3; + nsTArray 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* srcKeyArray, + const char* srcMsgIdString, + nsIMsgFolder* dstFolder, + bool isMove, + nsOfflineImapOperationType opType, + nsCOMArray &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..d76437905 --- /dev/null +++ b/mailnews/imap/src/nsImapUrl.cpp @@ -0,0 +1,1563 @@ +/* -*- 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 "nsAlgorithm.h" +#include "nsServiceManagerUtils.h" +#include + +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 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 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 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 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 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 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 server; + + nsCOMPtr 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 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 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 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 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 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 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 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 m_copyState; // now, refcounted. + nsCOMPtr m_file; + nsWeakPtr m_channelWeakPtr; + + // used by save message to disk + nsCOMPtr 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 &keys) +{ + // This is in the form ,, or : + 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 &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 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..ec547eb91 --- /dev/null +++ b/mailnews/imap/src/nsSyncRunnableHelpers.cpp @@ -0,0 +1,600 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 +struct RefType +{ + typedef T& type; +}; + +template<> +struct RefType +{ + typedef nsAString& type; +}; + +template<> +struct RefType +{ + typedef const nsAString& type; +}; + +template<> +struct RefType +{ + typedef nsACString& type; +}; + +template<> +struct RefType +{ + typedef const nsACString& type; +}; + +template<> +struct RefType +{ + 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 +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 +class SyncRunnable1 : public SyncRunnableBase +{ +public: + typedef nsresult (NS_STDCALL Receiver::*ReceiverMethod)(Arg1); + typedef typename RefType::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 +class SyncRunnable2 : public SyncRunnableBase +{ +public: + typedef nsresult (NS_STDCALL Receiver::*ReceiverMethod)(Arg1, Arg2); + typedef typename RefType::type Arg1Ref; + typedef typename RefType::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 +class SyncRunnable3 : public SyncRunnableBase +{ +public: + typedef nsresult (NS_STDCALL Receiver::*ReceiverMethod)(Arg1, Arg2, Arg3); + typedef typename RefType::type Arg1Ref; + typedef typename RefType::type Arg2Ref; + typedef typename RefType::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 +class SyncRunnable4 : public SyncRunnableBase +{ +public: + typedef nsresult (NS_STDCALL Receiver::*ReceiverMethod)(Arg1, Arg2, Arg3, Arg4); + typedef typename RefType::type Arg1Ref; + typedef typename RefType::type Arg2Ref; + typedef typename RefType::type Arg3Ref; + typedef typename RefType::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 +class SyncRunnable5 : public SyncRunnableBase +{ +public: + typedef nsresult (NS_STDCALL Receiver::*ReceiverMethod)(Arg1, Arg2, Arg3, Arg4, Arg5); + typedef typename RefType::type Arg1Ref; + typedef typename RefType::type Arg2Ref; + typedef typename RefType::type Arg3Ref; + typedef typename RefType::type Arg4Ref; + typedef typename RefType::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 r = \ + new SyncRunnable0 \ + (mReceiver, &nsI##iface::method); \ + return DispatchSyncRunnable(r); \ + } + + +#define NS_SYNCRUNNABLEMETHOD1(iface, method, \ + arg1) \ + NS_IMETHODIMP iface##Proxy::method(arg1 a1) { \ + RefPtr r = \ + new SyncRunnable1 \ + (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 r = \ + new SyncRunnable2 \ + (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 r = \ + new SyncRunnable3 \ + (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 r = \ + new SyncRunnable4 \ + (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 r = \ + new SyncRunnable5 \ + (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 r = \ + new SyncRunnable1 \ + (mReceiver, &nsI##iface::Get##attribute, a1); \ + return DispatchSyncRunnable(r); \ + } \ +NS_IMETHODIMP iface##Proxy::Set##attribute(type a1) { \ + RefPtr r = \ + new SyncRunnable1 \ + (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 &) + + +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 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 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 diff --git a/mailnews/imap/src/nsSyncRunnableHelpers.h b/mailnews/imap/src/nsSyncRunnableHelpers.h new file mode 100644 index 000000000..4fcadf465 --- /dev/null +++ b/mailnews/imap/src/nsSyncRunnableHelpers.h @@ -0,0 +1,146 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsSyncRunnableHelpers_h +#define nsSyncRunnableHelpers_h + +#include "nsThreadUtils.h" +#include "nsProxyRelease.h" + +#include "mozilla/Monitor.h" +#include "msgIOAuth2Module.h" +#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: + StreamListenerProxy(nsIStreamListener* receiver) + : mReceiver(receiver) + { } + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIREQUESTOBSERVER + NS_DECL_NSISTREAMLISTENER + +private: + ~StreamListenerProxy() { + NS_ReleaseOnMainThread(mReceiver.forget()); + } + nsCOMPtr mReceiver; +}; + +class ImapMailFolderSinkProxy final : public nsIImapMailFolderSink +{ +public: + 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 mReceiver; +}; + +class ImapServerSinkProxy final : public nsIImapServerSink +{ +public: + ImapServerSinkProxy(nsIImapServerSink* receiver) + : mReceiver(receiver) + { } + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIIMAPSERVERSINK + +private: + ~ImapServerSinkProxy() { + NS_ReleaseOnMainThread(mReceiver.forget()); + } + nsCOMPtr mReceiver; +}; + + +class ImapMessageSinkProxy final : public nsIImapMessageSink +{ +public: + ImapMessageSinkProxy(nsIImapMessageSink* receiver) + : mReceiver(receiver) + { } + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIIMAPMESSAGESINK + +private: + ~ImapMessageSinkProxy() { + NS_ReleaseOnMainThread(mReceiver.forget()); + } + nsCOMPtr mReceiver; +}; + +class ImapProtocolSinkProxy final : public nsIImapProtocolSink +{ +public: + ImapProtocolSinkProxy(nsIImapProtocolSink* receiver) + : mReceiver(receiver) + { } + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIIMAPPROTOCOLSINK + +private: + ~ImapProtocolSinkProxy() { + NS_ReleaseOnMainThread(mReceiver.forget()); + } + nsCOMPtr mReceiver; +}; + +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 mOAuth2Support; + nsCOMPtr mServer; + nsCString mOAuth2String; +}; + +} // namespace mailnews +} // namespace mozilla + +#endif // nsSyncRunnableHelpers_h -- cgit v1.2.3