summaryrefslogtreecommitdiffstats
path: root/mailnews/imap/src/nsImapProtocol.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'mailnews/imap/src/nsImapProtocol.cpp')
-rw-r--r--mailnews/imap/src/nsImapProtocol.cpp10046
1 files changed, 10046 insertions, 0 deletions
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 <algorithm>
+// 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<nsCString> gForceSelectServersArray;
+
+// let delete model control expunging, i.e., don't ever expunge when the
+// user chooses the imap delete model, otherwise, expunge when over the
+// threshhold. This is the normal TB behavior.
+static const int32_t kAutoExpungeDeleteModel = 0;
+// Expunge whenever the folder is opened
+static const int32_t kAutoExpungeAlways = 1;
+// Expunge when over the threshhold, independent of the delete model.
+static const int32_t kAutoExpungeOnThreshold = 2;
+static int32_t gExpungeOption = kAutoExpungeDeleteModel;
+static int32_t gExpungeThreshold = 20;
+
+const int32_t kAppBufSize = 100;
+// can't use static nsCString because it shows up as a leak.
+static char gAppName[kAppBufSize];
+static char gAppVersion[kAppBufSize];
+
+nsresult nsImapProtocol::GlobalInitialization(nsIPrefBranch *aPrefBranch)
+{
+ gInitialized = true;
+
+ aPrefBranch->GetIntPref("mail.imap.chunk_fast", &gTooFastTime); // secs we read too little too fast
+ aPrefBranch->GetIntPref("mail.imap.chunk_ideal", &gIdealTime); // secs we read enough in good time
+ aPrefBranch->GetIntPref("mail.imap.chunk_add", &gChunkAddSize); // buffer size to add when wasting time
+ aPrefBranch->GetIntPref("mail.imap.chunk_size", &gChunkSize);
+ aPrefBranch->GetIntPref("mail.imap.min_chunk_size_threshold",
+ &gChunkThreshold);
+ aPrefBranch->GetBoolPref("mail.imap.hide_other_users",
+ &gHideOtherUsersFromList);
+ aPrefBranch->GetBoolPref("mail.imap.hide_unused_namespaces",
+ &gHideUnusedNamespaces);
+ aPrefBranch->GetIntPref("mail.imap.noop_check_count",
+ &gPromoteNoopToCheckCount);
+ aPrefBranch->GetBoolPref("mail.imap.use_envelope_cmd",
+ &gUseEnvelopeCmd);
+ aPrefBranch->GetBoolPref("mail.imap.use_literal_plus", &gUseLiteralPlus);
+ aPrefBranch->GetBoolPref("mail.imap.expunge_after_delete",
+ &gExpungeAfterDelete);
+ aPrefBranch->GetBoolPref("mail.imap.check_deleted_before_expunge",
+ &gCheckDeletedBeforeExpunge);
+ aPrefBranch->GetIntPref("mail.imap.expunge_option", &gExpungeOption);
+ aPrefBranch->GetIntPref("mail.imap.expunge_threshold_number",
+ &gExpungeThreshold);
+ aPrefBranch->GetIntPref("mailnews.tcptimeout", &gResponseTimeout);
+ aPrefBranch->GetCharPref("mail.imap.force_select_detect",
+ getter_Copies(gForceSelectDetect));
+ ParseString(gForceSelectDetect, ';', gForceSelectServersArray);
+
+ nsCOMPtr<nsIXULAppInfo> appInfo(do_GetService(XULAPPINFO_SERVICE_CONTRACTID));
+
+ if (appInfo)
+ {
+ nsCString appName, appVersion;
+ appInfo->GetName(appName);
+ appInfo->GetVersion(appVersion);
+ PL_strncpyz(gAppName, appName.get(), kAppBufSize);
+ PL_strncpyz(gAppVersion, appVersion.get(), kAppBufSize);
+ }
+ return NS_OK;
+}
+
+nsImapProtocol::nsImapProtocol() : nsMsgProtocol(nullptr),
+ m_dataAvailableMonitor("imapDataAvailable"),
+ m_urlReadyToRunMonitor("imapUrlReadyToRun"),
+ m_pseudoInterruptMonitor("imapPseudoInterrupt"),
+ m_dataMemberMonitor("imapDataMember"),
+ m_threadDeathMonitor("imapThreadDeath"),
+ m_waitForBodyIdsMonitor("imapWaitForBodyIds"),
+ m_fetchBodyListMonitor("imapFetchBodyList"),
+ m_passwordReadyMonitor("imapPasswordReady"),
+ mLock("nsImapProtocol.mLock"),
+ m_parser(*this)
+{
+ m_urlInProgress = false;
+ m_idle = false;
+ m_retryUrlOnError = false;
+ m_useIdle = true; // by default, use it
+ m_useCondStore = true;
+ m_useCompressDeflate = true;
+ m_ignoreExpunges = false;
+ m_prefAuthMethods = kCapabilityUndefined;
+ m_failedAuthMethods = 0;
+ m_currentAuthMethod = kCapabilityUndefined;
+ m_socketType = nsMsgSocketType::trySTARTTLS;
+ m_connectionStatus = NS_OK;
+ m_safeToCloseConnection = false;
+ m_hostSessionList = nullptr;
+ m_flagState = nullptr;
+ m_fetchBodyIdList = nullptr;
+ m_isGmailServer = false;
+ m_fetchingWholeMessage = false;
+
+ nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID));
+ NS_ASSERTION(prefBranch, "FAILED to create the preference service");
+
+ // read in the accept languages preference
+ if (prefBranch)
+ {
+ if (!gInitialized)
+ GlobalInitialization(prefBranch);
+
+ nsCOMPtr<nsIPrefLocalizedString> prefString;
+ prefBranch->GetComplexValue("intl.accept_languages",
+ NS_GET_IID(nsIPrefLocalizedString),
+ getter_AddRefs(prefString));
+ if (prefString)
+ prefString->ToString(getter_Copies(mAcceptLanguages));
+
+ nsCString customDBHeaders;
+ prefBranch->GetCharPref("mailnews.customDBHeaders",
+ getter_Copies(customDBHeaders));
+
+ ParseString(customDBHeaders, ' ', mCustomDBHeaders);
+ prefBranch->GetBoolPref("mailnews.display.prefer_plaintext", &m_preferPlainText);
+
+ nsAutoCString customHeaders;;
+ prefBranch->GetCharPref("mailnews.customHeaders",
+ getter_Copies(customHeaders));
+ customHeaders.StripWhitespace();
+ ParseString(customHeaders, ':', mCustomHeaders);
+ }
+
+ // ***** Thread support *****
+ m_thread = nullptr;
+ m_imapThreadIsRunning = false;
+ m_currentServerCommandTagNumber = 0;
+ m_active = false;
+ m_folderNeedsSubscribing = false;
+ m_folderNeedsACLRefreshed = false;
+ m_threadShouldDie = false;
+ m_inThreadShouldDie = false;
+ m_pseudoInterrupted = false;
+ m_nextUrlReadyToRun = false;
+ m_trackingTime = false;
+ m_curFetchSize = 0;
+ m_startTime = 0;
+ m_endTime = 0;
+ m_lastActiveTime = 0;
+ m_lastProgressTime = 0;
+ ResetProgressInfo();
+
+ m_tooFastTime = 0;
+ m_idealTime = 0;
+ m_chunkAddSize = 0;
+ m_chunkStartSize = 0;
+ m_fetchByChunks = true;
+ m_sendID = true;
+ m_chunkSize = 0;
+ m_chunkThreshold = 0;
+ m_fromHeaderSeen = false;
+ m_closeNeededBeforeSelect = false;
+ m_needNoop = false;
+ m_noopCount = 0;
+ m_fetchBodyListIsNew = false;
+ m_flagChangeCount = 0;
+ m_lastCheckTime = PR_Now();
+
+ m_checkForNewMailDownloadsHeaders = true; // this should be on by default
+ m_hierarchyNameState = kNoOperationInProgress;
+ m_discoveryStatus = eContinue;
+
+ m_overRideUrlConnectionInfo = false;
+ // m_dataOutputBuf is used by Send Data
+ m_dataOutputBuf = (char *) PR_CALLOC(sizeof(char) * OUTPUT_BUFFER_SIZE);
+ m_allocatedSize = OUTPUT_BUFFER_SIZE;
+
+ // used to buffer incoming data by ReadNextLine
+ m_inputStreamBuffer = new nsMsgLineStreamBuffer(OUTPUT_BUFFER_SIZE, true /* allocate new lines */, false /* leave CRLFs on the returned string */);
+ m_currentBiffState = nsIMsgFolder::nsMsgBiffState_Unknown;
+ m_progressStringName.Truncate();
+
+ // since these are embedded in the nsImapProtocol object, but passed
+ // through proxied xpcom methods, just AddRef them here.
+ m_hdrDownloadCache = new nsMsgImapHdrXferInfo();
+ m_downloadLineCache = new nsMsgImapLineDownloadCache();
+
+ // subscription
+ m_autoSubscribe = true;
+ m_autoUnsubscribe = true;
+ m_autoSubscribeOnOpen = true;
+ m_deletableChildren = nullptr;
+
+ mFolderLastModSeq = 0;
+
+ Configure(gTooFastTime, gIdealTime, gChunkAddSize, gChunkSize,
+ gChunkThreshold, gFetchByChunks);
+ m_forceSelect = false;
+
+ // where should we do this? Perhaps in the factory object?
+ if (!IMAP)
+ IMAP = PR_NewLogModule("IMAP");
+}
+
+nsresult nsImapProtocol::Configure(int32_t TooFastTime, int32_t IdealTime,
+ int32_t ChunkAddSize, int32_t ChunkSize, int32_t ChunkThreshold,
+ bool FetchByChunks)
+{
+ m_tooFastTime = TooFastTime; // secs we read too little too fast
+ m_idealTime = IdealTime; // secs we read enough in good time
+ m_chunkAddSize = ChunkAddSize; // buffer size to add when wasting time
+ m_chunkStartSize = m_chunkSize = ChunkSize;
+ m_chunkThreshold = ChunkThreshold;
+ m_fetchByChunks = FetchByChunks;
+
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP
+nsImapProtocol::Initialize(nsIImapHostSessionList * aHostSessionList,
+ nsIImapIncomingServer *aServer)
+{
+ NS_PRECONDITION(aHostSessionList && aServer,
+ "oops...trying to initialize with a null host session list or server!");
+ if (!aHostSessionList || !aServer)
+ return NS_ERROR_NULL_POINTER;
+
+ nsresult rv = m_downloadLineCache->GrowBuffer(kDownLoadCacheSize);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ m_flagState = new nsImapFlagAndUidState(kImapFlagAndUidStateSize);
+ if (!m_flagState)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ aServer->GetUseIdle(&m_useIdle);
+ aServer->GetForceSelect(m_forceSelectValue);
+ aServer->GetUseCondStore(&m_useCondStore);
+ aServer->GetUseCompressDeflate(&m_useCompressDeflate);
+ NS_ADDREF(m_flagState);
+
+ m_hostSessionList = aHostSessionList; // 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<nsIStringBundle> bundle;
+ rv = IMAPGetStringBundle(getter_AddRefs(bundle));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = bundle->GetStringFromName(u"imapEmptyMimePart",
+ getter_Copies(m_emptyMimePartString));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Now initialize the thread for the connection
+ if (m_thread == nullptr)
+ {
+ nsresult rv = NS_NewThread(getter_AddRefs(m_iThread), this);
+ if (NS_FAILED(rv))
+ {
+ NS_ASSERTION(m_iThread, "Unable to create imap thread.\n");
+ return rv;
+ }
+ m_iThread->GetPRThread(&m_thread);
+
+ }
+ return NS_OK;
+}
+
+nsImapProtocol::~nsImapProtocol()
+{
+ PR_Free(m_fetchBodyIdList);
+
+ NS_IF_RELEASE(m_flagState);
+
+ PR_Free(m_dataOutputBuf);
+ 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<nsIURI> url = do_QueryInterface(m_runningUrl);
+ url->GetAsciiHost(m_hostName);
+ }
+
+ return m_hostName;
+}
+
+const nsCString&
+nsImapProtocol::GetImapUserName()
+{
+ if (m_userName.IsEmpty() && m_imapServerSink)
+ {
+ m_imapServerSink->GetOriginalUsername(m_userName);
+ }
+ return m_userName;
+}
+
+const char*
+nsImapProtocol::GetImapServerKey()
+{
+ if (m_serverKey.IsEmpty() && m_imapServerSink)
+ {
+ m_imapServerSink->GetServerKey(m_serverKey);
+ }
+ return m_serverKey.get();
+}
+
+nsresult
+nsImapProtocol::SetupSinkProxy()
+{
+ nsresult res;
+ if (m_runningUrl)
+ {
+ if (!m_imapMailFolderSink)
+ {
+ nsCOMPtr<nsIImapMailFolderSink> aImapMailFolderSink;
+ (void) m_runningUrl->GetImapMailFolderSink(getter_AddRefs(aImapMailFolderSink));
+ if (aImapMailFolderSink)
+ {
+ m_imapMailFolderSink = new ImapMailFolderSinkProxy(aImapMailFolderSink);
+ }
+ }
+
+ if (!m_imapMessageSink)
+ {
+ nsCOMPtr<nsIImapMessageSink> aImapMessageSink;
+ (void) m_runningUrl->GetImapMessageSink(getter_AddRefs(aImapMessageSink));
+ if (aImapMessageSink) {
+ m_imapMessageSink = new ImapMessageSinkProxy(aImapMessageSink);
+ } else {
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+ }
+ if (!m_imapServerSink)
+ {
+ nsCOMPtr<nsIImapServerSink> aImapServerSink;
+ res = m_runningUrl->GetImapServerSink(getter_AddRefs(aImapServerSink));
+ if (aImapServerSink) {
+ m_imapServerSink = new ImapServerSinkProxy(aImapServerSink);
+ } else {
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+ }
+ if (!m_imapProtocolSink)
+ {
+ nsCOMPtr<nsIImapProtocolSink> anImapProxyHelper(do_QueryInterface(NS_ISUPPORTS_CAST(nsIImapProtocolSink*, this), &res));
+ m_imapProtocolSink = new ImapProtocolSinkProxy(anImapProxyHelper);
+ }
+ }
+ return NS_OK;
+}
+
+static void SetSecurityCallbacksFromChannel(nsISocketTransport* aTrans, nsIChannel* aChannel)
+{
+ nsCOMPtr<nsIInterfaceRequestor> callbacks;
+ aChannel->GetNotificationCallbacks(getter_AddRefs(callbacks));
+
+ nsCOMPtr<nsILoadGroup> loadGroup;
+ aChannel->GetLoadGroup(getter_AddRefs(loadGroup));
+
+ nsCOMPtr<nsIInterfaceRequestor> securityCallbacks;
+ MsgNewNotificationCallbacksAggregation(callbacks, loadGroup,
+ getter_AddRefs(securityCallbacks));
+ if (securityCallbacks)
+ aTrans->SetSecurityCallbacks(securityCallbacks);
+}
+
+// Setup With Url is intended to set up data which is held on a PER URL basis and not
+// a per connection basis. If you have data which is independent of the url we are currently
+// running, then you should put it in Initialize().
+// This is only ever called from the UI thread. It is called from LoadUrl, right
+// before the url gets run - i.e., the url is next in line to run.
+nsresult nsImapProtocol::SetupWithUrl(nsIURI * aURL, nsISupports* aConsumer)
+{
+ nsresult rv = NS_ERROR_FAILURE;
+ NS_PRECONDITION(aURL, "null URL passed into Imap Protocol");
+ if (aURL)
+ {
+ m_runningUrl = do_QueryInterface(aURL, &rv);
+ if (NS_FAILED(rv)) return rv;
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_runningUrl);
+ nsCOMPtr<nsIMsgIncomingServer> server = do_QueryReferent(m_server);
+ if (!server)
+ {
+ rv = mailnewsUrl->GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+ m_server = do_GetWeakReference(server);
+ }
+ nsCOMPtr <nsIMsgFolder> folder;
+ mailnewsUrl->GetFolder(getter_AddRefs(folder));
+ mFolderLastModSeq = 0;
+ mFolderTotalMsgCount = 0;
+ mFolderHighestUID = 0;
+ m_uidValidity = kUidUnknown;
+ if (folder)
+ {
+ nsCOMPtr<nsIMsgDatabase> folderDB;
+ nsCOMPtr<nsIDBFolderInfo> folderInfo;
+ folder->GetDBFolderInfoAndDB(getter_AddRefs(folderInfo), getter_AddRefs(folderDB));
+ if (folderInfo)
+ {
+ nsCString modSeqStr;
+ folderInfo->GetCharProperty(kModSeqPropertyName, modSeqStr);
+ mFolderLastModSeq = ParseUint64Str(modSeqStr.get());
+ folderInfo->GetNumMessages(&mFolderTotalMsgCount);
+ folderInfo->GetUint32Property(kHighestRecordedUIDPropertyName, 0, &mFolderHighestUID);
+ folderInfo->GetImapUidValidity(&m_uidValidity);
+ }
+ }
+ nsCOMPtr<nsIImapIncomingServer> imapServer = do_QueryInterface(server);
+ nsCOMPtr<nsIStreamListener> aRealStreamListener = do_QueryInterface(aConsumer);
+ m_runningUrl->GetMockChannel(getter_AddRefs(m_mockChannel));
+ imapServer->GetIsGMailServer(&m_isGmailServer);
+ if (!m_mockChannel)
+ {
+
+ nsCOMPtr<nsIPrincipal> nullPrincipal =
+ do_CreateInstance("@mozilla.org/nullprincipal;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // there are several imap operations that aren't initiated via a nsIChannel::AsyncOpen call on the mock channel.
+ // such as selecting a folder. nsImapProtocol now insists on a mock channel when processing a url.
+ nsCOMPtr<nsIChannel> channel;
+ rv = NS_NewChannel(getter_AddRefs(channel),
+ aURL,
+ nullPrincipal,
+ nsILoadInfo::SEC_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<nsILoadGroup> loadGroup;
+ mailnewsUrl->GetLoadGroup(getter_AddRefs(loadGroup)); // get the message pane load group
+ nsCOMPtr<nsIRequest> ourRequest = do_QueryInterface(m_mockChannel);
+ if (loadGroup)
+ loadGroup->AddRequest(ourRequest, nullptr /* context isupports */);
+ }
+
+ if (m_mockChannel)
+ {
+ m_mockChannel->SetImapProtocol(this);
+ // if we have a listener from a mock channel, over-ride the consumer that was passed in
+ nsCOMPtr<nsIStreamListener> channelListener;
+ m_mockChannel->GetChannelListener(getter_AddRefs(channelListener));
+ if (channelListener) // only over-ride if we have a non null channel listener
+ aRealStreamListener = channelListener;
+ m_mockChannel->GetChannelContext(getter_AddRefs(m_channelContext));
+ nsCOMPtr<nsIMsgWindow> msgWindow;
+ GetMsgWindow(getter_AddRefs(msgWindow));
+ if (!msgWindow)
+ GetTopmostMsgWindow(getter_AddRefs(msgWindow));
+ if (msgWindow)
+ {
+ nsCOMPtr<nsIDocShell> docShell;
+ msgWindow->GetMessageWindowDocShell(getter_AddRefs(docShell));
+ nsCOMPtr<nsIInterfaceRequestor> ir(do_QueryInterface(docShell));
+ nsCOMPtr<nsIInterfaceRequestor> interfaceRequestor;
+ msgWindow->GetNotificationCallbacks(getter_AddRefs(interfaceRequestor));
+ nsCOMPtr<nsIInterfaceRequestor> aggregateIR;
+ MsgNewInterfaceRequestorAggregation(interfaceRequestor, ir, getter_AddRefs(aggregateIR));
+ m_mockChannel->SetNotificationCallbacks(aggregateIR);
+ }
+ }
+
+ // since we'll be making calls directly from the imap thread to the channel listener,
+ // we need to turn it into a proxy object....we'll assume that the listener is on the same thread
+ // as the event sink queue
+ if (aRealStreamListener)
+ {
+ NS_ASSERTION(!m_channelListener, "shouldn't already have a channel listener");
+ m_channelListener = new StreamListenerProxy(aRealStreamListener);
+ }
+
+ server->GetRealHostName(m_realHostName);
+ int32_t authMethod;
+ (void) server->GetAuthMethod(&authMethod);
+ 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<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID));
+ if (prefBranch)
+ {
+ bool preferPlainText;
+ prefBranch->GetBoolPref("mailnews.display.prefer_plaintext", &preferPlainText);
+ // If the pref has changed since the last time we ran a url,
+ // clear the shell cache for this host.
+ if (preferPlainText != m_preferPlainText)
+ {
+ m_hostSessionList->ClearShellCacheForHost(GetImapServerKey());
+ m_preferPlainText = preferPlainText;
+ }
+ }
+
+ if ( m_runningUrl && !m_transport /* and we don't have a transport yet */)
+ {
+ // extract the file name and create a file transport...
+ int32_t port=-1;
+ server->GetPort(&port);
+
+ if (port <= 0)
+ {
+ int32_t socketType;
+ // Be a bit smarter about setting the default port
+ port = (NS_SUCCEEDED(server->GetSocketType(&socketType)) &&
+ socketType == nsMsgSocketType::SSL) ?
+ nsIImapUrl::DEFAULT_IMAPS_PORT : nsIImapUrl::DEFAULT_IMAP_PORT;
+ }
+ nsCOMPtr<nsISocketTransportService> socketService =
+ do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv) && aURL)
+ {
+ aURL->GetPort(&port);
+
+ Log("SetupWithUrl", nullptr, "clearing IMAP_CONNECTION_IS_OPEN");
+ ClearFlag(IMAP_CONNECTION_IS_OPEN);
+ const char *connectionType = nullptr;
+
+ if (m_socketType == nsMsgSocketType::SSL)
+ connectionType = "ssl";
+ else if (m_socketType == nsMsgSocketType::alwaysSTARTTLS)
+ connectionType = "starttls";
+ // This can go away once we think everyone is migrated
+ // away from the trySTARTTLS socket type.
+ else if (m_socketType == nsMsgSocketType::trySTARTTLS)
+ connectionType = "starttls";
+
+ nsCOMPtr<nsIProxyInfo> proxyInfo;
+ if (m_mockChannel)
+ rv = MsgExamineForProxy(m_mockChannel, getter_AddRefs(proxyInfo));
+ if (NS_FAILED(rv))
+ proxyInfo = nullptr;
+
+ const nsACString *socketHost;
+ uint16_t socketPort;
+
+ if (m_overRideUrlConnectionInfo)
+ {
+ socketHost = &m_logonHost;
+ socketPort = m_logonPort;
+ }
+ else
+ {
+ socketHost = &m_realHostName;
+ socketPort = port;
+ }
+ rv = socketService->CreateTransport(&connectionType, connectionType != nullptr,
+ *socketHost, socketPort, proxyInfo,
+ getter_AddRefs(m_transport));
+ if (NS_FAILED(rv) && m_socketType == nsMsgSocketType::trySTARTTLS)
+ {
+ connectionType = nullptr;
+ m_socketType = nsMsgSocketType::plain;
+ rv = socketService->CreateTransport(&connectionType, connectionType != nullptr,
+ *socketHost, socketPort, proxyInfo,
+ getter_AddRefs(m_transport));
+ }
+ // remember so we can know whether we can issue a start tls or not...
+ m_connectionType = connectionType;
+ if (m_transport && m_mockChannel)
+ {
+ uint8_t qos;
+ rv = GetQoSBits(&qos);
+ if (NS_SUCCEEDED(rv))
+ m_transport->SetQoSBits(qos);
+
+ // Ensure that the socket can get the notification callbacks
+ SetSecurityCallbacksFromChannel(m_transport, m_mockChannel);
+
+ // open buffered, blocking input stream
+ rv = m_transport->OpenInputStream(nsITransport::OPEN_BLOCKING, 0, 0, getter_AddRefs(m_inputStream));
+ if (NS_FAILED(rv)) return rv;
+
+ // open buffered, blocking output stream
+ rv = m_transport->OpenOutputStream(nsITransport::OPEN_BLOCKING, 0, 0, getter_AddRefs(m_outputStream));
+ if (NS_FAILED(rv)) return rv;
+ SetFlag(IMAP_CONNECTION_IS_OPEN);
+ }
+ }
+ } // if m_runningUrl
+
+ if (m_transport && m_mockChannel)
+ {
+ m_transport->SetTimeout(nsISocketTransport::TIMEOUT_CONNECT, gResponseTimeout + 60);
+ int32_t readWriteTimeout = gResponseTimeout;
+ if (m_runningUrl)
+ {
+ m_runningUrl->GetImapAction(&m_imapAction);
+ // this is a silly hack, but the default of 100 seconds is way too long
+ // for things like APPEND, which should come back immediately.
+ if (m_imapAction == nsIImapUrl::nsImapAppendMsgFromFile ||
+ m_imapAction == nsIImapUrl::nsImapAppendDraftFromFile)
+ {
+ readWriteTimeout = 20;
+ }
+ else if (m_imapAction == nsIImapUrl::nsImapOnlineMove ||
+ m_imapAction == nsIImapUrl::nsImapOnlineCopy)
+ {
+ nsCString messageIdString;
+ m_runningUrl->GetListOfMessageIds(messageIdString);
+ uint32_t copyCount = CountMessagesInIdString(messageIdString.get());
+ // If we're move/copying a large number of messages,
+ // which should be rare, increase the timeout based on number
+ // of messages. 40 messages per second should be sufficiently slow.
+ if (copyCount > 2400) // 40 * 60, 60 is default read write timeout
+ readWriteTimeout = std::max(readWriteTimeout, (int32_t)copyCount/40);
+ }
+ }
+ m_transport->SetTimeout(nsISocketTransport::TIMEOUT_READ_WRITE, readWriteTimeout);
+ // set the security info for the mock channel to be the security status for our underlying transport.
+ nsCOMPtr<nsISupports> securityInfo;
+ m_transport->GetSecurityInfo(getter_AddRefs(securityInfo));
+ m_mockChannel->SetSecurityInfo(securityInfo);
+
+ SetSecurityCallbacksFromChannel(m_transport, m_mockChannel);
+
+ nsCOMPtr<nsITransportEventSink> sink = do_QueryInterface(m_mockChannel);
+ if (sink) {
+ nsCOMPtr<nsIThread> thread = do_GetMainThread();
+ m_transport->SetEventSink(sink, thread);
+ }
+
+ // and if we have a cache entry that we are saving the message to, set the security info on it too.
+ // since imap only uses the memory cache, passing this on is the right thing to do.
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_runningUrl);
+ if (mailnewsUrl)
+ {
+ nsCOMPtr<nsICacheEntry> cacheEntry;
+ mailnewsUrl->GetMemCacheEntry(getter_AddRefs(cacheEntry));
+ if (cacheEntry)
+ cacheEntry->SetSecurityInfo(securityInfo);
+ }
+ }
+ } // if aUR
+
+ return rv;
+}
+
+
+// when the connection is done processing the current state, free any per url state data...
+void nsImapProtocol::ReleaseUrlState(bool rerunning)
+{
+ // clear out the socket's reference to the notification callbacks for this transaction
+ {
+ MutexAutoLock mon(mLock);
+ if (m_transport)
+ {
+ m_transport->SetSecurityCallbacks(nullptr);
+ m_transport->SetEventSink(nullptr, nullptr);
+ }
+ }
+
+ if (m_mockChannel && !rerunning)
+ {
+ // Proxy the close of the channel to the ui thread.
+ if (m_imapMailFolderSink)
+ m_imapMailFolderSink->CloseMockChannel(m_mockChannel);
+ else
+ m_mockChannel->Close();
+
+ {
+ // grab a lock so m_mockChannel doesn't get cleared out
+ // from under us.
+ MutexAutoLock mon(mLock);
+ if (m_mockChannel)
+ {
+ // Proxy the release of the channel to the main thread. This is something
+ // that the xpcom proxy system should do for us!
+ NS_ReleaseOnMainThread(m_mockChannel.forget());
+ }
+ }
+ }
+
+ m_channelContext = nullptr; // this might be the url - null it out before the final release of the url
+ m_imapMessageSink = nullptr;
+
+ // Proxy the release of the listener to the main thread. This is something
+ // that the xpcom proxy system should do for us!
+ {
+ // grab a lock so the m_channelListener doesn't get cleared.
+ MutexAutoLock mon(mLock);
+ if (m_channelListener)
+ {
+ NS_ReleaseOnMainThread(m_channelListener.forget());
+ }
+ }
+ m_channelInputStream = nullptr;
+ m_channelOutputStream = nullptr;
+
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsurl;
+ nsCOMPtr<nsIImapMailFolderSink> saveFolderSink;
+
+ {
+ MutexAutoLock mon(mLock);
+ if (m_runningUrl)
+ {
+ mailnewsurl = do_QueryInterface(m_runningUrl);
+ // It is unclear what 'saveFolderSink' is used for, most likely to hold
+ // a reference for a little longer. See bug 1324893 and bug 391259.
+ saveFolderSink = m_imapMailFolderSink;
+
+ m_runningUrl = nullptr; // force us to release our last reference on the url
+ m_urlInProgress = false;
+ }
+ }
+ // Need to null this out whether we have an m_runningUrl or not
+ m_imapMailFolderSink = nullptr;
+
+ // we want to make sure the imap protocol's last reference to the url gets released
+ // back on the UI thread. This ensures that the objects the imap url hangs on to
+ // properly get released back on the UI thread.
+ if (mailnewsurl)
+ {
+ NS_ReleaseOnMainThread(mailnewsurl.forget());
+ }
+ saveFolderSink = nullptr;
+}
+
+
+class nsImapThreadShutdownEvent : public mozilla::Runnable {
+public:
+ nsImapThreadShutdownEvent(nsIThread *thread) : mThread(thread) {
+ }
+ NS_IMETHOD Run() {
+ mThread->Shutdown();
+ return NS_OK;
+ }
+private:
+ nsCOMPtr<nsIThread> mThread;
+};
+
+
+NS_IMETHODIMP nsImapProtocol::Run()
+{
+ PR_CEnterMonitor(this);
+ NS_ASSERTION(!m_imapThreadIsRunning,
+ "Oh. oh. thread is already running. What's wrong here?");
+ if (m_imapThreadIsRunning)
+ {
+ PR_CExitMonitor(this);
+ return NS_OK;
+ }
+
+ m_imapThreadIsRunning = true;
+ PR_CExitMonitor(this);
+
+ // call the platform specific main loop ....
+ ImapThreadMainLoop();
+
+ if (m_runningUrl)
+ {
+ NS_ReleaseOnMainThread(m_runningUrl.forget());
+ }
+
+ // close streams via UI thread if it's not already done
+ if (m_imapProtocolSink)
+ m_imapProtocolSink->CloseStreams();
+
+ m_imapMailFolderSink = nullptr;
+ m_imapMessageSink = nullptr;
+
+ // shutdown this thread, but do it from the main thread
+ nsCOMPtr<nsIRunnable> ev = new nsImapThreadShutdownEvent(m_iThread);
+ if (NS_FAILED(NS_DispatchToMainThread(ev)))
+ NS_WARNING("Failed to dispatch nsImapThreadShutdownEvent");
+ m_iThread = nullptr;
+
+ // Release protocol object on the main thread to avoid destruction of 'this'
+ // on the IMAP thread, which causes grief for weak references.
+ nsCOMPtr<nsIImapProtocol> releaseOnMain(this);
+ NS_ReleaseOnMainThread(releaseOnMain.forget());
+ return NS_OK;
+}
+
+//
+// Must be called from UI thread only
+//
+NS_IMETHODIMP nsImapProtocol::CloseStreams()
+{
+ // make sure that it is called by the UI thread
+ MOZ_ASSERT(NS_IsMainThread(), "CloseStreams() should not be called from an off UI thread");
+
+ {
+ MutexAutoLock mon(mLock);
+ if (m_transport)
+ {
+ // make sure the transport closes (even if someone is still indirectly
+ // referencing it).
+ m_transport->Close(NS_ERROR_ABORT);
+ m_transport = nullptr;
+ }
+ m_inputStream = nullptr;
+ m_outputStream = nullptr;
+ m_channelListener = nullptr;
+ m_channelContext = nullptr;
+ if (m_mockChannel)
+ {
+ m_mockChannel->Close();
+ m_mockChannel = nullptr;
+ }
+ m_channelInputStream = nullptr;
+ m_channelOutputStream = nullptr;
+
+ // Close scope because we must let go of the monitor before calling
+ // RemoveConnection to unblock anyone who tries to get a monitor to the
+ // protocol object while holding onto a monitor to the server.
+ }
+ nsCOMPtr<nsIMsgIncomingServer> me_server = do_QueryReferent(m_server);
+ if (me_server)
+ {
+ nsresult result;
+ nsCOMPtr<nsIImapIncomingServer>
+ aImapServer(do_QueryInterface(me_server, &result));
+ if (NS_SUCCEEDED(result))
+ aImapServer->RemoveConnection(this);
+ me_server = nullptr;
+ }
+ m_server = nullptr;
+ // take this opportunity of being on the UI thread to
+ // persist chunk prefs if they've changed
+ if (gChunkSizeDirty)
+ {
+ nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID));
+ if (prefBranch)
+ {
+ prefBranch->SetIntPref("mail.imap.chunk_size", gChunkSize);
+ prefBranch->SetIntPref("mail.imap.min_chunk_size_threshold", gChunkThreshold);
+ gChunkSizeDirty = false;
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapProtocol::GetUrlWindow(nsIMsgMailNewsUrl *aUrl,
+ nsIMsgWindow **aMsgWindow)
+{
+ NS_ENSURE_ARG_POINTER(aUrl);
+ NS_ENSURE_ARG_POINTER(aMsgWindow);
+ return aUrl->GetMsgWindow(aMsgWindow);
+}
+
+NS_IMETHODIMP nsImapProtocol::SetupMainThreadProxies()
+{
+ return SetupSinkProxy();
+}
+
+NS_IMETHODIMP nsImapProtocol::OnInputStreamReady(nsIAsyncInputStream *inStr)
+{
+ // should we check if it's a close vs. data available?
+ if (m_idle)
+ {
+ uint64_t bytesAvailable = 0;
+ (void) inStr->Available(&bytesAvailable);
+ // check if data available - might be a close
+ if (bytesAvailable != 0)
+ {
+ ReentrantMonitorAutoEnter mon(m_urlReadyToRunMonitor);
+ m_lastActiveTime = PR_Now();
+ m_nextUrlReadyToRun = true;
+ mon.Notify();
+ }
+ }
+ return NS_OK;
+}
+
+// this is to be called from the UI thread. It sets m_threadShouldDie,
+// and then signals the imap thread, which, when it wakes up, should exit.
+// The imap thread cleanup code will check m_safeToCloseConnection.
+NS_IMETHODIMP
+nsImapProtocol::TellThreadToDie(bool aIsSafeToClose)
+{
+ NS_WARNING_ASSERTION(NS_IsMainThread(),
+ "TellThreadToDie(aIsSafeToClose) should only be called from UI thread");
+ MutexAutoLock mon(mLock);
+
+ nsCOMPtr<nsIMsgIncomingServer> me_server = do_QueryReferent(m_server);
+ if (me_server)
+ {
+ nsresult rv;
+ nsCOMPtr<nsIImapIncomingServer>
+ aImapServer(do_QueryInterface(me_server, &rv));
+ if (NS_SUCCEEDED(rv))
+ aImapServer->RemoveConnection(this);
+ m_server = nullptr;
+ me_server = nullptr;
+ }
+ {
+ ReentrantMonitorAutoEnter deathMon(m_threadDeathMonitor);
+ m_safeToCloseConnection = aIsSafeToClose;
+ m_threadShouldDie = true;
+ }
+ ReentrantMonitorAutoEnter readyMon(m_urlReadyToRunMonitor);
+ m_nextUrlReadyToRun = true;
+ readyMon.Notify();
+ return NS_OK;
+}
+
+void
+nsImapProtocol::TellThreadToDie()
+{
+ nsresult rv = NS_OK;
+ NS_WARNING_ASSERTION(!NS_IsMainThread(),
+ "TellThreadToDie() should not be called from UI thread");
+
+ // prevent re-entering this method because it may lock the UI.
+ if (m_inThreadShouldDie)
+ return;
+ m_inThreadShouldDie = true;
+
+ // This routine is called only from the imap protocol thread.
+ // The UI thread causes this to be called by calling TellThreadToDie.
+ // In that case, m_safeToCloseConnection will be FALSE if it's dropping a
+ // timed out connection, true when closing a cached connection.
+ // We're using PR_CEnter/ExitMonitor because Monitors don't like having
+ // us to hold one monitor and call code that gets a different monitor. And
+ // some of the methods we call here use Monitors.
+ PR_CEnterMonitor(this);
+
+ m_urlInProgress = true; // let's say it's busy so no one tries to use
+ // this about to die connection.
+ bool urlWritingData = false;
+ bool connectionIdle = !m_runningUrl;
+
+ if (!connectionIdle)
+ urlWritingData = m_imapAction == nsIImapUrl::nsImapAppendMsgFromFile
+ || m_imapAction == nsIImapUrl::nsImapAppendDraftFromFile;
+
+ bool closeNeeded = GetServerStateParser().GetIMAPstate() ==
+ nsImapServerResponseParser::kFolderSelected && m_safeToCloseConnection;
+ nsCString command;
+ // if a url is writing data, we can't even logout, so we're just
+ // going to close the connection as if the user pressed stop.
+ if (m_currentServerCommandTagNumber > 0 && !urlWritingData)
+ {
+ bool isAlive = false;
+ if (m_transport)
+ rv = m_transport->IsAlive(&isAlive);
+
+ if (TestFlag(IMAP_CONNECTION_IS_OPEN) && m_idle && isAlive)
+ EndIdle(false);
+
+ if (NS_SUCCEEDED(rv) && isAlive && closeNeeded && GetDeleteIsMoveToTrash() &&
+ TestFlag(IMAP_CONNECTION_IS_OPEN) && m_outputStream)
+ Close(true, connectionIdle);
+
+ if (NS_SUCCEEDED(rv) && isAlive && TestFlag(IMAP_CONNECTION_IS_OPEN) &&
+ NS_SUCCEEDED(GetConnectionStatus()) && m_outputStream)
+ Logout(true, connectionIdle);
+ }
+ PR_CExitMonitor(this);
+ // close streams via UI thread
+ if (m_imapProtocolSink)
+ {
+ m_imapProtocolSink->CloseStreams();
+ m_imapProtocolSink = nullptr;
+ }
+ Log("TellThreadToDie", nullptr, "close socket connection");
+
+ {
+ ReentrantMonitorAutoEnter mon(m_threadDeathMonitor);
+ m_threadShouldDie = true;
+ }
+ {
+ ReentrantMonitorAutoEnter dataMon(m_dataAvailableMonitor);
+ dataMon.Notify();
+ }
+ ReentrantMonitorAutoEnter urlReadyMon(m_urlReadyToRunMonitor);
+ urlReadyMon.NotifyAll();
+}
+
+NS_IMETHODIMP
+nsImapProtocol::GetLastActiveTimeStamp(PRTime* aTimeStamp)
+{
+ if (aTimeStamp)
+ *aTimeStamp = m_lastActiveTime;
+ return NS_OK;
+}
+
+static void DoomCacheEntry(nsIMsgMailNewsUrl *url);
+NS_IMETHODIMP
+nsImapProtocol::PseudoInterruptMsgLoad(nsIMsgFolder *aImapFolder, nsIMsgWindow *aMsgWindow, bool *interrupted)
+{
+ NS_ENSURE_ARG (interrupted);
+
+ *interrupted = false;
+
+ PR_CEnterMonitor(this);
+
+ if (m_runningUrl && !TestFlag(IMAP_CLEAN_UP_URL_STATE))
+ {
+ nsImapAction imapAction;
+ m_runningUrl->GetImapAction(&imapAction);
+
+ if (imapAction == nsIImapUrl::nsImapMsgFetch)
+ {
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIImapUrl> runningImapURL;
+
+ rv = GetRunningImapURL(getter_AddRefs(runningImapURL));
+ if (NS_SUCCEEDED(rv) && runningImapURL)
+ {
+ nsCOMPtr <nsIMsgFolder> runningImapFolder;
+ nsCOMPtr <nsIMsgWindow> msgWindow;
+ nsCOMPtr <nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(runningImapURL);
+ mailnewsUrl->GetMsgWindow(getter_AddRefs(msgWindow));
+ mailnewsUrl->GetFolder(getter_AddRefs(runningImapFolder));
+ if (aImapFolder == runningImapFolder && msgWindow == aMsgWindow)
+ {
+ PseudoInterrupt(true);
+ *interrupted = true;
+ }
+ // If we're interrupted, doom any incomplete cache entry.
+ DoomCacheEntry(mailnewsUrl);
+ }
+ }
+ }
+ PR_CExitMonitor(this);
+#ifdef DEBUG_bienvenu
+ printf("interrupt msg load : %s\n", (*interrupted) ? "TRUE" : "FALSE");
+#endif
+ return NS_OK;
+}
+
+void
+nsImapProtocol::ImapThreadMainLoop()
+{
+ MOZ_LOG(IMAP, LogLevel::Debug, ("ImapThreadMainLoop entering [this=%x]\n", this));
+
+ PRIntervalTime sleepTime = kImapSleepTime;
+ while (!DeathSignalReceived())
+ {
+ nsresult rv = NS_OK;
+ bool readyToRun;
+
+ // wait for an URL to process...
+ {
+ ReentrantMonitorAutoEnter mon(m_urlReadyToRunMonitor);
+
+ while (NS_SUCCEEDED(rv) && !DeathSignalReceived() &&
+ !m_nextUrlReadyToRun && !m_threadShouldDie)
+ rv = mon.Wait(sleepTime);
+
+ readyToRun = m_nextUrlReadyToRun;
+ m_nextUrlReadyToRun = false;
+ }
+ // This will happen if the UI thread signals us to die
+ if (m_threadShouldDie)
+ {
+ TellThreadToDie();
+ break;
+ }
+
+ if (NS_FAILED(rv) && PR_PENDING_INTERRUPT_ERROR == PR_GetError())
+ {
+ printf("error waiting for monitor\n");
+ break;
+ }
+
+ if (readyToRun && m_runningUrl)
+ {
+ if (m_currentServerCommandTagNumber && m_transport)
+ {
+ bool isAlive;
+ rv = m_transport->IsAlive(&isAlive);
+ // if the transport is not alive, and we've ever sent a command with this connection, kill it.
+ // otherwise, we've probably just not finished setting it so don't kill it!
+ if (NS_FAILED(rv) || !isAlive)
+ {
+ // This says we never started running the url, which is the case.
+ m_runningUrl->SetRerunningUrl(false);
+ RetryUrl();
+ return;
+ }
+ }
+ //
+ // NOTE: Though we cleared m_nextUrlReadyToRun above, it may have been
+ // set by LoadImapUrl, which runs on the main thread. Because of this,
+ // we must not try to clear m_nextUrlReadyToRun here.
+ //
+ if (ProcessCurrentURL())
+ {
+ m_nextUrlReadyToRun = true;
+ m_imapMailFolderSink = nullptr;
+ }
+ else
+ {
+ // see if we want to go into idle mode. Might want to check a pref here too.
+ if (m_useIdle && !m_urlInProgress && GetServerStateParser().GetCapabilityFlag() & kHasIdleCapability
+ && GetServerStateParser().GetIMAPstate()
+ == nsImapServerResponseParser::kFolderSelected)
+ {
+ Idle(); // for now, lets just do it. We'll probably want to use a timer
+ }
+ else // if not idle, don't need to remember folder sink
+ m_imapMailFolderSink = nullptr;
+ }
+ }
+ else if (m_idle && !m_threadShouldDie)
+ {
+ HandleIdleResponses();
+ }
+ if (!GetServerStateParser().Connected())
+ break;
+#ifdef DEBUG_bienvenu
+ else
+ printf("ready to run but no url and not idle\n");
+#endif
+ // This can happen if the UI thread closes cached connections in the
+ // OnStopRunningUrl notification.
+ if (m_threadShouldDie)
+ TellThreadToDie();
+ }
+ m_imapThreadIsRunning = false;
+
+ MOZ_LOG(IMAP, LogLevel::Debug, ("ImapThreadMainLoop leaving [this=%x]\n", this));
+}
+
+void nsImapProtocol::HandleIdleResponses()
+{
+ // int32_t oldRecent = GetServerStateParser().NumberOfRecentMessages();
+ nsAutoCString commandBuffer(GetServerCommandTag());
+ commandBuffer.Append(" IDLE" CRLF);
+
+ do
+ {
+ ParseIMAPandCheckForNewMail(commandBuffer.get());
+ }
+ while (m_inputStreamBuffer->NextLineAvailable() && GetServerStateParser().Connected());
+
+ // if (oldRecent != GetServerStateParser().NumberOfRecentMessages())
+ // We might check that something actually changed, but for now we can
+ // just assume it. OnNewIdleMessages must run a url, so that
+ // we'll go back into asyncwait mode.
+ if (GetServerStateParser().Connected() && m_imapMailFolderSink)
+ m_imapMailFolderSink->OnNewIdleMessages();
+}
+
+void nsImapProtocol::EstablishServerConnection()
+{
+#define ESC_LENGTH(x) (sizeof(x) - 1)
+#define ESC_OK "* OK"
+#define ESC_OK_LEN ESC_LENGTH(ESC_OK)
+#define ESC_PREAUTH "* PREAUTH"
+#define ESC_PREAUTH_LEN ESC_LENGTH(ESC_PREAUTH)
+#define ESC_CAPABILITY_STAR "* "
+#define ESC_CAPABILITY_STAR_LEN ESC_LENGTH(ESC_CAPABILITY_STAR)
+#define ESC_CAPABILITY_OK "* OK ["
+#define ESC_CAPABILITY_OK_LEN ESC_LENGTH(ESC_CAPABILITY_OK)
+#define ESC_CAPABILITY_GREETING (ESC_CAPABILITY_OK "CAPABILITY")
+#define ESC_CAPABILITY_GREETING_LEN ESC_LENGTH(ESC_CAPABILITY_GREETING)
+
+ char * serverResponse = CreateNewLineFromSocket(); // read in the greeting
+
+ // record the fact that we've received a greeting for this connection so we don't ever
+ // try to do it again..
+ if (serverResponse)
+ SetFlag(IMAP_RECEIVED_GREETING);
+
+ if (!PL_strncasecmp(serverResponse, ESC_OK, ESC_OK_LEN))
+ {
+ SetConnectionStatus(NS_OK);
+
+ if (!PL_strncasecmp(serverResponse, ESC_CAPABILITY_GREETING, ESC_CAPABILITY_GREETING_LEN))
+ {
+ nsAutoCString tmpstr(serverResponse);
+ int32_t endIndex = tmpstr.FindChar(']', ESC_CAPABILITY_GREETING_LEN);
+ if (endIndex >= 0)
+ {
+ // Allocate the new buffer here. This buffer will be passed to
+ // ParseIMAPServerResponse() where it will be used to fill the
+ // fCurrentLine field and will be freed by the next call to
+ // ResetLexAnalyzer().
+ char *fakeServerResponse = (char*)PR_Malloc(PL_strlen(serverResponse));
+ // Munge the greeting into something that would pass for an IMAP
+ // server's response to a "CAPABILITY" command.
+ strcpy(fakeServerResponse, ESC_CAPABILITY_STAR);
+ strcat(fakeServerResponse, serverResponse + ESC_CAPABILITY_OK_LEN);
+ fakeServerResponse[endIndex - ESC_CAPABILITY_OK_LEN + ESC_CAPABILITY_STAR_LEN] = '\0';
+ // Tell the response parser that we just issued a "CAPABILITY" and
+ // got the following back.
+ GetServerStateParser().ParseIMAPServerResponse("1 CAPABILITY", true, fakeServerResponse);
+ }
+ }
+ }
+ else if (!PL_strncasecmp(serverResponse, ESC_PREAUTH, ESC_PREAUTH_LEN))
+ {
+ // we've been pre-authenticated.
+ // we can skip the whole password step, right into the
+ // kAuthenticated state
+ GetServerStateParser().PreauthSetAuthenticatedState();
+
+ if (GetServerStateParser().GetCapabilityFlag() == kCapabilityUndefined)
+ Capability();
+
+ if ( !(GetServerStateParser().GetCapabilityFlag() &
+ (kIMAP4Capability | kIMAP4rev1Capability | kIMAP4other) ) )
+ {
+ // AlertUserEvent_UsingId(MK_MSG_IMAP_SERVER_NOT_IMAP4);
+ SetConnectionStatus(NS_ERROR_FAILURE); // stop netlib
+ }
+ else
+ {
+ // let's record the user as authenticated.
+ m_imapServerSink->SetUserAuthenticated(true);
+
+ ProcessAfterAuthenticated();
+ // the connection was a success
+ SetConnectionStatus(NS_OK);
+ }
+ }
+
+ PR_Free(serverResponse); // we don't care about the greeting yet...
+
+#undef ESC_LENGTH
+#undef ESC_OK
+#undef ESC_OK_LEN
+#undef ESC_PREAUTH
+#undef ESC_PREAUTH_LEN
+#undef ESC_CAPABILITY_STAR
+#undef ESC_CAPABILITY_STAR_LEN
+#undef ESC_CAPABILITY_OK
+#undef ESC_CAPABILITY_OK_LEN
+#undef ESC_CAPABILITY_GREETING
+#undef ESC_CAPABILITY_GREETING_LEN
+}
+
+// This can get called from the UI thread or an imap thread.
+// It makes sure we don't get left with partial messages in
+// the memory cache.
+static void DoomCacheEntry(nsIMsgMailNewsUrl *url)
+{
+ bool readingFromMemCache = false;
+ nsCOMPtr<nsIImapUrl> imapUrl = do_QueryInterface(url);
+ imapUrl->GetMsgLoadingFromCache(&readingFromMemCache);
+ if (!readingFromMemCache)
+ {
+ nsCOMPtr<nsICacheEntry> cacheEntry;
+ url->GetMemCacheEntry(getter_AddRefs(cacheEntry));
+ if (cacheEntry)
+ cacheEntry->AsyncDoom(nullptr);
+ }
+}
+
+// returns true if another url was run, false otherwise.
+bool nsImapProtocol::ProcessCurrentURL()
+{
+ nsresult rv = NS_OK;
+ if (m_idle)
+ EndIdle();
+
+ if (m_retryUrlOnError)
+ {
+ // we clear this flag if we're re-running immediately, because that
+ // means we never sent a start running url notification, and later we
+ // don't send start running notification if we think we're rerunning
+ // the url (see first call to SetUrlState below). This means we won't
+ // send a start running notification, which means our stop running
+ // notification will be ignored because we don't think we were running.
+ m_runningUrl->SetRerunningUrl(false);
+ return RetryUrl();
+ }
+ Log("ProcessCurrentURL", nullptr, "entering");
+ (void) GetImapHostName(); // force m_hostName to get set.
+
+
+ bool logonFailed = false;
+ bool anotherUrlRun = false;
+ bool rerunningUrl = false;
+ bool isExternalUrl;
+ bool validUrl = true;
+
+ PseudoInterrupt(false); // clear this if left over from previous url.
+
+ m_runningUrl->GetRerunningUrl(&rerunningUrl);
+ m_runningUrl->GetExternalLinkUrl(&isExternalUrl);
+ m_runningUrl->GetValidUrl(&validUrl);
+ m_runningUrl->GetImapAction(&m_imapAction);
+
+ if (isExternalUrl)
+ {
+ if (m_imapAction == nsIImapUrl::nsImapSelectFolder)
+ {
+ // we need to send a start request so that the doc loader
+ // will call HandleContent on the imap service so we
+ // can abort this url, and run a new url in a new msg window
+ // to run the folder load url and get off this crazy merry-go-round.
+ if (m_channelListener)
+ {
+ nsCOMPtr<nsIRequest> request = do_QueryInterface(m_mockChannel);
+ m_channelListener->OnStartRequest(request, m_channelContext);
+ }
+ return false;
+ }
+ }
+
+ if (!m_imapMailFolderSink && m_imapProtocolSink)
+ {
+ // This occurs when running another URL in the main thread loop
+ rv = m_imapProtocolSink->SetupMainThreadProxies();
+ NS_ENSURE_SUCCESS(rv, false);
+ }
+
+ // Reinitialize the parser
+ GetServerStateParser().InitializeState();
+ GetServerStateParser().SetConnected(true);
+
+ // acknowledge that we are running the url now..
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsurl = do_QueryInterface(m_runningUrl, &rv);
+ nsAutoCString urlSpec;
+ rv = mailnewsurl->GetSpec(urlSpec);
+ NS_ENSURE_SUCCESS(rv, false);
+ Log("ProcessCurrentURL", urlSpec.get(), (validUrl) ? " = currentUrl\n" : " is not valid\n");
+ if (!validUrl)
+ return false;
+
+ if (NS_SUCCEEDED(rv) && mailnewsurl && m_imapMailFolderSink && !rerunningUrl)
+ m_imapMailFolderSink->SetUrlState(this, mailnewsurl, true, false,
+ NS_OK);
+
+ // if we are set up as a channel, we should notify our channel listener that we are starting...
+ // so pass in ourself as the channel and not the underlying socket or file channel the protocol
+ // happens to be using
+ if (m_channelListener) // ### not sure we want to do this if rerunning url...
+ {
+ nsCOMPtr<nsIRequest> request = do_QueryInterface(m_mockChannel);
+ m_channelListener->OnStartRequest(request, m_channelContext);
+ }
+ // If we haven't received the greeting yet, we need to make sure we strip
+ // it out of the input before we start to do useful things...
+ if (!TestFlag(IMAP_RECEIVED_GREETING))
+ EstablishServerConnection();
+
+ // Step 1: If we have not moved into the authenticated state yet then do so
+ // by attempting to logon.
+ if (!DeathSignalReceived() && NS_SUCCEEDED(GetConnectionStatus()) &&
+ (GetServerStateParser().GetIMAPstate() ==
+ nsImapServerResponseParser::kNonAuthenticated))
+ {
+ /* if we got here, the server's greeting should not have been PREAUTH */
+ if (GetServerStateParser().GetCapabilityFlag() == kCapabilityUndefined)
+ Capability();
+
+ if ( !(GetServerStateParser().GetCapabilityFlag() & (kIMAP4Capability | kIMAP4rev1Capability |
+ kIMAP4other) ) )
+ {
+ if (!DeathSignalReceived() && NS_SUCCEEDED(GetConnectionStatus()))
+ AlertUserEventUsingName("imapServerNotImap4");
+
+ SetConnectionStatus(NS_ERROR_FAILURE); // stop netlib
+ }
+ else
+ {
+ if ((m_connectionType.Equals("starttls")
+ && (m_socketType == nsMsgSocketType::trySTARTTLS
+ && (GetServerStateParser().GetCapabilityFlag() & kHasStartTLSCapability)))
+ || m_socketType == nsMsgSocketType::alwaysSTARTTLS)
+ {
+ StartTLS();
+ if (GetServerStateParser().LastCommandSuccessful())
+ {
+ nsCOMPtr<nsISupports> secInfo;
+ nsCOMPtr<nsISocketTransport> strans = do_QueryInterface(m_transport, &rv);
+ if (NS_FAILED(rv))
+ return false;
+
+ rv = strans->GetSecurityInfo(getter_AddRefs(secInfo));
+
+ if (NS_SUCCEEDED(rv) && secInfo)
+ {
+ nsCOMPtr<nsISSLSocketControl> sslControl = do_QueryInterface(secInfo, &rv);
+
+ if (NS_SUCCEEDED(rv) && sslControl)
+ {
+ rv = sslControl->StartTLS();
+ if (NS_SUCCEEDED(rv))
+ {
+ if (m_socketType == nsMsgSocketType::trySTARTTLS)
+ m_imapServerSink->UpdateTrySTARTTLSPref(true);
+ // force re-issue of "capability", because servers may
+ // enable other auth features (e.g. remove LOGINDISABLED
+ // and add AUTH=PLAIN) after we upgraded to SSL.
+ Capability();
+ eIMAPCapabilityFlags capabilityFlag = GetServerStateParser().GetCapabilityFlag();
+ // Courier imap doesn't return STARTTLS capability if we've done
+ // a STARTTLS! But we need to remember this capability so we'll
+ // try to use STARTTLS next time.
+ if (!(capabilityFlag & kHasStartTLSCapability))
+ {
+ capabilityFlag |= kHasStartTLSCapability;
+ GetServerStateParser().SetCapabilityFlag(capabilityFlag);
+ CommitCapability();
+ }
+ }
+ }
+ }
+ if (NS_FAILED(rv))
+ {
+ nsAutoCString logLine("STARTTLS negotiation failed. Error 0x");
+ logLine.AppendInt(static_cast<uint32_t>(rv), 16);
+ Log("ProcessCurrentURL", nullptr, logLine.get());
+ if (m_socketType == nsMsgSocketType::alwaysSTARTTLS)
+ {
+ SetConnectionStatus(rv); // stop netlib
+ m_transport->Close(rv);
+ }
+ else if (m_socketType == nsMsgSocketType::trySTARTTLS)
+ m_imapServerSink->UpdateTrySTARTTLSPref(false);
+ }
+ }
+ else if (m_socketType == nsMsgSocketType::alwaysSTARTTLS)
+ {
+ SetConnectionStatus(NS_ERROR_FAILURE); // stop netlib
+ if (m_transport)
+ m_transport->Close(rv);
+ }
+ else if (m_socketType == nsMsgSocketType::trySTARTTLS)
+ {
+ // STARTTLS failed, so downgrade socket type
+ m_imapServerSink->UpdateTrySTARTTLSPref(false);
+ }
+ }
+ else if (m_socketType == nsMsgSocketType::trySTARTTLS)
+ {
+ // we didn't know the server supported TLS when we created
+ // the socket, so we're going to retry with a STARTTLS socket
+ if (GetServerStateParser().GetCapabilityFlag() & kHasStartTLSCapability)
+ {
+ ClearFlag(IMAP_CONNECTION_IS_OPEN);
+ TellThreadToDie();
+ SetConnectionStatus(NS_ERROR_FAILURE);
+ return RetryUrl();
+ }
+ else
+ {
+ // trySTARTTLS set, but server doesn't have TLS capability,
+ // so downgrade socket type
+ m_imapServerSink->UpdateTrySTARTTLSPref(false);
+ m_socketType = nsMsgSocketType::plain;
+ }
+ }
+ logonFailed = !TryToLogon();
+ if (m_retryUrlOnError)
+ return RetryUrl();
+ }
+ } // if death signal not received
+
+ if (!DeathSignalReceived() && (NS_SUCCEEDED(GetConnectionStatus())))
+ {
+ // if the server supports a language extension then we should
+ // attempt to issue the language extension.
+ if ( GetServerStateParser().GetCapabilityFlag() & kHasLanguageCapability)
+ Language();
+
+ if (m_runningUrl)
+ FindMailboxesIfNecessary();
+
+ nsImapState imapState = nsIImapUrl::ImapStatusNone;
+ if (m_runningUrl)
+ m_runningUrl->GetRequiredImapState(&imapState);
+
+ if (imapState == nsIImapUrl::nsImapAuthenticatedState)
+ ProcessAuthenticatedStateURL();
+ else // must be a url that requires us to be in the selected state
+ ProcessSelectedStateURL();
+
+ if (m_retryUrlOnError)
+ return RetryUrl();
+
+ // The URL has now been processed
+ if ((!logonFailed && NS_FAILED(GetConnectionStatus())) ||
+ DeathSignalReceived())
+ HandleCurrentUrlError();
+
+ }
+ else if (!logonFailed)
+ HandleCurrentUrlError();
+
+// if we are set up as a channel, we should notify our channel listener that we are stopping...
+// so pass in ourself as the channel and not the underlying socket or file channel the protocol
+// happens to be using
+ if (m_channelListener)
+ {
+ nsCOMPtr<nsIRequest> request = do_QueryInterface(m_mockChannel);
+ NS_ASSERTION(request, "no request");
+ if (request) {
+ nsresult status;
+ request->GetStatus(&status);
+ if (!GetServerStateParser().LastCommandSuccessful() && NS_SUCCEEDED(status))
+ status = NS_MSG_ERROR_IMAP_COMMAND_FAILED;
+ rv = m_channelListener->OnStopRequest(request, m_channelContext, status);
+ }
+ }
+ bool suspendUrl = false;
+ m_runningUrl->GetMoreHeadersToDownload(&suspendUrl);
+ if (mailnewsurl && m_imapMailFolderSink)
+ {
+ if (logonFailed)
+ rv = NS_ERROR_FAILURE;
+ else if (GetServerStateParser().CommandFailed())
+ rv = NS_MSG_ERROR_IMAP_COMMAND_FAILED;
+ else
+ rv = GetConnectionStatus();
+ // we are done with this url.
+ m_imapMailFolderSink->SetUrlState(this, mailnewsurl, false, suspendUrl,
+ rv);
+ // doom the cache entry
+ if (NS_FAILED(rv) && DeathSignalReceived() && m_mockChannel)
+ DoomCacheEntry(mailnewsurl);
+ }
+ else
+ {
+ // That's seen at times in debug sessions.
+ NS_WARNING("missing url or sink");
+ }
+
+ // disable timeouts before caching connection.
+ if (m_transport)
+ m_transport->SetTimeout(nsISocketTransport::TIMEOUT_READ_WRITE, PR_UINT32_MAX);
+
+ SetFlag(IMAP_CLEAN_UP_URL_STATE);
+
+ nsCOMPtr <nsISupports> copyState;
+ if (m_runningUrl)
+ m_runningUrl->GetCopyState(getter_AddRefs(copyState));
+ // this is so hokey...we MUST clear any local references to the url
+ // BEFORE calling ReleaseUrlState
+ mailnewsurl = nullptr;
+
+ if (suspendUrl)
+ m_imapServerSink->SuspendUrl(m_runningUrl);
+ // save the imap folder sink since we need it to do the CopyNextStreamMessage
+ RefPtr<ImapMailFolderSinkProxy> imapMailFolderSink = m_imapMailFolderSink;
+ // release the url as we are done with it...
+ ReleaseUrlState(false);
+ ResetProgressInfo();
+
+ ClearFlag(IMAP_CLEAN_UP_URL_STATE);
+
+ if (imapMailFolderSink)
+ {
+ if (copyState)
+ {
+ rv = imapMailFolderSink->CopyNextStreamMessage(GetServerStateParser().LastCommandSuccessful() &&
+ NS_SUCCEEDED(GetConnectionStatus()),
+ copyState);
+ if (NS_FAILED(rv))
+ MOZ_LOG(IMAP, LogLevel::Info, ("CopyNextStreamMessage failed:%lx\n", rv));
+
+ NS_ReleaseOnMainThread(copyState.forget());
+ }
+ // we might need this to stick around for IDLE support
+ m_imapMailFolderSink = imapMailFolderSink;
+ imapMailFolderSink = nullptr;
+ }
+ else
+ MOZ_LOG(IMAP, LogLevel::Info, ("null imapMailFolderSink\n"));
+
+ // now try queued urls, now that we've released this connection.
+ if (m_imapServerSink)
+ {
+ if (NS_SUCCEEDED(GetConnectionStatus()))
+ rv = m_imapServerSink->LoadNextQueuedUrl(this, &anotherUrlRun);
+ else // if we don't do this, they'll just sit and spin until
+ // we run some other url on this server.
+ {
+ Log("ProcessCurrentURL", nullptr, "aborting queued urls");
+ rv = m_imapServerSink->AbortQueuedUrls();
+ }
+ }
+
+ // if we didn't run another url, release the server sink to
+ // cut circular refs.
+ if (!anotherUrlRun)
+ m_imapServerSink = nullptr;
+
+ if (NS_FAILED(GetConnectionStatus()) || !GetServerStateParser().Connected()
+ || GetServerStateParser().SyntaxError())
+ {
+ if (m_imapServerSink)
+ m_imapServerSink->RemoveServerConnection(this);
+
+ if (!DeathSignalReceived())
+ {
+ TellThreadToDie();
+ }
+ }
+ else
+ {
+ if (m_imapServerSink)
+ {
+ bool shuttingDown;
+ m_imapServerSink->GetServerShuttingDown(&shuttingDown);
+ if (shuttingDown)
+ m_useIdle = false;
+ }
+ }
+ return anotherUrlRun;
+}
+
+bool nsImapProtocol::RetryUrl()
+{
+ nsCOMPtr <nsIImapUrl> kungFuGripImapUrl = m_runningUrl;
+ nsCOMPtr <nsIImapMockChannel> saveMockChannel;
+
+ // the mock channel might be null - that's OK.
+ if (m_imapServerSink)
+ (void) m_imapServerSink->PrepareToRetryUrl(kungFuGripImapUrl, getter_AddRefs(saveMockChannel));
+
+ ReleaseUrlState(true);
+ if (m_imapServerSink)
+ {
+ m_imapServerSink->RemoveServerConnection(this);
+ m_imapServerSink->RetryUrl(kungFuGripImapUrl, saveMockChannel);
+ }
+ return (m_imapServerSink != nullptr); // we're running a url (the same url)
+}
+
+// ignoreBadAndNOResponses --> don't throw a error dialog if this command results in a NO or Bad response
+// from the server..in other words the command is "exploratory" and we don't really care if it succeeds or fails.
+void nsImapProtocol::ParseIMAPandCheckForNewMail(const char* commandString, bool aIgnoreBadAndNOResponses)
+{
+ if (commandString)
+ GetServerStateParser().ParseIMAPServerResponse(commandString, aIgnoreBadAndNOResponses);
+ else
+ GetServerStateParser().ParseIMAPServerResponse(m_currentCommand.get(), aIgnoreBadAndNOResponses);
+ // **** fix me for new mail biff state *****
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////////
+// End of nsIStreamListenerSupport
+//////////////////////////////////////////////////////////////////////////////////////////////
+
+NS_IMETHODIMP
+nsImapProtocol::GetRunningUrl(nsIURI **result)
+{
+ if (result && m_runningUrl)
+ return m_runningUrl->QueryInterface(NS_GET_IID(nsIURI), (void**)
+ result);
+ else
+ return NS_ERROR_NULL_POINTER;
+}
+
+
+NS_IMETHODIMP nsImapProtocol::GetRunningImapURL(nsIImapUrl **aImapUrl)
+{
+ if (aImapUrl && m_runningUrl)
+ return m_runningUrl->QueryInterface(NS_GET_IID(nsIImapUrl), (void**) aImapUrl);
+ else
+ return NS_ERROR_NULL_POINTER;
+
+}
+
+/*
+ * Writes the data contained in dataBuffer into the current output stream. It also informs
+ * the transport layer that this data is now available for transmission.
+ * Returns a positive number for success, 0 for failure (not all the bytes were written to the
+ * stream, etc). We need to make another pass through this file to install an error system (mscott)
+ */
+
+nsresult nsImapProtocol::SendData(const char * dataBuffer, bool aSuppressLogging)
+{
+ nsresult rv = NS_ERROR_NULL_POINTER;
+
+ if (!m_transport)
+ {
+ Log("SendData", nullptr, "clearing IMAP_CONNECTION_IS_OPEN");
+ // the connection died unexpectedly! so clear the open connection flag
+ ClearFlag(IMAP_CONNECTION_IS_OPEN);
+ TellThreadToDie();
+ SetConnectionStatus(NS_ERROR_FAILURE);
+ return NS_ERROR_FAILURE;
+ }
+
+ if (dataBuffer && m_outputStream)
+ {
+ m_currentCommand = dataBuffer;
+ if (!aSuppressLogging)
+ Log("SendData", nullptr, dataBuffer);
+ else
+ Log("SendData", nullptr, "Logging suppressed for this command (it probably contained authentication information)");
+
+ {
+ // don't allow someone to close the stream/transport out from under us
+ // this can happen when the ui thread calls TellThreadToDie.
+ PR_CEnterMonitor(this);
+ uint32_t n;
+ if (m_outputStream)
+ rv = m_outputStream->Write(dataBuffer, PL_strlen(dataBuffer), &n);
+ PR_CExitMonitor(this);
+ }
+ if (NS_FAILED(rv))
+ {
+ Log("SendData", nullptr, "clearing IMAP_CONNECTION_IS_OPEN");
+ // the connection died unexpectedly! so clear the open connection flag
+ ClearFlag(IMAP_CONNECTION_IS_OPEN);
+ TellThreadToDie();
+ SetConnectionStatus(rv);
+ if (m_runningUrl && !m_retryUrlOnError)
+ {
+ bool alreadyRerunningUrl;
+ m_runningUrl->GetRerunningUrl(&alreadyRerunningUrl);
+ if (!alreadyRerunningUrl)
+ {
+ m_runningUrl->SetRerunningUrl(true);
+ m_retryUrlOnError = true;
+ }
+ }
+ }
+ }
+
+ return rv;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////////
+// Begin protocol state machine functions...
+//////////////////////////////////////////////////////////////////////////////////////////////
+
+ // ProcessProtocolState - we override this only so we'll link - it should never get called.
+
+nsresult nsImapProtocol::ProcessProtocolState(nsIURI * url, nsIInputStream * inputStream,
+ uint64_t sourceOffset, uint32_t length)
+{
+ return NS_OK;
+}
+
+class UrlListenerNotifierEvent : public mozilla::Runnable
+{
+public:
+ UrlListenerNotifierEvent(nsIMsgMailNewsUrl *aUrl, nsIImapProtocol *aProtocol)
+ : mUrl(aUrl), mProtocol(aProtocol)
+ {}
+
+ NS_IMETHOD Run()
+ {
+ if (mUrl)
+ {
+ nsCOMPtr<nsIMsgFolder> folder;
+ mUrl->GetFolder(getter_AddRefs(folder));
+ NS_ENSURE_TRUE(folder, NS_OK);
+ nsCOMPtr<nsIImapMailFolderSink> folderSink(do_QueryInterface(folder));
+ // This causes the url listener to get OnStart and Stop notifications.
+ folderSink->SetUrlState(mProtocol, mUrl, true, false, NS_OK);
+ folderSink->SetUrlState(mProtocol, mUrl, false, false, NS_OK);
+ }
+ return NS_OK;
+ }
+
+private:
+ nsCOMPtr<nsIMsgMailNewsUrl> mUrl;
+ nsCOMPtr<nsIImapProtocol> mProtocol;
+};
+
+
+bool nsImapProtocol::TryToRunUrlLocally(nsIURI *aURL, nsISupports *aConsumer)
+{
+ nsresult rv;
+ nsCOMPtr<nsIImapUrl> imapUrl(do_QueryInterface(aURL, &rv));
+ NS_ENSURE_SUCCESS(rv, false);
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(aURL);
+ nsCString messageIdString;
+ imapUrl->GetListOfMessageIds(messageIdString);
+ bool useLocalCache = false;
+ if (!messageIdString.IsEmpty() && !HandlingMultipleMessages(messageIdString))
+ {
+ nsImapAction action;
+ imapUrl->GetImapAction(&action);
+ nsCOMPtr <nsIMsgFolder> folder;
+ mailnewsUrl->GetFolder(getter_AddRefs(folder));
+ NS_ENSURE_TRUE(folder, false);
+
+ folder->HasMsgOffline(strtoul(messageIdString.get(), nullptr, 10), &useLocalCache);
+ mailnewsUrl->SetMsgIsInLocalCache(useLocalCache);
+ // We're downloading a single message for offline use, and it's
+ // already offline. So we shouldn't do anything, but we do
+ // need to notify the url listener.
+ if (useLocalCache && action == nsIImapUrl::nsImapMsgDownloadForOffline)
+ {
+ nsCOMPtr<nsIRunnable> event = new UrlListenerNotifierEvent(mailnewsUrl,
+ this);
+ // Post this as an event because it can lead to re-entrant calls to
+ // LoadNextQueuedUrl if the listener runs a new url.
+ if (event)
+ NS_DispatchToCurrentThread(event);
+ return true;
+ }
+ }
+ if (!useLocalCache)
+ return false;
+
+ nsCOMPtr<nsIImapMockChannel> mockChannel;
+ imapUrl->GetMockChannel(getter_AddRefs(mockChannel));
+ if (!mockChannel)
+ return false;
+
+ nsImapMockChannel *imapChannel = static_cast<nsImapMockChannel *>(mockChannel.get());
+ if (!imapChannel)
+ return false;
+
+ nsCOMPtr <nsILoadGroup> loadGroup;
+ imapChannel->GetLoadGroup(getter_AddRefs(loadGroup));
+ if (!loadGroup) // if we don't have one, the url will snag one from the msg window...
+ mailnewsUrl->GetLoadGroup(getter_AddRefs(loadGroup));
+
+ if (loadGroup)
+ loadGroup->RemoveRequest((nsIRequest *) mockChannel, nullptr /* context isupports */, NS_OK);
+
+ if (imapChannel->ReadFromLocalCache())
+ {
+ (void) imapChannel->NotifyStartEndReadFromCache(true);
+ return true;
+ }
+ return false;
+}
+
+
+// LoadImapUrl takes a url, initializes all of our url specific data by calling SetupUrl.
+// If we don't have a connection yet, we open the connection. Finally, we signal the
+// url to run monitor to let the imap main thread loop process the current url (it is waiting
+// on this monitor). There is a contract that the imap thread has already been started b4 we
+// attempt to load a url....
+NS_IMETHODIMP nsImapProtocol::LoadImapUrl(nsIURI * aURL, nsISupports * aConsumer)
+{
+ nsresult rv;
+ if (aURL)
+ {
+#ifdef DEBUG_bienvenu
+ printf("loading url %s\n", aURL->GetSpecOrDefault().get());
+#endif
+ if (TryToRunUrlLocally(aURL, aConsumer))
+ return NS_OK;
+ m_urlInProgress = true;
+ m_imapMailFolderSink = nullptr;
+ rv = SetupWithUrl(aURL, aConsumer);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "error setting up imap url");
+ if (NS_FAILED(rv))
+ return rv;
+
+ rv = SetupSinkProxy(); // generate proxies for all of the event sinks in the url
+ if (NS_FAILED(rv)) // URL can be invalid.
+ return rv;
+
+ m_lastActiveTime = PR_Now();
+ if (m_transport && m_runningUrl)
+ {
+ nsImapAction imapAction;
+ m_runningUrl->GetImapAction(&imapAction);
+ // if we're shutting down, and not running the kinds of urls we run at
+ // shutdown, then this should fail because running urls during
+ // shutdown will very likely fail and potentially hang.
+ nsCOMPtr<nsIMsgAccountManager> accountMgr = do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ bool shuttingDown = false;
+ (void) accountMgr->GetShutdownInProgress(&shuttingDown);
+ if (shuttingDown && imapAction != nsIImapUrl::nsImapExpungeFolder &&
+ imapAction != nsIImapUrl::nsImapDeleteAllMsgs &&
+ imapAction != nsIImapUrl::nsImapDeleteFolder)
+ return NS_ERROR_FAILURE;
+
+ // if we're running a select or delete all, do a noop first.
+ // this should really be in the connection cache code when we know
+ // we're pulling out a selected state connection, but maybe we
+ // can get away with this.
+ m_needNoop = (imapAction == nsIImapUrl::nsImapSelectFolder || imapAction == nsIImapUrl::nsImapDeleteAllMsgs);
+
+ // We now have a url to run so signal the monitor for url ready to be processed...
+ ReentrantMonitorAutoEnter urlReadyMon(m_urlReadyToRunMonitor);
+ m_nextUrlReadyToRun = true;
+ urlReadyMon.Notify();
+
+ } // if we have an imap url and a transport
+ else
+ NS_ASSERTION(false, "missing channel or running url");
+
+ } // if we received a url!
+ else
+ rv = NS_ERROR_UNEXPECTED;
+
+ return rv;
+}
+
+NS_IMETHODIMP nsImapProtocol::IsBusy(bool *aIsConnectionBusy,
+ bool *isInboxConnection)
+{
+ if (!aIsConnectionBusy || !isInboxConnection)
+ return NS_ERROR_NULL_POINTER;
+ nsresult rv = NS_OK;
+ *aIsConnectionBusy = false;
+ *isInboxConnection = false;
+ if (!m_transport)
+ {
+ // this connection might not be fully set up yet.
+ rv = NS_ERROR_FAILURE;
+ }
+ else
+ {
+ if (m_urlInProgress) // do we have a url? That means we're working on it...
+ *aIsConnectionBusy = true;
+
+ if (GetServerStateParser().GetIMAPstate() ==
+ nsImapServerResponseParser::kFolderSelected && GetServerStateParser().GetSelectedMailboxName() &&
+ PL_strcasecmp(GetServerStateParser().GetSelectedMailboxName(),
+ "Inbox") == 0)
+ *isInboxConnection = true;
+
+ }
+ return rv;
+}
+
+#define IS_SUBSCRIPTION_RELATED_ACTION(action) (action == nsIImapUrl::nsImapSubscribe\
+|| action == nsIImapUrl::nsImapUnsubscribe || action == nsIImapUrl::nsImapDiscoverAllBoxesUrl || action == nsIImapUrl::nsImapListFolder)
+
+
+// canRunUrl means the connection is not busy, and is in the selected state
+// for the desired folder (or authenticated).
+// has to wait means it's in the right selected state, but busy.
+NS_IMETHODIMP nsImapProtocol::CanHandleUrl(nsIImapUrl * aImapUrl,
+ bool * aCanRunUrl,
+ bool * hasToWait)
+{
+ if (!aCanRunUrl || !hasToWait || !aImapUrl)
+ return NS_ERROR_NULL_POINTER;
+ nsresult rv = NS_OK;
+ MutexAutoLock mon(mLock);
+
+ *aCanRunUrl = false; // assume guilty until proven otherwise...
+ *hasToWait = false;
+
+ if (DeathSignalReceived())
+ return NS_ERROR_FAILURE;
+
+ bool isBusy = false;
+ bool isInboxConnection = false;
+
+ if (!m_transport)
+ {
+ // this connection might not be fully set up yet.
+ return NS_ERROR_FAILURE;
+ }
+ IsBusy(&isBusy, &isInboxConnection);
+ bool inSelectedState = GetServerStateParser().GetIMAPstate() ==
+ nsImapServerResponseParser::kFolderSelected;
+
+ nsAutoCString curSelectedUrlFolderName;
+ nsAutoCString pendingUrlFolderName;
+ if (inSelectedState)
+ curSelectedUrlFolderName = GetServerStateParser().GetSelectedMailboxName();
+
+ if (isBusy)
+ {
+ nsImapState curUrlImapState;
+ NS_ASSERTION(m_runningUrl,"isBusy, but no running url.");
+ if (m_runningUrl)
+ {
+ m_runningUrl->GetRequiredImapState(&curUrlImapState);
+ if (curUrlImapState == nsIImapUrl::nsImapSelectedState)
+ {
+ char *folderName = GetFolderPathString();
+ if (!curSelectedUrlFolderName.Equals(folderName))
+ pendingUrlFolderName.Assign(folderName);
+ inSelectedState = true;
+ PR_Free(folderName);
+ }
+ }
+ }
+
+ nsImapState imapState;
+ nsImapAction actionForProposedUrl;
+ aImapUrl->GetImapAction(&actionForProposedUrl);
+ aImapUrl->GetRequiredImapState(&imapState);
+
+ // OK, this is a bit of a hack - we're going to pretend that
+ // these types of urls requires a selected state connection on
+ // the folder in question. This isn't technically true,
+ // but we would much rather use that connection for several reasons,
+ // one is that some UW servers require us to use that connection
+ // the other is that we don't want to leave a connection dangling in
+ // the selected state for the deleted folder.
+ // If we don't find a connection in that selected state,
+ // we'll fall back to the first free connection.
+ bool isSelectedStateUrl = imapState == nsIImapUrl::nsImapSelectedState
+ || actionForProposedUrl == nsIImapUrl::nsImapDeleteFolder || actionForProposedUrl == nsIImapUrl::nsImapRenameFolder
+ || actionForProposedUrl == nsIImapUrl::nsImapMoveFolderHierarchy
+ || actionForProposedUrl == nsIImapUrl::nsImapAppendDraftFromFile
+ || actionForProposedUrl == nsIImapUrl::nsImapAppendMsgFromFile
+ || actionForProposedUrl == nsIImapUrl::nsImapFolderStatus;
+
+ nsCOMPtr<nsIMsgMailNewsUrl> msgUrl = do_QueryInterface(aImapUrl);
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = msgUrl->GetServer(getter_AddRefs(server));
+ if (NS_SUCCEEDED(rv))
+ {
+ // compare host/user between url and connection.
+ nsCString urlHostName;
+ nsCString urlUserName;
+ rv = server->GetHostName(urlHostName);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = server->GetUsername(urlUserName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if ((GetImapHostName().IsEmpty() ||
+ urlHostName.Equals(GetImapHostName(), nsCaseInsensitiveCStringComparator())) &&
+ (GetImapUserName().IsEmpty() ||
+ urlUserName.Equals(GetImapUserName(), nsCaseInsensitiveCStringComparator())))
+ {
+ if (isSelectedStateUrl)
+ {
+ if (inSelectedState)
+ {
+ // *** jt - in selected state can only run url with
+ // matching foldername
+ char *folderNameForProposedUrl = nullptr;
+ rv = aImapUrl->CreateServerSourceFolderPathString(
+ &folderNameForProposedUrl);
+ if (NS_SUCCEEDED(rv) && folderNameForProposedUrl)
+ {
+ bool isInbox =
+ PL_strcasecmp("Inbox", folderNameForProposedUrl) == 0;
+ if (!curSelectedUrlFolderName.IsEmpty() || !pendingUrlFolderName.IsEmpty())
+ {
+ bool matched = isInbox ?
+ PL_strcasecmp(curSelectedUrlFolderName.get(),
+ folderNameForProposedUrl) == 0 :
+ PL_strcmp(curSelectedUrlFolderName.get(),
+ folderNameForProposedUrl) == 0;
+ if (!matched && !pendingUrlFolderName.IsEmpty())
+ {
+ matched = isInbox ?
+ PL_strcasecmp(pendingUrlFolderName.get(),
+ folderNameForProposedUrl) == 0 :
+ PL_strcmp(pendingUrlFolderName.get(),
+ folderNameForProposedUrl) == 0;
+ }
+ if (matched)
+ {
+ if (isBusy)
+ *hasToWait = true;
+ else
+ *aCanRunUrl = true;
+ }
+ }
+ }
+ MOZ_LOG(IMAP, LogLevel::Debug,
+ ("proposed url = %s folder for connection %s has To Wait = %s can run = %s",
+ folderNameForProposedUrl, curSelectedUrlFolderName.get(),
+ (*hasToWait) ? "TRUE" : "FALSE", (*aCanRunUrl) ? "TRUE" : "FALSE"));
+ PR_FREEIF(folderNameForProposedUrl);
+ }
+ }
+ else // *** jt - an authenticated state url can be run in either
+ // authenticated or selected state
+ {
+ nsImapAction actionForRunningUrl;
+
+ // If proposed url is subscription related, and we are currently running
+ // a subscription url, then we want to queue the proposed url after the current url.
+ // Otherwise, we can run this url if we're not busy.
+ // If we never find a running subscription-related url, the caller will
+ // just use whatever free connection it can find, which is what we want.
+ if (IS_SUBSCRIPTION_RELATED_ACTION(actionForProposedUrl))
+ {
+ if (isBusy && m_runningUrl)
+ {
+ m_runningUrl->GetImapAction(&actionForRunningUrl);
+ if (IS_SUBSCRIPTION_RELATED_ACTION(actionForRunningUrl))
+ {
+ *aCanRunUrl = false;
+ *hasToWait = true;
+ }
+ }
+ }
+ else
+ {
+ if (!isBusy)
+ *aCanRunUrl = true;
+ }
+ }
+ }
+ }
+ return rv;
+}
+
+
+// Command tag handling stuff
+void nsImapProtocol::IncrementCommandTagNumber()
+{
+ sprintf(m_currentServerCommandTag, "%u", ++m_currentServerCommandTagNumber);
+}
+
+const char *nsImapProtocol::GetServerCommandTag()
+{
+ return m_currentServerCommandTag;
+}
+
+void nsImapProtocol::ProcessSelectedStateURL()
+{
+ nsCString mailboxName;
+ bool bMessageIdsAreUids = true;
+ bool moreHeadersToDownload;
+ imapMessageFlagsType msgFlags = 0;
+ nsCString urlHost;
+
+ // this can't fail, can it?
+ nsresult res;
+ res = m_runningUrl->GetImapAction(&m_imapAction);
+ m_runningUrl->MessageIdsAreUids(&bMessageIdsAreUids);
+ m_runningUrl->GetMsgFlags(&msgFlags);
+ m_runningUrl->GetMoreHeadersToDownload(&moreHeadersToDownload);
+
+ res = CreateServerSourceFolderPathString(getter_Copies(mailboxName));
+ if (NS_FAILED(res))
+ Log("ProcessSelectedStateURL", nullptr, "error getting source folder path string");
+
+ if (NS_SUCCEEDED(res) && !DeathSignalReceived())
+ {
+ bool selectIssued = false;
+ if (GetServerStateParser().GetIMAPstate() == nsImapServerResponseParser::kFolderSelected)
+ {
+ if (GetServerStateParser().GetSelectedMailboxName() &&
+ PL_strcmp(GetServerStateParser().GetSelectedMailboxName(),
+ mailboxName.get()))
+ { // we are selected in another folder
+ if (m_closeNeededBeforeSelect)
+ Close();
+ if (GetServerStateParser().LastCommandSuccessful())
+ {
+ selectIssued = true;
+ SelectMailbox(mailboxName.get());
+ }
+ }
+ else if (!GetServerStateParser().GetSelectedMailboxName())
+ { // why are we in the selected state with no box name?
+ SelectMailbox(mailboxName.get());
+ selectIssued = true;
+ }
+ else if (moreHeadersToDownload && m_imapMailFolderSink) // we need to fetch older headers
+ {
+ nsMsgKey *msgIdList = nullptr;
+ uint32_t msgCount = 0;
+ bool more;
+ m_imapMailFolderSink->GetMsgHdrsToDownload(&more, &m_progressCount,
+ &msgCount, &msgIdList);
+ if (msgIdList)
+ {
+ FolderHeaderDump(msgIdList, msgCount);
+ NS_Free(msgIdList);
+ m_runningUrl->SetMoreHeadersToDownload(more);
+ // We're going to be re-running this url.
+ if (more)
+ m_runningUrl->SetRerunningUrl(true);
+ }
+ HeaderFetchCompleted();
+ }
+ else
+ {
+ // get new message counts, if any, from server
+ if (m_needNoop)
+ {
+ // For some IMAP servers, to detect new email we must send imap
+ // SELECT even if already SELECTed on the same mailbox. For other
+ // servers that simply don't support IDLE, doing select here will
+ // cause emails to be properly marked "read" after they have been
+ // read in another email client.
+ if (m_forceSelect)
+ {
+ SelectMailbox(mailboxName.get());
+ selectIssued = true;
+ }
+
+ m_noopCount++;
+ if ((gPromoteNoopToCheckCount > 0 && (m_noopCount % gPromoteNoopToCheckCount) == 0) ||
+ CheckNeeded())
+ Check();
+ else
+ Noop(); // I think this is needed when we're using a cached connection
+ m_needNoop = false;
+ }
+ }
+ }
+ else
+ {
+ // go to selected state
+ SelectMailbox(mailboxName.get());
+ selectIssued = GetServerStateParser().LastCommandSuccessful();
+ }
+
+ if (selectIssued)
+ RefreshACLForFolderIfNecessary(mailboxName.get());
+
+ bool uidValidityOk = true;
+ if (GetServerStateParser().LastCommandSuccessful() && selectIssued &&
+ (m_imapAction != nsIImapUrl::nsImapSelectFolder) && (m_imapAction != nsIImapUrl::nsImapLiteSelectFolder))
+ {
+
+ // error on the side of caution, if the fe event fails to set uidStruct->returnValidity, then assume that UIDVALIDITY
+ // did not roll. This is a common case event for attachments that are fetched within a browser context.
+ if (!DeathSignalReceived())
+ uidValidityOk = m_uidValidity == kUidUnknown ||
+ m_uidValidity == GetServerStateParser().FolderUID();
+ }
+
+ if (!uidValidityOk)
+ Log("ProcessSelectedStateURL", nullptr, "uid validity not ok");
+ if (GetServerStateParser().LastCommandSuccessful() && !DeathSignalReceived() && (uidValidityOk || m_imapAction == nsIImapUrl::nsImapDeleteAllMsgs))
+ {
+ if (GetServerStateParser().CurrentFolderReadOnly())
+ {
+ Log("ProcessSelectedStateURL", nullptr, "current folder read only");
+ if (m_imapAction == nsIImapUrl::nsImapAddMsgFlags ||
+ m_imapAction == nsIImapUrl::nsImapSubtractMsgFlags)
+ {
+ bool canChangeFlag = false;
+ if (GetServerStateParser().ServerHasACLCapability() && m_imapMailFolderSink)
+ {
+ uint32_t aclFlags = 0;
+
+ if (NS_SUCCEEDED(m_imapMailFolderSink->GetAclFlags(&aclFlags))
+ && aclFlags != 0) // make sure we have some acl flags
+ canChangeFlag = ((msgFlags & kImapMsgSeenFlag) && (aclFlags & IMAP_ACL_STORE_SEEN_FLAG));
+ }
+ else
+ canChangeFlag = (GetServerStateParser().SettablePermanentFlags() & msgFlags) == msgFlags;
+ if (!canChangeFlag)
+ return;
+ }
+ if (m_imapAction == nsIImapUrl::nsImapExpungeFolder || m_imapAction == nsIImapUrl::nsImapDeleteMsg ||
+ m_imapAction == nsIImapUrl::nsImapDeleteAllMsgs)
+ return;
+ }
+ switch (m_imapAction)
+ {
+ case nsIImapUrl::nsImapLiteSelectFolder:
+ if (GetServerStateParser().LastCommandSuccessful() &&
+ m_imapMailFolderSink && !moreHeadersToDownload)
+ {
+ m_imapMailFolderSink->SetUidValidity(GetServerStateParser().FolderUID());
+ ProcessMailboxUpdate(false); // handle uidvalidity change
+ }
+ break;
+ case nsIImapUrl::nsImapSaveMessageToDisk:
+ case nsIImapUrl::nsImapMsgFetch:
+ case nsIImapUrl::nsImapMsgFetchPeek:
+ case nsIImapUrl::nsImapMsgDownloadForOffline:
+ case nsIImapUrl::nsImapMsgPreview:
+ {
+ nsCString messageIdString;
+ m_runningUrl->GetListOfMessageIds(messageIdString);
+ // we don't want to send the flags back in a group
+ if (HandlingMultipleMessages(messageIdString) || m_imapAction == nsIImapUrl::nsImapMsgDownloadForOffline
+ || m_imapAction == nsIImapUrl::nsImapMsgPreview)
+ {
+ // multiple messages, fetch them all
+ SetProgressString("imapFolderReceivingMessageOf2");
+
+ m_progressIndex = 0;
+ m_progressCount = CountMessagesInIdString(messageIdString.get());
+
+ // we need to set this so we'll get the msg from the memory cache.
+ if (m_imapAction == nsIImapUrl::nsImapMsgFetchPeek)
+ SetContentModified(IMAP_CONTENT_NOT_MODIFIED);
+
+ FetchMessage(messageIdString,
+ (m_imapAction == nsIImapUrl::nsImapMsgPreview)
+ ? kBodyStart : kEveryThingRFC822Peek);
+ if (m_imapAction == nsIImapUrl::nsImapMsgPreview)
+ HeaderFetchCompleted();
+ SetProgressString(nullptr);
+ }
+ else
+ {
+ // A single message ID
+ nsIMAPeFetchFields whatToFetch = kEveryThingRFC822;
+ if(m_imapAction == nsIImapUrl::nsImapMsgFetchPeek)
+ whatToFetch = kEveryThingRFC822Peek;
+
+ // First, let's see if we're requesting a specific MIME part
+ char *imappart = nullptr;
+ m_runningUrl->GetImapPartToFetch(&imappart);
+ if (imappart)
+ {
+ if (bMessageIdsAreUids)
+ {
+ // We actually want a specific MIME part of the message.
+ // The Body Shell will generate it, even though we haven't downloaded it yet.
+
+ IMAP_ContentModifiedType modType = GetShowAttachmentsInline() ?
+ IMAP_CONTENT_MODIFIED_VIEW_INLINE :
+ IMAP_CONTENT_MODIFIED_VIEW_AS_LINKS ;
+
+ RefPtr<nsIMAPBodyShell> foundShell;
+ res = m_hostSessionList->FindShellInCacheForHost(GetImapServerKey(),
+ GetServerStateParser().GetSelectedMailboxName(),
+ messageIdString.get(), modType, getter_AddRefs(foundShell));
+ if (!foundShell)
+ {
+ // The shell wasn't in the cache. Deal with this case later.
+ Log("SHELL",NULL,"Loading part, shell not found in cache!");
+ //MOZ_LOG(IMAP, out, ("BODYSHELL: Loading part, shell not found in cache!"));
+ // The parser will extract the part number from the current URL.
+ SetContentModified(modType);
+ Bodystructure(messageIdString, bMessageIdsAreUids);
+ }
+ else
+ {
+ Log("SHELL", NULL, "Loading Part, using cached shell.");
+ //MOZ_LOG(IMAP, out, ("BODYSHELL: Loading part, using cached shell."));
+ SetContentModified(modType);
+ foundShell->SetConnection(this);
+ GetServerStateParser().UseCachedShell(foundShell);
+ //Set the current uid in server state parser (in case it was used for new mail msgs earlier).
+ GetServerStateParser().SetCurrentResponseUID(strtoul(messageIdString.get(), nullptr, 10));
+ foundShell->Generate(imappart);
+ GetServerStateParser().UseCachedShell(NULL);
+ }
+ }
+ else
+ {
+ // Message IDs are not UIDs.
+ NS_ASSERTION(false, "message ids aren't uids");
+ }
+ PR_Free(imappart);
+ }
+ else
+ {
+ // downloading a single message: try to do it by bodystructure, and/or do it by chunks
+ uint32_t messageSize = GetMessageSize(messageIdString.get(), bMessageIdsAreUids);
+ // We need to check the format_out bits to see if we are allowed to leave out parts,
+ // or if we are required to get the whole thing. Some instances where we are allowed
+ // to do it by parts: when viewing a message, replying to a message, or viewing its source
+ // Some times when we're NOT allowed: when forwarding a message, saving it, moving it, etc.
+ // need to set a flag in the url, I guess, equiv to allow_content_changed.
+ bool allowedToBreakApart = true; // (ce && !DeathSignalReceived()) ? ce->URL_s->allow_content_change : false;
+ bool mimePartSelectorDetected;
+ bool urlOKToFetchByParts = false;
+ m_runningUrl->GetMimePartSelectorDetected(&mimePartSelectorDetected);
+ m_runningUrl->GetFetchPartsOnDemand(&urlOKToFetchByParts);
+
+#ifdef PR_LOGGING
+ {
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsurl = do_QueryInterface(m_runningUrl);
+ nsAutoCString urlSpec;
+ if (mailnewsurl)
+ urlSpec = mailnewsurl->GetSpecOrDefault();
+ MOZ_LOG(IMAP, LogLevel::Debug,
+ ("SHELL: URL %s, OKToFetchByParts %d, allowedToBreakApart %d, ShouldFetchAllParts %d",
+ urlSpec.get(), urlOKToFetchByParts, allowedToBreakApart,
+ GetShouldFetchAllParts()));
+ }
+#endif
+
+ if (urlOKToFetchByParts &&
+ allowedToBreakApart &&
+ !GetShouldFetchAllParts() &&
+ GetServerStateParser().ServerHasIMAP4Rev1Capability() /* &&
+ !mimePartSelectorDetected */) // if a ?part=, don't do BS.
+ {
+ // OK, we're doing bodystructure
+
+ // Before fetching the bodystructure, let's check our body shell cache to see if
+ // we already have it around.
+ RefPtr<nsIMAPBodyShell> foundShell;
+ IMAP_ContentModifiedType modType = GetShowAttachmentsInline() ?
+ IMAP_CONTENT_MODIFIED_VIEW_INLINE :
+ IMAP_CONTENT_MODIFIED_VIEW_AS_LINKS ;
+
+ bool wasStoringMsgOffline;
+ m_runningUrl->GetStoreResultsOffline(&wasStoringMsgOffline);
+ m_runningUrl->SetStoreOfflineOnFallback(wasStoringMsgOffline);
+ m_runningUrl->SetStoreResultsOffline(false);
+ SetContentModified(modType); // This will be looked at by the cache
+ if (bMessageIdsAreUids)
+ {
+ res = m_hostSessionList->FindShellInCacheForHost(GetImapServerKey(),
+ GetServerStateParser().GetSelectedMailboxName(),
+ messageIdString.get(), modType, getter_AddRefs(foundShell));
+ if (foundShell)
+ {
+ Log("SHELL",NULL,"Loading message, using cached shell.");
+ //MOZ_LOG(IMAP, out, ("BODYSHELL: Loading message, using cached shell."));
+ foundShell->SetConnection(this);
+ GetServerStateParser().UseCachedShell(foundShell);
+ //Set the current uid in server state parser (in case it was used for new mail msgs earlier).
+ GetServerStateParser().SetCurrentResponseUID(strtoul(messageIdString.get(), nullptr, 10));
+ foundShell->Generate(NULL);
+ GetServerStateParser().UseCachedShell(NULL);
+ }
+ }
+
+ if (!foundShell)
+ Bodystructure(messageIdString, bMessageIdsAreUids);
+ }
+ else
+ {
+ // Not doing bodystructure. Fetch the whole thing, and try to do
+ // it in chunks.
+ SetContentModified(IMAP_CONTENT_NOT_MODIFIED);
+ FetchTryChunking(messageIdString, whatToFetch,
+ bMessageIdsAreUids, NULL, messageSize, true);
+ }
+ }
+ if (GetServerStateParser().LastCommandSuccessful()
+ && m_imapAction != nsIImapUrl::nsImapMsgPreview
+ && m_imapAction != nsIImapUrl::nsImapMsgFetchPeek)
+ {
+ uint32_t uid = strtoul(messageIdString.get(), nullptr, 10);
+ int32_t index;
+ bool foundIt;
+ imapMessageFlagsType flags = m_flagState->GetMessageFlagsFromUID(uid, &foundIt, &index);
+ if (foundIt)
+ {
+ flags |= kImapMsgSeenFlag;
+ m_flagState->SetMessageFlags(index, flags);
+ }
+ }
+ }
+ }
+ break;
+ case nsIImapUrl::nsImapExpungeFolder:
+ Expunge();
+ // note fall through to next cases.
+ MOZ_FALLTHROUGH;
+ case nsIImapUrl::nsImapSelectFolder:
+ case nsIImapUrl::nsImapSelectNoopFolder:
+ if (!moreHeadersToDownload)
+ ProcessMailboxUpdate(true);
+ break;
+ case nsIImapUrl::nsImapMsgHeader:
+ {
+ nsCString messageIds;
+ m_runningUrl->GetListOfMessageIds(messageIds);
+
+ FetchMessage(messageIds,
+ kHeadersRFC822andUid);
+ // if we explicitly ask for headers, as opposed to getting them as a result
+ // of selecting the folder, or biff, send the headerFetchCompleted notification
+ // to flush out the header cache.
+ HeaderFetchCompleted();
+ }
+ break;
+ case nsIImapUrl::nsImapSearch:
+ {
+ nsAutoCString searchCriteriaString;
+ m_runningUrl->CreateSearchCriteriaString(getter_Copies(searchCriteriaString));
+ Search(searchCriteriaString.get(), bMessageIdsAreUids);
+ // drop the results on the floor for now
+ }
+ break;
+ case nsIImapUrl::nsImapUserDefinedMsgCommand:
+ {
+ nsCString messageIdString;
+ nsCString command;
+
+ m_runningUrl->GetCommand(command);
+ m_runningUrl->GetListOfMessageIds(messageIdString);
+ IssueUserDefinedMsgCommand(command.get(), messageIdString.get());
+ }
+ break;
+ case nsIImapUrl::nsImapUserDefinedFetchAttribute:
+ {
+ nsCString messageIdString;
+ nsCString attribute;
+
+ m_runningUrl->GetCustomAttributeToFetch(attribute);
+ m_runningUrl->GetListOfMessageIds(messageIdString);
+ FetchMsgAttribute(messageIdString, attribute);
+ }
+ break;
+ case nsIImapUrl::nsImapMsgStoreCustomKeywords:
+ {
+ // if the server doesn't support user defined flags, don't try to set them.
+ uint16_t userFlags;
+ GetSupportedUserFlags(&userFlags);
+ if (! (userFlags & kImapMsgSupportUserFlag))
+ break;
+ nsCString messageIdString;
+ nsCString addFlags;
+ nsCString subtractFlags;
+
+ m_runningUrl->GetListOfMessageIds(messageIdString);
+ m_runningUrl->GetCustomAddFlags(addFlags);
+ m_runningUrl->GetCustomSubtractFlags(subtractFlags);
+ if (!addFlags.IsEmpty())
+ {
+ nsAutoCString storeString("+FLAGS (");
+ storeString.Append(addFlags);
+ storeString.Append(")");
+ Store(messageIdString, storeString.get(), true);
+ }
+ if (!subtractFlags.IsEmpty())
+ {
+ nsAutoCString storeString("-FLAGS (");
+ storeString.Append(subtractFlags);
+ storeString.Append(")");
+ Store(messageIdString, storeString.get(), true);
+ }
+ }
+ break;
+ case nsIImapUrl::nsImapDeleteMsg:
+ {
+ nsCString messageIdString;
+ m_runningUrl->GetListOfMessageIds(messageIdString);
+
+ ProgressEventFunctionUsingName(HandlingMultipleMessages(messageIdString) ?
+ "imapDeletingMessages" :
+ "imapDeletingMessage");
+
+ Store(messageIdString, "+FLAGS (\\Deleted)", bMessageIdsAreUids);
+
+ if (GetServerStateParser().LastCommandSuccessful())
+ {
+ //delete_message_struct *deleteMsg = (delete_message_struct *) PR_Malloc (sizeof(delete_message_struct));
+ // convert name back from utf7
+ nsCString canonicalName;
+ const char *selectedMailboxName = GetServerStateParser().GetSelectedMailboxName();
+ if (selectedMailboxName)
+ {
+ m_runningUrl->AllocateCanonicalPath(selectedMailboxName,
+ kOnlineHierarchySeparatorUnknown, getter_Copies(canonicalName));
+ }
+
+ if (m_imapMessageSink)
+ m_imapMessageSink->NotifyMessageDeleted(canonicalName.get(), false, messageIdString.get());
+ // notice we don't wait for this to finish...
+ }
+ else
+ HandleMemoryFailure();
+ }
+ break;
+ case nsIImapUrl::nsImapDeleteFolderAndMsgs:
+ DeleteFolderAndMsgs(mailboxName.get());
+ break;
+ case nsIImapUrl::nsImapDeleteAllMsgs:
+ {
+ uint32_t numberOfMessages = GetServerStateParser().NumberOfMessages();
+ if (numberOfMessages)
+ {
+ Store(NS_LITERAL_CSTRING("1:*"), "+FLAGS.SILENT (\\Deleted)",
+ false); // use sequence #'s
+
+ if (GetServerStateParser().LastCommandSuccessful())
+ Expunge(); // expunge messages with deleted flag
+ if (GetServerStateParser().LastCommandSuccessful())
+ {
+ // convert name back from utf7
+ nsCString canonicalName;
+ const char *selectedMailboxName = GetServerStateParser().GetSelectedMailboxName();
+ if (selectedMailboxName )
+ {
+ m_runningUrl->AllocateCanonicalPath(selectedMailboxName,
+ kOnlineHierarchySeparatorUnknown, getter_Copies(canonicalName));
+ }
+
+ if (m_imapMessageSink)
+ m_imapMessageSink->NotifyMessageDeleted(canonicalName.get(), true, nullptr);
+ }
+
+ }
+ bool deleteSelf = false;
+ DeleteSubFolders(mailboxName.get(), deleteSelf); // don't delete self
+ }
+ break;
+ case nsIImapUrl::nsImapAppendDraftFromFile:
+ {
+ OnAppendMsgFromFile();
+ }
+ break;
+ case nsIImapUrl::nsImapAddMsgFlags:
+ {
+ nsCString messageIdString;
+ m_runningUrl->GetListOfMessageIds(messageIdString);
+
+ ProcessStoreFlags(messageIdString, bMessageIdsAreUids,
+ msgFlags, true);
+ }
+ break;
+ case nsIImapUrl::nsImapSubtractMsgFlags:
+ {
+ nsCString messageIdString;
+ m_runningUrl->GetListOfMessageIds(messageIdString);
+
+ ProcessStoreFlags(messageIdString, bMessageIdsAreUids,
+ msgFlags, false);
+ }
+ break;
+ case nsIImapUrl::nsImapSetMsgFlags:
+ {
+ nsCString messageIdString;
+ m_runningUrl->GetListOfMessageIds(messageIdString);
+
+ ProcessStoreFlags(messageIdString, bMessageIdsAreUids,
+ msgFlags, true);
+ ProcessStoreFlags(messageIdString, bMessageIdsAreUids,
+ ~msgFlags, false);
+ }
+ break;
+ case nsIImapUrl::nsImapBiff:
+ PeriodicBiff();
+ break;
+ case nsIImapUrl::nsImapOnlineCopy:
+ case nsIImapUrl::nsImapOnlineMove:
+ {
+ nsCString messageIdString;
+ m_runningUrl->GetListOfMessageIds(messageIdString);
+ char *destinationMailbox = OnCreateServerDestinationFolderPathString();
+
+ if (destinationMailbox)
+ {
+ if (m_imapAction == nsIImapUrl::nsImapOnlineMove)
+ {
+ if (HandlingMultipleMessages(messageIdString))
+ ProgressEventFunctionUsingNameWithString("imapMovingMessages", destinationMailbox);
+ else
+ ProgressEventFunctionUsingNameWithString("imapMovingMessage", destinationMailbox);
+ }
+ else {
+ if (HandlingMultipleMessages(messageIdString))
+ ProgressEventFunctionUsingNameWithString("imapCopyingMessages", destinationMailbox);
+ else
+ ProgressEventFunctionUsingNameWithString("imapCopyingMessage", destinationMailbox);
+ }
+ Copy(messageIdString.get(), destinationMailbox, bMessageIdsAreUids);
+ PR_FREEIF( destinationMailbox);
+ ImapOnlineCopyState copyState;
+ if (DeathSignalReceived())
+ copyState = ImapOnlineCopyStateType::kInterruptedState;
+ else
+ copyState = GetServerStateParser().LastCommandSuccessful() ?
+ (ImapOnlineCopyState) ImapOnlineCopyStateType::kSuccessfulCopy :
+ (ImapOnlineCopyState) ImapOnlineCopyStateType::kFailedCopy;
+ if (m_imapMailFolderSink)
+ m_imapMailFolderSink->OnlineCopyCompleted(this, copyState);
+ // Don't mark message 'Deleted' for AOL servers or standard imap servers
+ // that support MOVE since we already issued an 'xaol-move' or 'move' command.
+ if (GetServerStateParser().LastCommandSuccessful() &&
+ (m_imapAction == nsIImapUrl::nsImapOnlineMove) &&
+ !(GetServerStateParser().ServerIsAOLServer() ||
+ GetServerStateParser().GetCapabilityFlag() & kHasMoveCapability))
+ {
+ // Simulate MOVE for servers that don't support MOVE: do COPY-DELETE-EXPUNGE.
+ Store(messageIdString, "+FLAGS (\\Deleted \\Seen)",
+ bMessageIdsAreUids);
+ bool storeSuccessful = GetServerStateParser().LastCommandSuccessful();
+ if (storeSuccessful)
+ {
+ if(gExpungeAfterDelete)
+ {
+ // This will expunge all emails marked as deleted in mailbox,
+ // not just the ones marked as deleted above.
+ Expunge();
+ }
+ else
+ {
+ // Check if UIDPLUS capable so we can just expunge emails we just
+ // copied and marked as deleted. This prevents expunging emails
+ // that other clients may have marked as deleted in the mailbox
+ // and don't want them to disappear.
+ // Only do UidExpunge() when user selected delete method is "Move
+ // it to this folder" or "Remove it immediately", not when the
+ // delete method is "Just mark it as deleted".
+ if (!GetShowDeletedMessages() &&
+ (GetServerStateParser().GetCapabilityFlag() & kUidplusCapability))
+ {
+ UidExpunge(messageIdString);
+ }
+ }
+ }
+ if (m_imapMailFolderSink)
+ {
+ copyState = storeSuccessful ? (ImapOnlineCopyState) ImapOnlineCopyStateType::kSuccessfulDelete
+ : (ImapOnlineCopyState) ImapOnlineCopyStateType::kFailedDelete;
+ m_imapMailFolderSink->OnlineCopyCompleted(this, copyState);
+ }
+ }
+ }
+ else
+ HandleMemoryFailure();
+ }
+ break;
+ case nsIImapUrl::nsImapOnlineToOfflineCopy:
+ case nsIImapUrl::nsImapOnlineToOfflineMove:
+ {
+ nsCString messageIdString;
+ nsresult rv = m_runningUrl->GetListOfMessageIds(messageIdString);
+ if (NS_SUCCEEDED(rv))
+ {
+ SetProgressString("imapFolderReceivingMessageOf2");
+ m_progressIndex = 0;
+ m_progressCount = CountMessagesInIdString(messageIdString.get());
+
+ FetchMessage(messageIdString, kEveryThingRFC822Peek);
+
+ SetProgressString(nullptr);
+ if (m_imapMailFolderSink)
+ {
+ ImapOnlineCopyState copyStatus;
+ copyStatus = GetServerStateParser().LastCommandSuccessful() ?
+ ImapOnlineCopyStateType::kSuccessfulCopy : ImapOnlineCopyStateType::kFailedCopy;
+
+ m_imapMailFolderSink->OnlineCopyCompleted(this, copyStatus);
+ if (GetServerStateParser().LastCommandSuccessful() &&
+ (m_imapAction == nsIImapUrl::nsImapOnlineToOfflineMove))
+ {
+ Store(messageIdString, "+FLAGS (\\Deleted \\Seen)",bMessageIdsAreUids);
+ if (GetServerStateParser().LastCommandSuccessful())
+ {
+ copyStatus = ImapOnlineCopyStateType::kSuccessfulDelete;
+ if (gExpungeAfterDelete)
+ Expunge();
+ }
+ else
+ copyStatus = ImapOnlineCopyStateType::kFailedDelete;
+
+ m_imapMailFolderSink->OnlineCopyCompleted(this, copyStatus);
+ }
+ }
+ }
+ else
+ HandleMemoryFailure();
+ }
+ break;
+ default:
+ if (GetServerStateParser().LastCommandSuccessful() && !uidValidityOk)
+ ProcessMailboxUpdate(false); // handle uidvalidity change
+ break;
+ }
+ }
+ }
+ else if (!DeathSignalReceived())
+ HandleMemoryFailure();
+}
+
+nsresult nsImapProtocol::BeginMessageDownLoad(
+ uint32_t total_message_size, // for user, headers and body
+ const char *content_type)
+{
+ nsresult rv = NS_OK;
+ char *sizeString = PR_smprintf("OPEN Size: %ld", total_message_size);
+ Log("STREAM",sizeString,"Begin Message Download Stream");
+ PR_Free(sizeString);
+ // start counting how many bytes we see in this message after all transformations
+ m_bytesToChannel = 0;
+
+ if (content_type)
+ {
+ m_fromHeaderSeen = false;
+ if (GetServerStateParser().GetDownloadingHeaders())
+ {
+ // if we get multiple calls to BeginMessageDownload w/o intervening
+ // calls to NormalEndMessageDownload or Abort, then we're just
+ // going to fake a NormalMessageEndDownload. This will most likely
+ // cause an empty header to get written to the db, and the user
+ // will have to delete the empty header themselves, which
+ // should remove the message from the server as well.
+ if (m_curHdrInfo)
+ NormalMessageEndDownload();
+ if (!m_curHdrInfo)
+ m_curHdrInfo = m_hdrDownloadCache->StartNewHdr();
+ if (m_curHdrInfo)
+ m_curHdrInfo->SetMsgSize(total_message_size);
+ return NS_OK;
+ }
+ // if we have a mock channel, that means we have a channel listener who wants the
+ // message. So set up a pipe. We'll write the messsage into one end of the pipe
+ // and they will read it out of the other end.
+ else if (m_channelListener)
+ {
+ // create a pipe to pump the message into...the output will go to whoever
+ // is consuming the message display
+ // we create an "infinite" pipe in case we get extremely long lines from the imap server,
+ // and the consumer is waiting for a whole line
+ nsCOMPtr<nsIPipe> pipe = do_CreateInstance("@mozilla.org/pipe;1");
+ rv = pipe->Init(false, false, 4096, PR_UINT32_MAX);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // These always succeed because the pipe is initialized above.
+ MOZ_ALWAYS_SUCCEEDS(pipe->GetInputStream(getter_AddRefs(m_channelInputStream)));
+ MOZ_ALWAYS_SUCCEEDS(pipe->GetOutputStream(getter_AddRefs(m_channelOutputStream)));
+ }
+ // else, if we are saving the message to disk!
+ else if (m_imapMessageSink /* && m_imapAction == nsIImapUrl::nsImapSaveMessageToDisk */)
+ {
+ // we get here when download the inbox for offline use
+ nsCOMPtr<nsIFile> file;
+ bool addDummyEnvelope = true;
+ nsCOMPtr<nsIMsgMessageUrl> msgurl = do_QueryInterface(m_runningUrl);
+ msgurl->GetMessageFile(getter_AddRefs(file));
+ msgurl->GetAddDummyEnvelope(&addDummyEnvelope);
+ if (file)
+ rv = m_imapMessageSink->SetupMsgWriteStream(file, addDummyEnvelope);
+ }
+ if (m_imapMailFolderSink && m_runningUrl)
+ {
+ nsCOMPtr <nsISupports> copyState;
+ if (m_runningUrl)
+ {
+ m_runningUrl->GetCopyState(getter_AddRefs(copyState));
+ if (copyState) // only need this notification during copy
+ {
+ nsCOMPtr<nsIMsgMailNewsUrl> mailurl = do_QueryInterface(m_runningUrl);
+ m_imapMailFolderSink->StartMessage(mailurl);
+ }
+ }
+ }
+
+ }
+ else
+ HandleMemoryFailure();
+ return rv;
+}
+
+void
+nsImapProtocol::GetShouldDownloadAllHeaders(bool *aResult)
+{
+ if (m_imapMailFolderSink)
+ m_imapMailFolderSink->GetShouldDownloadAllHeaders(aResult);
+}
+
+void
+nsImapProtocol::GetArbitraryHeadersToDownload(nsCString &aResult)
+{
+ if (m_imapServerSink)
+ m_imapServerSink->GetArbitraryHeaders(aResult);
+}
+
+void
+nsImapProtocol::AdjustChunkSize()
+{
+ int32_t deltaInSeconds;
+ PRTime2Seconds(m_endTime - m_startTime, &deltaInSeconds);
+ m_trackingTime = false;
+ if (deltaInSeconds < 0)
+ return; // bogus for some reason
+
+ if (deltaInSeconds <= m_tooFastTime && m_curFetchSize >= m_chunkSize)
+ {
+ m_chunkSize += m_chunkAddSize;
+ m_chunkThreshold = m_chunkSize + (m_chunkSize / 2);
+ // we used to have a max for the chunk size - I don't think that's needed.
+ }
+ else if (deltaInSeconds <= m_idealTime)
+ return;
+ else
+ {
+ if (m_chunkSize > m_chunkStartSize)
+ m_chunkSize = m_chunkStartSize;
+ else if (m_chunkSize > (m_chunkAddSize * 2))
+ m_chunkSize -= m_chunkAddSize;
+ m_chunkThreshold = m_chunkSize + (m_chunkSize / 2);
+ }
+ // remember these new values globally so new connections
+ // can take advantage of them.
+ if (gChunkSize != m_chunkSize)
+ {
+ // will cause chunk size pref to be written in CloseStream.
+ gChunkSizeDirty = true;
+ gChunkSize = m_chunkSize;
+ gChunkThreshold = m_chunkThreshold;
+ }
+}
+
+// authenticated state commands
+
+// escape any backslashes or quotes. Backslashes are used a lot with our NT server
+void nsImapProtocol::CreateEscapedMailboxName(const char *rawName, nsCString &escapedName)
+{
+ escapedName.Assign(rawName);
+
+ for (int32_t strIndex = 0; *rawName; strIndex++)
+ {
+ char currentChar = *rawName++;
+ if ((currentChar == '\\') || (currentChar == '\"'))
+ escapedName.Insert('\\', strIndex++);
+ }
+}
+void nsImapProtocol::SelectMailbox(const char *mailboxName)
+{
+ ProgressEventFunctionUsingNameWithString("imapStatusSelectingMailbox", mailboxName);
+ IncrementCommandTagNumber();
+
+ m_closeNeededBeforeSelect = false; // initial value
+ GetServerStateParser().ResetFlagInfo();
+ nsCString escapedName;
+ CreateEscapedMailboxName(mailboxName, escapedName);
+ nsCString commandBuffer(GetServerCommandTag());
+ commandBuffer.Append(" select \"");
+ commandBuffer.Append(escapedName.get());
+ commandBuffer.Append("\"");
+ if (UseCondStore())
+ commandBuffer.Append(" (CONDSTORE)");
+ commandBuffer.Append(CRLF);
+
+ nsresult res;
+ res = SendData(commandBuffer.get());
+ if (NS_FAILED(res)) return;
+ ParseIMAPandCheckForNewMail();
+
+ int32_t numOfMessagesInFlagState = 0;
+ nsImapAction imapAction;
+ m_flagState->GetNumberOfMessages(&numOfMessagesInFlagState);
+ res = m_runningUrl->GetImapAction(&imapAction);
+ // if we've selected a mailbox, and we're not going to do an update because of the
+ // url type, but don't have the flags, go get them!
+ if (GetServerStateParser().LastCommandSuccessful() && NS_SUCCEEDED(res) &&
+ imapAction != nsIImapUrl::nsImapSelectFolder && imapAction != nsIImapUrl::nsImapExpungeFolder
+ && imapAction != nsIImapUrl::nsImapLiteSelectFolder &&
+ imapAction != nsIImapUrl::nsImapDeleteAllMsgs &&
+ ((GetServerStateParser().NumberOfMessages() != numOfMessagesInFlagState) && (numOfMessagesInFlagState == 0)))
+ {
+ ProcessMailboxUpdate(false);
+ }
+}
+
+// Please call only with a single message ID
+void nsImapProtocol::Bodystructure(const nsCString &messageId, bool idIsUid)
+{
+ IncrementCommandTagNumber();
+
+ nsCString commandString(GetServerCommandTag());
+ if (idIsUid)
+ commandString.Append(" UID");
+ commandString.Append(" fetch ");
+
+ commandString.Append(messageId);
+ commandString.Append(" (BODYSTRUCTURE)" CRLF);
+
+ nsresult rv = SendData(commandString.get());
+ if (NS_SUCCEEDED(rv))
+ ParseIMAPandCheckForNewMail(commandString.get());
+}
+
+void nsImapProtocol::PipelinedFetchMessageParts(const char *uid, nsIMAPMessagePartIDArray *parts)
+{
+ // assumes no chunking
+
+ // build up a string to fetch
+ nsCString stringToFetch, what;
+ uint32_t currentPartNum = 0;
+ while ((parts->GetNumParts() > currentPartNum) && !DeathSignalReceived())
+ {
+ nsIMAPMessagePartID *currentPart = parts->GetPart(currentPartNum);
+ if (currentPart)
+ {
+ // Do things here depending on the type of message part
+ // Append it to the fetch string
+ if (currentPartNum > 0)
+ stringToFetch.Append(" ");
+
+ switch (currentPart->GetFields())
+ {
+ case kMIMEHeader:
+ what = "BODY.PEEK[";
+ what.Append(currentPart->GetPartNumberString());
+ what.Append(".MIME]");
+ stringToFetch.Append(what);
+ break;
+ case kRFC822HeadersOnly:
+ if (currentPart->GetPartNumberString())
+ {
+ what = "BODY.PEEK[";
+ what.Append(currentPart->GetPartNumberString());
+ what.Append(".HEADER]");
+ stringToFetch.Append(what);
+ }
+ else
+ {
+ // headers for the top-level message
+ stringToFetch.Append("BODY.PEEK[HEADER]");
+ }
+ break;
+ default:
+ NS_ASSERTION(false, "we should only be pipelining MIME headers and Message headers");
+ break;
+ }
+
+ }
+ currentPartNum++;
+ }
+
+ // Run the single, pipelined fetch command
+ if ((parts->GetNumParts() > 0) && !DeathSignalReceived() && !GetPseudoInterrupted() && stringToFetch.get())
+ {
+ IncrementCommandTagNumber();
+
+ nsCString commandString(GetServerCommandTag());
+ commandString.Append(" UID fetch ");
+ commandString.Append(uid, 10);
+ commandString.Append(" (");
+ commandString.Append(stringToFetch);
+ commandString.Append(")" CRLF);
+ nsresult rv = SendData(commandString.get());
+ if (NS_SUCCEEDED(rv))
+ ParseIMAPandCheckForNewMail(commandString.get());
+ }
+}
+
+
+void nsImapProtocol::FetchMsgAttribute(const nsCString &messageIds, const nsCString &attribute)
+{
+ IncrementCommandTagNumber();
+
+ nsAutoCString commandString (GetServerCommandTag());
+ commandString.Append(" UID fetch ");
+ commandString.Append(messageIds);
+ commandString.Append(" (");
+ commandString.Append(attribute);
+ commandString.Append(")" CRLF);
+ nsresult rv = SendData(commandString.get());
+
+ if (NS_SUCCEEDED(rv))
+ ParseIMAPandCheckForNewMail(commandString.get());
+ GetServerStateParser().SetFetchingFlags(false);
+ // Always clear this flag after every fetch.
+ m_fetchingWholeMessage = false;
+}
+
+// this routine is used to fetch a message or messages, or headers for a
+// message...
+
+void nsImapProtocol::FallbackToFetchWholeMsg(const nsCString &messageId, uint32_t messageSize)
+{
+ if (m_imapMessageSink && m_runningUrl)
+ {
+ bool shouldStoreMsgOffline;
+ m_runningUrl->GetStoreOfflineOnFallback(&shouldStoreMsgOffline);
+ m_runningUrl->SetStoreResultsOffline(shouldStoreMsgOffline);
+ }
+ FetchTryChunking(messageId,
+ m_imapAction == nsIImapUrl::nsImapMsgFetchPeek ?
+ kEveryThingRFC822Peek : kEveryThingRFC822,
+ true, nullptr, messageSize, true);
+}
+
+void
+nsImapProtocol::FetchMessage(const nsCString &messageIds,
+ nsIMAPeFetchFields whatToFetch,
+ const char *fetchModifier,
+ uint32_t startByte, uint32_t numBytes,
+ char *part)
+{
+ IncrementCommandTagNumber();
+
+ nsCString commandString;
+ commandString = "%s UID fetch";
+
+ switch (whatToFetch) {
+ case kEveryThingRFC822:
+ m_flagChangeCount++;
+ m_fetchingWholeMessage = true;
+ if (m_trackingTime)
+ AdjustChunkSize(); // we started another segment
+ m_startTime = PR_Now(); // save start of download time
+ m_trackingTime = true;
+ MOZ_LOG(IMAP, LogLevel::Debug, ("FetchMessage everything: curFetchSize %u numBytes %u",
+ m_curFetchSize, numBytes));
+ if (numBytes > 0)
+ m_curFetchSize = numBytes;
+
+ if (GetServerStateParser().ServerHasIMAP4Rev1Capability())
+ {
+ if (GetServerStateParser().GetCapabilityFlag() & kHasXSenderCapability)
+ commandString.Append(" %s (XSENDER UID RFC822.SIZE BODY[]");
+ else
+ commandString.Append(" %s (UID RFC822.SIZE BODY[]");
+ }
+ else
+ {
+ if (GetServerStateParser().GetCapabilityFlag() & kHasXSenderCapability)
+ commandString.Append(" %s (XSENDER UID RFC822.SIZE RFC822");
+ else
+ commandString.Append(" %s (UID RFC822.SIZE RFC822");
+ }
+ if (numBytes > 0)
+ {
+ // if we are retrieving chunks
+ char *byterangeString = PR_smprintf("<%ld.%ld>",startByte, numBytes);
+ if (byterangeString)
+ {
+ commandString.Append(byterangeString);
+ PR_Free(byterangeString);
+ }
+ }
+ commandString.Append(")");
+
+ break;
+
+ case kEveryThingRFC822Peek:
+ {
+ MOZ_LOG(IMAP, LogLevel::Debug, ("FetchMessage peek: curFetchSize %u numBytes %u",
+ m_curFetchSize, numBytes));
+ if (numBytes > 0)
+ m_curFetchSize = numBytes;
+ const char *formatString = "";
+ eIMAPCapabilityFlags server_capabilityFlags = GetServerStateParser().GetCapabilityFlag();
+
+ m_fetchingWholeMessage = true;
+ if (server_capabilityFlags & kIMAP4rev1Capability)
+ {
+ // use body[].peek since rfc822.peek is not in IMAP4rev1
+ if (server_capabilityFlags & kHasXSenderCapability)
+ formatString = " %s (XSENDER UID RFC822.SIZE BODY.PEEK[]";
+ else
+ formatString = " %s (UID RFC822.SIZE BODY.PEEK[]";
+ }
+ else
+ {
+ if (server_capabilityFlags & kHasXSenderCapability)
+ formatString = " %s (XSENDER UID RFC822.SIZE RFC822.peek";
+ else
+ formatString = " %s (UID RFC822.SIZE RFC822.peek";
+ }
+
+ commandString.Append(formatString);
+ if (numBytes > 0)
+ {
+ // if we are retrieving chunks
+ char *byterangeString = PR_smprintf("<%ld.%ld>",startByte, numBytes);
+ if (byterangeString)
+ {
+ commandString.Append(byterangeString);
+ PR_Free(byterangeString);
+ }
+ }
+ commandString.Append(")");
+ }
+ break;
+ case kHeadersRFC822andUid:
+ if (GetServerStateParser().ServerHasIMAP4Rev1Capability())
+ {
+ eIMAPCapabilityFlags server_capabilityFlags = GetServerStateParser().GetCapabilityFlag();
+ bool aolImapServer = ((server_capabilityFlags & kAOLImapCapability) != 0);
+ bool downloadAllHeaders = false;
+ // checks if we're filtering on "any header" or running a spam filter requiring all headers
+ GetShouldDownloadAllHeaders(&downloadAllHeaders);
+
+ if (!downloadAllHeaders) // if it's ok -- no filters on any header, etc.
+ {
+ char *headersToDL = nullptr;
+ char *what = nullptr;
+ const char *dbHeaders = (gUseEnvelopeCmd) ? IMAP_DB_HEADERS : IMAP_ENV_AND_DB_HEADERS;
+ nsCString arbitraryHeaders;
+ GetArbitraryHeadersToDownload(arbitraryHeaders);
+ for (uint32_t i = 0; i < mCustomDBHeaders.Length(); i++)
+ {
+ if (arbitraryHeaders.Find(mCustomDBHeaders[i], CaseInsensitiveCompare) == kNotFound)
+ {
+ if (!arbitraryHeaders.IsEmpty())
+ arbitraryHeaders.Append(' ');
+ arbitraryHeaders.Append(mCustomDBHeaders[i]);
+ }
+ }
+ for (uint32_t i = 0; i < mCustomHeaders.Length(); i++)
+ {
+ if (arbitraryHeaders.Find(mCustomHeaders[i], CaseInsensitiveCompare) == kNotFound)
+ {
+ if (!arbitraryHeaders.IsEmpty())
+ arbitraryHeaders.Append(' ');
+ arbitraryHeaders.Append(mCustomHeaders[i]);
+ }
+ }
+ if (arbitraryHeaders.IsEmpty())
+ headersToDL = strdup(dbHeaders);
+ else
+ headersToDL = PR_smprintf("%s %s",dbHeaders, arbitraryHeaders.get());
+
+ if (gUseEnvelopeCmd)
+ what = PR_smprintf(" ENVELOPE BODY.PEEK[HEADER.FIELDS (%s)])", headersToDL);
+ else
+ what = PR_smprintf(" BODY.PEEK[HEADER.FIELDS (%s)])",headersToDL);
+ NS_Free(headersToDL);
+ if (what)
+ {
+ commandString.Append(" %s (UID ");
+ if (m_isGmailServer)
+ commandString.Append("X-GM-MSGID X-GM-THRID X-GM-LABELS ");
+ if (aolImapServer)
+ commandString.Append(" XAOL.SIZE") ;
+ else
+ commandString.Append("RFC822.SIZE");
+ commandString.Append(" FLAGS");
+ commandString.Append(what);
+ PR_Free(what);
+ }
+ else
+ {
+ commandString.Append(" %s (UID RFC822.SIZE BODY.PEEK[HEADER] FLAGS)");
+ }
+ }
+ else
+ commandString.Append(" %s (UID RFC822.SIZE BODY.PEEK[HEADER] FLAGS)");
+ }
+ else
+ commandString.Append(" %s (UID RFC822.SIZE RFC822.HEADER FLAGS)");
+ break;
+ case kUid:
+ commandString.Append(" %s (UID)");
+ break;
+ case kFlags:
+ GetServerStateParser().SetFetchingFlags(true);
+ commandString.Append(" %s (FLAGS)");
+ break;
+ case kRFC822Size:
+ commandString.Append(" %s (RFC822.SIZE)");
+ break;
+ case kBodyStart:
+ {
+ int32_t numBytesToFetch;
+ m_runningUrl->GetNumBytesToFetch(&numBytesToFetch);
+
+ commandString.Append(" %s (UID BODY.PEEK[HEADER.FIELDS (Content-Type Content-Transfer-Encoding)] BODY.PEEK[TEXT]<0.");
+ commandString.AppendInt(numBytesToFetch);
+ commandString.Append(">)");
+ }
+ break;
+ case kRFC822HeadersOnly:
+ if (GetServerStateParser().ServerHasIMAP4Rev1Capability())
+ {
+ if (part)
+ {
+ commandString.Append(" %s (BODY[");
+ char *what = PR_smprintf("%s.HEADER])", part);
+ if (what)
+ {
+ commandString.Append(what);
+ PR_Free(what);
+ }
+ else
+ HandleMemoryFailure();
+ }
+ else
+ {
+ // headers for the top-level message
+ commandString.Append(" %s (BODY[HEADER])");
+ }
+ }
+ else
+ commandString.Append(" %s (RFC822.HEADER)");
+ break;
+ case kMIMEPart:
+ commandString.Append(" %s (BODY.PEEK[%s]");
+ if (numBytes > 0)
+ {
+ // if we are retrieving chunks
+ char *byterangeString = PR_smprintf("<%ld.%ld>",startByte, numBytes);
+ if (byterangeString)
+ {
+ commandString.Append(byterangeString);
+ PR_Free(byterangeString);
+ }
+ }
+ commandString.Append(")");
+ break;
+ case kMIMEHeader:
+ commandString.Append(" %s (BODY[%s.MIME])");
+ break;
+ };
+
+ if (fetchModifier)
+ commandString.Append(fetchModifier);
+
+ commandString.Append(CRLF);
+
+ // since messageIds can be infinitely long, use a dynamic buffer rather than the fixed one
+ const char *commandTag = GetServerCommandTag();
+ int protocolStringSize = commandString.Length() + messageIds.Length() + PL_strlen(commandTag) + 1 +
+ (part ? PL_strlen(part) : 0);
+ char *protocolString = (char *) PR_CALLOC( protocolStringSize );
+
+ if (protocolString)
+ {
+ char *cCommandStr = ToNewCString(commandString);
+ if ((whatToFetch == kMIMEPart) ||
+ (whatToFetch == kMIMEHeader))
+ {
+ PR_snprintf(protocolString, // string to create
+ protocolStringSize, // max size
+ cCommandStr, // format string
+ commandTag, // command tag
+ messageIds.get(),
+ part);
+ }
+ else
+ {
+ PR_snprintf(protocolString, // string to create
+ protocolStringSize, // max size
+ cCommandStr, // format string
+ commandTag, // command tag
+ messageIds.get());
+ }
+
+ nsresult rv = SendData(protocolString);
+
+ free(cCommandStr);
+ if (NS_SUCCEEDED(rv))
+ ParseIMAPandCheckForNewMail(protocolString);
+ PR_Free(protocolString);
+ GetServerStateParser().SetFetchingFlags(false);
+ // Always clear this flag after every fetch.
+ m_fetchingWholeMessage = false;
+ if (GetServerStateParser().LastCommandSuccessful() && CheckNeeded())
+ Check();
+ }
+ else
+ HandleMemoryFailure();
+}
+
+void nsImapProtocol::FetchTryChunking(const nsCString &messageIds,
+ nsIMAPeFetchFields whatToFetch,
+ bool idIsUid,
+ char *part,
+ uint32_t downloadSize,
+ bool tryChunking)
+{
+ GetServerStateParser().SetTotalDownloadSize(downloadSize);
+ MOZ_LOG(IMAP, LogLevel::Debug, ("FetchTryChunking: curFetchSize %u", downloadSize));
+ m_curFetchSize = downloadSize; // we'll change this if chunking.
+ if (m_fetchByChunks && tryChunking &&
+ GetServerStateParser().ServerHasIMAP4Rev1Capability() &&
+ (downloadSize > (uint32_t) m_chunkThreshold))
+ {
+ uint32_t startByte = 0;
+ m_curFetchSize = m_chunkSize;
+ GetServerStateParser().ClearLastFetchChunkReceived();
+ while (!DeathSignalReceived() && !GetPseudoInterrupted() &&
+ !GetServerStateParser().GetLastFetchChunkReceived() &&
+ GetServerStateParser().ContinueParse())
+ {
+ FetchMessage(messageIds,
+ whatToFetch,
+ nullptr,
+ startByte, m_chunkSize,
+ part);
+ startByte += m_chunkSize;
+ }
+
+ // Only abort the stream if this is a normal message download
+ // Otherwise, let the body shell abort the stream.
+ if ((whatToFetch == kEveryThingRFC822)
+ &&
+ ((startByte > 0 && (startByte < downloadSize) &&
+ (DeathSignalReceived() || GetPseudoInterrupted())) ||
+ !GetServerStateParser().ContinueParse()))
+ {
+ AbortMessageDownLoad();
+ PseudoInterrupt(false);
+ }
+ }
+ else
+ {
+ // small message, or (we're not chunking and not doing bodystructure),
+ // or the server is not rev1.
+ // Just fetch the whole thing.
+ FetchMessage(messageIds, whatToFetch, nullptr, 0, 0, part);
+ }
+}
+
+
+void nsImapProtocol::PipelinedFetchMessageParts(nsCString &uid, nsIMAPMessagePartIDArray *parts)
+{
+ // assumes no chunking
+
+ // build up a string to fetch
+ nsCString stringToFetch;
+ nsCString what;
+
+ uint32_t currentPartNum = 0;
+ while ((parts->GetNumParts() > currentPartNum) && !DeathSignalReceived())
+ {
+ nsIMAPMessagePartID *currentPart = parts->GetPart(currentPartNum);
+ if (currentPart)
+ {
+ // Do things here depending on the type of message part
+ // Append it to the fetch string
+ if (currentPartNum > 0)
+ stringToFetch += " ";
+
+ switch (currentPart->GetFields())
+ {
+ case kMIMEHeader:
+ what = "BODY.PEEK[";
+ what += currentPart->GetPartNumberString();
+ what += ".MIME]";
+ stringToFetch += what;
+ break;
+ case kRFC822HeadersOnly:
+ if (currentPart->GetPartNumberString())
+ {
+ what = "BODY.PEEK[";
+ what += currentPart->GetPartNumberString();
+ what += ".HEADER]";
+ stringToFetch += what;
+ }
+ else
+ {
+ // headers for the top-level message
+ stringToFetch += "BODY.PEEK[HEADER]";
+ }
+ break;
+ default:
+ NS_ASSERTION(false, "we should only be pipelining MIME headers and Message headers");
+ break;
+ }
+
+ }
+ currentPartNum++;
+ }
+
+ // Run the single, pipelined fetch command
+ if ((parts->GetNumParts() > 0) && !DeathSignalReceived() && !GetPseudoInterrupted() && stringToFetch.get())
+ {
+ IncrementCommandTagNumber();
+
+ char *commandString = PR_smprintf("%s UID fetch %s (%s)%s",
+ GetServerCommandTag(), uid.get(),
+ stringToFetch.get(), CRLF);
+
+ if (commandString)
+ {
+ nsresult rv = SendData(commandString);
+ if (NS_SUCCEEDED(rv))
+ ParseIMAPandCheckForNewMail(commandString);
+ PR_Free(commandString);
+ }
+ else
+ HandleMemoryFailure();
+ }
+}
+
+
+void
+nsImapProtocol::PostLineDownLoadEvent(const char *line, uint32_t uidOfMessage)
+{
+ if (!GetServerStateParser().GetDownloadingHeaders())
+ {
+ uint32_t byteCount = PL_strlen(line);
+ bool echoLineToMessageSink = false;
+ // if we have a channel listener, then just spool the message
+ // directly to the listener
+ if (m_channelListener)
+ {
+ uint32_t count = 0;
+ if (m_channelOutputStream)
+ {
+ nsresult rv = m_channelOutputStream->Write(line, byteCount, &count);
+ NS_ASSERTION(count == byteCount, "IMAP channel pipe couldn't buffer entire write");
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr<nsIRequest> request = do_QueryInterface(m_mockChannel);
+ m_channelListener->OnDataAvailable(request, m_channelContext, m_channelInputStream, 0, count);
+ }
+ // else some sort of explosion?
+ }
+ }
+ if (m_runningUrl)
+ m_runningUrl->GetStoreResultsOffline(&echoLineToMessageSink);
+
+ m_bytesToChannel += byteCount;
+ if (m_imapMessageSink && line && echoLineToMessageSink && !GetPseudoInterrupted())
+ m_imapMessageSink->ParseAdoptedMsgLine(line, uidOfMessage, m_runningUrl);
+ }
+ // ***** We need to handle the pseudo interrupt here *****
+}
+
+// Handle a line seen by the parser.
+// * The argument |lineCopy| must be nullptr or should contain the same string as
+// |line|. |lineCopy| will be modified.
+// * A line may be passed by parts, e.g., "part1 part2\r\n" may be passed as
+// HandleMessageDownLoadLine("part 1 ", 1);
+// HandleMessageDownLoadLine("part 2\r\n", 0);
+// However, it is assumed that a CRLF or a CRCRLF is never split (i.e., this is
+// ensured *before* invoking this method).
+void nsImapProtocol::HandleMessageDownLoadLine(const char *line, bool isPartialLine,
+ char *lineCopy)
+{
+ NS_PRECONDITION(lineCopy == nullptr || !PL_strcmp(line, lineCopy),
+ "line and lineCopy must contain the same string");
+ const char *messageLine = line;
+ uint32_t lineLength = strlen(messageLine);
+ const char *cEndOfLine = messageLine + lineLength;
+ char *localMessageLine = nullptr;
+
+ // If we obtain a partial line (due to fetching by chunks), we do not
+ // add/modify the end-of-line terminator.
+ if (!isPartialLine)
+ {
+ // Change this line to native line termination, duplicate if necessary.
+ // Do not assume that the line really ends in CRLF
+ // to start with, even though it is supposed to be RFC822
+
+ // normalize line endings to CRLF unless we are saving the message to disk
+ bool canonicalLineEnding = true;
+ nsCOMPtr<nsIMsgMessageUrl> msgUrl = do_QueryInterface(m_runningUrl);
+
+ if (m_imapAction == nsIImapUrl::nsImapSaveMessageToDisk && msgUrl)
+ msgUrl->GetCanonicalLineEnding(&canonicalLineEnding);
+
+ NS_PRECONDITION(MSG_LINEBREAK_LEN == 1 ||
+ (MSG_LINEBREAK_LEN == 2 && !PL_strcmp(CRLF, MSG_LINEBREAK)),
+ "violated assumptions on MSG_LINEBREAK");
+ if (MSG_LINEBREAK_LEN == 1 && !canonicalLineEnding)
+ {
+ bool lineEndsWithCRorLF = lineLength >= 1 &&
+ (cEndOfLine[-1] == '\r' || cEndOfLine[-1] == '\n');
+ char *endOfLine;
+ if (lineCopy && lineEndsWithCRorLF) // true for most lines
+ {
+ endOfLine = lineCopy + lineLength;
+ messageLine = lineCopy;
+ }
+ else
+ {
+ // leave enough room for one more char, MSG_LINEBREAK[0]
+ localMessageLine = (char *) PR_MALLOC(lineLength + 2);
+ if (!localMessageLine) // memory failure
+ return;
+ PL_strcpy(localMessageLine, line);
+ endOfLine = localMessageLine + lineLength;
+ messageLine = localMessageLine;
+ }
+
+ if (lineLength >= 2 &&
+ endOfLine[-2] == '\r' &&
+ endOfLine[-1] == '\n')
+ {
+ if(lineLength>=3 && endOfLine[-3] == '\r') // CRCRLF
+ {
+ endOfLine--;
+ lineLength--;
+ }
+ /* CRLF -> CR or LF */
+ endOfLine[-2] = MSG_LINEBREAK[0];
+ endOfLine[-1] = '\0';
+ lineLength--;
+ }
+ else if (lineLength >= 1 &&
+ ((endOfLine[-1] == '\r') || (endOfLine[-1] == '\n')))
+ {
+ /* CR -> LF or LF -> CR */
+ endOfLine[-1] = MSG_LINEBREAK[0];
+ }
+ else // no eol characters at all
+ {
+ endOfLine[0] = MSG_LINEBREAK[0]; // CR or LF
+ endOfLine[1] = '\0';
+ lineLength++;
+ }
+ }
+ else // enforce canonical CRLF linebreaks
+ {
+ if (lineLength==0 || (lineLength == 1 && cEndOfLine[-1] == '\n'))
+ {
+ messageLine = CRLF;
+ lineLength = 2;
+ }
+ else if (cEndOfLine[-1] != '\n' || cEndOfLine[-2] != '\r' ||
+ (lineLength >=3 && cEndOfLine[-3] == '\r'))
+ {
+ // The line does not end in CRLF (or it ends in CRCRLF).
+ // Copy line and leave enough room for two more chars (CR and LF).
+ localMessageLine = (char *) PR_MALLOC(lineLength + 3);
+ if (!localMessageLine) // memory failure
+ return;
+ PL_strcpy(localMessageLine, line);
+ char *endOfLine = localMessageLine + lineLength;
+ messageLine = localMessageLine;
+
+ if (lineLength>=3 && endOfLine[-1] == '\n' &&
+ endOfLine[-2] == '\r')
+ {
+ // CRCRLF -> CRLF
+ endOfLine[-2] = '\n';
+ endOfLine[-1] = '\0';
+ lineLength--;
+ }
+ else if ((endOfLine[-1] == '\r') || (endOfLine[-1] == '\n'))
+ {
+ // LF -> CRLF or CR -> CRLF
+ endOfLine[-1] = '\r';
+ endOfLine[0] = '\n';
+ endOfLine[1] = '\0';
+ lineLength++;
+ }
+ else // no eol characters at all
+ {
+ endOfLine[0] = '\r';
+ endOfLine[1] = '\n';
+ endOfLine[2] = '\0';
+ lineLength += 2;
+ }
+ }
+ }
+ }
+ NS_ASSERTION(lineLength == PL_strlen(messageLine), "lineLength not accurate");
+
+ // check if sender obtained via XSENDER server extension matches "From:" field
+ const char *xSenderInfo = GetServerStateParser().GetXSenderInfo();
+ if (xSenderInfo && *xSenderInfo && !m_fromHeaderSeen)
+ {
+ if (!PL_strncmp("From: ", messageLine, 6))
+ {
+ m_fromHeaderSeen = true;
+ if (PL_strstr(messageLine, xSenderInfo) != NULL)
+ // Adding a X-Mozilla-Status line here is not very elegant but it
+ // works. Another X-Mozilla-Status line is added to the message when
+ // downloading to a local folder; this new line will also contain the
+ // 'authed' flag we are adding here. (If the message is again
+ // uploaded to the server, this flag is lost.)
+ // 0x0200 == nsMsgMessageFlags::SenderAuthed
+ HandleMessageDownLoadLine("X-Mozilla-Status: 0200\r\n", false);
+ GetServerStateParser().FreeXSenderInfo();
+ }
+ }
+
+ if (GetServerStateParser().GetDownloadingHeaders())
+ {
+ if (!m_curHdrInfo)
+ BeginMessageDownLoad(GetServerStateParser().SizeOfMostRecentMessage(), MESSAGE_RFC822);
+ if (m_curHdrInfo)
+ m_curHdrInfo->CacheLine(messageLine, GetServerStateParser().CurrentResponseUID());
+ PR_Free(localMessageLine);
+ return;
+ }
+ // if this line is for a different message, or the incoming line is too big
+ if (((m_downloadLineCache->CurrentUID() != GetServerStateParser().CurrentResponseUID()) && !m_downloadLineCache->CacheEmpty()) ||
+ (m_downloadLineCache->SpaceAvailable() < lineLength + 1) )
+ FlushDownloadCache();
+
+ // so now the cache is flushed, but this string might still be to big
+ if (m_downloadLineCache->SpaceAvailable() < lineLength + 1)
+ PostLineDownLoadEvent(messageLine, GetServerStateParser().CurrentResponseUID());
+ else
+ m_downloadLineCache->CacheLine(messageLine, GetServerStateParser().CurrentResponseUID());
+
+ PR_Free(localMessageLine);
+}
+
+void nsImapProtocol::FlushDownloadCache()
+{
+ if (!m_downloadLineCache->CacheEmpty())
+ {
+ msg_line_info *downloadLine = m_downloadLineCache->GetCurrentLineInfo();
+ PostLineDownLoadEvent(downloadLine->adoptedMessageLine,
+ downloadLine->uidOfMessage);
+ m_downloadLineCache->ResetCache();
+ }
+}
+
+void nsImapProtocol::NormalMessageEndDownload()
+{
+ Log("STREAM", "CLOSE", "Normal Message End Download Stream");
+
+ if (m_trackingTime)
+ AdjustChunkSize();
+ if (m_imapMailFolderSink && m_curHdrInfo && GetServerStateParser().GetDownloadingHeaders())
+ {
+ m_curHdrInfo->SetMsgSize(GetServerStateParser().SizeOfMostRecentMessage());
+ m_curHdrInfo->SetMsgUid(GetServerStateParser().CurrentResponseUID());
+ m_hdrDownloadCache->FinishCurrentHdr();
+ int32_t numHdrsCached;
+ m_hdrDownloadCache->GetNumHeaders(&numHdrsCached);
+ if (numHdrsCached == kNumHdrsToXfer)
+ {
+ m_imapMailFolderSink->ParseMsgHdrs(this, m_hdrDownloadCache);
+ m_hdrDownloadCache->ResetAll();
+ }
+ }
+ FlushDownloadCache();
+
+ if (!GetServerStateParser().GetDownloadingHeaders())
+ {
+ int32_t updatedMessageSize = -1;
+ if (m_fetchingWholeMessage)
+ {
+ updatedMessageSize = m_bytesToChannel;
+#ifdef PR_LOGGING
+ if (m_bytesToChannel != GetServerStateParser().SizeOfMostRecentMessage()) {
+ MOZ_LOG(IMAP, LogLevel::Debug, ("STREAM:CLOSE Server's RFC822.SIZE %u, actual size %u",
+ GetServerStateParser().SizeOfMostRecentMessage(),
+ m_bytesToChannel));
+ }
+#endif
+ }
+ // need to know if we're downloading for display or not. We'll use action == nsImapMsgFetch for now
+ nsImapAction imapAction = nsIImapUrl::nsImapSelectFolder; // just set it to some legal value
+ if (m_runningUrl)
+ m_runningUrl->GetImapAction(&imapAction);
+
+ if (m_imapMessageSink)
+ m_imapMessageSink->NormalEndMsgWriteStream(m_downloadLineCache->CurrentUID(), imapAction == nsIImapUrl::nsImapMsgFetch, m_runningUrl, updatedMessageSize);
+
+ if (m_runningUrl && m_imapMailFolderSink)
+ {
+ nsCOMPtr <nsISupports> copyState;
+ m_runningUrl->GetCopyState(getter_AddRefs(copyState));
+ if (copyState) // only need this notification during copy
+ {
+ nsCOMPtr<nsIMsgMailNewsUrl> mailUrl (do_QueryInterface(m_runningUrl));
+ m_imapMailFolderSink->EndMessage(mailUrl, m_downloadLineCache->CurrentUID());
+ }
+ }
+ }
+ m_curHdrInfo = nullptr;
+}
+
+void nsImapProtocol::AbortMessageDownLoad()
+{
+ Log("STREAM", "CLOSE", "Abort Message Download Stream");
+
+ if (m_trackingTime)
+ AdjustChunkSize();
+ FlushDownloadCache();
+ if (GetServerStateParser().GetDownloadingHeaders())
+ {
+ if (m_imapMailFolderSink)
+ m_imapMailFolderSink->AbortHeaderParseStream(this);
+ }
+ else if (m_imapMessageSink)
+ m_imapMessageSink->AbortMsgWriteStream();
+
+ m_curHdrInfo = nullptr;
+}
+
+
+void nsImapProtocol::ProcessMailboxUpdate(bool handlePossibleUndo)
+{
+ if (DeathSignalReceived())
+ return;
+
+ // Update quota information
+ char *boxName;
+ GetSelectedMailboxName(&boxName);
+ GetQuotaDataIfSupported(boxName);
+ PR_Free(boxName);
+
+ // fetch the flags and uids of all existing messages or new ones
+ if (!DeathSignalReceived() && GetServerStateParser().NumberOfMessages())
+ {
+ if (handlePossibleUndo)
+ {
+ // undo any delete flags we may have asked to
+ nsCString undoIdsStr;
+ nsAutoCString undoIds;
+
+ GetCurrentUrl()->GetListOfMessageIds(undoIdsStr);
+ undoIds.Assign(undoIdsStr);
+ if (!undoIds.IsEmpty())
+ {
+ char firstChar = (char) undoIds.CharAt(0);
+ undoIds.Cut(0, 1); // remove first character
+ // if this string started with a '-', then this is an undo of a delete
+ // if its a '+' its a redo
+ if (firstChar == '-')
+ Store(undoIds, "-FLAGS (\\Deleted)", true); // most servers will fail silently on a failure, deal with it?
+ else if (firstChar == '+')
+ Store(undoIds, "+FLAGS (\\Deleted)", true); // most servers will fail silently on a failure, deal with it?
+ else
+ NS_ASSERTION(false, "bogus undo Id's");
+ }
+ }
+
+ // make the parser record these flags
+ nsCString fetchStr;
+ int32_t added = 0, deleted = 0;
+
+ m_flagState->GetNumberOfMessages(&added);
+ deleted = m_flagState->NumberOfDeletedMessages();
+ bool flagStateEmpty = !added;
+ // Figure out if we need to do any kind of sync.
+ bool needFolderSync = (flagStateEmpty || added == deleted) && (!UseCondStore() ||
+ (GetServerStateParser().fHighestModSeq != mFolderLastModSeq) ||
+ (GetShowDeletedMessages() &&
+ GetServerStateParser().NumberOfMessages() != mFolderTotalMsgCount));
+
+ // Figure out if we need to do a full sync (UID Fetch Flags 1:*),
+ // a partial sync using CHANGEDSINCE, or a sync from the previous
+ // highwater mark.
+
+ // if the folder doesn't know about the highest uid, or the flag state
+ // is empty, and we're not using CondStore, we need a full sync.
+ bool needFullFolderSync = !mFolderHighestUID || (flagStateEmpty && !UseCondStore());
+
+ if (needFullFolderSync || needFolderSync)
+ {
+ nsCString idsToFetch("1:*");
+ char fetchModifier[40] = "";
+ if (!needFullFolderSync && !GetShowDeletedMessages() && UseCondStore())
+ PR_snprintf(fetchModifier, sizeof(fetchModifier), " (CHANGEDSINCE %llu)",
+ mFolderLastModSeq);
+ else
+ m_flagState->SetPartialUIDFetch(false);
+
+ FetchMessage(idsToFetch, kFlags, fetchModifier);
+ // lets see if we should expunge during a full sync of flags.
+ if (GetServerStateParser().LastCommandSuccessful())
+ {
+ // if we did a CHANGEDSINCE fetch, do a sanity check on the msg counts
+ // to see if some other client may have done an expunge.
+ if (m_flagState->GetPartialUIDFetch())
+ {
+ if (m_flagState->NumberOfDeletedMessages() +
+ mFolderTotalMsgCount != GetServerStateParser().NumberOfMessages())
+ {
+ // sanity check failed - fall back to full flag sync
+ m_flagState->Reset();
+ m_flagState->SetPartialUIDFetch(false);
+ FetchMessage(NS_LITERAL_CSTRING("1:*"), kFlags);
+ }
+ }
+ int32_t numDeleted = m_flagState->NumberOfDeletedMessages();
+ // Don't do expunge when we are lite selecting folder because we
+ // could be doing undo.
+ // Expunge if we're always expunging, or the number of deleted messages
+ // is over the threshhold, and we're either always respecting the
+ // threshhold, or we're expunging based on the delete model, and
+ // the delete model is not the imap delete model.
+ if (m_imapAction != nsIImapUrl::nsImapLiteSelectFolder &&
+ (gExpungeOption == kAutoExpungeAlways ||
+ (numDeleted >= gExpungeThreshold &&
+ (gExpungeOption == kAutoExpungeOnThreshold ||
+ (gExpungeOption == kAutoExpungeDeleteModel && !GetShowDeletedMessages())))))
+ Expunge();
+ }
+ }
+ else
+ {
+ uint32_t highestRecordedUID = GetServerStateParser().HighestRecordedUID();
+ // if we're using CONDSTORE, and the parser hasn't seen any UIDs, use
+ // the highest UID we've seen from the folder.
+ if (UseCondStore() && !highestRecordedUID)
+ highestRecordedUID = mFolderHighestUID;
+
+ AppendUid(fetchStr, highestRecordedUID + 1);
+ fetchStr.Append(":*");
+ FetchMessage(fetchStr, kFlags); // only new messages please
+ }
+ }
+ else if (GetServerStateParser().LastCommandSuccessful())
+ {
+ GetServerStateParser().ResetFlagInfo();
+ // the flag state is empty, but not partial.
+ m_flagState->SetPartialUIDFetch(false);
+ }
+
+ if (GetServerStateParser().LastCommandSuccessful())
+ {
+ nsImapAction imapAction;
+ nsresult res = m_runningUrl->GetImapAction(&imapAction);
+ if (NS_SUCCEEDED(res) && imapAction == nsIImapUrl::nsImapLiteSelectFolder)
+ return;
+ }
+
+ bool entered_waitForBodyIdsMonitor = false;
+
+ uint32_t *msgIdList = nullptr;
+ uint32_t msgCount = 0;
+
+ nsImapMailboxSpec *new_spec = GetServerStateParser().CreateCurrentMailboxSpec();
+ if (new_spec && GetServerStateParser().LastCommandSuccessful())
+ {
+ nsImapAction imapAction;
+ nsresult res = m_runningUrl->GetImapAction(&imapAction);
+ if (NS_SUCCEEDED(res) && imapAction == nsIImapUrl::nsImapExpungeFolder)
+ new_spec->mBoxFlags |= kJustExpunged;
+ m_waitForBodyIdsMonitor.Enter();
+ entered_waitForBodyIdsMonitor = true;
+
+ if (m_imapMailFolderSink)
+ {
+ bool more;
+ m_imapMailFolderSink->UpdateImapMailboxInfo(this, new_spec);
+ m_imapMailFolderSink->GetMsgHdrsToDownload(&more, &m_progressCount,
+ &msgCount, &msgIdList);
+ m_progressIndex = 0;
+ m_runningUrl->SetMoreHeadersToDownload(more);
+ // We're going to be re-running this url if there are more headers.
+ if (more)
+ m_runningUrl->SetRerunningUrl(true);
+ }
+ }
+ else if (!new_spec)
+ HandleMemoryFailure();
+
+ if (GetServerStateParser().LastCommandSuccessful())
+ {
+ if (entered_waitForBodyIdsMonitor)
+ m_waitForBodyIdsMonitor.Exit();
+
+ if (msgIdList && !DeathSignalReceived() && GetServerStateParser().LastCommandSuccessful())
+ {
+ FolderHeaderDump(msgIdList, msgCount);
+ NS_Free( msgIdList);
+ }
+ HeaderFetchCompleted();
+ // this might be bogus, how are we going to do pane notification and stuff when we fetch bodies without
+ // headers!
+ }
+ else if (entered_waitForBodyIdsMonitor) // need to exit this monitor if death signal received
+ m_waitForBodyIdsMonitor.Exit();
+
+ // wait for a list of bodies to fetch.
+ if (GetServerStateParser().LastCommandSuccessful())
+ {
+ WaitForPotentialListOfBodysToFetch(&msgIdList, msgCount);
+ if ( msgCount && GetServerStateParser().LastCommandSuccessful())
+ {
+ // Tell the url that it should store the msg fetch results offline,
+ // while we're dumping the messages, and then restore the setting.
+ bool wasStoringOffline;
+ m_runningUrl->GetStoreResultsOffline(&wasStoringOffline);
+ m_runningUrl->SetStoreResultsOffline(true);
+ m_progressIndex = 0;
+ m_progressCount = msgCount;
+ FolderMsgDump(msgIdList, msgCount, kEveryThingRFC822Peek);
+ m_runningUrl->SetStoreResultsOffline(wasStoringOffline);
+ }
+ }
+ if (!GetServerStateParser().LastCommandSuccessful())
+ GetServerStateParser().ResetFlagInfo();
+
+ NS_IF_RELEASE(new_spec);
+}
+
+void nsImapProtocol::FolderHeaderDump(uint32_t *msgUids, uint32_t msgCount)
+{
+ FolderMsgDump(msgUids, msgCount, kHeadersRFC822andUid);
+}
+
+void nsImapProtocol::FolderMsgDump(uint32_t *msgUids, uint32_t msgCount, nsIMAPeFetchFields fields)
+{
+ // lets worry about this progress stuff later.
+ switch (fields) {
+ case kHeadersRFC822andUid:
+ SetProgressString("imapReceivingMessageHeaders2");
+ break;
+ case kFlags:
+ SetProgressString("imapReceivingMessageFlags2");
+ break;
+ default:
+ SetProgressString("imapFolderReceivingMessageOf2");
+ break;
+ }
+
+ FolderMsgDumpLoop(msgUids, msgCount, fields);
+
+ SetProgressString(nullptr);
+}
+
+void nsImapProtocol::WaitForPotentialListOfBodysToFetch(uint32_t **msgIdList, uint32_t &msgCount)
+{
+ PRIntervalTime sleepTime = kImapSleepTime;
+
+ ReentrantMonitorAutoEnter fetchListMon(m_fetchBodyListMonitor);
+ while(!m_fetchBodyListIsNew && !DeathSignalReceived())
+ fetchListMon.Wait(sleepTime);
+ m_fetchBodyListIsNew = false;
+
+ *msgIdList = m_fetchBodyIdList;
+ msgCount = m_fetchBodyCount;
+}
+
+// libmsg uses this to notify a running imap url about message bodies it should download.
+// why not just have libmsg explicitly download the message bodies?
+NS_IMETHODIMP nsImapProtocol::NotifyBodysToDownload(uint32_t *keys, uint32_t keyCount)
+{
+ ReentrantMonitorAutoEnter fetchListMon(m_fetchBodyListMonitor);
+ PR_FREEIF(m_fetchBodyIdList);
+ m_fetchBodyIdList = (uint32_t *) PR_MALLOC(keyCount * sizeof(uint32_t));
+ if (m_fetchBodyIdList)
+ memcpy(m_fetchBodyIdList, keys, keyCount * sizeof(uint32_t));
+ m_fetchBodyCount = keyCount;
+ m_fetchBodyListIsNew = true;
+ fetchListMon.Notify();
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapProtocol::GetFlagsForUID(uint32_t uid, bool *foundIt, imapMessageFlagsType *resultFlags, char **customFlags)
+{
+ int32_t i;
+
+ imapMessageFlagsType flags = m_flagState->GetMessageFlagsFromUID(uid, foundIt, &i);
+ if (*foundIt)
+ {
+ *resultFlags = flags;
+ if ((flags & kImapMsgCustomKeywordFlag) && customFlags)
+ m_flagState->GetCustomFlags(uid, customFlags);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapProtocol::GetFlagAndUidState(nsIImapFlagAndUidState **aFlagState)
+{
+ NS_ENSURE_ARG_POINTER(aFlagState);
+ NS_IF_ADDREF(*aFlagState = m_flagState);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapProtocol::GetSupportedUserFlags(uint16_t *supportedFlags)
+{
+ if (!supportedFlags)
+ return NS_ERROR_NULL_POINTER;
+
+ *supportedFlags = m_flagState->GetSupportedUserFlags();
+ return NS_OK;
+}
+void nsImapProtocol::FolderMsgDumpLoop(uint32_t *msgUids, uint32_t msgCount, nsIMAPeFetchFields fields)
+{
+ int32_t msgCountLeft = msgCount;
+ uint32_t msgsDownloaded = 0;
+ do
+ {
+ nsCString idString;
+ uint32_t msgsToDownload = msgCountLeft;
+ AllocateImapUidString(msgUids + msgsDownloaded, msgsToDownload, m_flagState, idString); // 20 * 200
+ FetchMessage(idString, fields);
+ msgsDownloaded += msgsToDownload;
+ msgCountLeft -= msgsToDownload;
+ }
+ while (msgCountLeft > 0 && !DeathSignalReceived());
+}
+
+void nsImapProtocol::HeaderFetchCompleted()
+{
+ if (m_imapMailFolderSink)
+ m_imapMailFolderSink->ParseMsgHdrs(this, m_hdrDownloadCache);
+ m_hdrDownloadCache->ReleaseAll();
+
+ if (m_imapMailFolderSink)
+ m_imapMailFolderSink->HeaderFetchCompleted(this);
+}
+
+
+// Use the noop to tell the server we are still here, and therefore we are willing to receive
+// status updates. The recent or exists response from the server could tell us that there is
+// more mail waiting for us, but we need to check the flags of the mail and the high water mark
+// to make sure that we do not tell the user that there is new mail when perhaps they have
+// already read it in another machine.
+
+void nsImapProtocol::PeriodicBiff()
+{
+
+ nsMsgBiffState startingState = m_currentBiffState;
+
+ if (GetServerStateParser().GetIMAPstate() == nsImapServerResponseParser::kFolderSelected)
+ {
+ Noop(); // check the latest number of messages
+ int32_t numMessages = 0;
+ m_flagState->GetNumberOfMessages(&numMessages);
+ if (GetServerStateParser().NumberOfMessages() != numMessages)
+ {
+ uint32_t id = GetServerStateParser().HighestRecordedUID() + 1;
+ nsCString fetchStr; // only update flags
+ uint32_t added = 0, deleted = 0;
+
+ deleted = m_flagState->NumberOfDeletedMessages();
+ added = numMessages;
+ if (!added || (added == deleted)) // empty keys, get them all
+ id = 1;
+
+ //sprintf(fetchStr, "%ld:%ld", id, id + GetServerStateParser().NumberOfMessages() - fFlagState->GetNumberOfMessages());
+ AppendUid(fetchStr, id);
+ fetchStr.Append(":*");
+ FetchMessage(fetchStr, kFlags);
+ if (((uint32_t) m_flagState->GetHighestNonDeletedUID() >= id) && m_flagState->IsLastMessageUnseen())
+ m_currentBiffState = nsIMsgFolder::nsMsgBiffState_NewMail;
+ else
+ m_currentBiffState = nsIMsgFolder::nsMsgBiffState_NoMail;
+ }
+ else
+ m_currentBiffState = nsIMsgFolder::nsMsgBiffState_NoMail;
+ }
+ else
+ m_currentBiffState = nsIMsgFolder::nsMsgBiffState_Unknown;
+
+ if (startingState != m_currentBiffState)
+ SendSetBiffIndicatorEvent(m_currentBiffState);
+}
+
+void nsImapProtocol::SendSetBiffIndicatorEvent(nsMsgBiffState newState)
+{
+ if (m_imapMailFolderSink)
+ m_imapMailFolderSink->SetBiffStateAndUpdate(newState);
+}
+
+// We get called to see if there is mail waiting for us at the server, even if it may have been
+// read elsewhere. We just want to know if we should download headers or not.
+
+bool nsImapProtocol::CheckNewMail()
+{
+ return m_checkForNewMailDownloadsHeaders;
+}
+
+/* static */ void nsImapProtocol::LogImapUrl(const char *logMsg, nsIImapUrl *imapUrl)
+{
+ // nsImapProtocol is not always constructed before this static method is called
+ if (!IMAP)
+ IMAP = PR_NewLogModule("IMAP");
+
+ if (MOZ_LOG_TEST(IMAP, LogLevel::Info))
+ {
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(imapUrl);
+ if (mailnewsUrl)
+ {
+ nsAutoCString urlSpec, unescapedUrlSpec;
+ nsresult rv = mailnewsUrl->GetSpec(urlSpec);
+ if (NS_FAILED(rv))
+ return;
+ MsgUnescapeString(urlSpec, 0, unescapedUrlSpec);
+ MOZ_LOG(IMAP, LogLevel::Info, ("%s:%s", logMsg, unescapedUrlSpec.get()));
+ }
+ }
+}
+
+// log info including current state...
+void nsImapProtocol::Log(const char *logSubName, const char *extraInfo, const char *logData)
+{
+ if (MOZ_LOG_TEST(IMAP, LogLevel::Info))
+ {
+ static const char nonAuthStateName[] = "NA";
+ static const char authStateName[] = "A";
+ static const char selectedStateName[] = "S";
+ const nsCString& hostName = GetImapHostName(); // initilize to empty string
+
+ int32_t logDataLen = PL_strlen(logData); // PL_strlen checks for null
+ nsCString logDataLines;
+ const char *logDataToLog;
+ int32_t lastLineEnd;
+
+ const int kLogDataChunkSize = 400; // nspr line length is 512, and we
+ // allow some space for the log preamble.
+
+ // break up buffers > 400 bytes on line boundaries.
+ if (logDataLen > kLogDataChunkSize)
+ {
+ logDataLines.Assign(logData);
+ lastLineEnd = MsgRFindChar(logDataLines, '\n', kLogDataChunkSize);
+ // null terminate the last line
+ if (lastLineEnd == kNotFound)
+ lastLineEnd = kLogDataChunkSize - 1;
+
+ logDataLines.Insert( '\0', lastLineEnd + 1);
+ logDataToLog = logDataLines.get();
+ }
+ else
+ {
+ logDataToLog = logData;
+ lastLineEnd = logDataLen;
+ }
+ switch (GetServerStateParser().GetIMAPstate())
+ {
+ case nsImapServerResponseParser::kFolderSelected:
+ if (extraInfo)
+ MOZ_LOG(IMAP, LogLevel::Info, ("%x:%s:%s-%s:%s:%s: %.400s", this, hostName.get(),
+ selectedStateName, GetServerStateParser().GetSelectedMailboxName(),
+ logSubName, extraInfo, logDataToLog));
+ else
+ MOZ_LOG(IMAP, LogLevel::Info, ("%x:%s:%s-%s:%s: %.400s", this, hostName.get(),
+ selectedStateName, GetServerStateParser().GetSelectedMailboxName(),
+ logSubName, logDataToLog));
+ break;
+ case nsImapServerResponseParser::kNonAuthenticated:
+ case nsImapServerResponseParser::kAuthenticated:
+ {
+ const char *stateName = (GetServerStateParser().GetIMAPstate() ==
+ nsImapServerResponseParser::kNonAuthenticated)
+ ? nonAuthStateName : authStateName;
+ if (extraInfo)
+ MOZ_LOG(IMAP, LogLevel::Info, ("%x:%s:%s:%s:%s: %.400s", this,
+ hostName.get(),stateName,logSubName,extraInfo,logDataToLog));
+ else
+ MOZ_LOG(IMAP, LogLevel::Info, ("%x:%s:%s:%s: %.400s",this,
+ hostName.get(),stateName,logSubName,logDataToLog));
+ }
+ }
+
+ // dump the rest of the string in < 400 byte chunks
+ while (logDataLen > kLogDataChunkSize)
+ {
+ logDataLines.Cut(0, lastLineEnd + 2); // + 2 to account for the LF and the '\0' we added
+ logDataLen = logDataLines.Length();
+ lastLineEnd = (logDataLen > kLogDataChunkSize) ? MsgRFindChar(logDataLines, '\n', kLogDataChunkSize) : kNotFound;
+ // null terminate the last line
+ if (lastLineEnd == kNotFound)
+ lastLineEnd = kLogDataChunkSize - 1;
+ logDataLines.Insert( '\0', lastLineEnd + 1);
+ logDataToLog = logDataLines.get();
+ MOZ_LOG(IMAP, LogLevel::Info, ("%.400s", logDataToLog));
+ }
+ }
+}
+
+// In 4.5, this posted an event back to libmsg and blocked until it got a response.
+// We may still have to do this.It would be nice if we could preflight this value,
+// but we may not always know when we'll need it.
+uint32_t nsImapProtocol::GetMessageSize(const char * messageId,
+ bool idsAreUids)
+{
+ const char *folderFromParser = GetServerStateParser().GetSelectedMailboxName();
+ if (folderFromParser && messageId)
+ {
+ 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 <id>,<id>, or <id1>:<id2>
+ char curChar = *uidString;
+ bool isRange = false;
+ int32_t curToken;
+ int32_t saveStartToken=0;
+
+ for (char *curCharPtr = uidString; curChar && *curCharPtr;)
+ {
+ char *currentKeyToken = curCharPtr;
+ curChar = *curCharPtr;
+ while (curChar != ':' && curChar != ',' && curChar != '\0')
+ curChar = *curCharPtr++;
+ *(curCharPtr - 1) = '\0';
+ curToken = atol(currentKeyToken);
+ if (isRange)
+ {
+ while (saveStartToken < curToken)
+ {
+ numberOfMessages++;
+ saveStartToken++;
+ }
+ }
+
+ numberOfMessages++;
+ isRange = (curChar == ':');
+ if (isRange)
+ saveStartToken = curToken + 1;
+ }
+ PR_Free(uidString);
+ }
+ return numberOfMessages;
+}
+
+
+// It would be really nice not to have to use this method nearly as much as we did
+// in 4.5 - we need to think about this some. Some of it may just go away in the new world order
+bool nsImapProtocol::DeathSignalReceived()
+{
+ // ignore mock channel status if we've been pseudo interrupted
+ // ### need to make sure we clear pseudo interrupted status appropriately.
+ if (!GetPseudoInterrupted() && m_mockChannel)
+ {
+ nsCOMPtr<nsIRequest> request = do_QueryInterface(m_mockChannel);
+ if (request)
+ {
+ nsresult returnValue;
+ request->GetStatus(&returnValue);
+ if (NS_FAILED(returnValue))
+ return false;
+ }
+ }
+
+ // Check the other way of cancelling.
+ ReentrantMonitorAutoEnter threadDeathMon(m_threadDeathMonitor);
+ return m_threadShouldDie;
+}
+
+NS_IMETHODIMP nsImapProtocol::ResetToAuthenticatedState()
+{
+ GetServerStateParser().PreauthSetAuthenticatedState();
+ return NS_OK;
+}
+
+
+NS_IMETHODIMP nsImapProtocol::GetSelectedMailboxName(char ** folderName)
+{
+ if (!folderName) return NS_ERROR_NULL_POINTER;
+ if (GetServerStateParser().GetSelectedMailboxName())
+ *folderName =
+ PL_strdup((GetServerStateParser().GetSelectedMailboxName()));
+ return NS_OK;
+}
+
+bool nsImapProtocol::GetPseudoInterrupted()
+{
+ ReentrantMonitorAutoEnter pseudoInterruptMon(m_pseudoInterruptMonitor);
+ return m_pseudoInterrupted;
+}
+
+void nsImapProtocol::PseudoInterrupt(bool the_interrupt)
+{
+ ReentrantMonitorAutoEnter pseudoInterruptMon(m_pseudoInterruptMonitor);
+ m_pseudoInterrupted = the_interrupt;
+ if (the_interrupt)
+ Log("CONTROL", NULL, "PSEUDO-Interrupted");
+}
+
+void nsImapProtocol::SetActive(bool active)
+{
+ ReentrantMonitorAutoEnter dataMemberMon(m_dataMemberMonitor);
+ m_active = active;
+}
+
+bool nsImapProtocol::GetActive()
+{
+ ReentrantMonitorAutoEnter dataMemberMon(m_dataMemberMonitor);
+ return m_active;
+}
+
+bool nsImapProtocol::GetShowAttachmentsInline()
+{
+ bool showAttachmentsInline = true;
+ if (m_imapServerSink)
+ m_imapServerSink->GetShowAttachmentsInline(&showAttachmentsInline);
+ return showAttachmentsInline;
+
+}
+
+void nsImapProtocol::SetContentModified(IMAP_ContentModifiedType modified)
+{
+ if (m_runningUrl && m_imapMessageSink)
+ m_imapMessageSink->SetContentModified(m_runningUrl, modified);
+}
+
+
+bool nsImapProtocol::GetShouldFetchAllParts()
+{
+ if (m_runningUrl && !DeathSignalReceived())
+ {
+ nsImapContentModifiedType contentModified;
+ if (NS_SUCCEEDED(m_runningUrl->GetContentModified(&contentModified)))
+ return (contentModified == IMAP_CONTENT_FORCE_CONTENT_NOT_MODIFIED);
+ }
+ return true;
+}
+
+// Adds a set of rights for a given user on a given mailbox on the current host.
+// if userName is NULL, it means "me," or MYRIGHTS.
+void nsImapProtocol::AddFolderRightsForUser(const char *mailboxName, const char *userName, const char *rights)
+{
+ if (!userName)
+ userName = "";
+ if (m_imapServerSink)
+ m_imapServerSink->AddFolderRights(nsDependentCString(mailboxName), nsDependentCString(userName),
+ nsDependentCString(rights));
+}
+
+void nsImapProtocol::SetCopyResponseUid(const char *msgIdString)
+{
+ if (m_imapMailFolderSink)
+ m_imapMailFolderSink->SetCopyResponseUid(msgIdString, m_runningUrl);
+}
+
+void nsImapProtocol::CommitNamespacesForHostEvent()
+{
+ if (m_imapServerSink)
+ m_imapServerSink->CommitNamespaces();
+}
+
+// notifies libmsg that we have new capability data for the current host
+void nsImapProtocol::CommitCapability()
+{
+ if (m_imapServerSink)
+ {
+ m_imapServerSink->SetCapability(GetServerStateParser().GetCapabilityFlag());
+ }
+}
+
+// rights is a single string of rights, as specified by RFC2086, the IMAP ACL extension.
+// Clears all rights for a given folder, for all users.
+void nsImapProtocol::ClearAllFolderRights()
+{
+ if (m_imapMailFolderSink)
+ m_imapMailFolderSink->ClearFolderRights();
+}
+
+
+char* nsImapProtocol::CreateNewLineFromSocket()
+{
+ bool needMoreData = false;
+ char * newLine = nullptr;
+ uint32_t numBytesInLine = 0;
+ nsresult rv = NS_OK;
+ // we hold a ref to the input stream in case we get cancelled from the
+ // ui thread, which releases our ref to the input stream, and can
+ // cause the pipe to get deleted before the monitor the read is
+ // blocked on gets notified. When that happens, the imap thread
+ // will stay blocked.
+ nsCOMPtr <nsIInputStream> kungFuGrip = m_inputStream;
+ do
+ {
+ newLine = m_inputStreamBuffer->ReadNextLine(m_inputStream, numBytesInLine, needMoreData, &rv);
+ MOZ_LOG(IMAP, LogLevel::Debug, ("ReadNextLine [stream=%x nb=%u needmore=%u]\n",
+ m_inputStream.get(), numBytesInLine, needMoreData));
+
+ } while (!newLine && NS_SUCCEEDED(rv) && !DeathSignalReceived()); // until we get the next line and haven't been interrupted
+
+ kungFuGrip = nullptr;
+
+ if (NS_FAILED(rv))
+ {
+ switch (rv)
+ {
+ case NS_ERROR_UNKNOWN_HOST:
+ case NS_ERROR_UNKNOWN_PROXY_HOST:
+ AlertUserEventUsingName("imapUnknownHostError");
+ break;
+ case NS_ERROR_CONNECTION_REFUSED:
+ case NS_ERROR_PROXY_CONNECTION_REFUSED:
+ AlertUserEventUsingName("imapConnectionRefusedError");
+ break;
+ case NS_ERROR_NET_TIMEOUT:
+ case NS_ERROR_NET_RESET:
+ case NS_BASE_STREAM_CLOSED:
+ case NS_ERROR_NET_INTERRUPT:
+ // we should retry on RESET, especially for SSL...
+ if ((TestFlag(IMAP_RECEIVED_GREETING) || rv == NS_ERROR_NET_RESET) &&
+ m_runningUrl && !m_retryUrlOnError)
+ {
+ bool rerunningUrl;
+ nsImapAction imapAction;
+ m_runningUrl->GetRerunningUrl(&rerunningUrl);
+ m_runningUrl->GetImapAction(&imapAction);
+ // don't rerun if we already were rerunning. And don't rerun
+ // online move/copies that timeout.
+ if (!rerunningUrl && (rv != NS_ERROR_NET_TIMEOUT ||
+ (imapAction != nsIImapUrl::nsImapOnlineCopy &&
+ imapAction != nsIImapUrl::nsImapOnlineMove)))
+ {
+ m_runningUrl->SetRerunningUrl(true);
+ m_retryUrlOnError = true;
+ break;
+ }
+ }
+ if (rv == NS_ERROR_NET_TIMEOUT)
+ AlertUserEventUsingName("imapNetTimeoutError");
+ else
+ AlertUserEventUsingName(TestFlag(IMAP_RECEIVED_GREETING) ?
+ "imapServerDisconnected" :
+ "imapServerDroppedConnection");
+ break;
+ default:
+ break;
+ }
+
+ nsAutoCString logMsg("clearing IMAP_CONNECTION_IS_OPEN - rv = ");
+ logMsg.AppendInt(static_cast<uint32_t>(rv), 16);
+ Log("CreateNewLineFromSocket", nullptr, logMsg.get());
+ ClearFlag(IMAP_CONNECTION_IS_OPEN);
+ TellThreadToDie();
+ }
+ Log("CreateNewLineFromSocket", nullptr, newLine);
+ SetConnectionStatus(newLine && numBytesInLine ? NS_OK : rv); // set > 0 if string is not null or empty
+ return newLine;
+}
+
+nsresult
+nsImapProtocol::GetConnectionStatus()
+{
+ return m_connectionStatus;
+}
+
+void
+nsImapProtocol::SetConnectionStatus(nsresult status)
+{
+ m_connectionStatus = status;
+}
+
+void
+nsImapProtocol::NotifyMessageFlags(imapMessageFlagsType flags,
+ const nsACString &keywords,
+ nsMsgKey key, uint64_t highestModSeq)
+{
+ if (m_imapMessageSink)
+ {
+ // if we're selecting the folder, don't need to report the flags; we've already fetched them.
+ if (m_imapAction != nsIImapUrl::nsImapSelectFolder &&
+ (m_imapAction != nsIImapUrl::nsImapMsgFetch ||
+ (flags & ~kImapMsgRecentFlag) != kImapMsgSeenFlag))
+ m_imapMessageSink->NotifyMessageFlags(flags, keywords, key, highestModSeq);
+ }
+}
+
+void
+nsImapProtocol::NotifySearchHit(const char * hitLine)
+{
+ nsresult rv;
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_runningUrl, &rv);
+ if (m_imapMailFolderSink)
+ m_imapMailFolderSink->NotifySearchHit(mailnewsUrl, hitLine);
+}
+
+void nsImapProtocol::SetMailboxDiscoveryStatus(EMailboxDiscoverStatus status)
+{
+ ReentrantMonitorAutoEnter mon(m_dataMemberMonitor);
+ m_discoveryStatus = status;
+}
+
+EMailboxDiscoverStatus nsImapProtocol::GetMailboxDiscoveryStatus( )
+{
+ ReentrantMonitorAutoEnter mon(m_dataMemberMonitor);
+ return m_discoveryStatus;
+}
+
+bool
+nsImapProtocol::GetSubscribingNow()
+{
+ // ***** code me *****
+ return false;// ***** for now
+}
+
+void
+nsImapProtocol::DiscoverMailboxSpec(nsImapMailboxSpec * adoptedBoxSpec)
+{
+ nsIMAPNamespace *ns = nullptr;
+
+ NS_ASSERTION (m_hostSessionList, "fatal null host session list");
+ if (!m_hostSessionList)
+ return;
+
+ m_hostSessionList->GetDefaultNamespaceOfTypeForHost(
+ GetImapServerKey(), kPersonalNamespace, ns);
+ const char *nsPrefix = ns ? ns->GetPrefix() : 0;
+
+ if (m_specialXListMailboxes.Count() > 0)
+ {
+ int32_t hashValue = 0;
+ nsCString strHashKey(adoptedBoxSpec->mAllocatedPathName);
+ m_specialXListMailboxes.Get(strHashKey, &hashValue);
+ adoptedBoxSpec->mBoxFlags |= hashValue;
+ }
+
+ switch (m_hierarchyNameState)
+ {
+ case kXListing:
+ if (adoptedBoxSpec->mBoxFlags &
+ (kImapXListTrash|kImapAllMail|kImapInbox|kImapSent|kImapSpam|kImapDrafts))
+ {
+ nsCString mailboxName(adoptedBoxSpec->mAllocatedPathName);
+ m_specialXListMailboxes.Put(mailboxName, adoptedBoxSpec->mBoxFlags);
+ // Remember hierarchy delimiter in case this is the first time we've
+ // connected to the server and we need it to be correct for the two-level
+ // XLIST we send (INBOX is guaranteed to be in the first response).
+ if (adoptedBoxSpec->mBoxFlags & kImapInbox)
+ m_runningUrl->SetOnlineSubDirSeparator(adoptedBoxSpec->mHierarchySeparator);
+
+ }
+ NS_IF_RELEASE(adoptedBoxSpec);
+ break;
+ case kListingForFolderFlags:
+ {
+ // store mailbox flags from LIST for use by LSUB
+ nsCString mailboxName(adoptedBoxSpec->mAllocatedPathName);
+ m_standardListMailboxes.Put(mailboxName, adoptedBoxSpec->mBoxFlags);
+ }
+ NS_IF_RELEASE(adoptedBoxSpec);
+ break;
+ case kListingForCreate:
+ case kNoOperationInProgress:
+ case kDiscoverTrashFolderInProgress:
+ case kListingForInfoAndDiscovery:
+ {
+ // standard mailbox specs are stored in m_standardListMailboxes
+ // because LSUB does necessarily return all mailbox flags.
+ // count should be > 0 only when we are looking at response of LSUB
+ if (m_standardListMailboxes.Count() > 0)
+ {
+ int32_t hashValue = 0;
+ nsCString strHashKey(adoptedBoxSpec->mAllocatedPathName);
+ if (m_standardListMailboxes.Get(strHashKey, &hashValue))
+ adoptedBoxSpec->mBoxFlags |= hashValue;
+ else
+ // if mailbox is not in hash list, then it is subscribed but does not
+ // exist, so we make sure it can't be selected
+ adoptedBoxSpec->mBoxFlags |= kNoselect;
+ }
+ if (ns && nsPrefix) // if no personal namespace, there can be no Trash folder
+ {
+ bool onlineTrashFolderExists = false;
+ if (m_hostSessionList)
+ {
+ if (adoptedBoxSpec->mBoxFlags & (kImapTrash|kImapXListTrash))
+ {
+ m_hostSessionList->SetOnlineTrashFolderExistsForHost(GetImapServerKey(), true);
+ onlineTrashFolderExists = true;
+ }
+ else
+ {
+ m_hostSessionList->GetOnlineTrashFolderExistsForHost(
+ GetImapServerKey(), onlineTrashFolderExists);
+ }
+ }
+
+ // Don't set the Trash flag if not using the Trash model
+ if (GetDeleteIsMoveToTrash() && !onlineTrashFolderExists &&
+ adoptedBoxSpec->mAllocatedPathName.Find(m_trashFolderName, CaseInsensitiveCompare) != -1)
+ {
+ bool trashExists = false;
+ nsCString trashMatch(CreatePossibleTrashName(nsPrefix));
+ nsCString serverTrashName;
+ m_runningUrl->AllocateCanonicalPath(trashMatch.get(),
+ ns->GetDelimiter(),
+ getter_Copies(serverTrashName));
+ if (StringBeginsWith(serverTrashName,
+ NS_LITERAL_CSTRING("INBOX/"),
+ nsCaseInsensitiveCStringComparator()))
+ {
+ nsAutoCString pathName(adoptedBoxSpec->mAllocatedPathName.get() + 6);
+ trashExists =
+ StringBeginsWith(adoptedBoxSpec->mAllocatedPathName,
+ serverTrashName,
+ nsCaseInsensitiveCStringComparator()) && /* "INBOX/" */
+ pathName.Equals(Substring(serverTrashName, 6), nsCaseInsensitiveCStringComparator());
+ }
+ else
+ trashExists = adoptedBoxSpec->mAllocatedPathName.Equals(serverTrashName, nsCaseInsensitiveCStringComparator());
+
+ if (m_hostSessionList)
+ m_hostSessionList->SetOnlineTrashFolderExistsForHost(GetImapServerKey(), trashExists);
+
+ if (trashExists)
+ adoptedBoxSpec->mBoxFlags |= kImapTrash;
+ }
+ }
+
+ // Discover the folder (shuttle over to libmsg, yay)
+ // Do this only if the folder name is not empty (i.e. the root)
+ if (!adoptedBoxSpec->mAllocatedPathName.IsEmpty())
+ {
+ if (m_hierarchyNameState == kListingForCreate)
+ adoptedBoxSpec->mBoxFlags |= kNewlyCreatedFolder;
+
+ if (m_imapServerSink)
+ {
+ bool newFolder;
+
+ m_imapServerSink->PossibleImapMailbox(adoptedBoxSpec->mAllocatedPathName,
+ adoptedBoxSpec->mHierarchySeparator,
+ adoptedBoxSpec->mBoxFlags, &newFolder);
+ // if it's a new folder to the server sink, setting discovery status to
+ // eContinueNew will cause us to get the ACL for the new folder.
+ if (newFolder)
+ SetMailboxDiscoveryStatus(eContinueNew);
+
+ bool useSubscription = false;
+
+ if (m_hostSessionList)
+ m_hostSessionList->GetHostIsUsingSubscription(GetImapServerKey(),
+ useSubscription);
+
+ if ((GetMailboxDiscoveryStatus() != eContinue) &&
+ (GetMailboxDiscoveryStatus() != eContinueNew) &&
+ (GetMailboxDiscoveryStatus() != eListMyChildren))
+ {
+ SetConnectionStatus(NS_ERROR_FAILURE);
+ }
+ else if (!adoptedBoxSpec->mAllocatedPathName.IsEmpty() &&
+ (GetMailboxDiscoveryStatus() == eListMyChildren) &&
+ (!useSubscription || GetSubscribingNow()))
+ {
+ NS_ASSERTION (false,
+ "we should never get here anymore");
+ SetMailboxDiscoveryStatus(eContinue);
+ }
+ else if (GetMailboxDiscoveryStatus() == eContinueNew)
+ {
+ if (m_hierarchyNameState == kListingForInfoAndDiscovery &&
+ !adoptedBoxSpec->mAllocatedPathName.IsEmpty() &&
+ !(adoptedBoxSpec->mBoxFlags & kNameSpace))
+ {
+ // remember the info here also
+ nsIMAPMailboxInfo *mb = new nsIMAPMailboxInfo(adoptedBoxSpec->mAllocatedPathName, adoptedBoxSpec->mHierarchySeparator);
+ m_listedMailboxList.AppendElement(mb);
+ }
+ SetMailboxDiscoveryStatus(eContinue);
+ }
+ }
+ }
+ }
+ NS_IF_RELEASE( adoptedBoxSpec);
+ break;
+ case kDiscoverBaseFolderInProgress:
+ break;
+ case kDeleteSubFoldersInProgress:
+ {
+ NS_ASSERTION(m_deletableChildren, "Oops .. null m_deletableChildren\n");
+ m_deletableChildren->AppendElement(ToNewCString(adoptedBoxSpec->mAllocatedPathName));
+ NS_IF_RELEASE(adoptedBoxSpec);
+ }
+ break;
+ case kListingForInfoOnly:
+ {
+ //UpdateProgressWindowForUpgrade(adoptedBoxSpec->allocatedPathName);
+ ProgressEventFunctionUsingNameWithString("imapDiscoveringMailbox",
+ adoptedBoxSpec->mAllocatedPathName.get());
+ nsIMAPMailboxInfo *mb = new nsIMAPMailboxInfo(adoptedBoxSpec->mAllocatedPathName,
+ adoptedBoxSpec->mHierarchySeparator);
+ m_listedMailboxList.AppendElement(mb);
+ NS_IF_RELEASE(adoptedBoxSpec);
+ }
+ break;
+ case kDiscoveringNamespacesOnly:
+ {
+ NS_IF_RELEASE(adoptedBoxSpec);
+ }
+ break;
+ default:
+ NS_ASSERTION (false, "we aren't supposed to be here");
+ break;
+ }
+}
+
+void
+nsImapProtocol::AlertUserEventUsingName(const char* aMessageName)
+{
+ if (m_imapServerSink)
+ {
+ bool suppressErrorMsg = false;
+
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_runningUrl);
+ if (mailnewsUrl)
+ mailnewsUrl->GetSuppressErrorMsgs(&suppressErrorMsg);
+
+ if (!suppressErrorMsg)
+ m_imapServerSink->FEAlertWithName(aMessageName,
+ mailnewsUrl);
+ }
+}
+
+void
+nsImapProtocol::AlertUserEvent(const char * message)
+{
+ if (m_imapServerSink)
+ {
+ bool suppressErrorMsg = false;
+
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_runningUrl);
+ if (mailnewsUrl)
+ mailnewsUrl->GetSuppressErrorMsgs(&suppressErrorMsg);
+
+ if (!suppressErrorMsg)
+ m_imapServerSink->FEAlert(NS_ConvertASCIItoUTF16(message), mailnewsUrl);
+ }
+}
+
+void
+nsImapProtocol::AlertUserEventFromServer(const char * aServerEvent)
+{
+ if (m_imapServerSink && aServerEvent)
+ {
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_runningUrl);
+ m_imapServerSink->FEAlertFromServer(nsDependentCString(aServerEvent),
+ mailnewsUrl);
+ }
+}
+
+void nsImapProtocol::ResetProgressInfo()
+{
+ m_lastProgressTime = 0;
+ m_lastPercent = -1;
+ m_lastProgressStringName.Truncate();
+}
+
+void nsImapProtocol::SetProgressString(const char * stringName)
+{
+ m_progressStringName.Assign(stringName);
+ if (!m_progressStringName.IsEmpty() && m_imapServerSink)
+ m_imapServerSink->GetImapStringByName(stringName,
+ m_progressString);
+}
+
+void
+nsImapProtocol::ShowProgress()
+{
+ if (!m_progressString.IsEmpty() && !m_progressStringName.IsEmpty())
+ {
+ char16_t *progressString = NULL;
+ const char *mailboxName = GetServerStateParser().GetSelectedMailboxName();
+ nsString unicodeMailboxName;
+ nsresult rv = CopyMUTF7toUTF16(nsDependentCString(mailboxName),
+ unicodeMailboxName);
+ if (NS_SUCCEEDED(rv))
+ {
+ // ### should convert mailboxName to char16_t and change %s to %S in msg text
+ progressString = nsTextFormatter::smprintf(m_progressString.get(),
+ unicodeMailboxName.get(), ++m_progressIndex, m_progressCount);
+ if (progressString)
+ {
+ PercentProgressUpdateEvent(progressString, m_progressIndex, m_progressCount);
+ nsTextFormatter::smprintf_free(progressString);
+ }
+ }
+ }
+}
+
+void
+nsImapProtocol::ProgressEventFunctionUsingName(const char* aMsgName)
+{
+ if (m_imapMailFolderSink && !m_lastProgressStringName.Equals(aMsgName))
+ {
+ m_imapMailFolderSink->ProgressStatusString(this, aMsgName, nullptr);
+ m_lastProgressStringName.Assign(aMsgName);
+ // who's going to free this? Does ProgressStatusString complete synchronously?
+ }
+}
+
+void
+nsImapProtocol::ProgressEventFunctionUsingNameWithString(const char* aMsgName,
+ const char * aExtraInfo)
+{
+ if (m_imapMailFolderSink)
+ {
+ nsString unicodeStr;
+ nsresult rv = CopyMUTF7toUTF16(nsDependentCString(aExtraInfo), unicodeStr);
+ if (NS_SUCCEEDED(rv))
+ m_imapMailFolderSink->ProgressStatusString(this, aMsgName, unicodeStr.get());
+ }
+}
+
+void
+nsImapProtocol::PercentProgressUpdateEvent(char16_t *message, int64_t currentProgress, int64_t maxProgress)
+{
+ int64_t nowMS = 0;
+ int32_t percent = (100 * currentProgress) / maxProgress;
+ if (percent == m_lastPercent)
+ return; // hasn't changed, right? So just return. Do we need to clear this anywhere?
+
+ if (percent < 100) // always need to do 100%
+ {
+ nowMS = PR_IntervalToMilliseconds(PR_IntervalNow());
+ if (nowMS - m_lastProgressTime < 750)
+ return;
+ }
+
+ m_lastPercent = percent;
+ m_lastProgressTime = nowMS;
+
+ // set our max progress on the running URL
+ if (m_runningUrl)
+ {
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl(do_QueryInterface(m_runningUrl));
+ mailnewsUrl->SetMaxProgress(maxProgress);
+ }
+
+ if (m_imapMailFolderSink)
+ m_imapMailFolderSink->PercentProgress(this, message, currentProgress, maxProgress);
+}
+
+ // imap commands issued by the parser
+void
+nsImapProtocol::Store(const nsCString &messageList, const char * messageData,
+ bool idsAreUid)
+{
+
+ // turn messageList back into key array and then back into a message id list,
+ // but use the flag state to handle ranges correctly.
+ nsCString messageIdList;
+ nsTArray<nsMsgKey> msgKeys;
+ if (idsAreUid)
+ ParseUidString(messageList.get(), msgKeys);
+
+ int32_t msgCountLeft = msgKeys.Length();
+ uint32_t msgsHandled = 0;
+ do
+ {
+ nsCString idString;
+
+ uint32_t msgsToHandle = msgCountLeft;
+ if (idsAreUid)
+ AllocateImapUidString(msgKeys.Elements() + msgsHandled, msgsToHandle, m_flagState, idString); // 20 * 200
+ else
+ idString.Assign(messageList);
+
+
+ msgsHandled += msgsToHandle;
+ msgCountLeft -= msgsToHandle;
+
+ IncrementCommandTagNumber();
+ const char *formatString;
+ if (idsAreUid)
+ formatString = "%s uid store %s %s\015\012";
+ else
+ formatString = "%s store %s %s\015\012";
+
+ // we might need to close this mailbox after this
+ m_closeNeededBeforeSelect = GetDeleteIsMoveToTrash() &&
+ (PL_strcasestr(messageData, "\\Deleted"));
+
+ const char *commandTag = GetServerCommandTag();
+ int protocolStringSize = PL_strlen(formatString) +
+ messageList.Length() + PL_strlen(messageData) +
+ PL_strlen(commandTag) + 1;
+ char *protocolString = (char *) PR_CALLOC( protocolStringSize );
+
+ if (protocolString)
+ {
+ PR_snprintf(protocolString, // string to create
+ protocolStringSize, // max size
+ formatString, // format string
+ commandTag, // command tag
+ idString.get(),
+ messageData);
+
+ nsresult rv = SendData(protocolString);
+ if (NS_SUCCEEDED(rv))
+ {
+ m_flagChangeCount++;
+ ParseIMAPandCheckForNewMail(protocolString);
+ if (GetServerStateParser().LastCommandSuccessful() && CheckNeeded())
+ Check();
+ }
+ PR_Free(protocolString);
+ }
+ else
+ HandleMemoryFailure();
+ }
+ while (msgCountLeft > 0 && !DeathSignalReceived());
+
+}
+
+void
+nsImapProtocol::IssueUserDefinedMsgCommand(const char *command, const char * messageList)
+{
+ IncrementCommandTagNumber();
+
+ const char *formatString;
+ formatString = "%s uid %s %s\015\012";
+
+ const char *commandTag = GetServerCommandTag();
+ int protocolStringSize = PL_strlen(formatString) +
+ PL_strlen(messageList) + PL_strlen(command) +
+ PL_strlen(commandTag) + 1;
+ char *protocolString = (char *) PR_CALLOC( protocolStringSize );
+
+ if (protocolString)
+ {
+ PR_snprintf(protocolString, // string to create
+ protocolStringSize, // max size
+ formatString, // format string
+ commandTag, // command tag
+ command,
+ messageList);
+
+ nsresult rv = SendData(protocolString);
+ if (NS_SUCCEEDED(rv))
+ ParseIMAPandCheckForNewMail(protocolString);
+ PR_Free(protocolString);
+ }
+ else
+ HandleMemoryFailure();
+}
+
+void
+nsImapProtocol::UidExpunge(const nsCString &messageSet)
+{
+ IncrementCommandTagNumber();
+ nsCString command(GetServerCommandTag());
+ command.Append(" uid expunge ");
+ command.Append(messageSet);
+ command.Append(CRLF);
+ nsresult rv = SendData(command.get());
+ if (NS_SUCCEEDED(rv))
+ ParseIMAPandCheckForNewMail();
+}
+
+void
+nsImapProtocol::Expunge()
+{
+ uint32_t aclFlags = 0;
+ if (GetServerStateParser().ServerHasACLCapability() && m_imapMailFolderSink)
+ m_imapMailFolderSink->GetAclFlags(&aclFlags);
+
+ if (aclFlags && !(aclFlags & IMAP_ACL_EXPUNGE_FLAG))
+ return;
+ ProgressEventFunctionUsingName("imapStatusExpungingMailbox");
+
+ if(gCheckDeletedBeforeExpunge)
+ {
+ GetServerStateParser().ResetSearchResultSequence();
+ Search("SEARCH DELETED", false, false);
+ if (GetServerStateParser().LastCommandSuccessful())
+ {
+ nsImapSearchResultIterator *search = GetServerStateParser().CreateSearchResultIterator();
+ nsMsgKey key = search->GetNextMessageNumber();
+ delete search;
+ if (key == 0)
+ return; //no deleted messages to expunge (bug 235004)
+ }
+ }
+
+ IncrementCommandTagNumber();
+ nsAutoCString command(GetServerCommandTag());
+ command.Append(" expunge" CRLF);
+
+ nsresult rv = SendData(command.get());
+ if (NS_SUCCEEDED(rv))
+ ParseIMAPandCheckForNewMail();
+}
+
+void
+nsImapProtocol::HandleMemoryFailure()
+{
+ PR_CEnterMonitor(this);
+ // **** jefft fix me!!!!!! ******
+ // m_imapThreadIsRunning = false;
+ // SetConnectionStatus(-1);
+ PR_CExitMonitor(this);
+}
+
+void nsImapProtocol::HandleCurrentUrlError()
+{
+ // This is to handle a move/copy failing, especially because the user
+ // cancelled the password prompt.
+ (void) m_runningUrl->GetImapAction(&m_imapAction);
+ if (m_imapAction == nsIImapUrl::nsImapOfflineToOnlineMove || m_imapAction == nsIImapUrl::nsImapAppendMsgFromFile
+ || m_imapAction == nsIImapUrl::nsImapAppendDraftFromFile)
+ {
+ if (m_imapMailFolderSink)
+ m_imapMailFolderSink->OnlineCopyCompleted(this, ImapOnlineCopyStateType::kFailedCopy);
+ }
+}
+
+void nsImapProtocol::StartTLS()
+{
+ IncrementCommandTagNumber();
+ nsCString command(GetServerCommandTag());
+
+ command.Append(" STARTTLS" CRLF);
+
+ nsresult rv = SendData(command.get());
+ if (NS_SUCCEEDED(rv))
+ ParseIMAPandCheckForNewMail();
+}
+
+void nsImapProtocol::Capability()
+{
+
+ ProgressEventFunctionUsingName("imapStatusCheckCompat");
+ IncrementCommandTagNumber();
+ nsCString command(GetServerCommandTag());
+
+ command.Append(" capability" CRLF);
+
+ nsresult rv = SendData(command.get());
+ if (NS_SUCCEEDED(rv))
+ ParseIMAPandCheckForNewMail();
+ 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<nsMsgCompressIStream> new_in = new nsMsgCompressIStream();
+ if (!new_in)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ nsresult rv = new_in->InitInputStream(m_inputStream);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ m_inputStream = new_in;
+
+ RefPtr<nsMsgCompressOStream> new_out = new nsMsgCompressOStream();
+ if (!new_out)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ rv = new_out->InitOutputStream(m_outputStream);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ m_outputStream = new_out;
+ return rv;
+}
+
+void nsImapProtocol::Language()
+{
+ // only issue the language request if we haven't done so already...
+ if (!TestFlag(IMAP_ISSUED_LANGUAGE_REQUEST))
+ {
+ SetFlag(IMAP_ISSUED_LANGUAGE_REQUEST);
+ ProgressEventFunctionUsingName("imapStatusCheckCompat");
+ IncrementCommandTagNumber();
+ nsCString command(GetServerCommandTag());
+
+ // extract the desired language attribute from prefs
+ nsresult rv = NS_OK;
+
+ // we need to parse out the first language out of this comma separated list....
+ // i.e if we have en,ja we only want to send en to the server.
+ if (mAcceptLanguages.get())
+ {
+ nsAutoCString extractedLanguage;
+ LossyCopyUTF16toASCII(mAcceptLanguages, extractedLanguage);
+ int32_t pos = extractedLanguage.FindChar(',');
+ if (pos > 0) // we have a comma separated list of languages...
+ extractedLanguage.SetLength(pos); // truncate everything after the first comma (including the comma)
+
+ if (extractedLanguage.IsEmpty())
+ return;
+
+ command.Append(" LANGUAGE ");
+ command.Append(extractedLanguage);
+ command.Append(CRLF);
+
+ rv = SendData(command.get());
+ if (NS_SUCCEEDED(rv))
+ ParseIMAPandCheckForNewMail(nullptr, true /* ignore bad or no result from the server for this command */);
+ }
+ }
+}
+
+void nsImapProtocol::EscapeUserNamePasswordString(const char *strToEscape, nsCString *resultStr)
+{
+ if (strToEscape)
+ {
+ uint32_t i = 0;
+ uint32_t escapeStrlen = strlen(strToEscape);
+ for (i=0; i<escapeStrlen; i++)
+ {
+ if (strToEscape[i] == '\\' || strToEscape[i] == '\"')
+ {
+ resultStr->Append('\\');
+ }
+ resultStr->Append(strToEscape[i]);
+ }
+ }
+}
+
+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 "<NUL>userName<NUL>password" TODO nsAutoCString
+ int len = 1; // count for first <NUL> char
+ memset(plainstr, 0, 512);
+ PR_snprintf(&plainstr[1], 510, "%s", userName);
+ len += PL_strlen(userName);
+ len++; // count for second <NUL> char
+ PR_snprintf(&plainstr[len], 511-len, "%s", password.get());
+ len += password.Length();
+ char *base64Str = PL_Base64Encode(plainstr, len, nullptr);
+ PR_snprintf(m_dataOutputBuf, OUTPUT_BUFFER_SIZE, "%s" CRLF, base64Str);
+ PR_Free(base64Str);
+
+ bool isResend = false;
+ while (true)
+ {
+ // Send authentication string (true: suppress logging the string).
+ rv = SendData(m_dataOutputBuf, true);
+ if (NS_FAILED(rv))
+ break;
+ ParseIMAPandCheckForNewMail(currentCommand);
+ if (!GetServerStateParser().WaitingForMoreClientInput())
+ break;
+
+ // Server is asking for authentication string again. So we send the
+ // same string again although we already know that it will be
+ // rejected again. We do that to get a firm authentication failure
+ // instead of a resend request. That keeps things in order before
+ // failing "authenticate PLAIN" and trying another method if capable.
+ if (isResend)
+ {
+ rv = NS_ERROR_FAILURE;
+ break;
+ }
+ isResend = true;
+ }
+ } // if the last command succeeded
+ } // if auth plain capability
+ else if (flag & kHasAuthLoginCapability)
+ {
+ MOZ_LOG(IMAP, LogLevel::Debug, ("LOGIN auth"));
+ PR_snprintf(m_dataOutputBuf, OUTPUT_BUFFER_SIZE, "%s authenticate LOGIN" CRLF, GetServerCommandTag());
+ rv = SendData(m_dataOutputBuf);
+ NS_ENSURE_SUCCESS(rv, rv);
+ currentCommand = PL_strdup(m_dataOutputBuf);
+ ParseIMAPandCheckForNewMail();
+
+ if (GetServerStateParser().LastCommandSuccessful())
+ {
+ char *base64Str = PL_Base64Encode(userName, PL_strlen(userName), nullptr);
+ PR_snprintf(m_dataOutputBuf, OUTPUT_BUFFER_SIZE, "%s" CRLF, base64Str);
+ PR_Free(base64Str);
+ rv = SendData(m_dataOutputBuf, true /* suppress logging */);
+ if (NS_SUCCEEDED(rv))
+ {
+ ParseIMAPandCheckForNewMail(currentCommand);
+ if (GetServerStateParser().LastCommandSuccessful())
+ {
+ base64Str = PL_Base64Encode(password.get(), password.Length(), nullptr);
+ PR_snprintf(m_dataOutputBuf, OUTPUT_BUFFER_SIZE, "%s" CRLF, base64Str);
+ PR_Free(base64Str);
+ rv = SendData(m_dataOutputBuf, true /* suppress logging */);
+ if (NS_SUCCEEDED(rv))
+ ParseIMAPandCheckForNewMail(currentCommand);
+ } // if last command successful
+ } // if last command successful
+ } // if last command successful
+ } // if has auth login capability
+ else if (flag & kHasAuthOldLoginCapability)
+ {
+ MOZ_LOG(IMAP, LogLevel::Debug, ("old-style auth"));
+ ProgressEventFunctionUsingName("imapStatusSendingLogin");
+ IncrementCommandTagNumber();
+ nsCString command (GetServerCommandTag());
+ nsAutoCString escapedUserName;
+ command.Append(" login \"");
+ EscapeUserNamePasswordString(userName, &escapedUserName);
+ command.Append(escapedUserName);
+ command.Append("\" \"");
+
+ // if the password contains a \, login will fail
+ // turn foo\bar into foo\\bar
+ nsAutoCString correctedPassword;
+ EscapeUserNamePasswordString(password.get(), &correctedPassword);
+ command.Append(correctedPassword);
+ command.Append("\"" CRLF);
+ rv = SendData(command.get(), true /* suppress logging */);
+ NS_ENSURE_SUCCESS(rv, rv);
+ ParseIMAPandCheckForNewMail();
+ }
+ 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 <username>" like POP?
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+ else
+ {
+ MOZ_LOG(IMAP, LogLevel::Error, ("flags param has no auth scheme selected"));
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ PR_Free(currentCommand);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return GetServerStateParser().LastCommandSuccessful() ?
+ NS_OK : NS_ERROR_FAILURE;
+}
+
+void nsImapProtocol::OnLSubFolders()
+{
+ // **** use to find out whether Drafts, Sent, & Templates folder
+ // exists or not even the user didn't subscribe to it
+ char *mailboxName = OnCreateServerSourceFolderPathString();
+ if (mailboxName)
+ {
+ ProgressEventFunctionUsingName("imapStatusLookingForMailbox");
+ IncrementCommandTagNumber();
+ PR_snprintf(m_dataOutputBuf, OUTPUT_BUFFER_SIZE,"%s list \"\" \"%s\"" CRLF, GetServerCommandTag(), mailboxName);
+ nsresult rv = SendData(m_dataOutputBuf);
+ if (NS_SUCCEEDED(rv))
+ ParseIMAPandCheckForNewMail();
+ PR_Free(mailboxName);
+ }
+ else
+ {
+ HandleMemoryFailure();
+ }
+
+}
+
+void nsImapProtocol::OnAppendMsgFromFile()
+{
+ nsCOMPtr<nsIFile> file;
+ nsresult rv = NS_OK;
+ rv = m_runningUrl->GetMsgFile(getter_AddRefs(file));
+ if (NS_SUCCEEDED(rv) && file)
+ {
+ char *mailboxName = OnCreateServerSourceFolderPathString();
+ if (mailboxName)
+ {
+ imapMessageFlagsType flagsToSet = 0;
+ uint32_t msgFlags = 0;
+ PRTime date = 0;
+ nsCString keywords;
+ if (m_imapMessageSink)
+ m_imapMessageSink->GetCurMoveCopyMessageInfo(m_runningUrl, &date,
+ keywords, &msgFlags);
+
+ if (msgFlags & nsMsgMessageFlags::Read)
+ flagsToSet |= kImapMsgSeenFlag;
+ if (msgFlags & nsMsgMessageFlags::MDNReportSent)
+ flagsToSet |= kImapMsgMDNSentFlag;
+ // convert msg flag label (0xE000000) to imap flag label (0x0E00)
+ if (msgFlags & nsMsgMessageFlags::Labels)
+ flagsToSet |= (msgFlags & nsMsgMessageFlags::Labels) >> 16;
+ if (msgFlags & nsMsgMessageFlags::Marked)
+ flagsToSet |= kImapMsgFlaggedFlag;
+ if (msgFlags & nsMsgMessageFlags::Replied)
+ flagsToSet |= kImapMsgAnsweredFlag;
+ if (msgFlags & nsMsgMessageFlags::Forwarded)
+ flagsToSet |= kImapMsgForwardedFlag;
+
+ // If the message copied was a draft, flag it as such
+ nsImapAction imapAction;
+ rv = m_runningUrl->GetImapAction(&imapAction);
+ if (NS_SUCCEEDED(rv) && (imapAction == nsIImapUrl::nsImapAppendDraftFromFile))
+ flagsToSet |= kImapMsgDraftFlag;
+ UploadMessageFromFile(file, mailboxName, date, flagsToSet, keywords);
+ PR_Free( mailboxName );
+ }
+ else
+ {
+ HandleMemoryFailure();
+ }
+ }
+}
+
+void nsImapProtocol::UploadMessageFromFile (nsIFile* file,
+ const char* mailboxName,
+ PRTime date,
+ imapMessageFlagsType flags,
+ nsCString &keywords)
+{
+ if (!file || !mailboxName) return;
+ IncrementCommandTagNumber();
+
+ int64_t fileSize = 0;
+ int64_t totalSize;
+ uint32_t readCount;
+ char *dataBuffer = nullptr;
+ nsCString command(GetServerCommandTag());
+ nsCString escapedName;
+ CreateEscapedMailboxName(mailboxName, escapedName);
+ nsresult rv;
+ bool eof = false;
+ nsCString flagString;
+ bool hasLiteralPlus = (GetServerStateParser().GetCapabilityFlag() &
+ kLiteralPlusCapability);
+
+ nsCOMPtr <nsIInputStream> fileInputStream;
+
+ if (!escapedName.IsEmpty())
+ {
+ command.Append(" append \"");
+ command.Append(escapedName);
+ command.Append("\"");
+ if (flags || keywords.Length())
+ {
+ command.Append(" (");
+
+ if (flags)
+ {
+ SetupMessageFlagsString(flagString, flags,
+ GetServerStateParser().SupportsUserFlags());
+ command.Append(flagString);
+ }
+ if (keywords.Length())
+ {
+ if (flags)
+ command.Append(' ');
+ command.Append(keywords);
+ }
+ command.Append(")");
+ }
+
+ // date should never be 0, but just in case...
+ if (date)
+ {
+ /* Use PR_FormatTimeUSEnglish() to format the date in US English format,
+ then figure out what our local GMT offset is, and append it (since
+ PR_FormatTimeUSEnglish() can't do that.) Generate four digit years as
+ per RFC 1123 (superceding RFC 822.)
+ */
+ char szDateTime[64];
+ char dateStr[100];
+ PRExplodedTime exploded;
+ PR_ExplodeTime(date, PR_LocalTimeParameters, &exploded);
+ PR_FormatTimeUSEnglish(szDateTime, sizeof(szDateTime), "%d-%b-%Y %H:%M:%S", &exploded);
+ PRExplodedTime now;
+ PR_ExplodeTime(PR_Now(), PR_LocalTimeParameters, &now);
+ int gmtoffset = (now.tm_params.tp_gmt_offset + now.tm_params.tp_dst_offset) / 60;
+ PR_snprintf(dateStr, sizeof(dateStr),
+ " \"%s %c%02d%02d\"",
+ szDateTime,
+ (gmtoffset >= 0 ? '+' : '-'),
+ ((gmtoffset >= 0 ? gmtoffset : -gmtoffset) / 60),
+ ((gmtoffset >= 0 ? gmtoffset : -gmtoffset) % 60));
+
+ command.Append(dateStr);
+ }
+ command.Append(" {");
+
+ dataBuffer = (char*) PR_CALLOC(COPY_BUFFER_SIZE+1);
+ if (!dataBuffer) goto done;
+ rv = file->GetFileSize(&fileSize);
+ NS_ASSERTION(fileSize, "got empty file in UploadMessageFromFile");
+ if (NS_FAILED(rv) || !fileSize) goto done;
+ rv = NS_NewLocalFileInputStream(getter_AddRefs(fileInputStream), file);
+ if (NS_FAILED(rv) || !fileInputStream) goto done;
+ command.AppendInt((int32_t)fileSize);
+ 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 <nsIMsgFolder> msgFolder;
+
+ m_runningUrl->GetOnlineSubDirSeparator(&onlineSubDirDelimiter);
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_runningUrl);
+ mailnewsUrl->GetFolder(getter_AddRefs(msgFolder));
+ if (msgFolder)
+ {
+ nsCOMPtr <nsIMsgImapMailFolder> imapFolder = do_QueryInterface(msgFolder);
+ if (imapFolder)
+ {
+ imapFolder->GetHierarchyDelimiter(&hierarchyDelimiter);
+ if (hierarchyDelimiter != kOnlineHierarchySeparatorUnknown &&
+ onlineSubDirDelimiter != hierarchyDelimiter)
+ m_runningUrl->SetOnlineSubDirSeparator(hierarchyDelimiter);
+ }
+ }
+ m_runningUrl->CreateServerSourceFolderPathString(&sourceMailbox);
+
+ return sourceMailbox;
+}
+
+nsresult nsImapProtocol::CreateServerSourceFolderPathString(char **result)
+{
+ NS_ENSURE_ARG(result);
+ *result = OnCreateServerSourceFolderPathString();
+ return (*result) ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
+}
+
+//caller must free using PR_Free
+char * nsImapProtocol::OnCreateServerDestinationFolderPathString()
+{
+ char *destinationMailbox = nullptr;
+ char hierarchyDelimiter = 0;
+ char onlineDelimiter = 0;
+ m_runningUrl->GetOnlineSubDirSeparator(&hierarchyDelimiter);
+ if (m_imapMailFolderSink)
+ m_imapMailFolderSink->GetOnlineDelimiter(&onlineDelimiter);
+ if (onlineDelimiter != kOnlineHierarchySeparatorUnknown &&
+ onlineDelimiter != hierarchyDelimiter)
+ m_runningUrl->SetOnlineSubDirSeparator(onlineDelimiter);
+
+ m_runningUrl->CreateServerDestinationFolderPathString(&destinationMailbox);
+
+ return destinationMailbox;
+}
+
+void nsImapProtocol::OnCreateFolder(const char * aSourceMailbox)
+{
+ bool created = CreateMailboxRespectingSubscriptions(aSourceMailbox);
+ if (created)
+ {
+ m_hierarchyNameState = kListingForCreate;
+ nsCString mailboxWODelim(aSourceMailbox);
+ RemoveHierarchyDelimiter(mailboxWODelim);
+ List(mailboxWODelim.get(), false);
+ m_hierarchyNameState = kNoOperationInProgress;
+ }
+ else
+ FolderNotCreated(aSourceMailbox);
+}
+
+void nsImapProtocol::OnEnsureExistsFolder(const char * aSourceMailbox)
+{
+
+ List(aSourceMailbox, false); // how to tell if that succeeded?
+ bool exists = false;
+
+ // try converting aSourceMailbox to canonical format
+
+ nsIMAPNamespace *nsForMailbox = nullptr;
+ m_hostSessionList->GetNamespaceForMailboxForHost(GetImapServerKey(),
+ aSourceMailbox, nsForMailbox);
+ // NS_ASSERTION (nsForMailbox, "Oops .. null nsForMailbox\n");
+
+ nsCString name;
+
+ if (nsForMailbox)
+ m_runningUrl->AllocateCanonicalPath(aSourceMailbox,
+ nsForMailbox->GetDelimiter(),
+ getter_Copies(name));
+ else
+ m_runningUrl->AllocateCanonicalPath(aSourceMailbox,
+ kOnlineHierarchySeparatorUnknown,
+ getter_Copies(name));
+
+ if (m_imapServerSink)
+ m_imapServerSink->FolderVerifiedOnline(name, &exists);
+
+ if (exists)
+ {
+ Subscribe(aSourceMailbox);
+ }
+ else
+ {
+ bool created = CreateMailboxRespectingSubscriptions(aSourceMailbox);
+ if (created)
+ {
+ List(aSourceMailbox, false);
+ }
+ }
+ if (!GetServerStateParser().LastCommandSuccessful())
+ FolderNotCreated(aSourceMailbox);
+}
+
+
+void nsImapProtocol::OnSubscribe(const char * sourceMailbox)
+{
+ Subscribe(sourceMailbox);
+}
+
+void nsImapProtocol::OnUnsubscribe(const char * sourceMailbox)
+{
+ // When we try to auto-unsubscribe from \Noselect folders,
+ // some servers report errors if we were already unsubscribed
+ // from them.
+ bool lastReportingErrors = GetServerStateParser().GetReportingErrors();
+ GetServerStateParser().SetReportingErrors(false);
+ Unsubscribe(sourceMailbox);
+ GetServerStateParser().SetReportingErrors(lastReportingErrors);
+}
+
+void nsImapProtocol::RefreshACLForFolderIfNecessary(const char *mailboxName)
+{
+ if (GetServerStateParser().ServerHasACLCapability())
+ {
+ if (!m_folderNeedsACLRefreshed && m_imapMailFolderSink)
+ m_imapMailFolderSink->GetFolderNeedsACLListed(&m_folderNeedsACLRefreshed);
+ if (m_folderNeedsACLRefreshed)
+ {
+ RefreshACLForFolder(mailboxName);
+ m_folderNeedsACLRefreshed = false;
+ }
+ }
+}
+
+void nsImapProtocol::RefreshACLForFolder(const char *mailboxName)
+{
+
+ nsIMAPNamespace *ns = nullptr;
+ m_hostSessionList->GetNamespaceForMailboxForHost(GetImapServerKey(), mailboxName, ns);
+ if (ns)
+ {
+ switch (ns->GetType())
+ {
+ case kPersonalNamespace:
+ // It's a personal folder, most likely.
+ // I find it hard to imagine a server that supports ACL that doesn't support NAMESPACE,
+ // so most likely we KNOW that this is a personal, rather than the default, namespace.
+
+ // First, clear what we have.
+ ClearAllFolderRights();
+ // Now, get the new one.
+ GetMyRightsForFolder(mailboxName);
+ if (m_imapMailFolderSink)
+ {
+ uint32_t aclFlags = 0;
+ if (NS_SUCCEEDED(m_imapMailFolderSink->GetAclFlags(&aclFlags)) && aclFlags & IMAP_ACL_ADMINISTER_FLAG)
+ GetACLForFolder(mailboxName);
+ }
+
+ // We're all done, refresh the icon/flags for this folder
+ RefreshFolderACLView(mailboxName, ns);
+ break;
+ default:
+ // We know it's a public folder or other user's folder.
+ // We only want our own rights
+
+ // First, clear what we have
+ ClearAllFolderRights();
+ // Now, get the new one.
+ GetMyRightsForFolder(mailboxName);
+ // We're all done, refresh the icon/flags for this folder
+ RefreshFolderACLView(mailboxName, ns);
+ break;
+ }
+ }
+ else
+ {
+ // no namespace, not even default... can this happen?
+ NS_ASSERTION(false, "couldn't get namespace");
+ }
+}
+
+void nsImapProtocol::RefreshFolderACLView(const char *mailboxName, nsIMAPNamespace *nsForMailbox)
+{
+ nsCString canonicalMailboxName;
+
+ if (nsForMailbox)
+ m_runningUrl->AllocateCanonicalPath(mailboxName, nsForMailbox->GetDelimiter(), getter_Copies(canonicalMailboxName));
+ else
+ m_runningUrl->AllocateCanonicalPath(mailboxName, kOnlineHierarchySeparatorUnknown, getter_Copies(canonicalMailboxName));
+
+ if (m_imapServerSink)
+ m_imapServerSink->RefreshFolderRights(canonicalMailboxName);
+}
+
+void nsImapProtocol::GetACLForFolder(const char *mailboxName)
+{
+ IncrementCommandTagNumber();
+
+ nsCString command(GetServerCommandTag());
+ nsCString escapedName;
+ CreateEscapedMailboxName(mailboxName, escapedName);
+ command.Append(" getacl \"");
+ command.Append(escapedName);
+ command.Append("\"" CRLF);
+
+ nsresult rv = SendData(command.get());
+ if (NS_SUCCEEDED(rv))
+ ParseIMAPandCheckForNewMail();
+}
+
+void nsImapProtocol::OnRefreshAllACLs()
+{
+ m_hierarchyNameState = kListingForInfoOnly;
+ nsIMAPMailboxInfo *mb = NULL;
+
+ // This will fill in the list
+ List("*", true);
+
+ int32_t total = m_listedMailboxList.Length(), count = 0;
+ GetServerStateParser().SetReportingErrors(false);
+ for (int32_t i = 0; i < total; i++)
+ {
+ mb = m_listedMailboxList.ElementAt(i);
+ if (mb) // paranoia
+ {
+ char *onlineName = nullptr;
+ m_runningUrl->AllocateServerPath(PromiseFlatCString(mb->GetMailboxName()).get(), mb->GetDelimiter(), &onlineName);
+ if (onlineName)
+ {
+ RefreshACLForFolder(onlineName);
+ NS_Free(onlineName);
+ }
+ PercentProgressUpdateEvent(NULL, count, total);
+ delete mb;
+ count++;
+ }
+ }
+ m_listedMailboxList.Clear();
+
+ PercentProgressUpdateEvent(NULL, 100, 100);
+ GetServerStateParser().SetReportingErrors(true);
+ m_hierarchyNameState = kNoOperationInProgress;
+}
+
+// any state commands
+void nsImapProtocol::Logout(bool shuttingDown /* = false */,
+ bool waitForResponse /* = true */)
+{
+ if (!shuttingDown)
+ ProgressEventFunctionUsingName("imapStatusLoggingOut");
+
+/******************************************************************
+ * due to the undo functionality we cannot issule a close when logout; there
+ * is no way to do an undo if the message has been permanently expunge
+ * jt - 07/12/1999
+
+ bool closeNeeded = GetServerStateParser().GetIMAPstate() ==
+ nsImapServerResponseParser::kFolderSelected;
+
+ if (closeNeeded && GetDeleteIsMoveToTrash())
+ Close();
+********************/
+
+ IncrementCommandTagNumber();
+
+ nsCString command(GetServerCommandTag());
+
+ command.Append(" logout" CRLF);
+
+ nsresult rv = SendData(command.get());
+ if (m_transport && shuttingDown)
+ m_transport->SetTimeout(nsISocketTransport::TIMEOUT_READ_WRITE, 5);
+ // the socket may be dead before we read the response, so drop it.
+ if (NS_SUCCEEDED(rv) && waitForResponse)
+ ParseIMAPandCheckForNewMail();
+}
+
+void nsImapProtocol::Noop()
+{
+ //ProgressUpdateEvent("noop...");
+ IncrementCommandTagNumber();
+ nsCString command(GetServerCommandTag());
+
+ command.Append(" noop" CRLF);
+
+ nsresult rv = SendData(command.get());
+ if (NS_SUCCEEDED(rv))
+ ParseIMAPandCheckForNewMail();
+}
+
+void nsImapProtocol::XServerInfo()
+{
+
+ ProgressEventFunctionUsingName("imapGettingServerInfo");
+ IncrementCommandTagNumber();
+ nsCString command(GetServerCommandTag());
+
+ command.Append(" XSERVERINFO MANAGEACCOUNTURL MANAGELISTSURL MANAGEFILTERSURL" CRLF);
+
+ nsresult rv = SendData(command.get());
+ if (NS_SUCCEEDED(rv))
+ ParseIMAPandCheckForNewMail();
+}
+
+void nsImapProtocol::Netscape()
+{
+ ProgressEventFunctionUsingName("imapGettingServerInfo");
+ IncrementCommandTagNumber();
+
+ nsCString command(GetServerCommandTag());
+
+ command.Append(" netscape" CRLF);
+
+ nsresult rv = SendData(command.get());
+ if (NS_SUCCEEDED(rv))
+ ParseIMAPandCheckForNewMail();
+}
+
+
+
+void nsImapProtocol::XMailboxInfo(const char *mailboxName)
+{
+
+ ProgressEventFunctionUsingName("imapGettingMailboxInfo");
+ IncrementCommandTagNumber();
+ nsCString command(GetServerCommandTag());
+
+ command.Append(" XMAILBOXINFO \"");
+ command.Append(mailboxName);
+ command.Append("\" MANAGEURL POSTURL" CRLF);
+
+ nsresult rv = SendData(command.get());
+ if (NS_SUCCEEDED(rv))
+ ParseIMAPandCheckForNewMail();
+}
+
+void nsImapProtocol::Namespace()
+{
+
+ IncrementCommandTagNumber();
+
+ nsCString command(GetServerCommandTag());
+ command.Append(" namespace" CRLF);
+
+ nsresult rv = SendData(command.get());
+ if (NS_SUCCEEDED(rv))
+ ParseIMAPandCheckForNewMail();
+}
+
+
+void nsImapProtocol::MailboxData()
+{
+ IncrementCommandTagNumber();
+
+ nsCString command(GetServerCommandTag());
+ command.Append(" mailboxdata" CRLF);
+
+ nsresult rv = SendData(command.get());
+ if (NS_SUCCEEDED(rv))
+ ParseIMAPandCheckForNewMail();
+}
+
+
+void nsImapProtocol::GetMyRightsForFolder(const char *mailboxName)
+{
+ IncrementCommandTagNumber();
+
+ nsCString command(GetServerCommandTag());
+ nsCString escapedName;
+ CreateEscapedMailboxName(mailboxName, escapedName);
+
+ if (MailboxIsNoSelectMailbox(escapedName.get()))
+ return; // Don't issue myrights on Noselect folder
+
+ command.Append(" myrights \"");
+ command.Append(escapedName);
+ command.Append("\"" CRLF);
+
+ nsresult rv = SendData(command.get());
+ if (NS_SUCCEEDED(rv))
+ ParseIMAPandCheckForNewMail();
+}
+
+bool nsImapProtocol::FolderIsSelected(const char *mailboxName)
+{
+ return (GetServerStateParser().GetIMAPstate() ==
+ nsImapServerResponseParser::kFolderSelected && GetServerStateParser().GetSelectedMailboxName() &&
+ PL_strcmp(GetServerStateParser().GetSelectedMailboxName(),
+ mailboxName) == 0);
+}
+
+void nsImapProtocol::OnStatusForFolder(const char *mailboxName)
+{
+
+ if (FolderIsSelected(mailboxName))
+ {
+ int32_t prevNumMessages = GetServerStateParser().NumberOfMessages();
+ Noop();
+ // OnNewIdleMessages will cause the ui thread to update the folder
+ if (m_imapMailFolderSink && (GetServerStateParser().NumberOfRecentMessages()
+ || prevNumMessages != GetServerStateParser().NumberOfMessages()))
+ m_imapMailFolderSink->OnNewIdleMessages();
+ return;
+ }
+
+ IncrementCommandTagNumber();
+
+ nsAutoCString command(GetServerCommandTag());
+ nsCString escapedName;
+ CreateEscapedMailboxName(mailboxName, escapedName);
+
+ command.Append(" STATUS \"");
+ command.Append(escapedName);
+ command.Append("\" (UIDNEXT MESSAGES UNSEEN RECENT)" CRLF);
+
+ nsresult rv = SendData(command.get());
+ if (NS_SUCCEEDED(rv))
+ ParseIMAPandCheckForNewMail();
+
+ if (GetServerStateParser().LastCommandSuccessful())
+ {
+ nsImapMailboxSpec *new_spec = GetServerStateParser().CreateCurrentMailboxSpec(mailboxName);
+ if (new_spec && m_imapMailFolderSink)
+ m_imapMailFolderSink->UpdateImapMailboxStatus(this, new_spec);
+ NS_IF_RELEASE(new_spec);
+ }
+}
+
+
+void nsImapProtocol::OnListFolder(const char * aSourceMailbox, bool aBool)
+{
+ List(aSourceMailbox, aBool);
+}
+
+
+// Returns true if the mailbox is a NoSelect mailbox.
+// If we don't know about it, returns false.
+bool nsImapProtocol::MailboxIsNoSelectMailbox(const char *mailboxName)
+{
+ bool rv = false;
+
+ nsIMAPNamespace *nsForMailbox = nullptr;
+ m_hostSessionList->GetNamespaceForMailboxForHost(GetImapServerKey(),
+ mailboxName, nsForMailbox);
+ // NS_ASSERTION (nsForMailbox, "Oops .. null nsForMailbox\n");
+
+ nsCString name;
+
+ if (nsForMailbox)
+ m_runningUrl->AllocateCanonicalPath(mailboxName,
+ nsForMailbox->GetDelimiter(),
+ getter_Copies(name));
+ else
+ m_runningUrl->AllocateCanonicalPath(mailboxName,
+ kOnlineHierarchySeparatorUnknown,
+ getter_Copies(name));
+
+ if (name.IsEmpty())
+ return false;
+
+ NS_ASSERTION(m_imapServerSink, "unexpected, no imap server sink, see bug #194335");
+ if (m_imapServerSink)
+ m_imapServerSink->FolderIsNoSelect(name, &rv);
+ return rv;
+}
+
+nsresult nsImapProtocol::SetFolderAdminUrl(const char *mailboxName)
+{
+ nsresult rv = NS_ERROR_NULL_POINTER; // if m_imapServerSink is null, rv will be this.
+
+ nsIMAPNamespace *nsForMailbox = nullptr;
+ m_hostSessionList->GetNamespaceForMailboxForHost(GetImapServerKey(),
+ mailboxName, nsForMailbox);
+
+ nsCString name;
+
+ if (nsForMailbox)
+ m_runningUrl->AllocateCanonicalPath(mailboxName,
+ nsForMailbox->GetDelimiter(),
+ getter_Copies(name));
+ else
+ m_runningUrl->AllocateCanonicalPath(mailboxName,
+ kOnlineHierarchySeparatorUnknown,
+ getter_Copies(name));
+
+ if (m_imapServerSink)
+ rv = m_imapServerSink->SetFolderAdminURL(name, nsDependentCString(GetServerStateParser().GetManageFolderUrl()));
+ return rv;
+}
+// returns true is the delete succeeded (regardless of subscription changes)
+bool nsImapProtocol::DeleteMailboxRespectingSubscriptions(const char *mailboxName)
+{
+ bool rv = true;
+ if (!MailboxIsNoSelectMailbox(mailboxName))
+ {
+ // Only try to delete it if it really exists
+ DeleteMailbox(mailboxName);
+ rv = GetServerStateParser().LastCommandSuccessful();
+ }
+
+ // We can unsubscribe even if the mailbox doesn't exist.
+ if (rv && m_autoUnsubscribe) // auto-unsubscribe is on
+ {
+ bool reportingErrors = GetServerStateParser().GetReportingErrors();
+ GetServerStateParser().SetReportingErrors(false);
+ Unsubscribe(mailboxName);
+ GetServerStateParser().SetReportingErrors(reportingErrors);
+
+ }
+ return (rv);
+}
+
+// returns true is the rename succeeded (regardless of subscription changes)
+// reallyRename tells us if we should really do the rename (true) or if we should just move subscriptions (false)
+bool nsImapProtocol::RenameMailboxRespectingSubscriptions(const char *existingName, const char *newName, bool reallyRename)
+{
+ bool rv = true;
+ if (reallyRename && !MailboxIsNoSelectMailbox(existingName))
+ {
+ RenameMailbox(existingName, newName);
+ rv = GetServerStateParser().LastCommandSuccessful();
+ }
+
+ if (rv)
+ {
+ if (m_autoSubscribe) // if auto-subscribe is on
+ {
+ bool reportingErrors = GetServerStateParser().GetReportingErrors();
+ GetServerStateParser().SetReportingErrors(false);
+ Subscribe(newName);
+ GetServerStateParser().SetReportingErrors(reportingErrors);
+ }
+ if (m_autoUnsubscribe) // if auto-unsubscribe is on
+ {
+ bool reportingErrors = GetServerStateParser().GetReportingErrors();
+ GetServerStateParser().SetReportingErrors(false);
+ Unsubscribe(existingName);
+ GetServerStateParser().SetReportingErrors(reportingErrors);
+ }
+ }
+ return (rv);
+}
+
+bool nsImapProtocol::RenameHierarchyByHand(const char *oldParentMailboxName,
+ const char *newParentMailboxName)
+{
+ bool renameSucceeded = true;
+ char onlineDirSeparator = kOnlineHierarchySeparatorUnknown;
+ m_deletableChildren = new nsTArray<char*>();
+
+ bool nonHierarchicalRename =
+ ((GetServerStateParser().GetCapabilityFlag() & kNoHierarchyRename)
+ || MailboxIsNoSelectMailbox(oldParentMailboxName));
+
+ if (m_deletableChildren)
+ {
+ m_hierarchyNameState = kDeleteSubFoldersInProgress;
+ nsIMAPNamespace *ns = nullptr;
+ m_hostSessionList->GetNamespaceForMailboxForHost(GetImapServerKey(),
+ oldParentMailboxName,
+ ns); // for delimiter
+ if (!ns)
+ {
+ if (!PL_strcasecmp(oldParentMailboxName, "INBOX"))
+ m_hostSessionList->GetDefaultNamespaceOfTypeForHost(GetImapServerKey(),
+ kPersonalNamespace,
+ ns);
+ }
+ if (ns)
+ {
+ nsCString pattern(oldParentMailboxName);
+ pattern += ns->GetDelimiter();
+ pattern += "*";
+ bool isUsingSubscription = false;
+ m_hostSessionList->GetHostIsUsingSubscription(GetImapServerKey(),
+ isUsingSubscription);
+
+ if (isUsingSubscription)
+ Lsub(pattern.get(), false);
+ else
+ List(pattern.get(), false);
+ }
+ m_hierarchyNameState = kNoOperationInProgress;
+
+ if (GetServerStateParser().LastCommandSuccessful())
+ renameSucceeded = // rename this, and move subscriptions
+ RenameMailboxRespectingSubscriptions(oldParentMailboxName,
+ newParentMailboxName, true);
+
+ size_t numberToDelete = m_deletableChildren->Length();
+ size_t childIndex;
+
+ for (childIndex = 0;
+ (childIndex < numberToDelete) && renameSucceeded; childIndex++)
+ {
+ // the imap parser has already converted to a non UTF7 string in the canonical
+ // format so convert it back
+ char *currentName = m_deletableChildren->ElementAt(childIndex);
+ if (currentName)
+ {
+ char *serverName = nullptr;
+ m_runningUrl->AllocateServerPath(currentName,
+ onlineDirSeparator,
+ &serverName);
+ PR_FREEIF(currentName);
+ currentName = serverName;
+ }
+
+ // calculate the new name and do the rename
+ nsCString newChildName(newParentMailboxName);
+ newChildName += (currentName + PL_strlen(oldParentMailboxName));
+ // Pass in 'nonHierarchicalRename' to determine if we should really
+ // reanme, or just move subscriptions.
+ renameSucceeded =
+ RenameMailboxRespectingSubscriptions(currentName,
+ newChildName.get(),
+ nonHierarchicalRename);
+ PR_FREEIF(currentName);
+ }
+
+ delete m_deletableChildren;
+ m_deletableChildren = nullptr;
+ }
+
+ return renameSucceeded;
+}
+
+bool nsImapProtocol::DeleteSubFolders(const char* selectedMailbox, bool &aDeleteSelf)
+{
+ bool deleteSucceeded = true;
+ m_deletableChildren = new nsTArray<char*>();
+
+ if (m_deletableChildren)
+ {
+ bool folderDeleted = false;
+
+ m_hierarchyNameState = kDeleteSubFoldersInProgress;
+ nsCString pattern(selectedMailbox);
+ char onlineDirSeparator = kOnlineHierarchySeparatorUnknown;
+ m_runningUrl->GetOnlineSubDirSeparator(&onlineDirSeparator);
+ pattern.Append(onlineDirSeparator);
+ pattern.Append('*');
+
+ if (!pattern.IsEmpty())
+ {
+ List(pattern.get(), false);
+ }
+ m_hierarchyNameState = kNoOperationInProgress;
+
+ // this should be a short list so perform a sequential search for the
+ // longest name mailbox. Deleting the longest first will hopefully
+ // prevent the server from having problems about deleting parents
+ // ** jt - why? I don't understand this.
+ size_t numberToDelete = m_deletableChildren->Length();
+ size_t outerIndex, innerIndex;
+
+ // intelligently decide if myself(either plain format or following the dir-separator)
+ // is in the sub-folder list
+ bool folderInSubfolderList = false; // For Performance
+ char *selectedMailboxDir = nullptr;
+ {
+ int32_t length = strlen(selectedMailbox);
+ selectedMailboxDir = (char *)PR_MALLOC(length+2);
+ if( selectedMailboxDir ) // only do the intelligent test if there is enough memory
+ {
+ strcpy(selectedMailboxDir, selectedMailbox);
+ selectedMailboxDir[length] = onlineDirSeparator;
+ selectedMailboxDir[length+1] = '\0';
+ size_t i;
+ for( i=0; i<numberToDelete && !folderInSubfolderList; i++ )
+ {
+ char *currentName = m_deletableChildren->ElementAt(i);
+ if( !strcmp(currentName, selectedMailbox) || !strcmp(currentName, selectedMailboxDir) )
+ folderInSubfolderList = true;
+ }
+ }
+ }
+
+ deleteSucceeded = GetServerStateParser().LastCommandSuccessful();
+ for (outerIndex = 0;
+ (outerIndex < numberToDelete) && deleteSucceeded;
+ outerIndex++)
+ {
+ char* longestName = nullptr;
+ size_t longestIndex = 0; // fix bogus warning by initializing
+ for (innerIndex = 0;
+ innerIndex < m_deletableChildren->Length();
+ innerIndex++)
+ {
+ char *currentName = m_deletableChildren->ElementAt(innerIndex);
+ if (!longestName || strlen(longestName) < strlen(currentName))
+ {
+ longestName = currentName;
+ longestIndex = innerIndex;
+ }
+ }
+ // the imap parser has already converted to a non UTF7 string in
+ // the canonical format so convert it back
+ if (longestName)
+ {
+ char *serverName = nullptr;
+
+ m_deletableChildren->RemoveElementAt(longestIndex);
+ m_runningUrl->AllocateServerPath(longestName,
+ onlineDirSeparator,
+ &serverName);
+ PR_FREEIF(longestName);
+ longestName = serverName;
+ }
+
+ // some imap servers include the selectedMailbox in the list of
+ // subfolders of the selectedMailbox. Check for this so we don't
+ // delete the selectedMailbox (usually the trash and doing an
+ // empty trash)
+ // The Cyrus imap server ignores the "INBOX.Trash" constraining
+ // string passed to the list command. Be defensive and make sure
+ // we only delete children of the trash
+ if (longestName &&
+ strcmp(selectedMailbox, longestName) &&
+ !strncmp(selectedMailbox, longestName, strlen(selectedMailbox)))
+ {
+ if( selectedMailboxDir && !strcmp(selectedMailboxDir, longestName) ) // just myself
+ {
+ if( aDeleteSelf )
+ {
+ bool deleted = DeleteMailboxRespectingSubscriptions(longestName);
+ if (deleted)
+ FolderDeleted(longestName);
+ folderDeleted = deleted;
+ deleteSucceeded = deleted;
+ }
+ }
+ else
+ {
+ if (m_imapServerSink)
+ m_imapServerSink->ResetServerConnection(nsDependentCString(longestName));
+ bool deleted = false;
+ if( folderInSubfolderList ) // for performance
+ {
+ nsTArray<char*> *pDeletableChildren = m_deletableChildren;
+ m_deletableChildren = nullptr;
+ bool folderDeleted = true;
+ deleted = DeleteSubFolders(longestName, folderDeleted);
+ // longestName may have subfolder list including itself
+ if( !folderDeleted )
+ {
+ if (deleted)
+ deleted = DeleteMailboxRespectingSubscriptions(longestName);
+ if (deleted)
+ FolderDeleted(longestName);
+ }
+ m_deletableChildren = pDeletableChildren;
+ }
+ else
+ {
+ deleted = DeleteMailboxRespectingSubscriptions(longestName);
+ if (deleted)
+ FolderDeleted(longestName);
+ }
+ deleteSucceeded = deleted;
+ }
+ }
+ PR_FREEIF(longestName);
+ }
+
+ aDeleteSelf = folderDeleted; // feedback if myself is deleted
+ PR_Free(selectedMailboxDir);
+
+ delete m_deletableChildren;
+ m_deletableChildren = nullptr;
+ }
+ return deleteSucceeded;
+}
+
+void nsImapProtocol::FolderDeleted(const char *mailboxName)
+{
+ char onlineDelimiter = kOnlineHierarchySeparatorUnknown;
+ nsCString orphanedMailboxName;
+
+ if (mailboxName)
+ {
+ m_runningUrl->AllocateCanonicalPath(mailboxName, onlineDelimiter,
+ getter_Copies(orphanedMailboxName));
+ if (m_imapServerSink)
+ m_imapServerSink->OnlineFolderDelete(orphanedMailboxName);
+ }
+}
+
+void nsImapProtocol::FolderNotCreated(const char *folderName)
+{
+ if (folderName && m_imapServerSink)
+ m_imapServerSink->OnlineFolderCreateFailed(nsDependentCString(folderName));
+}
+
+void nsImapProtocol::FolderRenamed(const char *oldName,
+ const char *newName)
+{
+ char onlineDelimiter = kOnlineHierarchySeparatorUnknown;
+
+ if ((m_hierarchyNameState == kNoOperationInProgress) ||
+ (m_hierarchyNameState == kListingForInfoAndDiscovery))
+
+ {
+ nsCString canonicalOldName, canonicalNewName;
+ m_runningUrl->AllocateCanonicalPath(oldName,
+ onlineDelimiter,
+ getter_Copies(canonicalOldName));
+ m_runningUrl->AllocateCanonicalPath(newName,
+ onlineDelimiter,
+ getter_Copies(canonicalNewName));
+ AutoProxyReleaseMsgWindow msgWindow;
+ GetMsgWindow(getter_AddRefs(msgWindow));
+ m_imapServerSink->OnlineFolderRename(msgWindow, canonicalOldName, canonicalNewName);
+ }
+}
+
+void nsImapProtocol::OnDeleteFolder(const char * sourceMailbox)
+{
+ // intelligently delete the folder
+ bool folderDeleted = true;
+ bool deleted = DeleteSubFolders(sourceMailbox, folderDeleted);
+ if( !folderDeleted )
+ {
+ if (deleted)
+ deleted = DeleteMailboxRespectingSubscriptions(sourceMailbox);
+ if (deleted)
+ FolderDeleted(sourceMailbox);
+ }
+}
+
+void nsImapProtocol::RemoveMsgsAndExpunge()
+{
+ uint32_t numberOfMessages = GetServerStateParser().NumberOfMessages();
+ if (numberOfMessages)
+ {
+ // Remove all msgs and expunge the folder (ie, compact it).
+ Store(NS_LITERAL_CSTRING("1:*"), "+FLAGS.SILENT (\\Deleted)", false); // use sequence #'s
+ if (GetServerStateParser().LastCommandSuccessful())
+ Expunge();
+ }
+}
+
+void nsImapProtocol::DeleteFolderAndMsgs(const char * sourceMailbox)
+{
+ RemoveMsgsAndExpunge();
+ if (GetServerStateParser().LastCommandSuccessful())
+ {
+ // All msgs are deleted successfully - let's remove the folder itself.
+ bool reportingErrors = GetServerStateParser().GetReportingErrors();
+ GetServerStateParser().SetReportingErrors(false);
+ OnDeleteFolder(sourceMailbox);
+ GetServerStateParser().SetReportingErrors(reportingErrors);
+ }
+}
+
+void nsImapProtocol::OnRenameFolder(const char * sourceMailbox)
+{
+ char *destinationMailbox = OnCreateServerDestinationFolderPathString();
+
+ if (destinationMailbox)
+ {
+ bool renamed = RenameHierarchyByHand(sourceMailbox, destinationMailbox);
+ if (renamed)
+ FolderRenamed(sourceMailbox, destinationMailbox);
+
+ PR_Free( destinationMailbox);
+ }
+ else
+ HandleMemoryFailure();
+}
+
+void nsImapProtocol::OnMoveFolderHierarchy(const char * sourceMailbox)
+{
+ char *destinationMailbox = OnCreateServerDestinationFolderPathString();
+
+ if (destinationMailbox)
+ {
+ nsCString newBoxName;
+ newBoxName.Adopt(destinationMailbox);
+
+ char onlineDirSeparator = kOnlineHierarchySeparatorUnknown;
+ m_runningUrl->GetOnlineSubDirSeparator(&onlineDirSeparator);
+
+ nsCString oldBoxName(sourceMailbox);
+ int32_t leafStart = oldBoxName.RFindChar(onlineDirSeparator);
+ nsCString leafName;
+
+ if (-1 == leafStart)
+ leafName = oldBoxName; // this is a root level box
+ else
+ leafName = Substring(oldBoxName, leafStart+1);
+
+ if ( !newBoxName.IsEmpty() )
+ newBoxName.Append(onlineDirSeparator);
+ newBoxName.Append(leafName);
+ bool renamed = RenameHierarchyByHand(sourceMailbox,
+ newBoxName.get());
+ if (renamed)
+ FolderRenamed(sourceMailbox, newBoxName.get());
+ }
+ else
+ HandleMemoryFailure();
+}
+
+void nsImapProtocol::FindMailboxesIfNecessary()
+{
+ // biff should not discover mailboxes
+ bool foundMailboxesAlready = false;
+ nsImapAction imapAction;
+
+ // need to do this for every connection in order to see folders.
+ (void) m_runningUrl->GetImapAction(&imapAction);
+ nsresult rv = m_hostSessionList->GetHaveWeEverDiscoveredFoldersForHost(GetImapServerKey(), foundMailboxesAlready);
+ if (NS_SUCCEEDED(rv) && !foundMailboxesAlready &&
+ (imapAction != nsIImapUrl::nsImapBiff) &&
+ (imapAction != nsIImapUrl::nsImapVerifylogon) &&
+ (imapAction != nsIImapUrl::nsImapDiscoverAllBoxesUrl) &&
+ (imapAction != nsIImapUrl::nsImapUpgradeToSubscription) &&
+ !GetSubscribingNow())
+ DiscoverMailboxList();
+}
+
+void nsImapProtocol::DiscoverAllAndSubscribedBoxes()
+{
+ // used for subscribe pane
+ // iterate through all namespaces
+ uint32_t count = 0;
+ m_hostSessionList->GetNumberOfNamespacesForHost(GetImapServerKey(), count);
+
+ for (uint32_t i = 0; i < count; i++ )
+ {
+ nsIMAPNamespace *ns = nullptr;
+
+ m_hostSessionList->GetNamespaceNumberForHost(GetImapServerKey(), i,
+ ns);
+ if (ns &&
+ gHideOtherUsersFromList ? (ns->GetType() != kOtherUsersNamespace)
+ : true)
+ {
+ const char *prefix = ns->GetPrefix();
+ if (prefix)
+ {
+ nsAutoCString inboxNameWithDelim("INBOX");
+ inboxNameWithDelim.Append(ns->GetDelimiter());
+
+ if (!gHideUnusedNamespaces && *prefix &&
+ PL_strcasecmp(prefix, inboxNameWithDelim.get())) /* only do it for
+ non-empty namespace prefixes */
+ {
+ // Explicitly discover each Namespace, just so they're
+ // there in the subscribe UI
+ nsImapMailboxSpec *boxSpec = new nsImapMailboxSpec;
+ if (boxSpec)
+ {
+ NS_ADDREF(boxSpec);
+ boxSpec->mFolderSelected = false;
+ boxSpec->mHostName.Assign(GetImapHostName());
+ boxSpec->mConnection = this;
+ boxSpec->mFlagState = nullptr;
+ boxSpec->mDiscoveredFromLsub = true;
+ boxSpec->mOnlineVerified = true;
+ boxSpec->mBoxFlags = kNoselect;
+ boxSpec->mHierarchySeparator = ns->GetDelimiter();
+
+ m_runningUrl->AllocateCanonicalPath(ns->GetPrefix(), ns->GetDelimiter(),
+ getter_Copies(boxSpec->mAllocatedPathName));
+ boxSpec->mNamespaceForFolder = ns;
+ boxSpec->mBoxFlags |= kNameSpace;
+
+ switch (ns->GetType())
+ {
+ case kPersonalNamespace:
+ boxSpec->mBoxFlags |= kPersonalMailbox;
+ break;
+ case kPublicNamespace:
+ boxSpec->mBoxFlags |= kPublicMailbox;
+ break;
+ case kOtherUsersNamespace:
+ boxSpec->mBoxFlags |= kOtherUsersMailbox;
+ break;
+ default: // (kUnknownNamespace)
+ break;
+ }
+
+ DiscoverMailboxSpec(boxSpec);
+ }
+ else
+ HandleMemoryFailure();
+ }
+
+ nsAutoCString allPattern(prefix);
+ allPattern += '*';
+
+ nsAutoCString topLevelPattern(prefix);
+ topLevelPattern += '%';
+
+ nsAutoCString secondLevelPattern;
+
+ char delimiter = ns->GetDelimiter();
+ if (delimiter)
+ {
+ // Hierarchy delimiter might be NIL, in which case there's no hierarchy anyway
+ secondLevelPattern = prefix;
+ secondLevelPattern += '%';
+ secondLevelPattern += delimiter;
+ secondLevelPattern += '%';
+ }
+
+ if (!m_imapServerSink) return;
+
+ if (!allPattern.IsEmpty())
+ {
+ m_imapServerSink->SetServerDoingLsub(true);
+ Lsub(allPattern.get(), true); // LSUB all the subscribed
+ }
+ if (!topLevelPattern.IsEmpty())
+ {
+ m_imapServerSink->SetServerDoingLsub(false);
+ List(topLevelPattern.get(), true); // LIST the top level
+ }
+ if (!secondLevelPattern.IsEmpty())
+ {
+ m_imapServerSink->SetServerDoingLsub(false);
+ List(secondLevelPattern.get(), true); // LIST the second level
+ }
+ }
+ }
+ }
+}
+
+// DiscoverMailboxList() is used to actually do the discovery of folders
+// for a host. This is used both when we initially start up (and re-sync)
+// and also when the user manually requests a re-sync, by collapsing and
+// expanding a host in the folder pane. This is not used for the subscribe
+// pane.
+// DiscoverMailboxList() also gets the ACLs for each newly discovered folder
+void nsImapProtocol::DiscoverMailboxList()
+{
+ bool usingSubscription = false;
+
+ m_hostSessionList->GetHostIsUsingSubscription(GetImapServerKey(), usingSubscription);
+ // Pretend that the Trash folder doesn't exist, so we will rediscover it if we need to.
+ m_hostSessionList->SetOnlineTrashFolderExistsForHost(GetImapServerKey(), false);
+
+ // should we check a pref here, to be able to turn off XList?
+ bool hasXLIST = GetServerStateParser().GetCapabilityFlag() & kHasXListCapability;
+ if (hasXLIST && usingSubscription)
+ {
+ m_hierarchyNameState = kXListing;
+ nsAutoCString pattern("%");
+ List("%", true, true);
+ // We list the first and second levels since special folders are unlikely
+ // to be more than 2 levels deep.
+ char separator = 0;
+ m_runningUrl->GetOnlineSubDirSeparator(&separator);
+ pattern.Append(separator);
+ pattern += '%';
+ List(pattern.get(), true, true);
+ }
+
+ SetMailboxDiscoveryStatus(eContinue);
+ if (GetServerStateParser().ServerHasACLCapability())
+ m_hierarchyNameState = kListingForInfoAndDiscovery;
+ else
+ m_hierarchyNameState = kNoOperationInProgress;
+
+ // iterate through all namespaces and LSUB them.
+ uint32_t count = 0;
+ m_hostSessionList->GetNumberOfNamespacesForHost(GetImapServerKey(), count);
+ for (uint32_t i = 0; i < count; i++ )
+ {
+ nsIMAPNamespace * ns = nullptr;
+ m_hostSessionList->GetNamespaceNumberForHost(GetImapServerKey(),i,ns);
+ if (ns)
+ {
+ const char *prefix = ns->GetPrefix();
+ if (prefix)
+ {
+ nsAutoCString inboxNameWithDelim("INBOX");
+ inboxNameWithDelim.Append(ns->GetDelimiter());
+
+ // static bool gHideUnusedNamespaces = true;
+ // mscott -> WARNING!!! i where are we going to get this
+ // global variable for unused name spaces from???
+ // dmb - we should get this from a per-host preference,
+ // I'd say. But for now, just make it true;
+ if (!gHideUnusedNamespaces && *prefix &&
+ PL_strcasecmp(prefix, inboxNameWithDelim.get())) // only do it for
+ // non-empty namespace prefixes, and for non-INBOX prefix
+ {
+ // Explicitly discover each Namespace, so that we can
+ // create subfolders of them,
+ nsImapMailboxSpec *boxSpec = new nsImapMailboxSpec;
+ if (boxSpec)
+ {
+ NS_ADDREF(boxSpec);
+ boxSpec->mFolderSelected = false;
+ boxSpec->mHostName = GetImapHostName();
+ boxSpec->mConnection = this;
+ boxSpec->mFlagState = nullptr;
+ boxSpec->mDiscoveredFromLsub = true;
+ boxSpec->mOnlineVerified = true;
+ boxSpec->mBoxFlags = kNoselect;
+ boxSpec->mHierarchySeparator = ns->GetDelimiter();
+ // Until |AllocateCanonicalPath()| gets updated:
+ m_runningUrl->AllocateCanonicalPath(
+ ns->GetPrefix(), ns->GetDelimiter(),
+ getter_Copies(boxSpec->mAllocatedPathName));
+ boxSpec->mNamespaceForFolder = ns;
+ boxSpec->mBoxFlags |= kNameSpace;
+
+ switch (ns->GetType())
+ {
+ case kPersonalNamespace:
+ boxSpec->mBoxFlags |= kPersonalMailbox;
+ break;
+ case kPublicNamespace:
+ boxSpec->mBoxFlags |= kPublicMailbox;
+ break;
+ case kOtherUsersNamespace:
+ boxSpec->mBoxFlags |= kOtherUsersMailbox;
+ break;
+ default: // (kUnknownNamespace)
+ break;
+ }
+
+ DiscoverMailboxSpec(boxSpec);
+ }
+ else
+ HandleMemoryFailure();
+ }
+
+ // now do the folders within this namespace
+ nsCString pattern;
+ nsCString pattern2;
+ if (usingSubscription)
+ {
+ pattern.Append(prefix);
+ pattern.Append("*");
+ }
+ else
+ {
+ pattern.Append(prefix);
+ pattern.Append("%"); // mscott just need one percent right?
+ // pattern = PR_smprintf("%s%%", prefix);
+ char delimiter = ns->GetDelimiter();
+ if (delimiter)
+ {
+ // delimiter might be NIL, in which case there's no hierarchy anyway
+ pattern2 = prefix;
+ pattern2 += "%";
+ pattern2 += delimiter;
+ pattern2 += "%";
+ // pattern2 = PR_smprintf("%s%%%c%%", prefix, delimiter);
+ }
+ }
+ // Note: It is important to make sure we are respecting the server_sub_directory
+ // preference when calling List and Lsub (2nd arg = true), otherwise
+ // we end up with performance issues or even crashes when connecting to
+ // servers that expose the users entire home directory (like UW-IMAP).
+ if (usingSubscription) { // && !GetSubscribingNow()) should never get here from subscribe pane
+ if (GetServerStateParser().GetCapabilityFlag() & kHasListExtendedCapability)
+ Lsub(pattern.get(), true); // do LIST (SUBSCRIBED)
+ else {
+ // store mailbox flags from LIST
+ EMailboxHierarchyNameState currentState = m_hierarchyNameState;
+ m_hierarchyNameState = kListingForFolderFlags;
+ List(pattern.get(), true);
+ m_hierarchyNameState = currentState;
+ // then do LSUB using stored flags
+ Lsub(pattern.get(), true);
+ m_standardListMailboxes.Clear();
+ }
+ }
+ else
+ {
+ List(pattern.get(), true, hasXLIST);
+ List(pattern2.get(), true, hasXLIST);
+ }
+ }
+ }
+ }
+
+ // explicitly LIST the INBOX if (a) we're not using subscription, or (b) we are using subscription and
+ // the user wants us to always show the INBOX.
+ bool listInboxForHost = false;
+ m_hostSessionList->GetShouldAlwaysListInboxForHost(GetImapServerKey(), listInboxForHost);
+ if (!usingSubscription || listInboxForHost)
+ List("INBOX", true);
+
+ m_hierarchyNameState = kNoOperationInProgress;
+
+ MailboxDiscoveryFinished();
+
+ // Get the ACLs for newly discovered folders
+ if (GetServerStateParser().ServerHasACLCapability())
+ {
+ int32_t total = m_listedMailboxList.Length(), cnt = 0;
+ // Let's not turn this off here, since we don't turn it on after
+ // GetServerStateParser().SetReportingErrors(false);
+ if (total)
+ {
+ ProgressEventFunctionUsingName("imapGettingACLForFolder");
+ nsIMAPMailboxInfo * mb = nullptr;
+ do
+ {
+ if (m_listedMailboxList.Length() == 0)
+ break;
+
+ mb = m_listedMailboxList[0]; // get top element
+ m_listedMailboxList.RemoveElementAt(0); // XP_ListRemoveTopObject(fListedMailboxList);
+ if (mb)
+ {
+ if (FolderNeedsACLInitialized(PromiseFlatCString(mb->GetMailboxName()).get()))
+ {
+ char *onlineName = nullptr;
+ m_runningUrl->AllocateServerPath(PromiseFlatCString(mb->GetMailboxName()).get(),
+ mb->GetDelimiter(), &onlineName);
+ if (onlineName)
+ {
+ RefreshACLForFolder(onlineName);
+ PR_Free(onlineName);
+ }
+ }
+ PercentProgressUpdateEvent(NULL, cnt, total);
+ delete mb; // this is the last time we're using the list, so delete the entries here
+ cnt++;
+ }
+ } while (mb && !DeathSignalReceived());
+ }
+ }
+}
+
+bool nsImapProtocol::FolderNeedsACLInitialized(const char *folderName)
+{
+ bool rv = false;
+ m_imapServerSink->FolderNeedsACLInitialized(nsDependentCString(folderName), &rv);
+ return rv;
+}
+
+void nsImapProtocol::MailboxDiscoveryFinished()
+{
+ if (!DeathSignalReceived() && !GetSubscribingNow() &&
+ ((m_hierarchyNameState == kNoOperationInProgress) ||
+ (m_hierarchyNameState == kListingForInfoAndDiscovery)))
+ {
+ nsIMAPNamespace *ns = nullptr;
+ m_hostSessionList->GetDefaultNamespaceOfTypeForHost(GetImapServerKey(), kPersonalNamespace, ns);
+ const char *personalDir = ns ? ns->GetPrefix() : 0;
+
+ bool trashFolderExists = false;
+ bool usingSubscription = false;
+ m_hostSessionList->GetOnlineTrashFolderExistsForHost(GetImapServerKey(), trashFolderExists);
+ m_hostSessionList->GetHostIsUsingSubscription(GetImapServerKey(),usingSubscription);
+ if (!trashFolderExists && GetDeleteIsMoveToTrash() && usingSubscription)
+ {
+ // maybe we're not subscribed to the Trash folder
+ if (personalDir)
+ {
+ nsCString originalTrashName(CreatePossibleTrashName(personalDir));
+ m_hierarchyNameState = kDiscoverTrashFolderInProgress;
+ List(originalTrashName.get(), true);
+ m_hierarchyNameState = kNoOperationInProgress;
+ }
+ }
+
+ // There is no Trash folder (either LIST'd or LSUB'd), and we're using the
+ // Delete-is-move-to-Trash model, and there is a personal namespace
+ if (!trashFolderExists && GetDeleteIsMoveToTrash() && ns)
+ {
+ nsCString trashName(CreatePossibleTrashName(ns->GetPrefix()));
+ nsCString onlineTrashName;
+ m_runningUrl->AllocateServerPath(trashName.get(), ns->GetDelimiter(),
+ getter_Copies(onlineTrashName));
+
+ GetServerStateParser().SetReportingErrors(false);
+ bool created = CreateMailboxRespectingSubscriptions(onlineTrashName.get());
+ GetServerStateParser().SetReportingErrors(true);
+
+ // force discovery of new trash folder.
+ if (created)
+ {
+ m_hierarchyNameState = kDiscoverTrashFolderInProgress;
+ List(onlineTrashName.get(), false);
+ m_hierarchyNameState = kNoOperationInProgress;
+ }
+ else
+ m_hostSessionList->SetOnlineTrashFolderExistsForHost(GetImapServerKey(), true);
+ } //if trash folder doesn't exist
+ m_hostSessionList->SetHaveWeEverDiscoveredFoldersForHost(GetImapServerKey(), true);
+
+ // notify front end that folder discovery is complete....
+ if (m_imapServerSink)
+ m_imapServerSink->DiscoveryDone();
+ }
+}
+
+// returns the mailboxName with the IMAP delimiter removed from the tail end
+void nsImapProtocol::RemoveHierarchyDelimiter(nsCString &mailboxName)
+{
+ char onlineDelimiter[2] = {0, 0};
+ if (m_imapMailFolderSink)
+ m_imapMailFolderSink->GetOnlineDelimiter(&onlineDelimiter[0]);
+ // take the hierarchy delimiter off the end, if any.
+ if (onlineDelimiter[0])
+ mailboxName.Trim(onlineDelimiter, false, true);
+}
+
+// returns true is the create succeeded (regardless of subscription changes)
+bool nsImapProtocol::CreateMailboxRespectingSubscriptions(const char *mailboxName)
+{
+ CreateMailbox(mailboxName);
+ bool rv = GetServerStateParser().LastCommandSuccessful();
+ if (rv && m_autoSubscribe) // auto-subscribe is on
+ {
+ // create succeeded - let's subscribe to it
+ bool reportingErrors = GetServerStateParser().GetReportingErrors();
+ GetServerStateParser().SetReportingErrors(false);
+ nsCString mailboxWODelim(mailboxName);
+ RemoveHierarchyDelimiter(mailboxWODelim);
+ OnSubscribe(mailboxWODelim.get());
+ GetServerStateParser().SetReportingErrors(reportingErrors);
+ }
+ return rv;
+}
+
+void nsImapProtocol::CreateMailbox(const char *mailboxName)
+{
+ ProgressEventFunctionUsingName("imapStatusCreatingMailbox");
+
+ IncrementCommandTagNumber();
+
+ nsCString escapedName;
+ CreateEscapedMailboxName(mailboxName, escapedName);
+ nsCString command(GetServerCommandTag());
+ command += " create \"";
+ command += escapedName;
+ command += "\"" CRLF;
+
+ nsresult rv = SendData(command.get());
+ if(NS_SUCCEEDED(rv))
+ ParseIMAPandCheckForNewMail();
+ // If that failed, let's list the parent folder to see if
+ // it allows inferiors, so we won't try to create sub-folders
+ // of the parent folder again in the current session.
+ if (GetServerStateParser().CommandFailed())
+ {
+ // Figure out parent folder name.
+ nsCString parentName(mailboxName);
+ char hierarchyDelimiter;
+ m_runningUrl->GetOnlineSubDirSeparator(&hierarchyDelimiter);
+ int32_t leafPos = parentName.RFindChar(hierarchyDelimiter);
+ if (leafPos > 0)
+ {
+ parentName.SetLength(leafPos);
+ List(parentName.get(), false);
+ // We still want the caller to know the create failed, so restore that.
+ GetServerStateParser().SetCommandFailed(true);
+ }
+ }
+}
+
+void nsImapProtocol::DeleteMailbox(const char *mailboxName)
+{
+
+ // check if this connection currently has the folder to be deleted selected.
+ // If so, we should close it because at least some UW servers don't like you deleting
+ // a folder you have open.
+ if (FolderIsSelected(mailboxName))
+ Close();
+
+
+ ProgressEventFunctionUsingNameWithString("imapStatusDeletingMailbox", mailboxName);
+
+ IncrementCommandTagNumber();
+
+ nsCString escapedName;
+ CreateEscapedMailboxName(mailboxName, escapedName);
+ nsCString command(GetServerCommandTag());
+ command += " delete \"";
+ command += escapedName;
+ command += "\"" CRLF;
+
+ nsresult rv = SendData(command.get());
+ if (NS_SUCCEEDED(rv))
+ ParseIMAPandCheckForNewMail();
+}
+
+void nsImapProtocol::RenameMailbox(const char *existingName,
+ const char *newName)
+{
+ // just like DeleteMailbox; Some UW servers don't like it.
+ if (FolderIsSelected(existingName))
+ Close();
+
+ ProgressEventFunctionUsingNameWithString("imapStatusRenamingMailbox", existingName);
+
+ IncrementCommandTagNumber();
+
+ nsCString escapedExistingName;
+ nsCString escapedNewName;
+ CreateEscapedMailboxName(existingName, escapedExistingName);
+ CreateEscapedMailboxName(newName, escapedNewName);
+ nsCString command(GetServerCommandTag());
+ command += " rename \"";
+ command += escapedExistingName;
+ command += "\" \"";
+ command += escapedNewName;
+ command += "\"" CRLF;
+
+ nsresult rv = SendData(command.get());
+ if (NS_SUCCEEDED(rv))
+ ParseIMAPandCheckForNewMail();
+}
+
+nsCString nsImapProtocol::CreatePossibleTrashName(const char *prefix)
+{
+ nsCString returnTrash(prefix);
+ returnTrash += m_trashFolderName;
+ return returnTrash;
+}
+
+bool nsImapProtocol::GetListSubscribedIsBrokenOnServer()
+{
+ // This is a workaround for an issue with LIST(SUBSCRIBED) crashing older versions of Zimbra
+ if (GetServerStateParser().GetServerID().Find("\"NAME\" \"Zimbra\"", CaseInsensitiveCompare) != kNotFound) {
+ nsCString serverID(GetServerStateParser().GetServerID());
+ int start = serverID.Find("\"VERSION\" \"", CaseInsensitiveCompare) + 11;
+ int length = serverID.Find("\" ", start, CaseInsensitiveCompare);
+ const nsDependentCSubstring serverVersionSubstring = Substring(serverID, start, length);
+ nsCString serverVersionStr(serverVersionSubstring);
+ Version serverVersion(serverVersionStr.get());
+ Version sevenTwoThree("7.2.3_");
+ Version eightZeroZero("8.0.0_");
+ Version eightZeroThree("8.0.3_");
+ if ((serverVersion < sevenTwoThree) ||
+ ((serverVersion >= eightZeroZero) && (serverVersion < eightZeroThree)))
+ return true;
+ }
+ return false;
+}
+
+// This identifies servers that require an extra imap SELECT to detect new
+// email in a mailbox. Servers requiring this are found by comparing their
+// ID string, returned with imap ID command, to strings entered in
+// mail.imap.force_select_detect. Only openwave servers used by
+// Charter/Spectrum ISP returning an ID containing the strings ""name" "Email Mx""
+// and ""vendor" "Openwave Messaging"" are now known to have this issue. The
+// compared strings can be modified with the config editor if necessary
+// (e.g., a "version" substring could be added). Also, additional servers
+// having a different set of strings can be added if ever needed.
+// The mail.imap.force_select_detect uses semicolon delimiter between
+// servers and within a server substrings to compare are comma delimited.
+// This example force_select_detect value shows how two servers types
+// could be detected:
+// "name" "Email Mx","vendor" "Openwave Messaging";"vendor" "Yahoo! Inc.","name" "Y!IMAP";
+bool nsImapProtocol::IsExtraSelectNeeded()
+{
+ bool retVal;
+ for (uint32_t i = 0; i < gForceSelectServersArray.Length(); i++)
+ {
+ retVal = true;
+ nsTArray<nsCString> forceSelectStringsArray;
+ ParseString(gForceSelectServersArray[i], ',', forceSelectStringsArray);
+ for (uint32_t j = 0; j < forceSelectStringsArray.Length(); j++)
+ {
+ // Each substring within the server string must be contained in ID string.
+ // First un-escape any comma (%2c) or semicolon (%3b) within the substring.
+ nsAutoCString unescapedString;
+ MsgUnescapeString(forceSelectStringsArray[j], 0, unescapedString);
+ if (GetServerStateParser().GetServerID()
+ .Find(unescapedString, CaseInsensitiveCompare) == kNotFound)
+ {
+ retVal = false;
+ break;
+ }
+ }
+ // Matches found for all substrings for the server.
+ if (retVal)
+ return true;
+ }
+
+ // If reached, no substrings match for any server.
+ return false;
+}
+
+void nsImapProtocol::Lsub(const char *mailboxPattern, bool addDirectoryIfNecessary)
+{
+ ProgressEventFunctionUsingName("imapStatusLookingForMailbox");
+
+ IncrementCommandTagNumber();
+
+ char *boxnameWithOnlineDirectory = nullptr;
+ if (addDirectoryIfNecessary)
+ m_runningUrl->AddOnlineDirectoryIfNecessary(mailboxPattern, &boxnameWithOnlineDirectory);
+
+ nsCString escapedPattern;
+ CreateEscapedMailboxName(boxnameWithOnlineDirectory ?
+ boxnameWithOnlineDirectory :
+ mailboxPattern, escapedPattern);
+
+ nsCString command (GetServerCommandTag());
+ eIMAPCapabilityFlags flag = GetServerStateParser().GetCapabilityFlag();
+ bool useListSubscribed = (flag & kHasListExtendedCapability) &&
+ !GetListSubscribedIsBrokenOnServer();
+ if (useListSubscribed)
+ command += " list (subscribed)";
+ else
+ command += " lsub";
+ command += " \"\" \"";
+ command += escapedPattern;
+ if (useListSubscribed && (flag & kHasSpecialUseCapability))
+ command += "\" return (special-use)" CRLF;
+ else
+ command += "\"" CRLF;
+
+ PR_Free(boxnameWithOnlineDirectory);
+
+ nsresult rv = SendData(command.get());
+ if (NS_SUCCEEDED(rv))
+ ParseIMAPandCheckForNewMail(command.get(), true);
+}
+
+void nsImapProtocol::List(const char *mailboxPattern, bool addDirectoryIfNecessary,
+ bool useXLIST)
+{
+ ProgressEventFunctionUsingName("imapStatusLookingForMailbox");
+
+ IncrementCommandTagNumber();
+
+ char *boxnameWithOnlineDirectory = nullptr;
+ if (addDirectoryIfNecessary)
+ m_runningUrl->AddOnlineDirectoryIfNecessary(mailboxPattern, &boxnameWithOnlineDirectory);
+
+ nsCString escapedPattern;
+ CreateEscapedMailboxName(boxnameWithOnlineDirectory ?
+ boxnameWithOnlineDirectory :
+ mailboxPattern, escapedPattern);
+
+ nsCString command (GetServerCommandTag());
+ command += useXLIST ?
+ " xlist \"\" \"" : " list \"\" \"";
+ command += escapedPattern;
+ command += "\"" CRLF;
+
+ PR_Free(boxnameWithOnlineDirectory);
+
+ nsresult rv = SendData(command.get());
+ if (NS_SUCCEEDED(rv))
+ ParseIMAPandCheckForNewMail(command.get(), true);
+}
+
+void nsImapProtocol::Subscribe(const char *mailboxName)
+{
+ ProgressEventFunctionUsingNameWithString("imapStatusSubscribeToMailbox", mailboxName);
+
+ IncrementCommandTagNumber();
+
+ nsCString escapedName;
+ CreateEscapedMailboxName(mailboxName, escapedName);
+
+ nsCString command (GetServerCommandTag());
+ command += " subscribe \"";
+ command += escapedName;
+ command += "\"" CRLF;
+
+ nsresult rv = SendData(command.get());
+ if (NS_SUCCEEDED(rv))
+ ParseIMAPandCheckForNewMail();
+}
+
+void nsImapProtocol::Unsubscribe(const char *mailboxName)
+{
+ ProgressEventFunctionUsingNameWithString("imapStatusUnsubscribeMailbox", mailboxName);
+ IncrementCommandTagNumber();
+
+ nsCString escapedName;
+ CreateEscapedMailboxName(mailboxName, escapedName);
+
+ nsCString command (GetServerCommandTag());
+ command += " unsubscribe \"";
+ command += escapedName;
+ command += "\"" CRLF;
+
+ nsresult rv = SendData(command.get());
+ if (NS_SUCCEEDED(rv))
+ ParseIMAPandCheckForNewMail();
+}
+
+void nsImapProtocol::Idle()
+{
+ IncrementCommandTagNumber();
+
+ if (m_urlInProgress)
+ return;
+ nsAutoCString command (GetServerCommandTag());
+ command += " IDLE" CRLF;
+ nsresult rv = SendData(command.get());
+ if (NS_SUCCEEDED(rv))
+ {
+ 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 <nsIAsyncInputStream> 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 <nsIAsyncInputStream> asyncInputStream = do_QueryInterface(m_inputStream);
+ if (asyncInputStream)
+ asyncInputStream->AsyncWait(nullptr, 0, 0, nullptr);
+ nsresult rv = SendData("DONE" CRLF);
+ // set a short timeout if we don't want to wait for a response
+ if (m_transport && !waitForResponse)
+ m_transport->SetTimeout(nsISocketTransport::TIMEOUT_READ_WRITE, 5);
+ if (NS_SUCCEEDED(rv))
+ {
+ m_idle = false;
+ ParseIMAPandCheckForNewMail();
+ }
+ m_imapMailFolderSink = nullptr;
+}
+
+
+void nsImapProtocol::Search(const char * searchCriteria,
+ bool useUID,
+ bool notifyHit /* true */)
+{
+ m_notifySearchHit = notifyHit;
+ ProgressEventFunctionUsingName("imapStatusSearchMailbox");
+ IncrementCommandTagNumber();
+
+ nsCString protocolString(GetServerCommandTag());
+ // the searchCriteria string contains the 'search ....' string
+ if (useUID)
+ protocolString.Append(" uid");
+ protocolString.Append(" ");
+ protocolString.Append(searchCriteria);
+ // the search criteria can contain string literals, which means we
+ // need to break up the protocol string by CRLF's, and after sending CRLF,
+ // wait for the server to respond OK before sending more data
+ nsresult rv;
+ int32_t crlfIndex;
+ while (crlfIndex = protocolString.Find(CRLF), crlfIndex != kNotFound && !DeathSignalReceived())
+ {
+ nsAutoCString tempProtocolString;
+ tempProtocolString = StringHead(protocolString, crlfIndex + 2);
+ rv = SendData(tempProtocolString.get());
+ if (NS_FAILED(rv))
+ return;
+ ParseIMAPandCheckForNewMail();
+ protocolString.Cut(0, crlfIndex + 2);
+ }
+ protocolString.Append(CRLF);
+
+ rv = SendData(protocolString.get());
+ if (NS_SUCCEEDED(rv))
+ ParseIMAPandCheckForNewMail();
+}
+
+void nsImapProtocol::Copy(const char * messageList,
+ const char *destinationMailbox,
+ bool idsAreUid)
+{
+ IncrementCommandTagNumber();
+
+ nsCString escapedDestination;
+ CreateEscapedMailboxName(destinationMailbox, escapedDestination);
+
+ // turn messageList back into key array and then back into a message id list,
+ // but use the flag state to handle ranges correctly.
+ nsCString messageIdList;
+ nsTArray<nsMsgKey> msgKeys;
+ if (idsAreUid)
+ ParseUidString(messageList, msgKeys);
+
+ int32_t msgCountLeft = msgKeys.Length();
+ uint32_t msgsHandled = 0;
+
+ do
+ {
+ nsCString idString;
+
+ uint32_t msgsToHandle = msgCountLeft;
+ if (idsAreUid)
+ AllocateImapUidString(msgKeys.Elements() + msgsHandled, msgsToHandle, m_flagState, idString);
+ else
+ idString.Assign(messageList);
+
+ msgsHandled += msgsToHandle;
+ msgCountLeft -= msgsToHandle;
+
+ IncrementCommandTagNumber();
+ nsAutoCString protocolString(GetServerCommandTag());
+ if (idsAreUid)
+ protocolString.Append(" uid");
+ // If it's a MOVE operation on aol servers then use 'xaol-move' cmd.
+ if ((m_imapAction == nsIImapUrl::nsImapOnlineMove) &&
+ GetServerStateParser().ServerIsAOLServer())
+ protocolString.Append(" xaol-move ");
+ else if ((m_imapAction == nsIImapUrl::nsImapOnlineMove) &&
+ GetServerStateParser().GetCapabilityFlag() & kHasMoveCapability)
+ protocolString.Append(" move ");
+ else
+ protocolString.Append(" copy ");
+
+
+ protocolString.Append(idString);
+ protocolString.Append(" \"");
+ protocolString.Append(escapedDestination);
+ protocolString.Append("\"" CRLF);
+
+ nsresult rv = SendData(protocolString.get());
+ if (NS_SUCCEEDED(rv))
+ ParseIMAPandCheckForNewMail(protocolString.get());
+ }
+ while (msgCountLeft > 0 && !DeathSignalReceived());
+}
+
+void nsImapProtocol::NthLevelChildList(const char* onlineMailboxPrefix,
+ int32_t depth)
+{
+ NS_ASSERTION (depth >= 0,
+ "Oops ... depth must be equal or greater than 0");
+ if (depth < 0) return;
+
+ nsCString truncatedPrefix (onlineMailboxPrefix);
+ char16_t slash = '/';
+ if (truncatedPrefix.Last() == slash)
+ truncatedPrefix.SetLength(truncatedPrefix.Length()-1);
+
+ nsAutoCString pattern(truncatedPrefix);
+ nsAutoCString suffix;
+ int count = 0;
+ char separator = 0;
+ m_runningUrl->GetOnlineSubDirSeparator(&separator);
+ suffix.Assign(separator);
+ suffix += '%';
+
+ while (count < depth)
+ {
+ pattern += suffix;
+ count++;
+ List(pattern.get(), false);
+ }
+}
+
+void nsImapProtocol::ProcessAuthenticatedStateURL()
+{
+ nsImapAction imapAction;
+ char * sourceMailbox = nullptr;
+ m_runningUrl->GetImapAction(&imapAction);
+
+ // switch off of the imap url action and take an appropriate action
+ switch (imapAction)
+ {
+ case nsIImapUrl::nsImapLsubFolders:
+ OnLSubFolders();
+ break;
+ case nsIImapUrl::nsImapAppendMsgFromFile:
+ OnAppendMsgFromFile();
+ break;
+ case nsIImapUrl::nsImapDiscoverAllBoxesUrl:
+ NS_ASSERTION (!GetSubscribingNow(),
+ "Oops ... should not get here from subscribe UI");
+ DiscoverMailboxList();
+ break;
+ case nsIImapUrl::nsImapDiscoverAllAndSubscribedBoxesUrl:
+ DiscoverAllAndSubscribedBoxes();
+ break;
+ case nsIImapUrl::nsImapCreateFolder:
+ sourceMailbox = OnCreateServerSourceFolderPathString();
+ OnCreateFolder(sourceMailbox);
+ break;
+ case nsIImapUrl::nsImapEnsureExistsFolder:
+ sourceMailbox = OnCreateServerSourceFolderPathString();
+ OnEnsureExistsFolder(sourceMailbox);
+ break;
+ case nsIImapUrl::nsImapDiscoverChildrenUrl:
+ {
+ char *canonicalParent = nullptr;
+ m_runningUrl->CreateServerSourceFolderPathString(&canonicalParent);
+ if (canonicalParent)
+ {
+ NthLevelChildList(canonicalParent, 2);
+ PR_Free(canonicalParent);
+ }
+ break;
+ }
+ case nsIImapUrl::nsImapSubscribe:
+ sourceMailbox = OnCreateServerSourceFolderPathString();
+ OnSubscribe(sourceMailbox); // used to be called subscribe
+
+ if (GetServerStateParser().LastCommandSuccessful())
+ {
+ bool shouldList;
+ // if url is an external click url, then we should list the folder
+ // after subscribing to it, so we can select it.
+ m_runningUrl->GetExternalLinkUrl(&shouldList);
+ if (shouldList)
+ OnListFolder(sourceMailbox, true);
+ }
+ break;
+ case nsIImapUrl::nsImapUnsubscribe:
+ sourceMailbox = OnCreateServerSourceFolderPathString();
+ OnUnsubscribe(sourceMailbox);
+ break;
+ case nsIImapUrl::nsImapRefreshACL:
+ sourceMailbox = OnCreateServerSourceFolderPathString();
+ RefreshACLForFolder(sourceMailbox);
+ break;
+ case nsIImapUrl::nsImapRefreshAllACLs:
+ OnRefreshAllACLs();
+ break;
+ case nsIImapUrl::nsImapListFolder:
+ sourceMailbox = OnCreateServerSourceFolderPathString();
+ OnListFolder(sourceMailbox, false);
+ break;
+ case nsIImapUrl::nsImapFolderStatus:
+ sourceMailbox = OnCreateServerSourceFolderPathString();
+ OnStatusForFolder(sourceMailbox);
+ break;
+ case nsIImapUrl::nsImapRefreshFolderUrls:
+ sourceMailbox = OnCreateServerSourceFolderPathString();
+ XMailboxInfo(sourceMailbox);
+ if (GetServerStateParser().LastCommandSuccessful())
+ SetFolderAdminUrl(sourceMailbox);
+ break;
+ case nsIImapUrl::nsImapDeleteFolder:
+ sourceMailbox = OnCreateServerSourceFolderPathString();
+ OnDeleteFolder(sourceMailbox);
+ break;
+ case nsIImapUrl::nsImapRenameFolder:
+ sourceMailbox = OnCreateServerSourceFolderPathString();
+ OnRenameFolder(sourceMailbox);
+ break;
+ case nsIImapUrl::nsImapMoveFolderHierarchy:
+ sourceMailbox = OnCreateServerSourceFolderPathString();
+ OnMoveFolderHierarchy(sourceMailbox);
+ break;
+ case nsIImapUrl::nsImapVerifylogon:
+ break;
+ default:
+ break;
+ }
+ PR_Free(sourceMailbox);
+}
+
+void nsImapProtocol::ProcessAfterAuthenticated()
+{
+ // if we're a netscape server, and we haven't got the admin url, get it
+ bool hasAdminUrl = true;
+
+ if (NS_SUCCEEDED(m_hostSessionList->GetHostHasAdminURL(GetImapServerKey(), hasAdminUrl))
+ && !hasAdminUrl)
+ {
+ if (GetServerStateParser().ServerHasServerInfo())
+ {
+ XServerInfo();
+ if (GetServerStateParser().LastCommandSuccessful() && m_imapServerSink)
+ {
+ m_imapServerSink->SetMailServerUrls(GetServerStateParser().GetMailAccountUrl(),
+ GetServerStateParser().GetManageListsUrl(),
+ GetServerStateParser().GetManageFiltersUrl());
+ // we've tried to ask for it, so don't try again this session.
+ m_hostSessionList->SetHostHasAdminURL(GetImapServerKey(), true);
+ }
+ }
+ else if (GetServerStateParser().ServerIsNetscape3xServer())
+ {
+ Netscape();
+ if (GetServerStateParser().LastCommandSuccessful() && m_imapServerSink)
+ m_imapServerSink->SetMailServerUrls(GetServerStateParser().GetMailAccountUrl(),
+ EmptyCString(), EmptyCString());
+ }
+ }
+
+ if (GetServerStateParser().ServerHasNamespaceCapability())
+ {
+ bool nameSpacesOverridable = false;
+ bool haveNameSpacesForHost = false;
+ m_hostSessionList->GetNamespacesOverridableForHost(GetImapServerKey(), nameSpacesOverridable);
+ m_hostSessionList->GetGotNamespacesForHost(GetImapServerKey(), haveNameSpacesForHost);
+
+ // mscott: VERIFY THIS CLAUSE!!!!!!!
+ if (nameSpacesOverridable && !haveNameSpacesForHost)
+ Namespace();
+ }
+
+ // If the server supports compression, turn it on now.
+ // Choosing this spot (after login has finished) because
+ // many proxies (e.g. perdition, nginx) talk IMAP to the
+ // client until login is finished, then hand off to the
+ // backend. If we enable compression early the proxy
+ // will be confused.
+ if (UseCompressDeflate())
+ StartCompressDeflate();
+
+ if ((GetServerStateParser().GetCapabilityFlag() & kHasEnableCapability) &&
+ UseCondStore())
+ EnableCondStore();
+
+ bool haveIdResponse = false;
+ if ((GetServerStateParser().GetCapabilityFlag() & kHasIDCapability) &&
+ m_sendID)
+ {
+ ID();
+ if (m_imapServerSink && !GetServerStateParser().GetServerID().IsEmpty())
+ {
+ haveIdResponse = true;
+ // Determine value for m_forceSelect based on config editor
+ // entries and comparison to imap ID string returned by the server.
+ m_imapServerSink->SetServerID(GetServerStateParser().GetServerID());
+ switch (m_forceSelectValue.get()[0])
+ {
+ // Yes: Set to always force even if imap server doesn't need it.
+ case 'y':
+ case 'Y':
+ m_forceSelect = true;
+ break;
+
+ // No: Set to never force a select for this imap server.
+ case 'n':
+ case 'N':
+ m_forceSelect = false;
+ break;
+
+ // Auto: Set to force only if imap server requires it.
+ default:
+ nsAutoCString statusString;
+ m_forceSelect = IsExtraSelectNeeded();
+ // Setting to "yes-auto" or "no-auto" avoids doing redundant calls to
+ // IsExtraSelectNeeded() on subsequent ID() occurrences. It also
+ // provides feedback to the user regarding the detection status.
+ if (m_forceSelect)
+ {
+ // Set preference value to "yes-auto".
+ statusString.Assign("yes-auto");
+ }
+ else
+ {
+ // Set preference value to "no-auto".
+ statusString.Assign("no-auto");
+ }
+ m_imapServerSink->SetServerForceSelect(statusString);
+ break;
+ }
+ }
+ }
+
+ // If no ID capability or empty ID response, user may still want to
+ // change "force select".
+ if (!haveIdResponse)
+ {
+ switch (m_forceSelectValue.get()[0])
+ {
+ case 'a':
+ {
+ // If default "auto", set to "no-auto" so visible in config editor
+ // and set/keep m_forceSelect false.
+ nsAutoCString statusString;
+ statusString.Assign("no-auto");
+ m_imapServerSink->SetServerForceSelect(statusString);
+ m_forceSelect = false;
+ }
+ break;
+ case 'y':
+ case 'Y':
+ m_forceSelect = true;
+ break;
+ default:
+ m_forceSelect = false;
+ }
+ }
+}
+
+void nsImapProtocol::SetupMessageFlagsString(nsCString& flagString,
+ imapMessageFlagsType flags,
+ uint16_t userFlags)
+{
+ if (flags & kImapMsgSeenFlag)
+ flagString.Append("\\Seen ");
+ if (flags & kImapMsgAnsweredFlag)
+ flagString.Append("\\Answered ");
+ if (flags & kImapMsgFlaggedFlag)
+ flagString.Append("\\Flagged ");
+ if (flags & kImapMsgDeletedFlag)
+ flagString.Append("\\Deleted ");
+ if (flags & kImapMsgDraftFlag)
+ flagString.Append("\\Draft ");
+ if (flags & kImapMsgRecentFlag)
+ flagString.Append("\\Recent ");
+ if ((flags & kImapMsgForwardedFlag) &&
+ (userFlags & kImapMsgSupportForwardedFlag))
+ flagString.Append("$Forwarded "); // Not always available
+ if ((flags & kImapMsgMDNSentFlag) && (
+ userFlags & kImapMsgSupportMDNSentFlag))
+ flagString.Append("$MDNSent "); // Not always available
+
+ // eat the last space
+ if (!flagString.IsEmpty())
+ flagString.SetLength(flagString.Length()-1);
+}
+
+void nsImapProtocol::ProcessStoreFlags(const nsCString &messageIdsString,
+ bool idsAreUids,
+ imapMessageFlagsType flags,
+ bool addFlags)
+{
+ nsCString flagString;
+
+ uint16_t userFlags = GetServerStateParser().SupportsUserFlags();
+ uint16_t settableFlags = GetServerStateParser().SettablePermanentFlags();
+
+ if (!addFlags && (flags & userFlags) && !(flags & settableFlags))
+ {
+ if (m_runningUrl)
+ m_runningUrl->SetExtraStatus(nsIImapUrl::ImapStatusFlagsNotSettable);
+ return; // if cannot set any of the flags bail out
+ }
+
+ if (addFlags)
+ flagString = "+Flags (";
+ else
+ flagString = "-Flags (";
+
+ if (flags & kImapMsgSeenFlag && kImapMsgSeenFlag & settableFlags)
+ flagString .Append("\\Seen ");
+ if (flags & kImapMsgAnsweredFlag && kImapMsgAnsweredFlag & settableFlags)
+ flagString .Append("\\Answered ");
+ if (flags & kImapMsgFlaggedFlag && kImapMsgFlaggedFlag & settableFlags)
+ flagString .Append("\\Flagged ");
+ if (flags & kImapMsgDeletedFlag && kImapMsgDeletedFlag & settableFlags)
+ flagString .Append("\\Deleted ");
+ if (flags & kImapMsgDraftFlag && kImapMsgDraftFlag & settableFlags)
+ flagString .Append("\\Draft ");
+ if (flags & kImapMsgForwardedFlag && kImapMsgSupportForwardedFlag & userFlags)
+ flagString .Append("$Forwarded "); // if supported
+ if (flags & kImapMsgMDNSentFlag && kImapMsgSupportMDNSentFlag & userFlags)
+ flagString .Append("$MDNSent "); // if supported
+
+ if (flagString.Length() > 8) // if more than "+Flags ("
+ {
+ // replace the final space with ')'
+ flagString.SetCharAt(')',flagString.Length() - 1);
+
+ Store(messageIdsString, flagString.get(), idsAreUids);
+ if (m_runningUrl && idsAreUids)
+ {
+ nsCString messageIdString;
+ m_runningUrl->GetListOfMessageIds(messageIdString);
+ nsTArray<nsMsgKey> msgKeys;
+ ParseUidString(messageIdString.get(), msgKeys);
+
+ int32_t msgCount = msgKeys.Length();
+ for (int32_t i = 0; i < msgCount; i++)
+ {
+ bool found;
+ imapMessageFlagsType resultFlags;
+ // check if the flags were added/removed, and if the uid really exists.
+ nsresult rv = GetFlagsForUID(msgKeys[i], &found, &resultFlags, nullptr);
+ if (NS_FAILED(rv) || !found ||
+ (addFlags && ((flags & resultFlags) != flags)) ||
+ (!addFlags && ((flags & resultFlags) != 0)))
+ {
+ m_runningUrl->SetExtraStatus(nsIImapUrl::ImapStatusFlagChangeFailed);
+ break;
+ }
+ }
+
+ }
+ }
+}
+
+
+void nsImapProtocol::Close(bool shuttingDown /* = false */,
+ bool waitForResponse /* = true */)
+{
+ IncrementCommandTagNumber();
+
+ nsCString command(GetServerCommandTag());
+ command.Append(" close" CRLF);
+
+ if (!shuttingDown)
+ ProgressEventFunctionUsingName("imapStatusCloseMailbox");
+
+ GetServerStateParser().ResetFlagInfo();
+
+ nsresult rv = SendData(command.get());
+ if (m_transport && shuttingDown)
+ m_transport->SetTimeout(nsISocketTransport::TIMEOUT_READ_WRITE, 5);
+
+ if (NS_SUCCEEDED(rv) && waitForResponse)
+ ParseIMAPandCheckForNewMail();
+}
+
+void nsImapProtocol::XAOL_Option(const char *option)
+{
+ IncrementCommandTagNumber();
+
+ nsCString command(GetServerCommandTag());
+ command.Append(" XAOL-OPTION ");
+ command.Append(option);
+ command.Append(CRLF);
+
+ nsresult rv = SendData(command.get());
+ if (NS_SUCCEEDED(rv))
+ ParseIMAPandCheckForNewMail();
+}
+
+void nsImapProtocol::Check()
+{
+ //ProgressUpdateEvent("Checking mailbox...");
+
+ IncrementCommandTagNumber();
+
+ nsCString command(GetServerCommandTag());
+ command.Append(" check" CRLF);
+
+ nsresult rv = SendData(command.get());
+ if (NS_SUCCEEDED(rv))
+ {
+ m_flagChangeCount = 0;
+ m_lastCheckTime = PR_Now();
+ ParseIMAPandCheckForNewMail();
+ }
+}
+
+nsresult nsImapProtocol::GetMsgWindow(nsIMsgWindow **aMsgWindow)
+{
+ nsresult rv;
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl =
+ do_QueryInterface(m_runningUrl, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!m_imapProtocolSink)
+ return NS_ERROR_FAILURE;
+ return m_imapProtocolSink->GetUrlWindow(mailnewsUrl, aMsgWindow);
+}
+
+/**
+ * Get password from RAM, disk (password manager) or user (dialog)
+ * @return NS_MSG_PASSWORD_PROMPT_CANCELLED
+ * (which is NS_SUCCEEDED!) when user cancelled
+ * NS_FAILED(rv) for other errors
+ */
+nsresult nsImapProtocol::GetPassword(nsCString &password,
+ bool newPasswordRequested)
+{
+ // we are in the imap thread so *NEVER* try to extract the password with UI
+ // if logon redirection has changed the password, use the cookie as the password
+ if (m_overRideUrlConnectionInfo)
+ {
+ password.Assign(m_logonCookie);
+ return NS_OK;
+ }
+
+ NS_ENSURE_TRUE(m_imapServerSink, NS_ERROR_NULL_POINTER);
+ NS_ENSURE_TRUE(m_server, NS_ERROR_NULL_POINTER);
+ nsresult rv;
+
+ // Get the password already stored in mem
+ rv = m_imapServerSink->GetServerPassword(password);
+ if (NS_FAILED(rv) || password.IsEmpty())
+ {
+ AutoProxyReleaseMsgWindow msgWindow;
+ GetMsgWindow(getter_AddRefs(msgWindow));
+ NS_ENSURE_TRUE(msgWindow, NS_ERROR_NOT_AVAILABLE); // biff case
+
+ // Get the password from pw manager (harddisk) or user (dialog)
+ 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<nsIImapIncomingServer> imapServer = do_QueryReferent(m_server, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIMsgWindow> msgWindow;
+
+ *aResult = false;
+ GetMsgWindow(getter_AddRefs(msgWindow));
+ nsCString password = m_lastPasswordSent;
+ rv = imapServer->PromptPassword(msgWindow, password);
+ m_password = password;
+ m_passwordStatus = rv;
+ if (!m_password.IsEmpty())
+ *aResult = true;
+
+ // Notify the imap thread that we have a password.
+ ReentrantMonitorAutoEnter passwordMon(m_passwordReadyMonitor);
+ passwordMon.Notify();
+ return rv;
+}
+
+NS_IMETHODIMP
+nsImapProtocol::OnPromptAuthAvailable()
+{
+ nsresult rv;
+ nsCOMPtr<nsIMsgIncomingServer> imapServer = do_QueryReferent(m_server, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ m_passwordStatus = imapServer->GetPassword(m_password);
+ // Notify the imap thread that we have a password.
+ 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<nsIImapMockChannel> mChannelToUse;
+ nsCOMPtr<nsIStreamListener> mListener;
+};
+
+NS_IMPL_ADDREF(nsImapCacheStreamListener)
+NS_IMPL_RELEASE(nsImapCacheStreamListener)
+
+NS_INTERFACE_MAP_BEGIN(nsImapCacheStreamListener)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIStreamListener)
+ NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
+ NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
+NS_INTERFACE_MAP_END
+
+nsImapCacheStreamListener::nsImapCacheStreamListener()
+{
+}
+
+nsImapCacheStreamListener::~nsImapCacheStreamListener()
+{}
+
+nsresult nsImapCacheStreamListener::Init(nsIStreamListener * aStreamListener, nsIImapMockChannel * aMockChannelToUse)
+{
+ NS_ENSURE_ARG(aStreamListener);
+ NS_ENSURE_ARG(aMockChannelToUse);
+
+ mChannelToUse = aMockChannelToUse;
+ mListener = aStreamListener;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapCacheStreamListener::OnStartRequest(nsIRequest *request, nsISupports * aCtxt)
+{
+ if (!mChannelToUse)
+ {
+ NS_ERROR("OnStartRequest called after OnStopRequest");
+ return NS_ERROR_NULL_POINTER;
+ }
+ nsCOMPtr<nsILoadGroup> loadGroup;
+ mChannelToUse->GetLoadGroup(getter_AddRefs(loadGroup));
+ nsCOMPtr<nsIRequest> ourRequest = do_QueryInterface(mChannelToUse);
+ if (loadGroup)
+ loadGroup->AddRequest(ourRequest, nullptr /* context isupports */);
+ return mListener->OnStartRequest(ourRequest, aCtxt);
+}
+
+NS_IMETHODIMP
+nsImapCacheStreamListener::OnStopRequest(nsIRequest *request, nsISupports * aCtxt, nsresult aStatus)
+{
+ if (!mListener)
+ {
+ NS_ERROR("OnStopRequest called twice");
+ return NS_ERROR_NULL_POINTER;
+ }
+ nsresult rv = mListener->OnStopRequest(mChannelToUse, aCtxt, aStatus);
+ nsCOMPtr <nsILoadGroup> loadGroup;
+ mChannelToUse->GetLoadGroup(getter_AddRefs(loadGroup));
+ if (loadGroup)
+ loadGroup->RemoveRequest(mChannelToUse, nullptr, aStatus);
+
+ mListener = nullptr;
+ mChannelToUse->Close();
+ mChannelToUse = nullptr;
+ return rv;
+}
+
+NS_IMETHODIMP
+nsImapCacheStreamListener::OnDataAvailable(nsIRequest *request, nsISupports * aCtxt, nsIInputStream * aInStream, uint64_t aSourceOffset, uint32_t aCount)
+{
+ return mListener->OnDataAvailable(mChannelToUse, aCtxt, aInStream, aSourceOffset, aCount);
+}
+
+NS_IMPL_ISUPPORTS(nsImapMockChannel, nsIImapMockChannel, nsIChannel,
+ nsIRequest, nsICacheEntryOpenCallback, nsITransportEventSink, nsISupportsWeakReference)
+
+
+nsImapMockChannel::nsImapMockChannel()
+{
+ m_channelContext = nullptr;
+ m_cancelStatus = NS_OK;
+ mLoadFlags = 0;
+ mChannelClosed = false;
+ mReadingFromCache = false;
+ mTryingToReadPart = false;
+}
+
+nsImapMockChannel::~nsImapMockChannel()
+{
+ // if we're offline, we may not get to close the channel correctly.
+ // we need to do this to send the url state change notification in
+ // the case of mem and disk cache reads.
+ NS_WARNING_ASSERTION(NS_IsMainThread(), "should only access mock channel on ui thread");
+ if (!mChannelClosed)
+ Close();
+}
+
+nsresult nsImapMockChannel::NotifyStartEndReadFromCache(bool start)
+{
+ nsresult rv = NS_OK;
+ mReadingFromCache = start;
+ nsCOMPtr<nsIImapUrl> imapUrl = do_QueryInterface(m_url, &rv);
+ nsCOMPtr<nsIImapProtocol> imapProtocol = do_QueryReferent(mProtocol);
+ if (imapUrl)
+ {
+ nsCOMPtr<nsIImapMailFolderSink> folderSink;
+ rv = imapUrl->GetImapMailFolderSink(getter_AddRefs(folderSink));
+ if (folderSink)
+ {
+ nsCOMPtr<nsIMsgMailNewsUrl> mailUrl = do_QueryInterface(m_url);
+ rv = folderSink->SetUrlState(nullptr /* we don't know the protocol */,
+ mailUrl, start, false, m_cancelStatus);
+
+ // Required for killing ImapProtocol thread
+ if (NS_FAILED(m_cancelStatus) && imapProtocol)
+ imapProtocol->TellThreadToDie(false);
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapMockChannel::Close()
+{
+ if (mReadingFromCache)
+ NotifyStartEndReadFromCache(false);
+ else
+ {
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_url);
+ if (mailnewsUrl)
+ {
+ nsCOMPtr<nsICacheEntry> cacheEntry;
+ mailnewsUrl->GetMemCacheEntry(getter_AddRefs(cacheEntry));
+ if (cacheEntry)
+ cacheEntry->MarkValid();
+ // remove the channel from the load group
+ nsCOMPtr <nsILoadGroup> loadGroup;
+ GetLoadGroup(getter_AddRefs(loadGroup));
+ // if the mock channel wasn't initialized with a load group then
+ // use our load group (they may differ)
+ if (!loadGroup)
+ mailnewsUrl->GetLoadGroup(getter_AddRefs(loadGroup));
+ if (loadGroup)
+ loadGroup->RemoveRequest((nsIRequest *) this, nullptr, NS_OK);
+ }
+ }
+
+ m_channelListener = nullptr;
+ mCacheRequest = nullptr;
+ if (mTryingToReadPart)
+ {
+ // clear mem cache entry on imap part url in case it's holding
+ // onto last reference in mem cache. Need to do this on ui thread
+ nsresult rv;
+ nsCOMPtr <nsIImapUrl> imapUrl = do_QueryInterface(m_url, &rv);
+ if (imapUrl)
+ {
+ nsCOMPtr <nsIImapMailFolderSink> folderSink;
+ rv = imapUrl->GetImapMailFolderSink(getter_AddRefs(folderSink));
+ if (folderSink)
+ {
+ nsCOMPtr <nsIMsgMailNewsUrl> mailUrl = do_QueryInterface(m_url);
+ rv = folderSink->ReleaseUrlCacheEntry(mailUrl);
+ }
+ }
+ }
+ mChannelClosed = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMockChannel::GetProgressEventSink(nsIProgressEventSink ** aProgressEventSink)
+{
+ *aProgressEventSink = mProgressEventSink;
+ NS_IF_ADDREF(*aProgressEventSink);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMockChannel::SetProgressEventSink(nsIProgressEventSink * aProgressEventSink)
+{
+ mProgressEventSink = aProgressEventSink;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMockChannel::GetChannelListener(nsIStreamListener **aChannelListener)
+{
+ *aChannelListener = m_channelListener;
+ NS_IF_ADDREF(*aChannelListener);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMockChannel::GetChannelContext(nsISupports **aChannelContext)
+{
+ *aChannelContext = m_channelContext;
+ NS_IF_ADDREF(*aChannelContext);
+ return NS_OK;
+}
+
+// now implement our mock implementation of the channel interface...we forward all calls to the real
+// channel if we have one...otherwise we return something bogus...
+
+NS_IMETHODIMP nsImapMockChannel::SetLoadGroup(nsILoadGroup * aLoadGroup)
+{
+ m_loadGroup = aLoadGroup;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMockChannel::GetLoadGroup(nsILoadGroup * *aLoadGroup)
+{
+ *aLoadGroup = m_loadGroup;
+ NS_IF_ADDREF(*aLoadGroup);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMockChannel::GetLoadInfo(nsILoadInfo * *aLoadInfo)
+{
+ *aLoadInfo = m_loadInfo;
+ NS_IF_ADDREF(*aLoadInfo);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMockChannel::SetLoadInfo(nsILoadInfo * aLoadInfo)
+{
+ m_loadInfo = aLoadInfo;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMockChannel::GetOriginalURI(nsIURI* *aURI)
+{
+ // IMap does not seem to have the notion of an original URI :-(
+ // *aURI = m_originalUrl ? m_originalUrl : m_url;
+ *aURI = m_url;
+ NS_IF_ADDREF(*aURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMockChannel::SetOriginalURI(nsIURI* aURI)
+{
+ // IMap does not seem to have the notion of an original URI :-(
+ // NS_NOTREACHED("nsImapMockChannel::SetOriginalURI");
+ // return NS_ERROR_NOT_IMPLEMENTED;
+ return NS_OK; // ignore
+}
+
+NS_IMETHODIMP nsImapMockChannel::GetURI(nsIURI* *aURI)
+{
+ *aURI = m_url;
+ NS_IF_ADDREF(*aURI);
+ return NS_OK ;
+}
+
+NS_IMETHODIMP nsImapMockChannel::SetURI(nsIURI* aURI)
+{
+ m_url = aURI;
+#ifdef DEBUG_bienvenu
+ if (!aURI)
+ printf("Clearing URI\n");
+#endif
+ if (m_url)
+ {
+ // if we don't have a progress event sink yet, get it from the url for now...
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_url);
+ if (mailnewsUrl && !mProgressEventSink)
+ {
+ nsCOMPtr<nsIMsgStatusFeedback> statusFeedback;
+ mailnewsUrl->GetStatusFeedback(getter_AddRefs(statusFeedback));
+ mProgressEventSink = do_QueryInterface(statusFeedback);
+ }
+ // If this is a fetch URL and we can, get the message size from the message
+ // header and set it to be the content length.
+ // Note that for an attachment URL, this will set the content length to be
+ // equal to the size of the entire message.
+ nsCOMPtr<nsIImapUrl> imapUrl(do_QueryInterface(m_url));
+ nsImapAction imapAction;
+ imapUrl->GetImapAction(&imapAction);
+ if (imapAction == nsIImapUrl::nsImapMsgFetch)
+ {
+ nsCOMPtr<nsIMsgMessageUrl> msgUrl(do_QueryInterface(m_url));
+ if (msgUrl)
+ {
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ // A failure to get a message header isn't an error
+ msgUrl->GetMessageHeader(getter_AddRefs(msgHdr));
+ if (msgHdr)
+ {
+ uint32_t messageSize;
+ if (NS_SUCCEEDED(msgHdr->GetMessageSize(&messageSize)))
+ SetContentLength(messageSize);
+ }
+ }
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMockChannel::Open(nsIInputStream **_retval)
+{
+ return NS_ImplementChannelOpen(this, _retval);
+}
+
+NS_IMETHODIMP nsImapMockChannel::Open2(nsIInputStream **_retval)
+{
+ nsCOMPtr<nsIStreamListener> listener;
+ nsresult rv = nsContentSecurityManager::doContentSecurityCheck(this, listener);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return Open(_retval);
+}
+
+NS_IMETHODIMP
+nsImapMockChannel::OnCacheEntryAvailable(nsICacheEntry *entry, bool aNew, nsIApplicationCache* aAppCache, nsresult status)
+{
+ nsresult rv = NS_OK;
+
+ // make sure we didn't close the channel before the async call back came in...
+ // hmmm....if we had write access and we canceled this mock channel then I wonder if we should
+ // be invalidating the cache entry before kicking out...
+ if (mChannelClosed)
+ {
+ entry->AsyncDoom(nullptr);
+ return NS_OK;
+ }
+
+ if (!m_url) {
+ // Something has gone terribly wrong.
+ NS_WARNING("m_url is null in OnCacheEntryAvailable");
+ return Cancel(NS_ERROR_UNEXPECTED);
+ }
+
+ do {
+ // For "normal" read/write access we always receive NS_OK here. aNew
+ // indicates whether the cache entry is new and needs to be written, or not
+ // new and can be read. If AsyncOpenURI() was called with access read-only,
+ // status==NS_ERROR_CACHE_KEY_NOT_FOUND can be received here and we just read
+ // the data directly.
+ if (NS_FAILED(status))
+ break;
+
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_url, &rv);
+ mailnewsUrl->SetMemCacheEntry(entry);
+
+ // For URLs not related to parts, the processing is easy:
+ // aNew==true means that we need to write to the entry,
+ // aNew==false means that we can read it.
+ //
+ // Parts processing is a little complicated, we distinguish two cases:
+ // 1) The caller a) knows that the part is there or
+ // b) it is not there and can also not be read from the
+ // entire message.
+ // In this case, the URL used as cache key addresses the part and
+ // mTryingToReadPart==false.
+ // The caller has already set up part extraction.
+ // This case is no different to non-part processing.
+ // 2) The caller wants to try to extract the part from the cache entry
+ // of the entire message.
+ // In this case, the URL used as cache key addresses the message and
+ // mTryingToReadPart==true.
+ if (mTryingToReadPart)
+ {
+ // We are here with the URI of the entire message which we know exists.
+ MOZ_ASSERT(!aNew,
+ "Logic error: Trying to read part from entire message which doesn't exist");
+ if (!aNew)
+ {
+ // Check the meta data.
+ nsCString annotation;
+ rv = entry->GetMetaDataElement("ContentModified", getter_Copies(annotation));
+ if (NS_FAILED(rv) || !annotation.EqualsLiteral("Not Modified"))
+ {
+ // The cache entry is not marked "Not Modified", that means it doesn't
+ // contain the entire message, so we can't use it.
+ // Call OpenCacheEntry() a second time to get the part.
+ rv = OpenCacheEntry();
+ if (NS_SUCCEEDED(rv))
+ return rv;
+
+ // Something has gone wrong, fall back to reading from the imap
+ // connection so the protocol doesn't hang.
+ break;
+ }
+ }
+ }
+
+ if (aNew)
+ {
+ // If we are writing, then insert a "stream listener Tee" into the flow
+ // to force data into the cache and to our current channel listener.
+ nsCOMPtr<nsIStreamListenerTee> tee = do_CreateInstance(NS_STREAMLISTENERTEE_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv))
+ {
+ nsCOMPtr<nsIOutputStream> out;
+ // This will fail with the cache turned off, so we need to fall through
+ // to ReadFromImapConnection instead of aborting with NS_ENSURE_SUCCESS(rv,rv).
+ rv = entry->OpenOutputStream(0, getter_AddRefs(out));
+ if (NS_SUCCEEDED(rv))
+ {
+ rv = tee->Init(m_channelListener, out, nullptr);
+ m_channelListener = do_QueryInterface(tee);
+ }
+ else
+ NS_WARNING("IMAP Protocol failed to open output stream to Necko cache");
+ }
+ }
+ else
+ {
+ rv = ReadFromMemCache(entry);
+ if (NS_SUCCEEDED(rv))
+ {
+ NotifyStartEndReadFromCache(true);
+ entry->MarkValid();
+ return NS_OK; // Kick out if reading from the cache succeeded.
+ }
+ entry->AsyncDoom(nullptr); // Doom entry if we failed to read from cache.
+ mailnewsUrl->SetMemCacheEntry(nullptr); // We aren't going to be reading from the cache.
+ }
+ } while (false);
+
+ // If reading from the cache failed or if we are writing into the cache, default to ReadFromImapConnection.
+ return ReadFromImapConnection();
+}
+
+NS_IMETHODIMP
+nsImapMockChannel::OnCacheEntryCheck(nsICacheEntry* entry, nsIApplicationCache* appCache,
+ uint32_t* aResult)
+{
+ *aResult = nsICacheEntryOpenCallback::ENTRY_WANTED;
+
+ // Check concurrent read: We can't read concurrently since we don't know
+ // that the entry will ever be written successfully. It may be aborted
+ // due to a size limitation. If reading concurrently, the following function
+ // will return NS_ERROR_IN_PROGRESS. Then we tell the cache to wait until
+ // the write is finished.
+ int64_t size = 0;
+ nsresult rv = entry->GetDataSize(&size);
+ if (rv == NS_ERROR_IN_PROGRESS)
+ *aResult = nsICacheEntryOpenCallback::RECHECK_AFTER_WRITE_FINISHED;
+
+ return NS_OK;
+}
+
+nsresult nsImapMockChannel::OpenCacheEntry()
+{
+ nsresult rv;
+ // get the cache session from our imap service...
+ nsCOMPtr<nsIImapService> imapService = do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsICacheStorage> cacheStorage;
+ rv = imapService->GetCacheStorage(getter_AddRefs(cacheStorage));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t uidValidity = -1;
+ nsCacheAccessMode cacheAccess = nsICacheStorage::OPEN_NORMALLY;
+
+ nsCOMPtr<nsIImapUrl> imapUrl = do_QueryInterface(m_url, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool storeResultsOffline;
+ nsCOMPtr<nsIImapMailFolderSink> folderSink;
+
+ rv = imapUrl->GetImapMailFolderSink(getter_AddRefs(folderSink));
+ if (folderSink)
+ folderSink->GetUidValidity(&uidValidity);
+ imapUrl->GetStoreResultsOffline(&storeResultsOffline);
+ // If we're storing the message in the offline store, don't
+ // write to the memory cache.
+ if (storeResultsOffline)
+ cacheAccess = nsICacheStorage::OPEN_READONLY;
+
+ // Use the uid validity as part of the cache key, so that if the uid validity
+ // changes, we won't re-use the wrong cache entries.
+ nsAutoCString extension;
+ extension.AppendInt(uidValidity, 16);
+
+ // Open a cache entry where the key is the potentially modified URL.
+ nsCOMPtr<nsIURI> newUri;
+ m_url->Clone(getter_AddRefs(newUri));
+ nsAutoCString path;
+ newUri->GetPath(path);
+
+ // First we need to "normalise" the URL by extracting ?part= and &filename.
+ // The path should only contain: ?part=x.y&filename=file.ext
+ // These are seen in the wild:
+ // /;section=2?part=1.2&filename=A01.JPG
+ // ?section=2?part=1.2&filename=A01.JPG&type=image/jpeg&filename=A01.JPG
+ // ?part=1.2&type=image/jpeg&filename=IMG_C0030.jpg
+ // ?header=quotebody&part=1.2&filename=lijbmghmkilicioj.png
+ nsAutoCString partQuery = MsgExtractQueryPart(path, "?part=");
+ if (partQuery.IsEmpty()) {
+ partQuery = MsgExtractQueryPart(path, "&part=");
+ if (!partQuery.IsEmpty()) {
+ // ? indicates a part query, so set the first character to that.
+ partQuery.SetCharAt('?', 0);
+ }
+ }
+ nsAutoCString filenameQuery = MsgExtractQueryPart(path, "&filename=");
+
+ // Truncate path at either /; or ?
+ int32_t ind = path.FindChar('?');
+ if (ind != kNotFound)
+ path.SetLength(ind);
+ ind = path.Find("/;");
+ if (ind != kNotFound)
+ path.SetLength(ind);
+
+ if (partQuery.IsEmpty())
+ {
+ // Not looking for a part. That's the easy part.
+ newUri->SetPath(path);
+ return cacheStorage->AsyncOpenURI(newUri, extension, cacheAccess, this);
+ }
+
+ /**
+ * Part processing (rest of this function).
+ */
+ if (mTryingToReadPart)
+ {
+ // If mTryingToReadPart is set, we are here for the second time.
+ // We tried to read a part from the entire message but the meta data didn't
+ // allow it. So we come back here.
+ // Now request the part with its full URL.
+ mTryingToReadPart = false;
+
+ // Note that part extraction was already set the first time.
+ newUri->SetPath(path + partQuery + filenameQuery);
+ return cacheStorage->AsyncOpenURI(newUri, extension, cacheAccess, this);
+ }
+
+ // First time processing. Set up part extraction.
+ SetupPartExtractorListener(imapUrl, m_channelListener);
+
+ // Check whether part is in the cache.
+ bool exists = false;
+ newUri->SetPath(path + partQuery + filenameQuery);
+ rv = cacheStorage->Exists(newUri, extension, &exists);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (exists) {
+ return cacheStorage->AsyncOpenURI(newUri, extension, cacheAccess, this);
+ }
+
+ // Let's see whether we have the entire message instead.
+ newUri->SetPath(path);
+ rv = cacheStorage->Exists(newUri, extension, &exists);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!exists) {
+ // The entire message is not in the cache. Request the part.
+ newUri->SetPath(path + partQuery + filenameQuery);
+ return cacheStorage->AsyncOpenURI(newUri, extension, cacheAccess, this);
+ }
+
+ // This is where is gets complicated. The entire message is in the cache,
+ // but we don't know whether it's suitable for use. Its meta data
+ // might indicate that the message is incomplete.
+ mTryingToReadPart = true;
+ return cacheStorage->AsyncOpenURI(newUri, extension, cacheAccess, this);
+}
+
+nsresult nsImapMockChannel::ReadFromMemCache(nsICacheEntry *entry)
+{
+ NS_ENSURE_ARG(entry);
+
+ nsCString annotation;
+ nsAutoCString entryKey;
+ nsAutoCString contentType;
+ nsresult rv = NS_OK;
+ bool shouldUseCacheEntry = false;
+
+ entry->GetKey(entryKey);
+ if (entryKey.FindChar('?') != kNotFound)
+ {
+ // Part processing: If we have a part, then we should use the cache entry.
+ entry->GetMetaDataElement("contentType", getter_Copies(contentType));
+ if (!contentType.IsEmpty())
+ SetContentType(contentType);
+ shouldUseCacheEntry = true;
+ }
+ else
+ {
+ // Whole message processing: We should make sure the content isn't modified.
+ rv = entry->GetMetaDataElement("ContentModified", getter_Copies(annotation));
+ if (NS_SUCCEEDED(rv) && !annotation.IsEmpty())
+ shouldUseCacheEntry = annotation.EqualsLiteral("Not Modified");
+
+ // Compare cache entry size with message size.
+ if (shouldUseCacheEntry)
+ {
+ int64_t entrySize;
+
+ rv = entry->GetDataSize(&entrySize);
+ // We don't expect concurrent read here, so this call should always work.
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgMessageUrl> msgUrl(do_QueryInterface(m_url));
+ if (msgUrl)
+ {
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ // A failure to get a message header isn't an error
+ msgUrl->GetMessageHeader(getter_AddRefs(msgHdr));
+ if (msgHdr)
+ {
+ uint32_t messageSize;
+ if (NS_SUCCEEDED(msgHdr->GetMessageSize(&messageSize)) &&
+ messageSize != entrySize)
+ {
+ MOZ_LOG(IMAP, LogLevel::Warning,
+ ("ReadFromMemCache size mismatch for %s: message %d, cache %ld\n",
+ entryKey.get(), messageSize, entrySize));
+ shouldUseCacheEntry = false;
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Common processing for full messages and message parts.
+ */
+
+ // Check header of full message or part.
+ if (shouldUseCacheEntry)
+ {
+ nsCOMPtr<nsIInputStream> in;
+ uint32_t readCount;
+ rv = entry->OpenInputStream(0, getter_AddRefs(in));
+ NS_ENSURE_SUCCESS(rv, rv);
+ const int kFirstBlockSize = 100;
+ char firstBlock[kFirstBlockSize + 1];
+
+ // Note: This will not work for a cache2 disk cache.
+ // (see bug 1302422 comment #4)
+ rv = in->Read(firstBlock, sizeof(firstBlock), &readCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+ firstBlock[kFirstBlockSize] = '\0';
+ int32_t findPos = MsgFindCharInSet(nsDependentCString(firstBlock),
+ ":\n\r", 0);
+ // Check that the first line is a header line, i.e., with a ':' in it
+ // Or that it begins with "From " because some IMAP servers allow that,
+ // even though it's technically invalid.
+ shouldUseCacheEntry = ((findPos != -1 && firstBlock[findPos] == ':') ||
+ !(strncmp(firstBlock, "From ", 5)));
+ in->Close();
+ }
+
+ if (shouldUseCacheEntry)
+ {
+ nsCOMPtr<nsIInputStream> in;
+ rv = entry->OpenInputStream(0, getter_AddRefs(in));
+ NS_ENSURE_SUCCESS(rv, rv);
+ // if mem cache entry is broken or empty, return error.
+ uint64_t bytesAvailable;
+ rv = in->Available(&bytesAvailable);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!bytesAvailable)
+ return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIInputStreamPump> pump;
+ rv = NS_NewInputStreamPump(getter_AddRefs(pump), in);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // if we are going to read from the cache, then create a mock stream listener class and use it
+ nsImapCacheStreamListener * cacheListener = new nsImapCacheStreamListener();
+ NS_ADDREF(cacheListener);
+ cacheListener->Init(m_channelListener, this);
+ rv = pump->AsyncRead(cacheListener, m_channelContext);
+ NS_RELEASE(cacheListener);
+
+ if (NS_SUCCEEDED(rv)) // ONLY if we succeeded in actually starting the read should we return
+ {
+ mCacheRequest = pump;
+ nsCOMPtr<nsIImapUrl> imapUrl = do_QueryInterface(m_url);
+ // if the msg is unread, we should mark it read on the server. This lets
+ // the code running this url we're loading from the cache, if it cares.
+ imapUrl->SetMsgLoadingFromCache(true);
+
+ // be sure to set the cache entry's security info status as our security info status...
+ nsCOMPtr<nsISupports> securityInfo;
+ entry->GetSecurityInfo(getter_AddRefs(securityInfo));
+ SetSecurityInfo(securityInfo);
+ return NS_OK;
+ } // if AsyncRead succeeded.
+ } // if content is not modified
+ else
+ {
+ // Content is modified so return an error so we try to open it the
+ // old fashioned way.
+ rv = NS_ERROR_FAILURE;
+ }
+
+ return rv;
+}
+
+class nsReadFromImapConnectionFailure : public mozilla::Runnable
+{
+public:
+ nsReadFromImapConnectionFailure(nsImapMockChannel *aChannel)
+ : mImapMockChannel(aChannel)
+ {}
+
+ NS_IMETHOD Run()
+ {
+ if (mImapMockChannel) {
+ mImapMockChannel->RunOnStopRequestFailure();
+ }
+ return NS_OK;
+ }
+private:
+ RefPtr<nsImapMockChannel> mImapMockChannel;
+};
+
+
+nsresult nsImapMockChannel::RunOnStopRequestFailure()
+{
+ if (m_channelListener) {
+ m_channelListener->OnStopRequest(this, m_channelContext,
+ NS_MSG_ERROR_MSG_NOT_OFFLINE);
+ }
+ return NS_OK;
+}
+
+// the requested url isn't in any of our caches so create an imap connection
+// to process it.
+nsresult nsImapMockChannel::ReadFromImapConnection()
+{
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIImapUrl> imapUrl = do_QueryInterface(m_url);
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_url);
+
+ bool localOnly = false;
+ imapUrl->GetLocalFetchOnly(&localOnly);
+ if (localOnly)
+ {
+ // This will cause an OnStartRunningUrl, and the subsequent close
+ // will then cause an OnStopRunningUrl with the cancel status.
+ NotifyStartEndReadFromCache(true);
+ Cancel(NS_MSG_ERROR_MSG_NOT_OFFLINE);
+
+ // Dispatch error notification, so ReadFromImapConnection() returns *before*
+ // the error is sent to the listener's OnStopRequest(). This avoids
+ // endless recursion where the caller relies on async execution.
+ nsCOMPtr<nsIRunnable> event = new nsReadFromImapConnectionFailure(this);
+ NS_DispatchToCurrentThread(event);
+ return NS_MSG_ERROR_MSG_NOT_OFFLINE;
+ }
+
+ nsCOMPtr <nsILoadGroup> loadGroup;
+ GetLoadGroup(getter_AddRefs(loadGroup));
+ if (!loadGroup) // if we don't have one, the url will snag one from the msg window...
+ mailnewsUrl->GetLoadGroup(getter_AddRefs(loadGroup));
+
+ // okay, add the mock channel to the load group..
+ if (loadGroup)
+ loadGroup->AddRequest((nsIRequest *) this, nullptr /* context isupports */);
+
+ // loading the url consists of asking the server to add the url to it's imap event queue....
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = mailnewsUrl->GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIImapIncomingServer> imapServer (do_QueryInterface(server, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Assume AsyncRead is always called from the UI thread.....
+ return imapServer->GetImapConnectionAndLoadUrl(imapUrl, m_channelListener);
+}
+
+// for messages stored in our offline cache, we have special code to handle that...
+// If it's in the local cache, we return true and we can abort the download because
+// this method does the rest of the work.
+bool nsImapMockChannel::ReadFromLocalCache()
+{
+ nsresult rv = NS_OK;
+
+ nsCOMPtr<nsIImapUrl> imapUrl = do_QueryInterface(m_url);
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_url, &rv);
+
+ bool useLocalCache = false;
+ mailnewsUrl->GetMsgIsInLocalCache(&useLocalCache);
+ if (useLocalCache)
+ {
+ nsAutoCString messageIdString;
+
+ SetupPartExtractorListener(imapUrl, m_channelListener);
+
+ imapUrl->GetListOfMessageIds(messageIdString);
+ nsCOMPtr <nsIMsgFolder> folder;
+ rv = mailnewsUrl->GetFolder(getter_AddRefs(folder));
+ if (folder && NS_SUCCEEDED(rv))
+ {
+ // we want to create a file channel and read the msg from there.
+ nsCOMPtr<nsIInputStream> fileStream;
+ nsMsgKey msgKey = strtoul(messageIdString.get(), nullptr, 10);
+ uint32_t size;
+ int64_t offset;
+ rv = folder->GetOfflineFileStream(msgKey, &offset, &size, getter_AddRefs(fileStream));
+ // get the file channel from the folder, somehow (through the message or
+ // folder sink?) We also need to set the transfer offset to the message offset
+ if (fileStream && NS_SUCCEEDED(rv))
+ {
+ // dougt - This may break the ablity to "cancel" a read from offline mail reading.
+ // fileChannel->SetLoadGroup(m_loadGroup);
+ nsImapCacheStreamListener * cacheListener = new nsImapCacheStreamListener();
+ NS_ADDREF(cacheListener);
+ cacheListener->Init(m_channelListener, this);
+
+ // create a stream pump that will async read the specified amount of data.
+ // XXX make offset/size 64-bit ints
+ nsCOMPtr<nsIInputStreamPump> pump;
+ rv = NS_NewInputStreamPump(getter_AddRefs(pump), fileStream,
+ offset, (int64_t) size);
+ if (NS_SUCCEEDED(rv))
+ rv = pump->AsyncRead(cacheListener, m_channelContext);
+
+ NS_RELEASE(cacheListener);
+
+ if (NS_SUCCEEDED(rv)) // ONLY if we succeeded in actually starting the read should we return
+ {
+ // if the msg is unread, we should mark it read on the server. This lets
+ // the code running this url we're loading from the cache, if it cares.
+ imapUrl->SetMsgLoadingFromCache(true);
+ return true;
+ }
+ } // if we got an offline file transport
+ } // if we got the folder for this url
+ } // if use local cache
+
+ return false;
+}
+
+NS_IMETHODIMP nsImapMockChannel::AsyncOpen(nsIStreamListener *listener, nsISupports *ctxt)
+{
+ nsresult rv = NS_OK;
+
+ int32_t port;
+ if (!m_url)
+ return NS_ERROR_NULL_POINTER;
+ rv = m_url->GetPort(&port);
+ if (NS_FAILED(rv))
+ return rv;
+
+ rv = NS_CheckPortSafety(port, "imap");
+ if (NS_FAILED(rv))
+ return rv;
+
+ // set the stream listener and then load the url
+ m_channelContext = ctxt;
+ NS_ASSERTION(!m_channelListener, "shouldn't already have a listener");
+ m_channelListener = listener;
+ nsCOMPtr<nsIImapUrl> imapUrl (do_QueryInterface(m_url));
+
+ nsImapAction imapAction;
+ imapUrl->GetImapAction(&imapAction);
+
+ bool externalLink = true;
+ imapUrl->GetExternalLinkUrl(&externalLink);
+
+ if (externalLink)
+ {
+ // for security purposes, only allow imap urls originating from external sources
+ // perform a limited set of actions.
+ // Currently the allowed set includes:
+ // 1) folder selection
+ // 2) message fetch
+ // 3) message part fetch
+
+ if (! (imapAction == nsIImapUrl::nsImapSelectFolder || imapAction == nsIImapUrl::nsImapMsgFetch || imapAction == nsIImapUrl::nsImapOpenMimePart
+ || imapAction == nsIImapUrl::nsImapMsgFetchPeek))
+ return NS_ERROR_FAILURE; // abort the running of this url....it failed a security check
+ }
+
+ if (ReadFromLocalCache())
+ {
+ (void) NotifyStartEndReadFromCache(true);
+ return NS_OK;
+ }
+
+ // okay, it's not in the local cache, now check the memory cache...
+ // but we can't download for offline use from the memory cache
+ if (imapAction != nsIImapUrl::nsImapMsgDownloadForOffline)
+ {
+ rv = OpenCacheEntry();
+ if (NS_SUCCEEDED(rv))
+ return rv;
+ }
+
+ SetupPartExtractorListener(imapUrl, m_channelListener);
+ // if for some reason open cache entry failed then just default to opening an imap connection for the url
+ return ReadFromImapConnection();
+}
+
+NS_IMETHODIMP nsImapMockChannel::AsyncOpen2(nsIStreamListener *aListener)
+{
+ nsCOMPtr<nsIStreamListener> listener = aListener;
+ nsresult rv = nsContentSecurityManager::doContentSecurityCheck(this, listener);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return AsyncOpen(listener, nullptr);
+}
+
+nsresult nsImapMockChannel::SetupPartExtractorListener(nsIImapUrl * aUrl, nsIStreamListener * aConsumer)
+{
+ // if the url we are loading refers to a specific part then we need
+ // libmime to extract that part from the message for us.
+ bool refersToPart = false;
+ aUrl->GetMimePartSelectorDetected(&refersToPart);
+ if (refersToPart)
+ {
+ nsCOMPtr<nsIStreamConverterService> converter = do_GetService("@mozilla.org/streamConverters;1");
+ if (converter && aConsumer)
+ {
+ nsCOMPtr<nsIStreamListener> newConsumer;
+ converter->AsyncConvertData("message/rfc822", "*/*",
+ aConsumer, static_cast<nsIChannel *>(this), getter_AddRefs(newConsumer));
+ if (newConsumer)
+ m_channelListener = newConsumer;
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMockChannel::GetLoadFlags(nsLoadFlags *aLoadFlags)
+{
+ //*aLoadFlags = nsIRequest::LOAD_NORMAL;
+ *aLoadFlags = mLoadFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMockChannel::SetLoadFlags(nsLoadFlags aLoadFlags)
+{
+ mLoadFlags = aLoadFlags;
+ return NS_OK; // don't fail when trying to set this
+}
+
+NS_IMETHODIMP nsImapMockChannel::GetContentType(nsACString &aContentType)
+{
+ if (mContentType.IsEmpty())
+ {
+ nsImapAction imapAction = 0;
+ if (m_url)
+ {
+ nsCOMPtr<nsIImapUrl> imapUrl = do_QueryInterface(m_url);
+ if (imapUrl)
+ {
+ imapUrl->GetImapAction(&imapAction);
+ }
+ }
+ if (imapAction == nsIImapUrl::nsImapSelectFolder)
+ aContentType.AssignLiteral("x-application-imapfolder");
+ else
+ aContentType.AssignLiteral("message/rfc822");
+ }
+ else
+ aContentType = mContentType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMockChannel::SetContentType(const nsACString &aContentType)
+{
+ nsAutoCString charset;
+ nsresult rv = NS_ParseResponseContentType(aContentType, mContentType, charset);
+ if (NS_FAILED(rv) || mContentType.IsEmpty())
+ mContentType.AssignLiteral(UNKNOWN_CONTENT_TYPE);
+ return rv;
+}
+
+NS_IMETHODIMP nsImapMockChannel::GetContentCharset(nsACString &aContentCharset)
+{
+ aContentCharset.Assign(mCharset);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMockChannel::SetContentCharset(const nsACString &aContentCharset)
+{
+ mCharset.Assign(aContentCharset);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapMockChannel::GetContentDisposition(uint32_t *aContentDisposition)
+{
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsImapMockChannel::SetContentDisposition(uint32_t aContentDisposition)
+{
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsImapMockChannel::GetContentDispositionFilename(nsAString &aContentDispositionFilename)
+{
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsImapMockChannel::SetContentDispositionFilename(const nsAString &aContentDispositionFilename)
+{
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+nsImapMockChannel::GetContentDispositionHeader(nsACString &aContentDispositionHeader)
+{
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP nsImapMockChannel::GetContentLength(int64_t * aContentLength)
+{
+ *aContentLength = mContentLength;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapMockChannel::SetContentLength(int64_t aContentLength)
+{
+ mContentLength = aContentLength;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMockChannel::GetOwner(nsISupports * *aPrincipal)
+{
+ *aPrincipal = mOwner;
+ NS_IF_ADDREF(*aPrincipal);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMockChannel::SetOwner(nsISupports * aPrincipal)
+{
+ mOwner = aPrincipal;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMockChannel::GetSecurityInfo(nsISupports * *aSecurityInfo)
+{
+ NS_IF_ADDREF(*aSecurityInfo = mSecurityInfo);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMockChannel::SetSecurityInfo(nsISupports *aSecurityInfo)
+{
+ mSecurityInfo = aSecurityInfo;
+ return NS_OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// From nsIRequest
+////////////////////////////////////////////////////////////////////////////////
+
+NS_IMETHODIMP nsImapMockChannel::GetName(nsACString &result)
+{
+ if (m_url)
+ return m_url->GetSpec(result);
+ result.Truncate();
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMockChannel::IsPending(bool *result)
+{
+ *result = m_channelListener != nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMockChannel::GetStatus(nsresult *status)
+{
+ *status = m_cancelStatus;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMockChannel::SetImapProtocol(nsIImapProtocol *aProtocol)
+{
+ mProtocol = do_GetWeakReference(aProtocol);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMockChannel::Cancel(nsresult status)
+{
+ NS_WARNING_ASSERTION(NS_IsMainThread(),
+ "nsImapMockChannel::Cancel should only be called from UI thread");
+ m_cancelStatus = status;
+ nsCOMPtr<nsIImapProtocol> imapProtocol = do_QueryReferent(mProtocol);
+
+ // if we aren't reading from the cache and we get canceled...doom our cache entry...
+ if (m_url)
+ {
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_url);
+ DoomCacheEntry(mailnewsUrl);
+ }
+
+ // Required for killing ImapProtocol thread
+ if (imapProtocol)
+ imapProtocol->TellThreadToDie(false);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMockChannel::Suspend()
+{
+ NS_NOTREACHED("nsImapMockChannel::Suspend");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsImapMockChannel::Resume()
+{
+ NS_NOTREACHED("nsImapMockChannel::Resume");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsImapMockChannel::GetNotificationCallbacks(nsIInterfaceRequestor* *aNotificationCallbacks)
+{
+ *aNotificationCallbacks = mCallbacks.get();
+ NS_IF_ADDREF(*aNotificationCallbacks);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapMockChannel::SetNotificationCallbacks(nsIInterfaceRequestor* aNotificationCallbacks)
+{
+ mCallbacks = aNotificationCallbacks;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapMockChannel::OnTransportStatus(nsITransport *transport, nsresult status,
+ int64_t progress, int64_t progressMax)
+{
+ if (NS_FAILED(m_cancelStatus) || (mLoadFlags & LOAD_BACKGROUND) || !m_url)
+ return NS_OK;
+
+ // these transport events should not generate any status messages
+ if (status == NS_NET_STATUS_RECEIVING_FROM ||
+ status == NS_NET_STATUS_SENDING_TO)
+ return NS_OK;
+
+ if (!mProgressEventSink)
+ {
+ NS_QueryNotificationCallbacks(mCallbacks, m_loadGroup, mProgressEventSink);
+ if (!mProgressEventSink)
+ return NS_OK;
+ }
+
+ nsAutoCString host;
+ m_url->GetHost(host);
+
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_url);
+ if (mailnewsUrl)
+ {
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ mailnewsUrl->GetServer(getter_AddRefs(server));
+ if (server)
+ server->GetRealHostName(host);
+ }
+ mProgressEventSink->OnStatus(this, nullptr, status,
+ NS_ConvertUTF8toUTF16(host).get());
+
+ return NS_OK;
+}
+
+
+nsIMAPMailboxInfo::nsIMAPMailboxInfo(const nsACString &aName, char aDelimiter)
+{
+ mMailboxName.Assign(aName);
+ mDelimiter = aDelimiter;
+ mChildrenListed = false;
+}
+
+nsIMAPMailboxInfo::~nsIMAPMailboxInfo()
+{
+}
+
+void nsIMAPMailboxInfo::SetChildrenListed(bool childrenListed)
+{
+ mChildrenListed = childrenListed;
+}
+
+bool nsIMAPMailboxInfo::GetChildrenListed()
+{
+ return mChildrenListed;
+}
+
+const nsACString& nsIMAPMailboxInfo::GetMailboxName()
+{
+ return mMailboxName;
+}
+
+char nsIMAPMailboxInfo::GetDelimiter()
+{
+ return mDelimiter;
+}