diff options
Diffstat (limited to 'comm/mailnews/imap/src/nsImapProtocol.cpp')
-rw-r--r-- | comm/mailnews/imap/src/nsImapProtocol.cpp | 9915 |
1 files changed, 9915 insertions, 0 deletions
diff --git a/comm/mailnews/imap/src/nsImapProtocol.cpp b/comm/mailnews/imap/src/nsImapProtocol.cpp new file mode 100644 index 0000000000..b0a9214749 --- /dev/null +++ b/comm/mailnews/imap/src/nsImapProtocol.cpp @@ -0,0 +1,9915 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// as does this +#include "msgCore.h" // for pre-compiled headers +#include "nsMsgUtils.h" + +#include "nsImapStringBundle.h" +#include "nsVersionComparator.h" + +#include "nsThreadUtils.h" +#include "nsIMsgStatusFeedback.h" +#include "nsImapCore.h" +#include "nsImapProtocol.h" +#include "nsIMsgMailNewsUrl.h" +#include "../public/nsIImapHostSessionList.h" +#include "nsImapMailFolder.h" +#include "nsIMsgAccountManager.h" +#include "nsImapServerResponseParser.h" +#include "nspr.h" +#include "plbase64.h" +#include "nsIEventTarget.h" +#include "nsIImapService.h" +#include "nsISocketTransportService.h" +#include "nsIStreamListenerTee.h" +#include "nsIInputStreamPump.h" +#include "nsNetUtil.h" +#include "nsIDBFolderInfo.h" +#include "nsIPipe.h" +#include "nsIMsgFolder.h" +#include "nsMsgMessageFlags.h" +#include "nsTextFormatter.h" +#include "nsTransportUtils.h" +#include "nsIMsgHdr.h" +#include "nsMsgI18N.h" +// for the memory cache... +#include "nsICacheEntry.h" +#include "nsICacheStorage.h" +#include "nsICacheEntryOpenCallback.h" +#include "CacheObserver.h" +#include "nsIURIMutator.h" + +#include "nsIDocShell.h" +#include "nsILoadInfo.h" +#include "nsCOMPtr.h" +#include "nsMimeTypes.h" +#include "nsIInterfaceRequestor.h" +#include "nsXPCOMCIDInternal.h" +#include "nsIXULAppInfo.h" +#include "nsSocketTransportService2.h" +#include "nsSyncRunnableHelpers.h" +#include "nsICancelable.h" + +// netlib required files +#include "nsIStreamListener.h" +#include "nsIMsgIncomingServer.h" +#include "nsIImapIncomingServer.h" +#include "nsIPrefBranch.h" +#include "nsIPrefService.h" +#include "nsIPrefLocalizedString.h" +#include "nsImapUtils.h" +#include "nsIStreamConverterService.h" +#include "nsIProxyInfo.h" +#include "nsITLSSocketControl.h" +#include "nsITransportSecurityInfo.h" +#include "nsProxyRelease.h" +#include "nsDebug.h" +#include "nsMsgCompressIStream.h" +#include "nsMsgCompressOStream.h" +#include "mozilla/Logging.h" +#include "mozilla/Attributes.h" +#include "mozilla/SlicedInputStream.h" +#include "nsIPrincipal.h" +#include "nsContentSecurityManager.h" + +// imap event sinks +#include "nsIImapMailFolderSink.h" +#include "nsIImapServerSink.h" +#include "nsIImapMessageSink.h" + +#include "mozilla/dom/InternalResponse.h" +#include "mozilla/NullPrincipal.h" + +// TLS alerts +#include "NSSErrorsService.h" + +#include "mozilla/SyncRunnable.h" + +using namespace mozilla; + +LazyLogModule IMAP("IMAP"); +LazyLogModule IMAP_CS("IMAP_CS"); +LazyLogModule IMAPCache("IMAPCache"); + +#define ONE_SECOND ((uint32_t)1000) // one second + +#define OUTPUT_BUFFER_SIZE (4096 * 2) + +#define IMAP_ENV_HEADERS "From To Cc Bcc Subject Date Message-ID " +#define IMAP_DB_HEADERS \ + "Priority X-Priority References Newsgroups In-Reply-To Content-Type " \ + "Reply-To" +#define IMAP_ENV_AND_DB_HEADERS IMAP_ENV_HEADERS IMAP_DB_HEADERS +static const PRIntervalTime kImapSleepTime = PR_MillisecondsToInterval(60000); +static int32_t gPromoteNoopToCheckCount = 0; +static const uint32_t kFlagChangesBeforeCheck = 10; +static const int32_t kMaxSecondsBeforeCheck = 600; + +class AutoProxyReleaseMsgWindow { + public: + AutoProxyReleaseMsgWindow() : mMsgWindow() {} + ~AutoProxyReleaseMsgWindow() { + NS_ReleaseOnMainThread("AutoProxyReleaseMsgWindow::mMsgWindow", + dont_AddRef(mMsgWindow)); + } + nsIMsgWindow** StartAssignment() { + MOZ_ASSERT(!mMsgWindow); + return &mMsgWindow; + } + operator nsIMsgWindow*() { return mMsgWindow; } + + private: + nsIMsgWindow* mMsgWindow; +}; + +nsIMsgWindow** getter_AddRefs(AutoProxyReleaseMsgWindow& aSmartPtr) { + return aSmartPtr.StartAssignment(); +} + +NS_IMPL_ISUPPORTS(nsMsgImapHdrXferInfo, nsIImapHeaderXferInfo) + +nsMsgImapHdrXferInfo::nsMsgImapHdrXferInfo() : m_hdrInfos(kNumHdrsToXfer) { + m_nextFreeHdrInfo = 0; +} + +nsMsgImapHdrXferInfo::~nsMsgImapHdrXferInfo() {} + +NS_IMETHODIMP nsMsgImapHdrXferInfo::GetNumHeaders(int32_t* aNumHeaders) { + *aNumHeaders = m_nextFreeHdrInfo; + return NS_OK; +} + +NS_IMETHODIMP nsMsgImapHdrXferInfo::GetHeader(int32_t hdrIndex, + nsIImapHeaderInfo** aResult) { + // If the header index is more than (or equal to) our next free pointer, then + // its a header we haven't really got and the caller has done something + // wrong. + NS_ENSURE_TRUE(hdrIndex < m_nextFreeHdrInfo, NS_ERROR_NULL_POINTER); + + NS_IF_ADDREF(*aResult = m_hdrInfos.SafeObjectAt(hdrIndex)); + if (!*aResult) return NS_ERROR_NULL_POINTER; + return NS_OK; +} + +static const int32_t kInitLineHdrCacheSize = 512; // should be about right + +nsIImapHeaderInfo* nsMsgImapHdrXferInfo::StartNewHdr() { + if (m_nextFreeHdrInfo >= kNumHdrsToXfer) return nullptr; + + nsIImapHeaderInfo* result = m_hdrInfos.SafeObjectAt(m_nextFreeHdrInfo++); + if (result) return result; + + nsMsgImapLineDownloadCache* lineCache = new nsMsgImapLineDownloadCache(); + if (!lineCache) return nullptr; + + lineCache->GrowBuffer(kInitLineHdrCacheSize); + + m_hdrInfos.AppendObject(lineCache); + + return lineCache; +} + +// maybe not needed... +void nsMsgImapHdrXferInfo::FinishCurrentHdr() { + // nothing to do? +} + +void nsMsgImapHdrXferInfo::ResetAll() { + int32_t count = m_hdrInfos.Count(); + for (int32_t i = 0; i < count; i++) { + nsIImapHeaderInfo* hdrInfo = m_hdrInfos[i]; + if (hdrInfo) hdrInfo->ResetCache(); + } + m_nextFreeHdrInfo = 0; +} + +void nsMsgImapHdrXferInfo::ReleaseAll() { + m_hdrInfos.Clear(); + m_nextFreeHdrInfo = 0; +} + +NS_IMPL_ISUPPORTS(nsMsgImapLineDownloadCache, nsIImapHeaderInfo) + +// **** helper class for downloading line **** +nsMsgImapLineDownloadCache::nsMsgImapLineDownloadCache() { + fLineInfo = (msg_line_info*)PR_CALLOC(sizeof(msg_line_info)); + fLineInfo->uidOfMessage = nsMsgKey_None; + m_msgSize = 0; +} + +nsMsgImapLineDownloadCache::~nsMsgImapLineDownloadCache() { + PR_Free(fLineInfo); +} + +uint32_t nsMsgImapLineDownloadCache::CurrentUID() { + return fLineInfo->uidOfMessage; +} + +uint32_t nsMsgImapLineDownloadCache::SpaceAvailable() { + MOZ_ASSERT(kDownLoadCacheSize >= m_bufferPos); + if (kDownLoadCacheSize <= m_bufferPos) return 0; + return kDownLoadCacheSize - m_bufferPos; +} + +msg_line_info* nsMsgImapLineDownloadCache::GetCurrentLineInfo() { + AppendBuffer("", 1); // null terminate the buffer + fLineInfo->adoptedMessageLine = GetBuffer(); + return fLineInfo; +} + +NS_IMETHODIMP nsMsgImapLineDownloadCache::ResetCache() { + ResetWritePos(); + return NS_OK; +} + +bool nsMsgImapLineDownloadCache::CacheEmpty() { return m_bufferPos == 0; } + +NS_IMETHODIMP nsMsgImapLineDownloadCache::CacheLine(const char* line, + uint32_t uid) { + fLineInfo->uidOfMessage = uid; + return AppendString(line); +} + +/* attribute nsMsgKey msgUid; */ +NS_IMETHODIMP nsMsgImapLineDownloadCache::GetMsgUid(nsMsgKey* aMsgUid) { + *aMsgUid = fLineInfo->uidOfMessage; + return NS_OK; +} +NS_IMETHODIMP nsMsgImapLineDownloadCache::SetMsgUid(nsMsgKey aMsgUid) { + fLineInfo->uidOfMessage = aMsgUid; + return NS_OK; +} + +/* attribute long msgSize; */ +NS_IMETHODIMP nsMsgImapLineDownloadCache::GetMsgSize(int32_t* aMsgSize) { + *aMsgSize = m_msgSize; + return NS_OK; +} + +NS_IMETHODIMP nsMsgImapLineDownloadCache::SetMsgSize(int32_t aMsgSize) { + m_msgSize = aMsgSize; + return NS_OK; +} + +/* readonly attribute ACString msgHdrs; */ +NS_IMETHODIMP nsMsgImapLineDownloadCache::GetMsgHdrs(nsACString& aMsgHdrs) { + AppendBuffer("", 1); // null terminate the buffer + aMsgHdrs.Assign(GetBuffer()); + return NS_OK; +} + +// The following macros actually implement addref, release and query interface +// for our component. +NS_IMPL_ADDREF_INHERITED(nsImapProtocol, nsMsgProtocol) +NS_IMPL_RELEASE_INHERITED(nsImapProtocol, nsMsgProtocol) + +NS_INTERFACE_MAP_BEGIN(nsImapProtocol) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIImapProtocol) + NS_INTERFACE_MAP_ENTRY(nsIImapProtocol) + NS_INTERFACE_MAP_ENTRY(nsIProtocolProxyCallback) + NS_INTERFACE_MAP_ENTRY(nsIInputStreamCallback) + NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) + NS_INTERFACE_MAP_ENTRY(nsIImapProtocolSink) + NS_INTERFACE_MAP_ENTRY(nsIMsgAsyncPromptListener) +NS_INTERFACE_MAP_END + +static int32_t gTooFastTime = 2; +static int32_t gIdealTime = 4; +static int32_t gChunkAddSize = 16384; +static int32_t gChunkSize = 250000; +static int32_t gChunkThreshold = gChunkSize + gChunkSize / 2; +static bool gChunkSizeDirty = false; +static bool gFetchByChunks = true; +static bool gInitialized = false; +static bool gHideUnusedNamespaces = true; +static bool gHideOtherUsersFromList = false; +static bool gUseEnvelopeCmd = false; +static bool gUseLiteralPlus = true; +static bool gExpungeAfterDelete = false; +static bool gCheckDeletedBeforeExpunge = false; // bug 235004 +static int32_t gResponseTimeout = 100; +static int32_t gAppendTimeout = gResponseTimeout / 5; +static nsImapProtocol::TCPKeepalive gTCPKeepalive; +static bool gUseDiskCache2 = true; // Use disk cache instead of memory cache +static nsCOMPtr<nsICacheStorage> gCache2Storage; + +// let delete model control expunging, i.e., don't ever expunge when the +// user chooses the imap delete model, otherwise, expunge when over the +// threshold. This is the normal TB behavior. +static const int32_t kAutoExpungeDeleteModel = 0; // default +// Expunge whenever the folder is opened regardless of delete model or number +// of marked deleted messages present in the folder. +static const int32_t kAutoExpungeAlways = 1; +// Expunge when over the threshold, independent of the delete model. +static const int32_t kAutoExpungeOnThreshold = 2; +// Set mail.imap.expunge_option to kAutoExpungeNever to NEVER do an auto- +// expunge. This is useful when doing a bulk transfer of folders and messages +// between imap servers. +static const int32_t kAutoExpungeNever = 3; + +static int32_t gExpungeOption = kAutoExpungeDeleteModel; +static int32_t gExpungeThreshold = 20; + +const int32_t kAppBufSize = 100; +// can't use static nsCString because it shows up as a leak. +static char gAppName[kAppBufSize]; +static char gAppVersion[kAppBufSize]; + +nsresult nsImapProtocol::GlobalInitialization(nsIPrefBranch* aPrefBranch) { + gInitialized = true; + + aPrefBranch->GetIntPref("mail.imap.chunk_fast", + &gTooFastTime); // secs we read too little too fast + aPrefBranch->GetIntPref("mail.imap.chunk_ideal", + &gIdealTime); // secs we read enough in good time + aPrefBranch->GetIntPref( + "mail.imap.chunk_add", + &gChunkAddSize); // buffer size to add when wasting time + aPrefBranch->GetIntPref("mail.imap.chunk_size", &gChunkSize); + aPrefBranch->GetIntPref("mail.imap.min_chunk_size_threshold", + &gChunkThreshold); + aPrefBranch->GetBoolPref("mail.imap.hide_other_users", + &gHideOtherUsersFromList); + aPrefBranch->GetBoolPref("mail.imap.hide_unused_namespaces", + &gHideUnusedNamespaces); + aPrefBranch->GetIntPref("mail.imap.noop_check_count", + &gPromoteNoopToCheckCount); + aPrefBranch->GetBoolPref("mail.imap.use_envelope_cmd", &gUseEnvelopeCmd); + aPrefBranch->GetBoolPref("mail.imap.use_literal_plus", &gUseLiteralPlus); + aPrefBranch->GetBoolPref("mail.imap.expunge_after_delete", + &gExpungeAfterDelete); + aPrefBranch->GetBoolPref("mail.imap.use_disk_cache2", &gUseDiskCache2); + aPrefBranch->GetBoolPref("mail.imap.check_deleted_before_expunge", + &gCheckDeletedBeforeExpunge); + aPrefBranch->GetIntPref("mail.imap.expunge_option", &gExpungeOption); + aPrefBranch->GetIntPref("mail.imap.expunge_threshold_number", + &gExpungeThreshold); + aPrefBranch->GetIntPref("mailnews.tcptimeout", &gResponseTimeout); + gAppendTimeout = gResponseTimeout / 5; + + gTCPKeepalive.enabled.store(false, std::memory_order_relaxed); + gTCPKeepalive.idleTimeS.store(-1, std::memory_order_relaxed); + gTCPKeepalive.retryIntervalS.store(-1, std::memory_order_relaxed); + + nsCOMPtr<nsIXULAppInfo> appInfo(do_GetService(XULAPPINFO_SERVICE_CONTRACTID)); + + if (appInfo) { + nsCString appName, appVersion; + appInfo->GetName(appName); + appInfo->GetVersion(appVersion); + PL_strncpyz(gAppName, appName.get(), kAppBufSize); + PL_strncpyz(gAppVersion, appVersion.get(), kAppBufSize); + } + return NS_OK; +} + +class nsImapTransportEventSink final : public nsITransportEventSink { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSITRANSPORTEVENTSINK + + private: + friend class nsImapProtocol; + + virtual ~nsImapTransportEventSink() = default; + nsresult ApplyTCPKeepalive(nsISocketTransport* aTransport); + + nsCOMPtr<nsITransportEventSink> m_proxy; +}; + +NS_IMPL_ISUPPORTS(nsImapTransportEventSink, nsITransportEventSink) + +NS_IMETHODIMP +nsImapTransportEventSink::OnTransportStatus(nsITransport* aTransport, + nsresult aStatus, int64_t aProgress, + int64_t aProgressMax) { + if (aStatus == NS_NET_STATUS_CONNECTED_TO) { + nsCOMPtr<nsISocketTransport> sockTrans(do_QueryInterface(aTransport)); + if (!NS_WARN_IF(!sockTrans)) ApplyTCPKeepalive(sockTrans); + } + + if (NS_WARN_IF(!m_proxy)) return NS_OK; + + return m_proxy->OnTransportStatus(aTransport, aStatus, aProgress, + aProgressMax); +} + +nsresult nsImapTransportEventSink::ApplyTCPKeepalive( + nsISocketTransport* aTransport) { + nsresult rv; + + bool kaEnabled = gTCPKeepalive.enabled.load(std::memory_order_relaxed); + if (kaEnabled) { + // TCP keepalive idle time, don't mistake with IMAP IDLE. + int32_t kaIdleTime = + gTCPKeepalive.idleTimeS.load(std::memory_order_relaxed); + int32_t kaRetryInterval = + gTCPKeepalive.retryIntervalS.load(std::memory_order_relaxed); + + if (kaIdleTime < 0 || kaRetryInterval < 0) { + if (NS_WARN_IF(!net::gSocketTransportService)) + return NS_ERROR_NOT_INITIALIZED; + } + if (kaIdleTime < 0) { + rv = net::gSocketTransportService->GetKeepaliveIdleTime(&kaIdleTime); + if (NS_FAILED(rv)) { + MOZ_LOG(IMAP, LogLevel::Error, + ("GetKeepaliveIdleTime() failed, %" PRIx32, + static_cast<uint32_t>(rv))); + return rv; + } + } + if (kaRetryInterval < 0) { + rv = net::gSocketTransportService->GetKeepaliveRetryInterval( + &kaRetryInterval); + if (NS_FAILED(rv)) { + MOZ_LOG(IMAP, LogLevel::Error, + ("GetKeepaliveRetryInterval() failed, %" PRIx32, + static_cast<uint32_t>(rv))); + return rv; + } + } + + MOZ_ASSERT(kaIdleTime > 0); + MOZ_ASSERT(kaRetryInterval > 0); + rv = aTransport->SetKeepaliveVals(kaIdleTime, kaRetryInterval); + if (NS_FAILED(rv)) { + MOZ_LOG(IMAP, LogLevel::Error, + ("SetKeepaliveVals(%" PRId32 ", %" PRId32 ") failed, %" PRIx32, + kaIdleTime, kaRetryInterval, static_cast<uint32_t>(rv))); + return rv; + } + } + + rv = aTransport->SetKeepaliveEnabled(kaEnabled); + if (NS_FAILED(rv)) { + MOZ_LOG(IMAP, LogLevel::Error, + ("SetKeepaliveEnabled(%s) failed, %" PRIx32, + kaEnabled ? "true" : "false", static_cast<uint32_t>(rv))); + return rv; + } + return NS_OK; +} + +// This runnable runs on IMAP thread. +class nsImapProtocolMainLoopRunnable final : public mozilla::Runnable { + public: + explicit nsImapProtocolMainLoopRunnable(nsImapProtocol* aProtocol) + : mozilla::Runnable("nsImapProtocolEventLoopRunnable"), + mProtocol(aProtocol) {} + + NS_IMETHOD Run() { + MOZ_ASSERT(!NS_IsMainThread()); + + if (!mProtocol->RunImapThreadMainLoop()) { + // We already run another IMAP event loop. + return NS_OK; + } + + // Release protocol object on the main thread to avoid destruction of + // nsImapProtocol on the IMAP thread, which causes grief for weak + // references. + NS_ReleaseOnMainThread("nsImapProtocol::this", mProtocol.forget()); + + // shutdown this thread, but do it from the main thread + nsCOMPtr<nsIThread> imapThread(do_GetCurrentThread()); + if (NS_FAILED(NS_DispatchToMainThread( + NS_NewRunnableFunction("nsImapProtorolMainLoopRunnable::Run", + [imapThread = std::move(imapThread)]() { + imapThread->Shutdown(); + })))) { + NS_WARNING("Failed to dispatch nsImapThreadShutdownEvent"); + } + return NS_OK; + } + + private: + RefPtr<nsImapProtocol> mProtocol; +}; + +nsImapProtocol::nsImapProtocol() + : nsMsgProtocol(nullptr), + m_dataAvailableMonitor("imapDataAvailable"), + m_urlReadyToRunMonitor("imapUrlReadyToRun"), + m_pseudoInterruptMonitor("imapPseudoInterrupt"), + m_dataMemberMonitor("imapDataMember"), + m_threadDeathMonitor("imapThreadDeath"), + m_waitForBodyIdsMonitor("imapWaitForBodyIds"), + m_fetchBodyListMonitor("imapFetchBodyList"), + m_passwordReadyMonitor("imapPasswordReady"), + mLock("nsImapProtocol.mLock"), + m_parser(*this) { + m_urlInProgress = false; + m_idle = false; + m_retryUrlOnError = false; + m_useIdle = true; // by default, use it + m_useCondStore = true; + m_useCompressDeflate = true; + m_ignoreExpunges = false; + m_prefAuthMethods = kCapabilityUndefined; + m_failedAuthMethods = 0; + m_currentAuthMethod = kCapabilityUndefined; + m_socketType = nsMsgSocketType::trySTARTTLS; + m_connectionStatus = NS_OK; + m_safeToCloseConnection = false; + m_hostSessionList = nullptr; + m_isGmailServer = false; + m_fetchingWholeMessage = false; + m_allowUTF8Accept = false; + + nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID)); + NS_ASSERTION(prefBranch, "FAILED to create the preference service"); + + // read in the accept languages preference + if (prefBranch) { + if (!gInitialized) GlobalInitialization(prefBranch); + + nsCOMPtr<nsIPrefLocalizedString> prefString; + prefBranch->GetComplexValue("intl.accept_languages", + NS_GET_IID(nsIPrefLocalizedString), + getter_AddRefs(prefString)); + if (prefString) prefString->ToString(getter_Copies(mAcceptLanguages)); + + nsCString customDBHeaders; + prefBranch->GetCharPref("mailnews.customDBHeaders", customDBHeaders); + + ParseString(customDBHeaders, ' ', mCustomDBHeaders); + prefBranch->GetBoolPref("mailnews.display.prefer_plaintext", + &m_preferPlainText); + + nsAutoCString customHeaders; + prefBranch->GetCharPref("mailnews.customHeaders", customHeaders); + customHeaders.StripWhitespace(); + ParseString(customHeaders, ':', mCustomHeaders); + + nsresult rv; + bool bVal = false; + rv = prefBranch->GetBoolPref("mail.imap.tcp_keepalive.enabled", &bVal); + if (NS_SUCCEEDED(rv)) + gTCPKeepalive.enabled.store(bVal, std::memory_order_relaxed); + + if (bVal) { + int32_t val; + // TCP keepalive idle time, don't mistake with IMAP IDLE. + rv = prefBranch->GetIntPref("mail.imap.tcp_keepalive.idle_time", &val); + if (NS_SUCCEEDED(rv) && val >= 0) + gTCPKeepalive.idleTimeS.store( + std::min<int32_t>(std::max(val, 1), net::kMaxTCPKeepIdle), + std::memory_order_relaxed); + + rv = prefBranch->GetIntPref("mail.imap.tcp_keepalive.retry_interval", + &val); + if (NS_SUCCEEDED(rv) && val >= 0) + gTCPKeepalive.retryIntervalS.store( + std::min<int32_t>(std::max(val, 1), net::kMaxTCPKeepIntvl), + std::memory_order_relaxed); + } + } + + // ***** Thread support ***** + m_thread = nullptr; + m_imapThreadIsRunning = false; + m_currentServerCommandTagNumber = 0; + m_active = false; + m_folderNeedsSubscribing = false; + m_folderNeedsACLRefreshed = false; + m_threadShouldDie = false; + m_inThreadShouldDie = false; + m_pseudoInterrupted = false; + m_nextUrlReadyToRun = false; + m_idleResponseReadyToHandle = false; + m_trackingTime = false; + m_curFetchSize = 0; + m_startTime = 0; + m_endTime = 0; + m_lastActiveTime = 0; + m_lastProgressTime = 0; + ResetProgressInfo(); + + m_tooFastTime = 0; + m_idealTime = 0; + m_chunkAddSize = 0; + m_chunkStartSize = 0; + m_fetchByChunks = true; + m_sendID = true; + m_chunkSize = 0; + m_chunkThreshold = 0; + m_fromHeaderSeen = false; + m_closeNeededBeforeSelect = false; + m_needNoop = false; + m_noopCount = 0; + m_fetchBodyListIsNew = false; + m_flagChangeCount = 0; + m_lastCheckTime = PR_Now(); + + m_hierarchyNameState = kNoOperationInProgress; + m_discoveryStatus = eContinue; + + // m_dataOutputBuf is used by Send Data + m_dataOutputBuf = (char*)PR_CALLOC(sizeof(char) * OUTPUT_BUFFER_SIZE); + + // used to buffer incoming data by ReadNextLine + m_inputStreamBuffer = new nsMsgLineStreamBuffer( + OUTPUT_BUFFER_SIZE, true /* allocate new lines */, + false /* leave CRLFs on the returned string */); + m_currentBiffState = nsIMsgFolder::nsMsgBiffState_Unknown; + m_progressStringName.Truncate(); + m_stringIndex = IMAP_EMPTY_STRING_INDEX; + m_progressExpectedNumber = 0; + memset(m_progressCurrentNumber, 0, sizeof m_progressCurrentNumber); + + // since these are embedded in the nsImapProtocol object, but passed + // through proxied xpcom methods, just AddRef them here. + m_hdrDownloadCache = new nsMsgImapHdrXferInfo(); + m_downloadLineCache = new nsMsgImapLineDownloadCache(); + + // subscription + m_autoSubscribe = true; + m_autoUnsubscribe = true; + m_autoSubscribeOnOpen = true; + m_deletableChildren = nullptr; + + mFolderLastModSeq = 0; + + Configure(gTooFastTime, gIdealTime, gChunkAddSize, gChunkSize, + gChunkThreshold, gFetchByChunks); + m_forceSelect = false; + m_capabilityResponseOccurred = true; + + m_imapAction = 0; + m_bytesToChannel = 0; + m_passwordStatus = NS_OK; + m_passwordObtained = false; + mFolderTotalMsgCount = 0; + mFolderHighestUID = 0; + m_notifySearchHit = false; + m_preferPlainText = false; + m_uidValidity = kUidUnknown; +} + +nsresult nsImapProtocol::Configure(int32_t TooFastTime, int32_t IdealTime, + int32_t ChunkAddSize, int32_t ChunkSize, + int32_t ChunkThreshold, bool FetchByChunks) { + m_tooFastTime = TooFastTime; // secs we read too little too fast + m_idealTime = IdealTime; // secs we read enough in good time + m_chunkAddSize = ChunkAddSize; // buffer size to add when wasting time + m_chunkStartSize = m_chunkSize = ChunkSize; + m_chunkThreshold = ChunkThreshold; + m_fetchByChunks = FetchByChunks; + + return NS_OK; +} + +NS_IMETHODIMP +nsImapProtocol::Initialize(nsIImapHostSessionList* aHostSessionList, + nsIImapIncomingServer* aServer) { + NS_ASSERTION( + aHostSessionList && aServer, + "oops...trying to initialize with a null host session list or server!"); + if (!aHostSessionList || !aServer) return NS_ERROR_NULL_POINTER; + + nsresult rv = m_downloadLineCache->GrowBuffer(kDownLoadCacheSize); + NS_ENSURE_SUCCESS(rv, rv); + + m_flagState = new nsImapFlagAndUidState(kImapFlagAndUidStateSize); + if (!m_flagState) return NS_ERROR_OUT_OF_MEMORY; + + aServer->GetUseIdle(&m_useIdle); + aServer->GetForceSelect(&m_forceSelect); + aServer->GetUseCondStore(&m_useCondStore); + aServer->GetUseCompressDeflate(&m_useCompressDeflate); + aServer->GetAllowUTF8Accept(&m_allowUTF8Accept); + + m_hostSessionList = aHostSessionList; + m_parser.SetHostSessionList(aHostSessionList); + m_parser.SetFlagState(m_flagState); + + // Initialize the empty mime part string on the main thread. + nsCOMPtr<nsIStringBundle> bundle; + rv = IMAPGetStringBundle(getter_AddRefs(bundle)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = bundle->GetStringFromName("imapEmptyMimePart", m_emptyMimePartString); + NS_ENSURE_SUCCESS(rv, rv); + + // Now initialize the thread for the connection + if (m_thread == nullptr) { + nsCOMPtr<nsIThread> imapThread; + nsresult rv = NS_NewNamedThread("IMAP", getter_AddRefs(imapThread)); + if (NS_FAILED(rv)) { + NS_ASSERTION(imapThread, "Unable to create imap thread."); + return rv; + } + RefPtr<nsImapProtocolMainLoopRunnable> runnable = + new nsImapProtocolMainLoopRunnable(this); + imapThread->Dispatch(runnable.forget(), NS_DISPATCH_NORMAL); + imapThread->GetPRThread(&m_thread); + } + return NS_OK; +} + +nsImapProtocol::~nsImapProtocol() { + PR_Free(m_dataOutputBuf); + + // **** We must be out of the thread main loop function + NS_ASSERTION(!m_imapThreadIsRunning, "Oops, thread is still running."); + MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread()); +} + +const nsCString& nsImapProtocol::GetImapHostName() { + if (m_runningUrl && m_hostName.IsEmpty()) { + nsCOMPtr<nsIURI> url = do_QueryInterface(m_runningUrl); + url->GetAsciiHost(m_hostName); + } + + return m_hostName; +} + +const nsCString& nsImapProtocol::GetImapUserName() { + if (m_userName.IsEmpty() && m_imapServerSink) { + m_imapServerSink->GetOriginalUsername(m_userName); + } + return m_userName; +} + +const char* nsImapProtocol::GetImapServerKey() { + if (m_serverKey.IsEmpty() && m_imapServerSink) { + m_imapServerSink->GetServerKey(m_serverKey); + } + return m_serverKey.get(); +} + +nsresult nsImapProtocol::SetupSinkProxy() { + if (!m_runningUrl) return NS_OK; + nsresult res; + bool newFolderSink = false; + if (!m_imapMailFolderSink) { + nsCOMPtr<nsIImapMailFolderSink> aImapMailFolderSink; + (void)m_runningUrl->GetImapMailFolderSink( + getter_AddRefs(aImapMailFolderSink)); + if (aImapMailFolderSink) { + m_imapMailFolderSink = new ImapMailFolderSinkProxy(aImapMailFolderSink); + newFolderSink = true; + } + } + if (newFolderSink) Log("SetupSinkProxy", nullptr, "got m_imapMailFolderSink"); + + if (!m_imapMessageSink) { + nsCOMPtr<nsIImapMessageSink> aImapMessageSink; + (void)m_runningUrl->GetImapMessageSink(getter_AddRefs(aImapMessageSink)); + if (aImapMessageSink) { + m_imapMessageSink = new ImapMessageSinkProxy(aImapMessageSink); + } else { + return NS_ERROR_ILLEGAL_VALUE; + } + } + if (!m_imapServerSink) { + nsCOMPtr<nsIImapServerSink> aImapServerSink; + res = m_runningUrl->GetImapServerSink(getter_AddRefs(aImapServerSink)); + if (aImapServerSink) { + m_imapServerSink = new ImapServerSinkProxy(aImapServerSink); + m_imapServerSinkLatest = m_imapServerSink; + } else { + return NS_ERROR_ILLEGAL_VALUE; + } + } + if (!m_imapProtocolSink) { + nsCOMPtr<nsIImapProtocolSink> anImapProxyHelper( + do_QueryInterface(NS_ISUPPORTS_CAST(nsIImapProtocolSink*, this), &res)); + m_imapProtocolSink = new ImapProtocolSinkProxy(anImapProxyHelper); + } + return NS_OK; +} + +static void SetSecurityCallbacksFromChannel(nsISocketTransport* aTrans, + nsIChannel* aChannel) { + nsCOMPtr<nsIInterfaceRequestor> callbacks; + aChannel->GetNotificationCallbacks(getter_AddRefs(callbacks)); + + nsCOMPtr<nsILoadGroup> loadGroup; + aChannel->GetLoadGroup(getter_AddRefs(loadGroup)); + + nsCOMPtr<nsIInterfaceRequestor> securityCallbacks; + NS_NewNotificationCallbacksAggregation(callbacks, loadGroup, + getter_AddRefs(securityCallbacks)); + if (securityCallbacks) aTrans->SetSecurityCallbacks(securityCallbacks); +} + +// Setup With Url is intended to set up data which is held on a PER URL basis +// and not a per connection basis. If you have data which is independent of the +// url we are currently running, then you should put it in Initialize(). This is +// only ever called from the UI thread. It is called from LoadImapUrl, right +// before the url gets run - i.e., the url is next in line to run. +// See also ReleaseUrlState(), which frees a bunch of the things set up in here. +nsresult nsImapProtocol::SetupWithUrl(nsIURI* aURL, nsISupports* aConsumer) { + nsresult rv = NS_ERROR_FAILURE; + NS_ASSERTION(aURL, "null URL passed into Imap Protocol"); + if (aURL) { + nsCOMPtr<nsIImapUrl> imapURL = do_QueryInterface(aURL, &rv); + NS_ENSURE_SUCCESS(rv, rv); + m_runningUrl = imapURL; + m_runningUrlLatest = m_runningUrl; + + nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_runningUrl); + nsCOMPtr<nsIMsgIncomingServer> server = do_QueryReferent(m_server); + if (!server) { + rv = mailnewsUrl->GetServer(getter_AddRefs(server)); + NS_ENSURE_SUCCESS(rv, rv); + m_server = do_GetWeakReference(server); + } + nsCOMPtr<nsIMsgFolder> folder; + mailnewsUrl->GetFolder(getter_AddRefs(folder)); + mFolderLastModSeq = 0; + mFolderTotalMsgCount = 0; + mFolderHighestUID = 0; + m_uidValidity = kUidUnknown; + if (folder) { + nsCOMPtr<nsIMsgDatabase> folderDB; + nsCOMPtr<nsIDBFolderInfo> folderInfo; + folder->GetDBFolderInfoAndDB(getter_AddRefs(folderInfo), + getter_AddRefs(folderDB)); + if (folderInfo) { + nsCString modSeqStr; + folderInfo->GetCharProperty(kModSeqPropertyName, modSeqStr); + mFolderLastModSeq = ParseUint64Str(modSeqStr.get()); + folderInfo->GetNumMessages(&mFolderTotalMsgCount); + folderInfo->GetUint32Property(kHighestRecordedUIDPropertyName, 0, + &mFolderHighestUID); + folderInfo->GetImapUidValidity(&m_uidValidity); + } + } + nsCOMPtr<nsIImapIncomingServer> imapServer = do_QueryInterface(server); + nsCOMPtr<nsIStreamListener> aRealStreamListener = + do_QueryInterface(aConsumer); + m_runningUrl->GetMockChannel(getter_AddRefs(m_mockChannel)); + imapServer->GetIsGMailServer(&m_isGmailServer); + if (!m_mockChannel) { + nsCOMPtr<nsIPrincipal> nullPrincipal = + NullPrincipal::CreateWithoutOriginAttributes(); + + // there are several imap operations that aren't initiated via a + // nsIChannel::AsyncOpen call on the mock channel. such as selecting a + // folder. nsImapProtocol now insists on a mock channel when processing a + // url. + nsCOMPtr<nsIChannel> channel; + rv = + NS_NewChannel(getter_AddRefs(channel), aURL, nullPrincipal, + nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL, + nsIContentPolicy::TYPE_OTHER); + m_mockChannel = do_QueryInterface(channel); + NS_ASSERTION(m_mockChannel, + "failed to get a mock channel in nsImapProtocol"); + + // Certain imap operations (not initiated by the IO Service via AsyncOpen) + // can be interrupted by the stop button on the toolbar. We do this by + // using the loadgroup of the docshell for the message pane. We really + // shouldn't be doing this.. See the comment in + // nsMsgMailNewsUrl::GetLoadGroup. + nsCOMPtr<nsILoadGroup> loadGroup; + mailnewsUrl->GetLoadGroup( + getter_AddRefs(loadGroup)); // get the message pane load group + if (loadGroup) + loadGroup->AddRequest(m_mockChannel, nullptr /* context isupports */); + } + + if (m_mockChannel) { + m_mockChannel->SetImapProtocol(this); + // if we have a listener from a mock channel, over-ride the consumer that + // was passed in + nsCOMPtr<nsIStreamListener> channelListener; + m_mockChannel->GetChannelListener(getter_AddRefs(channelListener)); + if (channelListener) // only over-ride if we have a non null channel + // listener + aRealStreamListener = channelListener; + nsCOMPtr<nsIMsgWindow> msgWindow; + GetMsgWindow(getter_AddRefs(msgWindow)); + if (!msgWindow) GetTopmostMsgWindow(getter_AddRefs(msgWindow)); + if (msgWindow) { + // Set up the MockChannel to attempt nsIProgressEventSink callbacks on + // the messageWindow, with fallback to the docShell (and the + // loadgroup). + nsCOMPtr<nsIDocShell> docShell; + msgWindow->GetMessageWindowDocShell(getter_AddRefs(docShell)); + nsCOMPtr<nsIInterfaceRequestor> ir(do_QueryInterface(docShell)); + nsCOMPtr<nsIInterfaceRequestor> interfaceRequestor; + msgWindow->GetNotificationCallbacks(getter_AddRefs(interfaceRequestor)); + nsCOMPtr<nsIInterfaceRequestor> aggregateIR; + NS_NewInterfaceRequestorAggregation(interfaceRequestor, ir, + getter_AddRefs(aggregateIR)); + m_mockChannel->SetNotificationCallbacks(aggregateIR); + } + } + + // since we'll be making calls directly from the imap thread to the channel + // listener, we need to turn it into a proxy object....we'll assume that the + // listener is on the same thread as the event sink queue + if (aRealStreamListener) { + NS_ASSERTION(!m_channelListener, + "shouldn't already have a channel listener"); + m_channelListener = new StreamListenerProxy(aRealStreamListener); + } + + server->GetHostName(m_hostName); + int32_t authMethod; + (void)server->GetAuthMethod(&authMethod); + InitPrefAuthMethods(authMethod, server); + (void)server->GetSocketType(&m_socketType); + bool shuttingDown; + (void)imapServer->GetShuttingDown(&shuttingDown); + if (!shuttingDown) + (void)imapServer->GetUseIdle(&m_useIdle); + else + m_useIdle = false; + imapServer->GetFetchByChunks(&m_fetchByChunks); + imapServer->GetSendID(&m_sendID); + + nsAutoString trashFolderPath; + if (NS_SUCCEEDED(imapServer->GetTrashFolderName(trashFolderPath))) { + if (m_allowUTF8Accept) + CopyUTF16toUTF8(trashFolderPath, m_trashFolderPath); + else + CopyUTF16toMUTF7(trashFolderPath, m_trashFolderPath); + } + + nsCOMPtr<nsIPrefBranch> prefBranch( + do_GetService(NS_PREFSERVICE_CONTRACTID)); + if (prefBranch) { + bool preferPlainText; + prefBranch->GetBoolPref("mailnews.display.prefer_plaintext", + &preferPlainText); + // If the pref has changed since the last time we ran a url, + // clear the shell cache for this host. (bodyshell no longer exists.) + if (preferPlainText != m_preferPlainText) { + m_preferPlainText = preferPlainText; + } + } + // If enabled, retrieve the clientid so that we can use it later. + bool clientidEnabled = false; + if (NS_SUCCEEDED(server->GetClientidEnabled(&clientidEnabled)) && + clientidEnabled) + server->GetClientid(m_clientId); + else { + m_clientId.Truncate(); + } + + bool proxyCallback = false; + if (m_runningUrl && !m_transport /* and we don't have a transport yet */) { + if (m_mockChannel) { + rv = MsgExamineForProxyAsync(m_mockChannel, this, + getter_AddRefs(m_proxyRequest)); + if (NS_FAILED(rv)) { + rv = SetupWithUrlCallback(nullptr); + } else { + proxyCallback = true; + } + } + } + + if (!proxyCallback) rv = LoadImapUrlInternal(); + } + + return rv; +} + +// nsIProtocolProxyCallback +NS_IMETHODIMP +nsImapProtocol::OnProxyAvailable(nsICancelable* aRequest, nsIChannel* aChannel, + nsIProxyInfo* aProxyInfo, nsresult aStatus) { + // If we're called with NS_BINDING_ABORTED, the IMAP thread already died, + // so we can't carry on. Otherwise, no checking of 'aStatus' here, see + // nsHttpChannel::OnProxyAvailable(). Status is non-fatal and we just kick on. + if (aStatus == NS_BINDING_ABORTED) return NS_ERROR_FAILURE; + + nsresult rv = SetupWithUrlCallback(aProxyInfo); + if (NS_FAILED(rv)) { + // Cancel the protocol and be done. + if (m_mockChannel) m_mockChannel->Cancel(rv); + return rv; + } + + rv = LoadImapUrlInternal(); + if (NS_FAILED(rv)) { + if (m_mockChannel) m_mockChannel->Cancel(rv); + } + + return rv; +} + +nsresult nsImapProtocol::SetupWithUrlCallback(nsIProxyInfo* aProxyInfo) { + m_proxyRequest = nullptr; + + nsresult rv; + + nsCOMPtr<nsISocketTransportService> socketService = + do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv); + if (NS_FAILED(rv)) return rv; + + Log("SetupWithUrlCallback", nullptr, "clearing IMAP_CONNECTION_IS_OPEN"); + ClearFlag(IMAP_CONNECTION_IS_OPEN); + const char* connectionType = nullptr; + + if (m_socketType == nsMsgSocketType::SSL) + connectionType = "ssl"; + else if (m_socketType == nsMsgSocketType::alwaysSTARTTLS) + connectionType = "starttls"; + // This can go away once we think everyone is migrated + // away from the trySTARTTLS socket type. + else if (m_socketType == nsMsgSocketType::trySTARTTLS) + connectionType = "starttls"; + + int32_t port = -1; + nsCOMPtr<nsIURI> uri = do_QueryInterface(m_runningUrl, &rv); + if (NS_FAILED(rv)) return rv; + uri->GetPort(&port); + + AutoTArray<nsCString, 1> connectionTypeArray; + if (connectionType) connectionTypeArray.AppendElement(connectionType); + // NOTE: Some errors won't show up until the first read attempt (SSL bad + // certificate errors, for example). + rv = socketService->CreateTransport(connectionTypeArray, m_hostName, port, + aProxyInfo, nullptr, + getter_AddRefs(m_transport)); + if (NS_FAILED(rv) && m_socketType == nsMsgSocketType::trySTARTTLS) { + connectionType = nullptr; + m_socketType = nsMsgSocketType::plain; + rv = socketService->CreateTransport(connectionTypeArray, m_hostName, port, + aProxyInfo, nullptr, + getter_AddRefs(m_transport)); + } + + // remember so we can know whether we can issue a start tls or not... + m_connectionType = connectionType; + if (m_transport && m_mockChannel) { + uint8_t qos; + rv = GetQoSBits(&qos); + if (NS_SUCCEEDED(rv)) m_transport->SetQoSBits(qos); + + // Ensure that the socket can get the notification callbacks + SetSecurityCallbacksFromChannel(m_transport, m_mockChannel); + + // open buffered, blocking input stream + rv = m_transport->OpenInputStream(nsITransport::OPEN_BLOCKING, 0, 0, + getter_AddRefs(m_inputStream)); + if (NS_FAILED(rv)) return rv; + + // open buffered, blocking output stream + rv = m_transport->OpenOutputStream(nsITransport::OPEN_BLOCKING, 0, 0, + getter_AddRefs(m_outputStream)); + if (NS_FAILED(rv)) return rv; + SetFlag(IMAP_CONNECTION_IS_OPEN); + } + + return rv; +} + +// when the connection is done processing the current state, free any per url +// state data... +void nsImapProtocol::ReleaseUrlState(bool rerunning) { + // clear out the socket's reference to the notification callbacks for this + // transaction + { + MutexAutoLock mon(mLock); + if (m_transport) { + m_transport->SetSecurityCallbacks(nullptr); + m_transport->SetEventSink(nullptr, nullptr); + } + } + + if (m_mockChannel && !rerunning) { + // Proxy the close of the channel to the ui thread. + if (m_imapMailFolderSink) + m_imapMailFolderSink->CloseMockChannel(m_mockChannel); + else + m_mockChannel->Close(); + + { + // grab a lock so m_mockChannel doesn't get cleared out + // from under us. + MutexAutoLock mon(mLock); + if (m_mockChannel) { + // Proxy the release of the channel to the main thread. This is + // something that the xpcom proxy system should do for us! + NS_ReleaseOnMainThread("nsImapProtocol::m_mockChannel", + m_mockChannel.forget()); + } + } + } + + m_imapMessageSink = nullptr; + + // Proxy the release of the listener to the main thread. This is something + // that the xpcom proxy system should do for us! + { + // grab a lock so the m_channelListener doesn't get cleared. + MutexAutoLock mon(mLock); + if (m_channelListener) { + NS_ReleaseOnMainThread("nsImapProtocol::m_channelListener", + m_channelListener.forget()); + } + } + m_channelInputStream = nullptr; + m_channelOutputStream = nullptr; + + nsCOMPtr<nsIMsgMailNewsUrl> mailnewsurl; + nsCOMPtr<nsIImapMailFolderSink> saveFolderSink; + + { + MutexAutoLock mon(mLock); + if (m_runningUrl) { + mailnewsurl = do_QueryInterface(m_runningUrl); + // It is unclear what 'saveFolderSink' is used for, most likely to hold + // a reference for a little longer. See bug 1324893 and bug 391259. + saveFolderSink = m_imapMailFolderSink; + + m_runningUrl = + nullptr; // force us to release our last reference on the url + m_urlInProgress = false; + } + } + // Need to null this out whether we have an m_runningUrl or not + m_imapMailFolderSink = nullptr; + + // we want to make sure the imap protocol's last reference to the url gets + // released back on the UI thread. This ensures that the objects the imap url + // hangs on to properly get released back on the UI thread. + if (mailnewsurl) { + NS_ReleaseOnMainThread("nsImapProtocol::m_runningUrl", + mailnewsurl.forget()); + } + saveFolderSink = nullptr; +} + +class nsImapCancelProxy : public mozilla::Runnable { + public: + explicit nsImapCancelProxy(nsICancelable* aProxyRequest) + : mozilla::Runnable("nsImapCancelProxy"), mRequest(aProxyRequest) {} + NS_IMETHOD Run() { + if (mRequest) mRequest->Cancel(NS_BINDING_ABORTED); + return NS_OK; + } + + private: + nsCOMPtr<nsICancelable> mRequest; +}; + +bool nsImapProtocol::RunImapThreadMainLoop() { + PR_CEnterMonitor(this); + NS_ASSERTION(!m_imapThreadIsRunning, + "Oh. oh. thread is already running. What's wrong here?"); + if (m_imapThreadIsRunning) { + PR_CExitMonitor(this); + return false; + } + + m_imapThreadIsRunning = true; + PR_CExitMonitor(this); + + // call the platform specific main loop .... + ImapThreadMainLoop(); + + if (m_proxyRequest) { + // Cancel proxy on main thread. + RefPtr<nsImapCancelProxy> cancelProxy = + new nsImapCancelProxy(m_proxyRequest); + NS_DispatchAndSpinEventLoopUntilComplete( + "nsImapProtocol::RunImapThreadMainLoop"_ns, + GetMainThreadSerialEventTarget(), cancelProxy.forget()); + m_proxyRequest = nullptr; + } + + if (m_runningUrl) { + NS_ReleaseOnMainThread("nsImapProtocol::m_runningUrl", + m_runningUrl.forget()); + } + + // close streams via UI thread if it's not already done + if (m_imapProtocolSink) m_imapProtocolSink->CloseStreams(); + + m_imapMailFolderSink = nullptr; + m_imapMailFolderSinkSelected = nullptr; + m_imapMessageSink = nullptr; + + return true; +} + +// +// Must be called from UI thread only +// +NS_IMETHODIMP nsImapProtocol::CloseStreams() { + // make sure that it is called by the UI thread + MOZ_ASSERT(NS_IsMainThread(), + "CloseStreams() should not be called from an off UI thread"); + + { + MutexAutoLock mon(mLock); + if (m_transport) { + // make sure the transport closes (even if someone is still indirectly + // referencing it). + m_transport->Close(NS_ERROR_ABORT); + m_transport = nullptr; + } + m_inputStream = nullptr; + m_outputStream = nullptr; + m_channelListener = nullptr; + if (m_mockChannel) { + m_mockChannel->Close(); + m_mockChannel = nullptr; + } + m_channelInputStream = nullptr; + m_channelOutputStream = nullptr; + + // Close scope because we must let go of the monitor before calling + // RemoveConnection to unblock anyone who tries to get a monitor to the + // protocol object while holding onto a monitor to the server. + } + nsCOMPtr<nsIMsgIncomingServer> me_server = do_QueryReferent(m_server); + if (me_server) { + nsresult result; + nsCOMPtr<nsIImapIncomingServer> aImapServer( + do_QueryInterface(me_server, &result)); + if (NS_SUCCEEDED(result)) aImapServer->RemoveConnection(this); + me_server = nullptr; + } + m_server = nullptr; + // take this opportunity of being on the UI thread to + // persist chunk prefs if they've changed + if (gChunkSizeDirty) { + nsCOMPtr<nsIPrefBranch> prefBranch( + do_GetService(NS_PREFSERVICE_CONTRACTID)); + if (prefBranch) { + prefBranch->SetIntPref("mail.imap.chunk_size", gChunkSize); + prefBranch->SetIntPref("mail.imap.min_chunk_size_threshold", + gChunkThreshold); + gChunkSizeDirty = false; + } + } + return NS_OK; +} + +NS_IMETHODIMP nsImapProtocol::GetUrlWindow(nsIMsgMailNewsUrl* aUrl, + nsIMsgWindow** aMsgWindow) { + NS_ENSURE_ARG_POINTER(aUrl); + NS_ENSURE_ARG_POINTER(aMsgWindow); + return aUrl->GetMsgWindow(aMsgWindow); +} + +NS_IMETHODIMP nsImapProtocol::SetupMainThreadProxies() { + return SetupSinkProxy(); +} + +NS_IMETHODIMP nsImapProtocol::OnInputStreamReady(nsIAsyncInputStream* inStr) { + // should we check if it's a close vs. data available? + if (m_idle) { + uint64_t bytesAvailable = 0; + (void)inStr->Available(&bytesAvailable); + // check if data available - might be a close + if (bytesAvailable != 0) { + ReentrantMonitorAutoEnter mon(m_urlReadyToRunMonitor); + m_lastActiveTime = PR_Now(); + m_idleResponseReadyToHandle = true; + mon.Notify(); + } + } + return NS_OK; +} + +// this is to be called from the UI thread. It sets m_threadShouldDie, +// and then signals the imap thread, which, when it wakes up, should exit. +// The imap thread cleanup code will check m_safeToCloseConnection. +NS_IMETHODIMP +nsImapProtocol::TellThreadToDie(bool aIsSafeToClose) { + MOZ_DIAGNOSTIC_ASSERT( + NS_IsMainThread(), + "TellThreadToDie(aIsSafeToClose) should only be called from UI thread"); + MutexAutoLock mon(mLock); + + nsCOMPtr<nsIMsgIncomingServer> me_server = do_QueryReferent(m_server); + if (me_server) { + nsresult rv; + nsCOMPtr<nsIImapIncomingServer> aImapServer( + do_QueryInterface(me_server, &rv)); + if (NS_SUCCEEDED(rv)) aImapServer->RemoveConnection(this); + m_server = nullptr; + me_server = nullptr; + } + { + ReentrantMonitorAutoEnter deathMon(m_threadDeathMonitor); + m_safeToCloseConnection = aIsSafeToClose; + m_threadShouldDie = true; + } + ReentrantMonitorAutoEnter readyMon(m_urlReadyToRunMonitor); + m_nextUrlReadyToRun = true; + readyMon.Notify(); + return NS_OK; +} + +/** + * Dispatch socket thread to to determine if connection is alive. + */ +nsresult nsImapProtocol::IsTransportAlive(bool* alive) { + nsresult rv; + auto GetIsAlive = [transport = nsCOMPtr{m_transport}, &rv, alive]() mutable { + rv = transport->IsAlive(alive); + }; + nsCOMPtr<nsIEventTarget> socketThread( + do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID)); + if (socketThread) { + mozilla::SyncRunnable::DispatchToThread( + socketThread, + NS_NewRunnableFunction("nsImapProtocol::IsTransportAlive", GetIsAlive)); + } else { + rv = NS_ERROR_NOT_AVAILABLE; + } + return rv; +} + +/** + * Dispatch socket thread to initiate STARTTLS handshakes. + */ +nsresult nsImapProtocol::TransportStartTLS() { + nsresult rv = NS_ERROR_NOT_AVAILABLE; + nsCOMPtr<nsITLSSocketControl> tlsSocketControl; + if (m_transport && + NS_SUCCEEDED( + m_transport->GetTlsSocketControl(getter_AddRefs(tlsSocketControl))) && + tlsSocketControl) { + auto CallStartTLS = [sockCon = nsCOMPtr{tlsSocketControl}, &rv]() mutable { + rv = sockCon->StartTLS(); + }; + nsCOMPtr<nsIEventTarget> socketThread( + do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID)); + if (socketThread) { + mozilla::SyncRunnable::DispatchToThread( + socketThread, NS_NewRunnableFunction( + "nsImapProtocol::TransportStartTLS", CallStartTLS)); + } else { + rv = NS_ERROR_NOT_AVAILABLE; + } + } + return rv; +} + +/** + * Dispatch socket thread to obtain transport security information. + */ +void nsImapProtocol::GetTransportSecurityInfo( + nsITransportSecurityInfo** aSecurityInfo) { + *aSecurityInfo = nullptr; + nsCOMPtr<nsIEventTarget> socketThread( + do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID)); + nsCOMPtr<nsITLSSocketControl> tlsSocketControl; + if (socketThread && + NS_SUCCEEDED( + m_transport->GetTlsSocketControl(getter_AddRefs(tlsSocketControl))) && + tlsSocketControl) { + if (socketThread) { + nsCOMPtr<nsITransportSecurityInfo> secInfo; + auto GetSecurityInfo = [&tlsSocketControl, &secInfo]() mutable { + tlsSocketControl->GetSecurityInfo(getter_AddRefs(secInfo)); + }; + mozilla::SyncRunnable::DispatchToThread( + socketThread, + NS_NewRunnableFunction("nsImapProtocol::GetTransportSecurityInfo", + GetSecurityInfo)); + NS_IF_ADDREF(*aSecurityInfo = secInfo); + } + } +} + +void nsImapProtocol::TellThreadToDie() { + nsresult rv = NS_OK; + MOZ_DIAGNOSTIC_ASSERT( + !NS_IsMainThread(), + "TellThreadToDie() should not be called from UI thread"); + + // prevent re-entering this method because it may lock the UI. + if (m_inThreadShouldDie) return; + m_inThreadShouldDie = true; + + // This routine is called only from the imap protocol thread. + // The UI thread causes this to be called by calling TellThreadToDie. + // In that case, m_safeToCloseConnection will be FALSE if it's dropping a + // timed out connection, true when closing a cached connection. + // We're using PR_CEnter/ExitMonitor because Monitors don't like having + // us to hold one monitor and call code that gets a different monitor. And + // some of the methods we call here use Monitors. + PR_CEnterMonitor(this); + + m_urlInProgress = true; // let's say it's busy so no one tries to use + // this about to die connection. + bool urlWritingData = false; + bool connectionIdle = !m_runningUrl; + + if (!connectionIdle) + urlWritingData = m_imapAction == nsIImapUrl::nsImapAppendMsgFromFile || + m_imapAction == nsIImapUrl::nsImapAppendDraftFromFile; + + bool closeNeeded = GetServerStateParser().GetIMAPstate() == + nsImapServerResponseParser::kFolderSelected && + m_safeToCloseConnection; + nsCString command; + // if a url is writing data, we can't even logout, so we're just + // going to close the connection as if the user pressed stop. + if (m_currentServerCommandTagNumber > 0 && !urlWritingData) { + bool isAlive = false; + if (m_transport) rv = IsTransportAlive(&isAlive); + + if (TestFlag(IMAP_CONNECTION_IS_OPEN) && m_idle && isAlive) EndIdle(false); + + if (NS_SUCCEEDED(rv) && isAlive && closeNeeded && + GetDeleteIsMoveToTrash() && TestFlag(IMAP_CONNECTION_IS_OPEN) && + m_outputStream) + ImapClose(true, connectionIdle); + + if (NS_SUCCEEDED(rv) && isAlive && TestFlag(IMAP_CONNECTION_IS_OPEN) && + NS_SUCCEEDED(GetConnectionStatus()) && m_outputStream) + Logout(true, connectionIdle); + } + PR_CExitMonitor(this); + // close streams via UI thread + if (m_imapProtocolSink) { + m_imapProtocolSink->CloseStreams(); + m_imapProtocolSink = nullptr; + } + Log("TellThreadToDie", nullptr, "close socket connection"); + + { + ReentrantMonitorAutoEnter mon(m_threadDeathMonitor); + m_threadShouldDie = true; + } + { + ReentrantMonitorAutoEnter dataMon(m_dataAvailableMonitor); + dataMon.Notify(); + } + ReentrantMonitorAutoEnter urlReadyMon(m_urlReadyToRunMonitor); + urlReadyMon.NotifyAll(); +} + +NS_IMETHODIMP +nsImapProtocol::GetLastActiveTimeStamp(PRTime* aTimeStamp) { + if (aTimeStamp) *aTimeStamp = m_lastActiveTime; + return NS_OK; +} + +static void DoomCacheEntry(nsIMsgMailNewsUrl* url); +NS_IMETHODIMP +nsImapProtocol::PseudoInterruptMsgLoad(nsIMsgFolder* aImapFolder, + nsIMsgWindow* aMsgWindow, + bool* interrupted) { + NS_ENSURE_ARG(interrupted); + + *interrupted = false; + + PR_CEnterMonitor(this); + + if (m_runningUrl && !TestFlag(IMAP_CLEAN_UP_URL_STATE)) { + nsImapAction imapAction; + m_runningUrl->GetImapAction(&imapAction); + + if (imapAction == nsIImapUrl::nsImapMsgFetch) { + nsresult rv = NS_OK; + nsCOMPtr<nsIImapUrl> runningImapURL; + + rv = GetRunningImapURL(getter_AddRefs(runningImapURL)); + if (NS_SUCCEEDED(rv) && runningImapURL) { + nsCOMPtr<nsIMsgFolder> runningImapFolder; + nsCOMPtr<nsIMsgWindow> msgWindow; + nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = + do_QueryInterface(runningImapURL); + mailnewsUrl->GetMsgWindow(getter_AddRefs(msgWindow)); + mailnewsUrl->GetFolder(getter_AddRefs(runningImapFolder)); + if (aImapFolder == runningImapFolder && msgWindow == aMsgWindow) { + MOZ_LOG(IMAPCache, LogLevel::Debug, + ("%s: Set PseudoInterrupt", __func__)); + PseudoInterrupt(true); + *interrupted = true; + } + // If we're pseudo-interrupted, doom any incomplete cache entry. + // But if mock channel indicates fetch is complete so cache write is + // done, then don't doom the cache entry. + bool cacheWriteInProgress = true; + if (m_mockChannel) + m_mockChannel->GetWritingToCache(&cacheWriteInProgress); + if (cacheWriteInProgress) { + MOZ_LOG(IMAPCache, LogLevel::Debug, + ("%s: Call DoomCacheEntry()", __func__)); + DoomCacheEntry(mailnewsUrl); + } + } + } + } + PR_CExitMonitor(this); +#ifdef DEBUG_bienvenu + printf("interrupt msg load : %s\n", (*interrupted) ? "TRUE" : "FALSE"); +#endif + return NS_OK; +} + +void nsImapProtocol::ImapThreadMainLoop() { + MOZ_LOG(IMAP, LogLevel::Debug, + ("ImapThreadMainLoop entering [this=%p]", this)); + + PRIntervalTime sleepTime = kImapSleepTime; + bool idlePending = false; + while (!DeathSignalReceived()) { + nsresult rv = NS_OK; + bool urlReadyToRun; + + // wait for a URL or idle response to process... + { + ReentrantMonitorAutoEnter mon(m_urlReadyToRunMonitor); + + while (NS_SUCCEEDED(rv) && !DeathSignalReceived() && + !m_nextUrlReadyToRun && !m_idleResponseReadyToHandle && + !m_threadShouldDie) { + rv = mon.Wait(sleepTime); + if (idlePending) break; + } + + urlReadyToRun = m_nextUrlReadyToRun; + m_nextUrlReadyToRun = false; + } + // This will happen if the UI thread signals us to die + if (m_threadShouldDie) { + TellThreadToDie(); + break; + } + + if (NS_FAILED(rv) && PR_PENDING_INTERRUPT_ERROR == PR_GetError()) { + printf("error waiting for monitor\n"); + break; + } + + // If an idle response has occurred, handle only it on this pass through + // the loop. + if (m_idleResponseReadyToHandle && !m_threadShouldDie) { + m_idleResponseReadyToHandle = false; + HandleIdleResponses(); + if (urlReadyToRun) { + // A URL is also ready. Process it on next loop. + m_nextUrlReadyToRun = true; + urlReadyToRun = false; + } + } + + if (urlReadyToRun && m_runningUrl) { + if (m_currentServerCommandTagNumber && m_transport) { + bool isAlive; + rv = IsTransportAlive(&isAlive); + // if the transport is not alive, and we've ever sent a command with + // this connection, kill it. otherwise, we've probably just not finished + // setting it so don't kill it! + if (NS_FAILED(rv) || !isAlive) { + // This says we never started running the url, which is the case. + m_runningUrl->SetRerunningUrl(false); + RetryUrl(); + return; + } + } + // + // NOTE: Although we cleared m_nextUrlReadyToRun above, it may now be set + // again by LoadImapUrlInternal(), which runs on the main thread. + // Because of this, we must not clear m_nextUrlReadyToRun here. + // + if (ProcessCurrentURL()) { + // Another URL has been setup to run. Process it on next loop. + m_nextUrlReadyToRun = true; + m_imapMailFolderSink = nullptr; + } else { + // No more URLs setup to run. Set idle pending if user has configured + // idle and if a URL is not in progress and if the server has IDLE + // capability. Just set idlePending since want to wait a short time + // to see if more URLs occurs before actually entering idle. + if (!idlePending && m_useIdle && !m_urlInProgress && + GetServerStateParser().GetCapabilityFlag() & kHasIdleCapability && + GetServerStateParser().GetIMAPstate() == + nsImapServerResponseParser::kFolderSelected) { + // Set-up short wait time in milliseconds + static const PRIntervalTime kIdleWait = + PR_MillisecondsToInterval(2000); + sleepTime = kIdleWait; + idlePending = true; + Log("ImapThreadMainLoop", nullptr, "idlePending set"); + } else { + // if not idle used, don't need to remember folder sink + m_imapMailFolderSink = nullptr; + } + } + } else { + // No URL to run detected on wake up. + if (idlePending) { + // Have seen no URLs for the short time (kIdleWait) so go into idle mode + // and set the loop sleep time back to its original longer time. + Idle(); + if (!m_idle) { + // Server rejected IDLE. Treat like IDLE not enabled or available. + m_imapMailFolderSink = nullptr; + } + idlePending = false; + sleepTime = kImapSleepTime; + } + } + if (!GetServerStateParser().Connected()) break; +#ifdef DEBUG_bienvenu + else + printf("ready to run but no url and not idle\n"); +#endif + // This can happen if the UI thread closes cached connections in the + // OnStopRunningUrl notification. + if (m_threadShouldDie) TellThreadToDie(); + } + m_imapThreadIsRunning = false; + + MOZ_LOG(IMAP, LogLevel::Debug, + ("ImapThreadMainLoop leaving [this=%p]", this)); +} + +// This handles the response to Idle() command and handles responses sent by +// the server after in idle mode. Returns true if a BAD or NO response is +// not seen which is needed when called from Idle(). +bool nsImapProtocol::HandleIdleResponses() { + bool rvIdleOk = true; + bool untagged = false; + NS_ASSERTION(PL_strstr(m_currentCommand.get(), " IDLE"), "not IDLE!"); + do { + ParseIMAPandCheckForNewMail(); + rvIdleOk = rvIdleOk && !GetServerStateParser().CommandFailed(); + untagged = untagged || GetServerStateParser().UntaggedResponse(); + } while (m_inputStreamBuffer->NextLineAvailable() && + GetServerStateParser().Connected()); + + // If still connected and rvIdleOk is true and an untagged response was + // detected and have the sink pointer, OnNewIdleMessage will invoke a URL to + // update the folder. Otherwise just setup so we get notified of idle response + // data on the socket transport thread by OnInputStreamReady() above, which + // will trigger the imap thread main loop to run and call this function again. + if (GetServerStateParser().Connected() && rvIdleOk) { + if (m_imapMailFolderSinkSelected && untagged) { + Log("HandleIdleResponses", nullptr, "idle response"); + m_imapMailFolderSinkSelected->OnNewIdleMessages(); + } else { + // Enable async wait mode. Occurs when Idle() called. + nsCOMPtr<nsIAsyncInputStream> asyncInputStream = + do_QueryInterface(m_inputStream); + if (asyncInputStream) { + asyncInputStream->AsyncWait(this, 0, 0, nullptr); + Log("HandleIdleResponses", nullptr, "idle mode async waiting"); + } + } + } + return rvIdleOk; +} + +void nsImapProtocol::EstablishServerConnection() { +#define ESC_LENGTH(x) (sizeof(x) - 1) +#define ESC_OK "* OK" +#define ESC_OK_LEN ESC_LENGTH(ESC_OK) +#define ESC_PREAUTH "* PREAUTH" +#define ESC_PREAUTH_LEN ESC_LENGTH(ESC_PREAUTH) +#define ESC_CAPABILITY_STAR "* " +#define ESC_CAPABILITY_STAR_LEN ESC_LENGTH(ESC_CAPABILITY_STAR) +#define ESC_CAPABILITY_OK "* OK [" +#define ESC_CAPABILITY_OK_LEN ESC_LENGTH(ESC_CAPABILITY_OK) +#define ESC_CAPABILITY_GREETING (ESC_CAPABILITY_OK "CAPABILITY") +#define ESC_CAPABILITY_GREETING_LEN ESC_LENGTH(ESC_CAPABILITY_GREETING) + + char* serverResponse = CreateNewLineFromSocket(); // read in the greeting + // record the fact that we've received a greeting for this connection so we + // don't ever try to do it again.. + if (serverResponse) SetFlag(IMAP_RECEIVED_GREETING); + + if (!PL_strncasecmp(serverResponse, ESC_OK, ESC_OK_LEN)) { + SetConnectionStatus(NS_OK); + + if (!PL_strncasecmp(serverResponse, ESC_CAPABILITY_GREETING, + ESC_CAPABILITY_GREETING_LEN)) { + nsAutoCString tmpstr(serverResponse); + int32_t endIndex = tmpstr.FindChar(']', ESC_CAPABILITY_GREETING_LEN); + if (endIndex >= 0) { + // Allocate the new buffer here. This buffer will be passed to + // ParseIMAPServerResponse() where it will be used to fill the + // fCurrentLine field and will be freed by the next call to + // ResetLexAnalyzer(). + char* fakeServerResponse = (char*)PR_Malloc(PL_strlen(serverResponse)); + // Munge the greeting into something that would pass for an IMAP + // server's response to a "CAPABILITY" command. + strcpy(fakeServerResponse, ESC_CAPABILITY_STAR); + strcat(fakeServerResponse, serverResponse + ESC_CAPABILITY_OK_LEN); + fakeServerResponse[endIndex - ESC_CAPABILITY_OK_LEN + + ESC_CAPABILITY_STAR_LEN] = '\0'; + // Tell the response parser that we just issued a "CAPABILITY" and + // got the following back. + GetServerStateParser().ParseIMAPServerResponse("1 CAPABILITY", true, + fakeServerResponse); + } + } + } else if (!PL_strncasecmp(serverResponse, ESC_PREAUTH, ESC_PREAUTH_LEN)) { + // PREAUTH greeting received. We've been pre-authenticated by the server. + // We can skip sending a password and transition right into the + // kAuthenticated state; but we won't if the user has configured STARTTLS. + // (STARTTLS can only occur with the server in non-authenticated state.) + if (!(m_socketType == nsMsgSocketType::alwaysSTARTTLS || + m_socketType == nsMsgSocketType::trySTARTTLS)) { + GetServerStateParser().PreauthSetAuthenticatedState(); + + if (GetServerStateParser().GetCapabilityFlag() == kCapabilityUndefined) + Capability(); + + if (!(GetServerStateParser().GetCapabilityFlag() & + (kIMAP4Capability | kIMAP4rev1Capability | kIMAP4other))) { + // AlertUserEventUsingId(MK_MSG_IMAP_SERVER_NOT_IMAP4); + SetConnectionStatus(NS_ERROR_FAILURE); // stop netlib + } else { + // let's record the user as authenticated. + m_imapServerSink->SetUserAuthenticated(true); + m_hostSessionList->SetPasswordVerifiedOnline(GetImapServerKey()); + + ProcessAfterAuthenticated(); + // the connection was a success + SetConnectionStatus(NS_OK); + } + } else { + // STARTTLS is configured so don't transition to authenticated state. Just + // alert the user, log the error and drop the connection. This may + // indicate a man-in-the middle attack if the user is not expecting + // PREAUTH. The user must change the connection security setting to other + // than STARTTLS to allow PREAUTH to be accepted on subsequent IMAP + // connections. + AlertUserEventUsingName("imapServerDisconnected"); + const nsCString& hostName = GetImapHostName(); + MOZ_LOG( + IMAP, LogLevel::Error, + ("PREAUTH received from IMAP server %s because STARTTLS selected. " + "Connection dropped", + hostName.get())); + SetConnectionStatus(NS_ERROR_FAILURE); // stop netlib + } + } + + PR_Free(serverResponse); // we don't care about the greeting yet... + +#undef ESC_LENGTH +#undef ESC_OK +#undef ESC_OK_LEN +#undef ESC_PREAUTH +#undef ESC_PREAUTH_LEN +#undef ESC_CAPABILITY_STAR +#undef ESC_CAPABILITY_STAR_LEN +#undef ESC_CAPABILITY_OK +#undef ESC_CAPABILITY_OK_LEN +#undef ESC_CAPABILITY_GREETING +#undef ESC_CAPABILITY_GREETING_LEN +} + +// This can get called from the UI thread or an imap thread. +// It makes sure we don't get left with partial messages in +// the memory cache. +static void DoomCacheEntry(nsIMsgMailNewsUrl* url) { + bool readingFromMemCache = false; + nsCOMPtr<nsIImapUrl> imapUrl = do_QueryInterface(url); + imapUrl->GetMsgLoadingFromCache(&readingFromMemCache); + if (!readingFromMemCache) { + nsCOMPtr<nsICacheEntry> cacheEntry; + url->GetMemCacheEntry(getter_AddRefs(cacheEntry)); + if (cacheEntry) { + MOZ_LOG(IMAPCache, LogLevel::Debug, + ("%s: Call AsyncDoom(), url=%s", __func__, + url->GetSpecOrDefault().get())); + cacheEntry->AsyncDoom(nullptr); + } + } +} + +/** + * ProcessCurrentURL() runs the current URL (m_runningUrl). + * Things to remember: + * - IMAP protocol URLs don't correspond directly to IMAP commands. A single + * URL might cause multiple IMAP commands to be issued. + * - This is all synchronous. But that's OK, because we're running in our + * own thread. + * + * @return true if another url was run, false otherwise. + */ +bool nsImapProtocol::ProcessCurrentURL() { + nsresult rv = NS_OK; + if (m_idle) EndIdle(); + + if (m_retryUrlOnError) { + // we clear this flag if we're re-running immediately, because that + // means we never sent a start running url notification, and later we + // don't send start running notification if we think we're rerunning + // the url (see first call to SetUrlState below). This means we won't + // send a start running notification, which means our stop running + // notification will be ignored because we don't think we were running. + m_runningUrl->SetRerunningUrl(false); + return RetryUrl(); + } + Log("ProcessCurrentURL", nullptr, "entering"); + (void)GetImapHostName(); // force m_hostName to get set. + + bool logonFailed = false; + bool anotherUrlRun = false; + bool rerunningUrl = false; + bool isExternalUrl; + bool validUrl = true; + + PseudoInterrupt(false); // clear this if left over from previous url. + + m_runningUrl->GetRerunningUrl(&rerunningUrl); + m_runningUrl->GetExternalLinkUrl(&isExternalUrl); + m_runningUrl->GetValidUrl(&validUrl); + m_runningUrl->GetImapAction(&m_imapAction); + + if (isExternalUrl) { + if (m_imapAction == nsIImapUrl::nsImapSelectFolder) { + // we need to send a start request so that the doc loader + // will call HandleContent on the imap service so we + // can abort this url, and run a new url in a new msg window + // to run the folder load url and get off this crazy merry-go-round. + if (m_channelListener) { + m_channelListener->OnStartRequest(m_mockChannel); + } + return false; + } + } + + if (!m_imapMailFolderSink && m_imapProtocolSink) { + // This occurs when running another URL in the main thread loop + rv = m_imapProtocolSink->SetupMainThreadProxies(); + NS_ENSURE_SUCCESS(rv, false); + } + + // Reinitialize the parser + GetServerStateParser().InitializeState(); + GetServerStateParser().SetConnected(true); + + // acknowledge that we are running the url now.. + nsCOMPtr<nsIMsgMailNewsUrl> mailnewsurl = + do_QueryInterface(m_runningUrl, &rv); + nsAutoCString urlSpec; + rv = mailnewsurl->GetSpec(urlSpec); + NS_ENSURE_SUCCESS(rv, false); + Log("ProcessCurrentURL", urlSpec.get(), + (validUrl) ? " = currentUrl" : " is not valid"); + if (!validUrl) return false; + + if (NS_SUCCEEDED(rv) && mailnewsurl && m_imapMailFolderSink && !rerunningUrl) + m_imapMailFolderSink->SetUrlState(this, mailnewsurl, true, false, NS_OK); + + // if we are set up as a channel, we should notify our channel listener that + // we are starting... so pass in ourself as the channel and not the underlying + // socket or file channel the protocol happens to be using + if (m_channelListener) // ### not sure we want to do this if rerunning url... + { + m_channelListener->OnStartRequest(m_mockChannel); + } + // If we haven't received the greeting yet, we need to make sure we strip + // it out of the input before we start to do useful things... + if (!TestFlag(IMAP_RECEIVED_GREETING)) EstablishServerConnection(); + + // Step 1: If we have not moved into the authenticated state yet then do so + // by attempting to logon. + if (!DeathSignalReceived() && NS_SUCCEEDED(GetConnectionStatus()) && + (GetServerStateParser().GetIMAPstate() == + nsImapServerResponseParser::kNonAuthenticated)) { + /* if we got here, the server's greeting should not have been PREAUTH */ + // If greeting did not contain a capability response and if user has not + // configured STARTTLS, request capabilities. If STARTTLS configured, + // capabilities will be requested after TLS handshakes are complete. + if ((GetServerStateParser().GetCapabilityFlag() == kCapabilityUndefined) && + (m_socketType != nsMsgSocketType::alwaysSTARTTLS)) { + Capability(); + } + + // If capability response has yet to occur and STARTTLS is not + // configured then drop the connection since this should not happen. Also + // drop the connection if capability response has occurred and + // the imap version is unacceptable. Show alert only for wrong version. + if (((GetServerStateParser().GetCapabilityFlag() == kCapabilityUndefined) && + (m_socketType != nsMsgSocketType::alwaysSTARTTLS)) || + (GetServerStateParser().GetCapabilityFlag() && + !(GetServerStateParser().GetCapabilityFlag() & + (kIMAP4Capability | kIMAP4rev1Capability | kIMAP4other)))) { + if (!DeathSignalReceived() && NS_SUCCEEDED(GetConnectionStatus()) && + GetServerStateParser().GetCapabilityFlag()) + AlertUserEventUsingName("imapServerNotImap4"); + + SetConnectionStatus(NS_ERROR_FAILURE); // stop netlib + } else { + if ((m_connectionType.EqualsLiteral("starttls") && + (m_socketType == nsMsgSocketType::trySTARTTLS && + (GetServerStateParser().GetCapabilityFlag() & + kHasStartTLSCapability))) || + m_socketType == nsMsgSocketType::alwaysSTARTTLS) { + StartTLS(); // Send imap STARTTLS command + if (GetServerStateParser().LastCommandSuccessful()) { + NS_ENSURE_TRUE(m_transport, false); + MOZ_ASSERT(!NS_IsMainThread()); + rv = TransportStartTLS(); // Initiate STARTTLS handshakes + if (NS_SUCCEEDED(rv)) { + // Transition to secure state is now enabled but handshakes and + // negotiation has not yet occurred. Make sure that + // the stream input response buffer is drained to avoid false + // responses to subsequent commands (capability, login etc), + // i.e., due to possible MitM attack doing pre-TLS response + // injection. We are discarding any possible malicious data + // stored prior to TransportStartTLS(). + // Note: If any non-TLS related data arrives while transitioning + // to secure state (after TransportStartTLS()), it will + // cause the TLS negotiation to fail so any injected data is never + // accessed since the transport connection will be dropped. + char discardBuf[80]; + uint64_t numBytesInStream = 0; + uint32_t numBytesRead; + rv = m_inputStream->Available(&numBytesInStream); + nsCOMPtr<nsIInputStream> kungFuGrip = m_inputStream; + // Read and discard any data available in socket buffer. + while (numBytesInStream > 0 && NS_SUCCEEDED(rv)) { + rv = m_inputStream->Read( + discardBuf, + std::min(uint64_t(sizeof discardBuf), numBytesInStream), + &numBytesRead); + numBytesInStream -= numBytesRead; + } + kungFuGrip = nullptr; + + // Discard any data lines previously read from socket buffer. + m_inputStreamBuffer->ClearBuffer(); + + // Force re-issue of "capability", because servers may + // enable other auth features (e.g. remove LOGINDISABLED + // and add AUTH=PLAIN). Sending imap data here first triggers + // the TLS negotiation handshakes. + Capability(); + + // If user has set pref mail.server.serverX.socketType to 1 + // (trySTARTTLS, now deprecated in UI) and Capability() + // succeeds, indicating TLS handshakes succeeded, set and + // latch the socketType to 2 (alwaysSTARTTLS) for this server. + if ((m_socketType == nsMsgSocketType::trySTARTTLS) && + GetServerStateParser().LastCommandSuccessful()) + m_imapServerSink->UpdateTrySTARTTLSPref(true); + + // Courier imap doesn't return STARTTLS capability if we've done + // a STARTTLS! But we need to remember this capability so we'll + // try to use STARTTLS next time. + // Update: This may not be a problem since "next time" will be + // on a new connection that is not yet in secure state. So the + // capability greeting *will* contain STARTTLS. I observed and + // tested this on Courier imap server. But keep this to be sure. + eIMAPCapabilityFlags capabilityFlag = + GetServerStateParser().GetCapabilityFlag(); + if (!(capabilityFlag & kHasStartTLSCapability)) { + capabilityFlag |= kHasStartTLSCapability; + GetServerStateParser().SetCapabilityFlag(capabilityFlag); + CommitCapability(); + } + } + if (NS_FAILED(rv)) { + nsAutoCString logLine("Enable of STARTTLS failed. Error 0x"); + logLine.AppendInt(static_cast<uint32_t>(rv), 16); + Log("ProcessCurrentURL", nullptr, logLine.get()); + if (m_socketType == nsMsgSocketType::alwaysSTARTTLS) { + SetConnectionStatus(rv); // stop netlib + if (m_transport) m_transport->Close(rv); + } else if (m_socketType == nsMsgSocketType::trySTARTTLS) + m_imapServerSink->UpdateTrySTARTTLSPref(false); + } + } else if (m_socketType == nsMsgSocketType::alwaysSTARTTLS) { + SetConnectionStatus(NS_ERROR_FAILURE); // stop netlib + if (m_transport) m_transport->Close(rv); + } else if (m_socketType == nsMsgSocketType::trySTARTTLS) { + // STARTTLS failed, so downgrade socket type + m_imapServerSink->UpdateTrySTARTTLSPref(false); + } + } else if (m_socketType == nsMsgSocketType::trySTARTTLS) { + // we didn't know the server supported TLS when we created + // the socket, so we're going to retry with a STARTTLS socket + if (GetServerStateParser().GetCapabilityFlag() & + kHasStartTLSCapability) { + ClearFlag(IMAP_CONNECTION_IS_OPEN); + TellThreadToDie(); + SetConnectionStatus(NS_ERROR_FAILURE); + return RetryUrl(); + } + // trySTARTTLS set, but server doesn't have TLS capability, + // so downgrade socket type + m_imapServerSink->UpdateTrySTARTTLSPref(false); + m_socketType = nsMsgSocketType::plain; + } + if (!DeathSignalReceived() && (NS_SUCCEEDED(GetConnectionStatus()))) { + logonFailed = !TryToLogon(); + } + if (m_retryUrlOnError) return RetryUrl(); + } + } // if death signal not received + + // We assume one IMAP thread is used for exactly one server, only. + if (m_transport && !m_securityInfo) { + MOZ_ASSERT(!NS_IsMainThread()); + GetTransportSecurityInfo(getter_AddRefs(m_securityInfo)); + } + + if (!DeathSignalReceived() && (NS_SUCCEEDED(GetConnectionStatus()))) { + // if the server supports a language extension then we should + // attempt to issue the language extension. + if (GetServerStateParser().GetCapabilityFlag() & kHasLanguageCapability) + Language(); + + if (m_runningUrl) { + bool foundMailboxesAlready = false; + m_hostSessionList->GetHaveWeEverDiscoveredFoldersForHost( + GetImapServerKey(), foundMailboxesAlready); + if (!foundMailboxesAlready) FindMailboxesIfNecessary(); + } + + nsImapState imapState = nsIImapUrl::ImapStatusNone; + if (m_runningUrl) m_runningUrl->GetRequiredImapState(&imapState); + + if (imapState == nsIImapUrl::nsImapAuthenticatedState) + ProcessAuthenticatedStateURL(); + else // must be a url that requires us to be in the selected state + ProcessSelectedStateURL(); + + if (m_retryUrlOnError) return RetryUrl(); + + // The URL has now been processed + if ((!logonFailed && NS_FAILED(GetConnectionStatus())) || + DeathSignalReceived()) + HandleCurrentUrlError(); + + } else if (!logonFailed) + HandleCurrentUrlError(); + + // if we are set up as a channel, we should notify our channel listener that + // we are stopping... so pass in ourself as the channel and not the underlying + // socket or file channel the protocol happens to be using + if (m_channelListener) { + NS_ASSERTION(m_mockChannel, "no request"); + if (m_mockChannel) { + nsresult status; + m_mockChannel->GetStatus(&status); + if (!GetServerStateParser().LastCommandSuccessful() && + NS_SUCCEEDED(status)) + status = NS_MSG_ERROR_IMAP_COMMAND_FAILED; + rv = m_channelListener->OnStopRequest(m_mockChannel, status); + } + } + bool suspendUrl = false; + m_runningUrl->GetMoreHeadersToDownload(&suspendUrl); + if (mailnewsurl && m_imapMailFolderSink) { + rv = GetConnectionStatus(); + // There are error conditions to check even if the connection is OK. + if (NS_SUCCEEDED(rv)) { + if (logonFailed) { + rv = NS_ERROR_FAILURE; + } else if (GetServerStateParser().CommandFailed()) { + rv = NS_MSG_ERROR_IMAP_COMMAND_FAILED; + } + } + if (NS_FAILED(rv)) { + MOZ_LOG( + IMAP, LogLevel::Debug, + ("URL failed with code 0x%" PRIx32 " (%s)", static_cast<uint32_t>(rv), + mailnewsurl->GetSpecOrDefault().get())); + // If discovery URL fails, clear the in-progress flag. + if (m_imapAction == nsIImapUrl::nsImapDiscoverAllBoxesUrl) { + m_hostSessionList->SetDiscoveryForHostInProgress(GetImapServerKey(), + false); + } + } + // Inform any nsIUrlListeners that the URL has finished. This will invoke + // nsIUrlListener.onStopRunningUrl(). + m_imapMailFolderSink->SetUrlState(this, mailnewsurl, false, suspendUrl, rv); + + // Doom the cache entry if shutting down or thread is terminated. + if (NS_FAILED(rv) && DeathSignalReceived() && m_mockChannel) { + MOZ_LOG(IMAPCache, LogLevel::Debug, + ("ProcessCurrentURL(): Call DoomCacheEntry()")); + DoomCacheEntry(mailnewsurl); + } + } else { + // That's seen at times in debug sessions. + NS_WARNING("missing url or sink"); + } + + // disable timeouts before caching connection. + if (m_transport) + m_transport->SetTimeout(nsISocketTransport::TIMEOUT_READ_WRITE, + PR_UINT32_MAX); + + SetFlag(IMAP_CLEAN_UP_URL_STATE); + + nsCOMPtr<nsISupports> copyState; + if (m_runningUrl) m_runningUrl->GetCopyState(getter_AddRefs(copyState)); + // this is so hokey...we MUST clear any local references to the url + // BEFORE calling ReleaseUrlState + mailnewsurl = nullptr; + + if (suspendUrl) m_imapServerSink->SuspendUrl(m_runningUrl); + // save the imap folder sink since we need it to do the CopyNextStreamMessage + RefPtr<ImapMailFolderSinkProxy> imapMailFolderSink = m_imapMailFolderSink; + // release the url as we are done with it... + ReleaseUrlState(false); + ResetProgressInfo(); + + ClearFlag(IMAP_CLEAN_UP_URL_STATE); + + if (imapMailFolderSink) { + if (copyState) { + rv = imapMailFolderSink->CopyNextStreamMessage( + GetServerStateParser().LastCommandSuccessful() && + NS_SUCCEEDED(GetConnectionStatus()), + copyState); + if (NS_FAILED(rv)) + MOZ_LOG(IMAP, LogLevel::Info, + ("CopyNextStreamMessage failed: %" PRIx32, + static_cast<uint32_t>(rv))); + + NS_ReleaseOnMainThread("nsImapProtocol, copyState", copyState.forget()); + } + // we might need this to stick around for IDLE support + m_imapMailFolderSink = imapMailFolderSink; + imapMailFolderSink = nullptr; + } else + MOZ_LOG(IMAP, LogLevel::Info, ("null imapMailFolderSink")); + + // now try queued urls, now that we've released this connection. + if (m_imapServerSink) { + if (NS_SUCCEEDED(GetConnectionStatus())) + rv = m_imapServerSink->LoadNextQueuedUrl(this, &anotherUrlRun); + else // if we don't do this, they'll just sit and spin until + // we run some other url on this server. + { + Log("ProcessCurrentURL", nullptr, "aborting queued urls"); + rv = m_imapServerSink->AbortQueuedUrls(); + } + } + + // if we didn't run another url, release the server sink to + // cut circular refs. + if (!anotherUrlRun) m_imapServerSink = nullptr; + + if (NS_FAILED(GetConnectionStatus()) || !GetServerStateParser().Connected() || + GetServerStateParser().SyntaxError()) { + if (m_imapServerSink) m_imapServerSink->RemoveServerConnection(this); + + if (!DeathSignalReceived()) { + TellThreadToDie(); + } + } else { + if (m_imapServerSink) { + bool shuttingDown; + m_imapServerSink->GetServerShuttingDown(&shuttingDown); + if (shuttingDown) m_useIdle = false; + } + } + return anotherUrlRun; +} + +bool nsImapProtocol::RetryUrl() { + nsCOMPtr<nsIImapUrl> kungFuGripImapUrl = m_runningUrl; + nsCOMPtr<nsIImapMockChannel> saveMockChannel; + + // the mock channel might be null - that's OK. + if (m_imapServerSink) + (void)m_imapServerSink->PrepareToRetryUrl(kungFuGripImapUrl, + getter_AddRefs(saveMockChannel)); + + ReleaseUrlState(true); + if (m_imapServerSink) { + m_imapServerSink->RemoveServerConnection(this); + m_imapServerSink->RetryUrl(kungFuGripImapUrl, saveMockChannel); + } + + // Hack for Bug 1586494. + // (this is a workaround to try and prevent a specific crash, and + // does nothing clarify the threading mess!) + // RetryUrl() is only ever called from the imap thread. + // Mockchannel dtor insists upon being run on the main thread. + // So make sure we don't accidentally cause the mockchannel to die right now. + if (saveMockChannel) { + NS_ReleaseOnMainThread("nsImapProtocol::RetryUrl", + saveMockChannel.forget()); + } + + return (m_imapServerSink != nullptr); // we're running a url (the same url) +} + +// ignoreBadAndNOResponses --> don't throw a error dialog if this command +// results in a NO or Bad response from the server..in other words the command +// is "exploratory" and we don't really care if it succeeds or fails. +void nsImapProtocol::ParseIMAPandCheckForNewMail( + const char* commandString, bool aIgnoreBadAndNOResponses) { + if (commandString) + GetServerStateParser().ParseIMAPServerResponse(commandString, + aIgnoreBadAndNOResponses); + else + GetServerStateParser().ParseIMAPServerResponse(m_currentCommand.get(), + aIgnoreBadAndNOResponses); + // **** fix me for new mail biff state ***** +} + +///////////////////////////////////////////////////////////////////////////////////////////// +// End of nsIStreamListenerSupport +////////////////////////////////////////////////////////////////////////////////////////////// + +NS_IMETHODIMP +nsImapProtocol::GetRunningUrl(nsIURI** result) { + if (result && m_runningUrl) + return m_runningUrl->QueryInterface(NS_GET_IID(nsIURI), (void**)result); + return NS_ERROR_NULL_POINTER; +} + +NS_IMETHODIMP nsImapProtocol::GetRunningImapURL(nsIImapUrl** aImapUrl) { + if (aImapUrl && m_runningUrl) + return m_runningUrl->QueryInterface(NS_GET_IID(nsIImapUrl), + (void**)aImapUrl); + return NS_ERROR_NULL_POINTER; +} + +/* + * Writes the data contained in dataBuffer into the current output stream. It + * also informs the transport layer that this data is now available for + * transmission. Returns a positive number for success, 0 for failure (not all + * the bytes were written to the stream, etc). We need to make another pass + * through this file to install an error system (mscott) + */ + +nsresult nsImapProtocol::SendData(const char* dataBuffer, + bool aSuppressLogging) { + nsresult rv = NS_ERROR_NULL_POINTER; + + if (!m_transport) { + Log("SendData", nullptr, "clearing IMAP_CONNECTION_IS_OPEN"); + // the connection died unexpectedly! so clear the open connection flag + ClearFlag(IMAP_CONNECTION_IS_OPEN); + TellThreadToDie(); + SetConnectionStatus(NS_ERROR_FAILURE); + return NS_ERROR_FAILURE; + } + + if (dataBuffer && m_outputStream) { + m_currentCommand = dataBuffer; + if (!aSuppressLogging) + Log("SendData", nullptr, dataBuffer); + else + Log("SendData", nullptr, + "Logging suppressed for this command (it probably contained " + "authentication information)"); + + { + // don't allow someone to close the stream/transport out from under us + // this can happen when the ui thread calls TellThreadToDie. + PR_CEnterMonitor(this); + uint32_t n; + if (m_outputStream) + rv = m_outputStream->Write(dataBuffer, PL_strlen(dataBuffer), &n); + PR_CExitMonitor(this); + } + if (NS_FAILED(rv)) { + Log("SendData", nullptr, "clearing IMAP_CONNECTION_IS_OPEN"); + // the connection died unexpectedly! so clear the open connection flag + ClearFlag(IMAP_CONNECTION_IS_OPEN); + TellThreadToDie(); + SetConnectionStatus(rv); + if (m_runningUrl && !m_retryUrlOnError) { + bool alreadyRerunningUrl; + m_runningUrl->GetRerunningUrl(&alreadyRerunningUrl); + if (!alreadyRerunningUrl) { + m_runningUrl->SetRerunningUrl(true); + m_retryUrlOnError = true; + } + } + } + } + + return rv; +} + +///////////////////////////////////////////////////////////////////////////////////////////// +// Begin protocol state machine functions... +////////////////////////////////////////////////////////////////////////////////////////////// + +// ProcessProtocolState - we override this only so we'll link - it should never +// get called. + +nsresult nsImapProtocol::ProcessProtocolState(nsIURI* url, + nsIInputStream* inputStream, + uint64_t sourceOffset, + uint32_t length) { + return NS_OK; +} + +class UrlListenerNotifierEvent : public mozilla::Runnable { + public: + UrlListenerNotifierEvent(nsIMsgMailNewsUrl* aUrl, nsIImapProtocol* aProtocol) + : mozilla::Runnable("UrlListenerNotifierEvent"), + mUrl(aUrl), + mProtocol(aProtocol) {} + + NS_IMETHOD Run() { + if (mUrl) { + nsCOMPtr<nsIMsgFolder> folder; + mUrl->GetFolder(getter_AddRefs(folder)); + NS_ENSURE_TRUE(folder, NS_OK); + nsCOMPtr<nsIImapMailFolderSink> folderSink(do_QueryInterface(folder)); + // This causes the url listener to get OnStart and Stop notifications. + folderSink->SetUrlState(mProtocol, mUrl, true, false, NS_OK); + folderSink->SetUrlState(mProtocol, mUrl, false, false, NS_OK); + } + return NS_OK; + } + + private: + nsCOMPtr<nsIMsgMailNewsUrl> mUrl; + nsCOMPtr<nsIImapProtocol> mProtocol; +}; + +bool nsImapProtocol::TryToRunUrlLocally(nsIURI* aURL, nsISupports* aConsumer) { + nsresult rv; + nsCOMPtr<nsIImapUrl> imapUrl(do_QueryInterface(aURL, &rv)); + NS_ENSURE_SUCCESS(rv, false); + nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(aURL); + nsCString messageIdString; + imapUrl->GetListOfMessageIds(messageIdString); + bool useLocalCache = false; + if (!messageIdString.IsEmpty() && + !HandlingMultipleMessages(messageIdString)) { + nsImapAction action; + imapUrl->GetImapAction(&action); + nsCOMPtr<nsIMsgFolder> folder; + mailnewsUrl->GetFolder(getter_AddRefs(folder)); + NS_ENSURE_TRUE(folder, false); + + folder->HasMsgOffline(strtoul(messageIdString.get(), nullptr, 10), + &useLocalCache); + mailnewsUrl->SetMsgIsInLocalCache(useLocalCache); + // We're downloading a single message for offline use, and it's + // already offline. So we shouldn't do anything, but we do + // need to notify the url listener. + if (useLocalCache && action == nsIImapUrl::nsImapMsgDownloadForOffline) { + nsCOMPtr<nsIRunnable> event = + new UrlListenerNotifierEvent(mailnewsUrl, this); + // Post this as an event because it can lead to re-entrant calls to + // LoadNextQueuedUrl if the listener runs a new url. + if (event) NS_DispatchToCurrentThread(event); + return true; + } + } + if (!useLocalCache) return false; + + nsCOMPtr<nsIImapMockChannel> mockChannel; + imapUrl->GetMockChannel(getter_AddRefs(mockChannel)); + if (!mockChannel) return false; + + nsImapMockChannel* imapChannel = + static_cast<nsImapMockChannel*>(mockChannel.get()); + if (!imapChannel) return false; + + nsCOMPtr<nsILoadGroup> loadGroup; + imapChannel->GetLoadGroup(getter_AddRefs(loadGroup)); + if (!loadGroup) // if we don't have one, the url will snag one from the msg + // window... + mailnewsUrl->GetLoadGroup(getter_AddRefs(loadGroup)); + + if (loadGroup) + loadGroup->RemoveRequest((nsIRequest*)mockChannel, + nullptr /* context isupports */, NS_OK); + + if (imapChannel->ReadFromLocalCache()) { + (void)imapChannel->NotifyStartEndReadFromCache(true); + return true; + } + return false; +} + +// LoadImapUrl takes a url, initializes all of our url specific data by calling +// SetupUrl. Finally, we signal the url to run monitor to let the imap main +// thread loop process the current url (it is waiting on this monitor). There +// is a contract that the imap thread has already been started before we +// attempt to load a url... +// LoadImapUrl() is called by nsImapIncomingServer to run a queued url on a free +// connection. +NS_IMETHODIMP nsImapProtocol::LoadImapUrl(nsIURI* aURL, + nsISupports* aConsumer) { + nsresult rv = NS_ERROR_FAILURE; + if (aURL) { +#ifdef DEBUG_bienvenu + printf("loading url %s\n", aURL->GetSpecOrDefault().get()); +#endif + // We might be able to fulfill the request locally (e.g. fetching a message + // which is already stored offline). + if (TryToRunUrlLocally(aURL, aConsumer)) return NS_OK; + m_urlInProgress = true; + m_imapMailFolderSink = nullptr; + rv = SetupWithUrl(aURL, aConsumer); + m_lastActiveTime = PR_Now(); + } + return rv; +} + +nsresult nsImapProtocol::LoadImapUrlInternal() { + nsresult rv = NS_ERROR_FAILURE; + + if (m_transport && m_mockChannel) { + m_transport->SetTimeout(nsISocketTransport::TIMEOUT_CONNECT, + gResponseTimeout + 60); + int32_t readWriteTimeout = gResponseTimeout; + if (m_runningUrl) { + m_runningUrl->GetImapAction(&m_imapAction); + // This is a silly hack, but the default of 100 seconds is typically way + // too long for things like APPEND, which should come back immediately. + // However, for large messages on some servers the final append response + // time can be longer. So now it is one-fifth of the configured + // `mailnews.tcptimeout' which defaults to 20 seconds. + if (m_imapAction == nsIImapUrl::nsImapAppendMsgFromFile || + m_imapAction == nsIImapUrl::nsImapAppendDraftFromFile) { + readWriteTimeout = gAppendTimeout; + } else if (m_imapAction == nsIImapUrl::nsImapOnlineMove || + m_imapAction == nsIImapUrl::nsImapOnlineCopy) { + nsCString messageIdString; + m_runningUrl->GetListOfMessageIds(messageIdString); + uint32_t copyCount = CountMessagesInIdString(messageIdString.get()); + // If we're move/copying a large number of messages, + // which should be rare, increase the timeout based on number + // of messages. 40 messages per second should be sufficiently slow. + if (copyCount > 2400) // 40 * 60, 60 is default read write timeout + readWriteTimeout = + std::max(readWriteTimeout, (int32_t)copyCount / 40); + } + } + m_transport->SetTimeout(nsISocketTransport::TIMEOUT_READ_WRITE, + readWriteTimeout); + // Set the security info for the mock channel to be the security info for + // our underlying transport. + if (m_securityInfo) { + m_mockChannel->SetSecurityInfo(m_securityInfo); + } + + SetSecurityCallbacksFromChannel(m_transport, m_mockChannel); + + nsCOMPtr<nsITransportEventSink> sinkMC = do_QueryInterface(m_mockChannel); + if (sinkMC) { + nsCOMPtr<nsIThread> thread = do_GetMainThread(); + RefPtr<nsImapTransportEventSink> sink = new nsImapTransportEventSink; + rv = net_NewTransportEventSinkProxy(getter_AddRefs(sink->m_proxy), sinkMC, + thread); + NS_ENSURE_SUCCESS(rv, rv); + m_transport->SetEventSink(sink, nullptr); + } + + // And if we have a cache2 entry that we are saving the message to, set the + // security info on it too. + nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_runningUrl); + if (mailnewsUrl && m_securityInfo) { + nsCOMPtr<nsICacheEntry> cacheEntry; + mailnewsUrl->GetMemCacheEntry(getter_AddRefs(cacheEntry)); + if (cacheEntry) { + cacheEntry->SetSecurityInfo(m_securityInfo); + } + } + } + + rv = SetupSinkProxy(); // generate proxies for all of the event sinks in the + // url + if (NS_FAILED(rv)) // URL can be invalid. + return rv; + + if (m_transport && m_runningUrl) { + nsImapAction imapAction; + m_runningUrl->GetImapAction(&imapAction); + // if we're shutting down, and not running the kinds of urls we run at + // shutdown, then this should fail because running urls during + // shutdown will very likely fail and potentially hang. + nsCOMPtr<nsIMsgAccountManager> accountMgr = + do_GetService("@mozilla.org/messenger/account-manager;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + bool shuttingDown = false; + (void)accountMgr->GetShutdownInProgress(&shuttingDown); + if (shuttingDown && imapAction != nsIImapUrl::nsImapExpungeFolder && + imapAction != nsIImapUrl::nsImapDeleteAllMsgs && + imapAction != nsIImapUrl::nsImapDeleteFolder) + return NS_ERROR_FAILURE; + + // if we're running a select or delete all, do a noop first. + // this should really be in the connection cache code when we know + // we're pulling out a selected state connection, but maybe we + // can get away with this. + m_needNoop = (imapAction == nsIImapUrl::nsImapSelectFolder || + imapAction == nsIImapUrl::nsImapDeleteAllMsgs); + + // We now have a url to run so signal the monitor for url ready to be + // processed... + ReentrantMonitorAutoEnter urlReadyMon(m_urlReadyToRunMonitor); + m_nextUrlReadyToRun = true; + urlReadyMon.Notify(); + + } // if we have an imap url and a transport + else { + NS_ASSERTION(false, "missing channel or running url"); + } + + return rv; +} + +NS_IMETHODIMP nsImapProtocol::IsBusy(bool* aIsConnectionBusy, + bool* isInboxConnection) { + if (!aIsConnectionBusy || !isInboxConnection) return NS_ERROR_NULL_POINTER; + nsresult rv = NS_OK; + *aIsConnectionBusy = false; + *isInboxConnection = false; + if (!m_transport) { + // this connection might not be fully set up yet. + rv = NS_ERROR_FAILURE; + } else { + if (m_urlInProgress) // do we have a url? That means we're working on it... + *aIsConnectionBusy = true; + + if (GetServerStateParser().GetIMAPstate() == + nsImapServerResponseParser::kFolderSelected && + GetServerStateParser().GetSelectedMailboxName() && + PL_strcasecmp(GetServerStateParser().GetSelectedMailboxName(), + "Inbox") == 0) + *isInboxConnection = true; + } + return rv; +} + +#define IS_SUBSCRIPTION_RELATED_ACTION(action) \ + (action == nsIImapUrl::nsImapSubscribe || \ + action == nsIImapUrl::nsImapUnsubscribe || \ + action == nsIImapUrl::nsImapDiscoverAllBoxesUrl || \ + action == nsIImapUrl::nsImapListFolder) + +// canRunUrl means the connection is not busy, and is in the selected state +// for the desired folder (or authenticated). +// has to wait means it's in the right selected state, but busy. +NS_IMETHODIMP nsImapProtocol::CanHandleUrl(nsIImapUrl* aImapUrl, + bool* aCanRunUrl, bool* hasToWait) { + if (!aCanRunUrl || !hasToWait || !aImapUrl) return NS_ERROR_NULL_POINTER; + nsresult rv = NS_OK; + MutexAutoLock mon(mLock); + + *aCanRunUrl = false; // assume guilty until proven otherwise... + *hasToWait = false; + + if (DeathSignalReceived()) return NS_ERROR_FAILURE; + + bool isBusy = false; + bool isInboxConnection = false; + + if (!m_transport) { + // this connection might not be fully set up yet. + return NS_ERROR_FAILURE; + } + IsBusy(&isBusy, &isInboxConnection); + bool inSelectedState = GetServerStateParser().GetIMAPstate() == + nsImapServerResponseParser::kFolderSelected; + + nsAutoCString curSelectedUrlFolderName; + nsAutoCString pendingUrlFolderName; + if (inSelectedState) + curSelectedUrlFolderName = GetServerStateParser().GetSelectedMailboxName(); + + if (isBusy) { + nsImapState curUrlImapState; + NS_ASSERTION(m_runningUrl, "isBusy, but no running url."); + if (m_runningUrl) { + m_runningUrl->GetRequiredImapState(&curUrlImapState); + if (curUrlImapState == nsIImapUrl::nsImapSelectedState) { + char* folderName = GetFolderPathString(); + if (!curSelectedUrlFolderName.Equals(folderName)) + pendingUrlFolderName.Assign(folderName); + inSelectedState = true; + PR_Free(folderName); + } + } + } + + nsImapState imapState; + nsImapAction actionForProposedUrl; + aImapUrl->GetImapAction(&actionForProposedUrl); + aImapUrl->GetRequiredImapState(&imapState); + + // OK, this is a bit of a hack - we're going to pretend that + // these types of urls requires a selected state connection on + // the folder in question. This isn't technically true, + // but we would much rather use that connection for several reasons, + // one is that some UW servers require us to use that connection + // the other is that we don't want to leave a connection dangling in + // the selected state for the deleted folder. + // If we don't find a connection in that selected state, + // we'll fall back to the first free connection. + bool isSelectedStateUrl = + imapState == nsIImapUrl::nsImapSelectedState || + actionForProposedUrl == nsIImapUrl::nsImapDeleteFolder || + actionForProposedUrl == nsIImapUrl::nsImapRenameFolder || + actionForProposedUrl == nsIImapUrl::nsImapMoveFolderHierarchy || + actionForProposedUrl == nsIImapUrl::nsImapAppendDraftFromFile || + actionForProposedUrl == nsIImapUrl::nsImapAppendMsgFromFile || + actionForProposedUrl == nsIImapUrl::nsImapFolderStatus; + + nsCOMPtr<nsIMsgMailNewsUrl> msgUrl = do_QueryInterface(aImapUrl); + nsCOMPtr<nsIMsgIncomingServer> server; + rv = msgUrl->GetServer(getter_AddRefs(server)); + if (NS_SUCCEEDED(rv)) { + // compare host/user between url and connection. + nsCString urlHostName; + nsCString urlUserName; + rv = server->GetHostName(urlHostName); + NS_ENSURE_SUCCESS(rv, rv); + rv = server->GetUsername(urlUserName); + NS_ENSURE_SUCCESS(rv, rv); + + if ((GetImapHostName().IsEmpty() || + urlHostName.Equals(GetImapHostName(), + nsCaseInsensitiveCStringComparator)) && + (GetImapUserName().IsEmpty() || + urlUserName.Equals(GetImapUserName(), + nsCaseInsensitiveCStringComparator))) { + if (isSelectedStateUrl) { + if (inSelectedState) { + // *** jt - in selected state can only run url with + // matching foldername + char* folderNameForProposedUrl = nullptr; + rv = aImapUrl->CreateServerSourceFolderPathString( + &folderNameForProposedUrl); + if (NS_SUCCEEDED(rv) && folderNameForProposedUrl) { + bool isInbox = + PL_strcasecmp("Inbox", folderNameForProposedUrl) == 0; + if (!curSelectedUrlFolderName.IsEmpty() || + !pendingUrlFolderName.IsEmpty()) { + bool matched = isInbox + ? PL_strcasecmp(curSelectedUrlFolderName.get(), + folderNameForProposedUrl) == 0 + : PL_strcmp(curSelectedUrlFolderName.get(), + folderNameForProposedUrl) == 0; + if (!matched && !pendingUrlFolderName.IsEmpty()) { + matched = isInbox ? PL_strcasecmp(pendingUrlFolderName.get(), + folderNameForProposedUrl) == 0 + : PL_strcmp(pendingUrlFolderName.get(), + folderNameForProposedUrl) == 0; + } + if (matched) { + if (isBusy) + *hasToWait = true; + else + *aCanRunUrl = true; + } + } + } + MOZ_LOG(IMAP, LogLevel::Debug, + ("proposed url = %s folder for connection %s has To Wait = " + "%s can run = %s", + folderNameForProposedUrl, curSelectedUrlFolderName.get(), + (*hasToWait) ? "true" : "false", + (*aCanRunUrl) ? "true" : "false")); + PR_FREEIF(folderNameForProposedUrl); + } + } else // *** jt - an authenticated state url can be run in either + // authenticated or selected state + { + nsImapAction actionForRunningUrl; + + // If proposed url is subscription related, and we are currently running + // a subscription url, then we want to queue the proposed url after the + // current url. Otherwise, we can run this url if we're not busy. If we + // never find a running subscription-related url, the caller will just + // use whatever free connection it can find, which is what we want. + if (IS_SUBSCRIPTION_RELATED_ACTION(actionForProposedUrl)) { + if (isBusy && m_runningUrl) { + m_runningUrl->GetImapAction(&actionForRunningUrl); + if (IS_SUBSCRIPTION_RELATED_ACTION(actionForRunningUrl)) { + *aCanRunUrl = false; + *hasToWait = true; + } + } + } else { + if (!isBusy) *aCanRunUrl = true; + } + } + } + } + return rv; +} + +// Command tag handling stuff. +// Zero tag number indicates never used so set it to an initial random number +// between 1 and 100. Otherwise just increment the uint32_t value unless it +// rolls to zero then set it to 1. Then convert the tag number to a string for +// use in IMAP commands. +void nsImapProtocol::IncrementCommandTagNumber() { + if (m_currentServerCommandTagNumber == 0) { + srand((unsigned)m_lastCheckTime); + m_currentServerCommandTagNumber = 1 + (rand() % 100); + } else if (++m_currentServerCommandTagNumber == 0) { + m_currentServerCommandTagNumber = 1; + } + sprintf(m_currentServerCommandTag, "%u", m_currentServerCommandTagNumber); +} + +const char* nsImapProtocol::GetServerCommandTag() { + return m_currentServerCommandTag; +} + +/** + * ProcessSelectedStateURL() is a helper for ProcessCurrentURL(). It handles + * running URLs which require the connection to be in the selected state. + * It will issue SELECT commands if needed to make sure the correct mailbox + * is selected. + */ +void nsImapProtocol::ProcessSelectedStateURL() { + nsCString mailboxName; + bool bMessageIdsAreUids = true; + bool moreHeadersToDownload; + imapMessageFlagsType msgFlags = 0; + nsCString urlHost; + + // this can't fail, can it? + nsresult res; + res = m_runningUrl->GetImapAction(&m_imapAction); + m_runningUrl->MessageIdsAreUids(&bMessageIdsAreUids); + m_runningUrl->GetMsgFlags(&msgFlags); + m_runningUrl->GetMoreHeadersToDownload(&moreHeadersToDownload); + + res = CreateServerSourceFolderPathString(getter_Copies(mailboxName)); + if (NS_FAILED(res)) + Log("ProcessSelectedStateURL", nullptr, + "error getting source folder path string"); + + if (NS_SUCCEEDED(res) && !DeathSignalReceived()) { + bool selectIssued = false; + if (GetServerStateParser().GetIMAPstate() == + nsImapServerResponseParser::kFolderSelected) { + if (GetServerStateParser().GetSelectedMailboxName() && + PL_strcmp(GetServerStateParser().GetSelectedMailboxName(), + mailboxName.get())) { // we are selected in another folder + if (m_closeNeededBeforeSelect) ImapClose(); + if (GetServerStateParser().LastCommandSuccessful()) { + selectIssued = true; + SelectMailbox(mailboxName.get()); + } + } else if (!GetServerStateParser() + .GetSelectedMailboxName()) { // why are we in the + // selected state with no + // box name? + SelectMailbox(mailboxName.get()); + selectIssued = true; + } else if (moreHeadersToDownload && + m_imapMailFolderSink) // we need to fetch older headers + { + nsTArray<nsMsgKey> msgIdList; + bool more; + m_imapMailFolderSink->GetMsgHdrsToDownload( + &more, &m_progressExpectedNumber, msgIdList); + if (msgIdList.Length() > 0) { + FolderHeaderDump(msgIdList.Elements(), msgIdList.Length()); + m_runningUrl->SetMoreHeadersToDownload(more); + // We're going to be re-running this url. + if (more) m_runningUrl->SetRerunningUrl(true); + } + HeaderFetchCompleted(); + } else { + // get new message counts, if any, from server + if (m_needNoop) { + // For some IMAP servers, to detect new email we must send imap + // SELECT even if already SELECTed on the same mailbox. + if (m_forceSelect) { + SelectMailbox(mailboxName.get()); + selectIssued = true; + } + + m_noopCount++; + if ((gPromoteNoopToCheckCount > 0 && + (m_noopCount % gPromoteNoopToCheckCount) == 0) || + CheckNeeded()) + Check(); + else + Noop(); // I think this is needed when we're using a cached + // connection + m_needNoop = false; + } + } + } else { + // go to selected state + SelectMailbox(mailboxName.get()); + selectIssued = GetServerStateParser().LastCommandSuccessful(); + } + + if (selectIssued) RefreshACLForFolderIfNecessary(mailboxName.get()); + + bool uidValidityOk = true; + if (GetServerStateParser().LastCommandSuccessful() && selectIssued && + (m_imapAction != nsIImapUrl::nsImapSelectFolder) && + (m_imapAction != nsIImapUrl::nsImapLiteSelectFolder)) { + // error on the side of caution, if the fe event fails to set + // uidStruct->returnValidity, then assume that UIDVALIDITY did not roll. + // This is a common case event for attachments that are fetched within a + // browser context. + if (!DeathSignalReceived()) + uidValidityOk = m_uidValidity == kUidUnknown || + m_uidValidity == GetServerStateParser().FolderUID(); + } + + if (!uidValidityOk) + Log("ProcessSelectedStateURL", nullptr, "uid validity not ok"); + if (GetServerStateParser().LastCommandSuccessful() && + !DeathSignalReceived() && + (uidValidityOk || m_imapAction == nsIImapUrl::nsImapDeleteAllMsgs)) { + if (GetServerStateParser().CurrentFolderReadOnly()) { + Log("ProcessSelectedStateURL", nullptr, "current folder read only"); + if (m_imapAction == nsIImapUrl::nsImapAddMsgFlags || + m_imapAction == nsIImapUrl::nsImapSubtractMsgFlags) { + bool canChangeFlag = false; + if (GetServerStateParser().ServerHasACLCapability() && + m_imapMailFolderSink) { + uint32_t aclFlags = 0; + + if (NS_SUCCEEDED(m_imapMailFolderSink->GetAclFlags(&aclFlags)) && + aclFlags != 0) // make sure we have some acl flags + canChangeFlag = ((msgFlags & kImapMsgSeenFlag) && + (aclFlags & IMAP_ACL_STORE_SEEN_FLAG)); + } else + canChangeFlag = (GetServerStateParser().SettablePermanentFlags() & + msgFlags) == msgFlags; + if (!canChangeFlag) return; + } + if (m_imapAction == nsIImapUrl::nsImapExpungeFolder || + m_imapAction == nsIImapUrl::nsImapDeleteMsg || + m_imapAction == nsIImapUrl::nsImapDeleteAllMsgs) + return; + } + switch (m_imapAction) { + case nsIImapUrl::nsImapLiteSelectFolder: + if (GetServerStateParser().LastCommandSuccessful() && + m_imapMailFolderSink && !moreHeadersToDownload) { + m_imapMailFolderSink->SetUidValidity( + GetServerStateParser().FolderUID()); + ProcessMailboxUpdate(false); // handle uidvalidity change + } + break; + case nsIImapUrl::nsImapSaveMessageToDisk: + case nsIImapUrl::nsImapMsgFetch: + case nsIImapUrl::nsImapMsgFetchPeek: + case nsIImapUrl::nsImapMsgDownloadForOffline: + case nsIImapUrl::nsImapMsgPreview: { + nsCString messageIdString; + m_runningUrl->GetListOfMessageIds(messageIdString); + // we don't want to send the flags back in a group + if (HandlingMultipleMessages(messageIdString) || + m_imapAction == nsIImapUrl::nsImapMsgDownloadForOffline || + m_imapAction == nsIImapUrl::nsImapMsgPreview) { + // multiple messages, fetch them all + SetProgressString(IMAP_MESSAGES_STRING_INDEX); + + m_progressCurrentNumber[m_stringIndex] = 0; + m_progressExpectedNumber = + CountMessagesInIdString(messageIdString.get()); + + FetchMessage(messageIdString, + (m_imapAction == nsIImapUrl::nsImapMsgPreview) + ? kBodyStart + : kEveryThingRFC822Peek); + if (m_imapAction == nsIImapUrl::nsImapMsgPreview) + HeaderFetchCompleted(); + SetProgressString(IMAP_EMPTY_STRING_INDEX); + } else { + // A single message ID + nsIMAPeFetchFields whatToFetch = kEveryThingRFC822; + if (m_imapAction == nsIImapUrl::nsImapMsgFetchPeek) + whatToFetch = kEveryThingRFC822Peek; + + // Note: Should no longer fetch a specific imap section (part). + // First, let's see if we're requesting a specific MIME part. + char* imappart = nullptr; + m_runningUrl->GetImapPartToFetch(&imappart); + MOZ_ASSERT(!imappart, "no longer fetching imap section/imappart"); + // downloading a single message: try to do it by bodystructure, + // and/or do it by chunks + // Note: No longer doing bodystructure. + uint32_t messageSize = GetMessageSize(messageIdString); + + // The "wontFit" and cache parameter calculations (customLimit, + // realLimit) are only for debug information logging below. + if (MOZ_LOG_TEST(IMAPCache, LogLevel::Debug)) { + nsCOMPtr<nsIMsgMailNewsUrl> mailnewsurl = + do_QueryInterface(m_runningUrl); + if (mailnewsurl) { + bool wontFit = net::CacheObserver::EntryIsTooBig( + messageSize, gUseDiskCache2); + int64_t customLimit; + int64_t realLimit; + if (gUseDiskCache2) { + customLimit = net::CacheObserver::MaxDiskEntrySize(); + realLimit = net::CacheObserver::DiskCacheCapacity(); + } else { + customLimit = net::CacheObserver::MaxMemoryEntrySize(); + realLimit = net::CacheObserver::MemoryCacheCapacity(); + } + if (!(customLimit & (int64_t)0x80000000)) + customLimit <<= 10; // multiply by 1024 to get num bytes + else + customLimit = (int32_t)customLimit; // make it negative + realLimit <<= (10 - 3); // 1/8th capacity, num bytes. + if (customLimit > -1 && customLimit < realLimit) + realLimit = customLimit; + MOZ_LOG(IMAPCache, LogLevel::Debug, + ("%s: customLimit=%" PRId64 ", realLimit=%" PRId64, + __func__, customLimit, realLimit)); + MOZ_LOG(IMAPCache, LogLevel::Debug, + ("%s: URL=%s, messageSize=%d, cache too small=%d(bool)", + __func__, mailnewsurl->GetSpecOrDefault().get(), + messageSize, wontFit)); + } + } + // Note again: No longer doing bodystructure. + // Fetch the whole thing, and try to do it in chunks. + MOZ_LOG( + IMAPCache, LogLevel::Debug, + ("%s: Fetch entire message with FetchTryChunking", __func__)); + FetchTryChunking(messageIdString, whatToFetch, bMessageIdsAreUids, + NULL, messageSize, true); + // If fetch was not a peek, ensure that the message displays as + // read (not bold) in case the server fails to mark the message + // as SEEN. + if (GetServerStateParser().LastCommandSuccessful() && + m_imapAction != nsIImapUrl::nsImapMsgFetchPeek) { + uint32_t uid = strtoul(messageIdString.get(), nullptr, 10); + int32_t index; + bool foundIt; + imapMessageFlagsType flags = + m_flagState->GetMessageFlagsFromUID(uid, &foundIt, &index); + if (foundIt) { + flags |= kImapMsgSeenFlag; + m_flagState->SetMessageFlags(index, flags); + } + } + } + } break; + case nsIImapUrl::nsImapExpungeFolder: + Expunge(); + // note fall through to next cases. + [[fallthrough]]; + case nsIImapUrl::nsImapSelectFolder: + case nsIImapUrl::nsImapSelectNoopFolder: + if (!moreHeadersToDownload) ProcessMailboxUpdate(true); + break; + case nsIImapUrl::nsImapMsgHeader: { + nsCString messageIds; + m_runningUrl->GetListOfMessageIds(messageIds); + + FetchMessage(messageIds, kHeadersRFC822andUid); + // if we explicitly ask for headers, as opposed to getting them as a + // result of selecting the folder, or biff, send the + // headerFetchCompleted notification to flush out the header cache. + HeaderFetchCompleted(); + } break; + case nsIImapUrl::nsImapSearch: { + nsAutoCString searchCriteriaString; + m_runningUrl->CreateSearchCriteriaString( + getter_Copies(searchCriteriaString)); + Search(searchCriteriaString.get(), bMessageIdsAreUids); + // drop the results on the floor for now + } break; + case nsIImapUrl::nsImapUserDefinedMsgCommand: { + nsCString messageIdString; + nsCString command; + + m_runningUrl->GetCommand(command); + m_runningUrl->GetListOfMessageIds(messageIdString); + IssueUserDefinedMsgCommand(command.get(), messageIdString.get()); + } break; + case nsIImapUrl::nsImapUserDefinedFetchAttribute: { + nsCString messageIdString; + nsCString attribute; + + m_runningUrl->GetCustomAttributeToFetch(attribute); + m_runningUrl->GetListOfMessageIds(messageIdString); + FetchMsgAttribute(messageIdString, attribute); + } break; + case nsIImapUrl::nsImapMsgStoreCustomKeywords: { + // If the server doesn't support user defined flags, don't try to + // define/set new ones. But if this is an attempt by TB to set or + // reset flags "Junk" or "NonJunk", change "Junk" or "NonJunk" to + // "$Junk" or "$NotJunk" respectively and store the modified flag + // name if the server doesn't support storing user defined flags + // and the server does allow storing the almost-standard flag names + // "$Junk" and "$NotJunk". Yahoo imap server is an example of this. + uint16_t userFlags = 0; + GetSupportedUserFlags(&userFlags); + bool userDefinedSettable = userFlags & kImapMsgSupportUserFlag; + bool stdJunkOk = GetServerStateParser().IsStdJunkNotJunkUseOk(); + + nsCString messageIdString; + nsCString addFlags; + nsCString subtractFlags; + + m_runningUrl->GetListOfMessageIds(messageIdString); + m_runningUrl->GetCustomAddFlags(addFlags); + m_runningUrl->GetCustomSubtractFlags(subtractFlags); + if (!addFlags.IsEmpty()) { + if (!userDefinedSettable) { + if (stdJunkOk) { + if (addFlags.EqualsIgnoreCase("junk")) + addFlags = "$Junk"; + else if (addFlags.EqualsIgnoreCase("nonjunk")) + addFlags = "$NotJunk"; + else + break; + } else + break; + } + nsAutoCString storeString("+FLAGS ("); + storeString.Append(addFlags); + storeString.Append(')'); + Store(messageIdString, storeString.get(), true); + } + if (!subtractFlags.IsEmpty()) { + if (!userDefinedSettable) { + if (stdJunkOk) { + if (subtractFlags.EqualsIgnoreCase("junk")) + subtractFlags = "$Junk"; + else if (subtractFlags.EqualsIgnoreCase("nonjunk")) + subtractFlags = "$NotJunk"; + else + break; + } else + break; + } + nsAutoCString storeString("-FLAGS ("); + storeString.Append(subtractFlags); + storeString.Append(')'); + Store(messageIdString, storeString.get(), true); + } + } break; + case nsIImapUrl::nsImapDeleteMsg: { + nsCString messageIdString; + m_runningUrl->GetListOfMessageIds(messageIdString); + + ProgressEventFunctionUsingName( + HandlingMultipleMessages(messageIdString) + ? "imapDeletingMessages" + : "imapDeletingMessage"); + + Store(messageIdString, "+FLAGS (\\Deleted)", bMessageIdsAreUids); + + if (GetServerStateParser().LastCommandSuccessful()) { + nsCString canonicalName; + const char* selectedMailboxName = + GetServerStateParser().GetSelectedMailboxName(); + if (selectedMailboxName) { + m_runningUrl->AllocateCanonicalPath( + selectedMailboxName, kOnlineHierarchySeparatorUnknown, + getter_Copies(canonicalName)); + } + + if (m_imapMessageSink) + m_imapMessageSink->NotifyMessageDeleted( + canonicalName.get(), false, messageIdString.get()); + // notice we don't wait for this to finish... + } else + HandleMemoryFailure(); + } break; + case nsIImapUrl::nsImapDeleteFolderAndMsgs: + DeleteFolderAndMsgs(mailboxName.get()); + break; + case nsIImapUrl::nsImapDeleteAllMsgs: { + uint32_t numberOfMessages = GetServerStateParser().NumberOfMessages(); + if (numberOfMessages) { + Store("1:*"_ns, "+FLAGS.SILENT (\\Deleted)", + false); // use sequence #'s + + if (GetServerStateParser().LastCommandSuccessful()) + Expunge(); // expunge messages with deleted flag + if (GetServerStateParser().LastCommandSuccessful()) { + nsCString canonicalName; + const char* selectedMailboxName = + GetServerStateParser().GetSelectedMailboxName(); + if (selectedMailboxName) { + m_runningUrl->AllocateCanonicalPath( + selectedMailboxName, kOnlineHierarchySeparatorUnknown, + getter_Copies(canonicalName)); + } + + if (m_imapMessageSink) + m_imapMessageSink->NotifyMessageDeleted(canonicalName.get(), + true, nullptr); + } + } + bool deleteSelf = false; + DeleteSubFolders(mailboxName.get(), deleteSelf); // don't delete self + } break; + case nsIImapUrl::nsImapAppendDraftFromFile: { + OnAppendMsgFromFile(); + } break; + case nsIImapUrl::nsImapAddMsgFlags: { + nsCString messageIdString; + m_runningUrl->GetListOfMessageIds(messageIdString); + + ProcessStoreFlags(messageIdString, bMessageIdsAreUids, msgFlags, + true); + } break; + case nsIImapUrl::nsImapSubtractMsgFlags: { + nsCString messageIdString; + m_runningUrl->GetListOfMessageIds(messageIdString); + + ProcessStoreFlags(messageIdString, bMessageIdsAreUids, msgFlags, + false); + } break; + case nsIImapUrl::nsImapSetMsgFlags: { + nsCString messageIdString; + m_runningUrl->GetListOfMessageIds(messageIdString); + + ProcessStoreFlags(messageIdString, bMessageIdsAreUids, msgFlags, + true); + ProcessStoreFlags(messageIdString, bMessageIdsAreUids, ~msgFlags, + false); + } break; + case nsIImapUrl::nsImapBiff: + PeriodicBiff(); + break; + case nsIImapUrl::nsImapOnlineCopy: + case nsIImapUrl::nsImapOnlineMove: { + nsCString messageIdString; + m_runningUrl->GetListOfMessageIds(messageIdString); + char* destinationMailbox = + OnCreateServerDestinationFolderPathString(); + + if (destinationMailbox) { + if (m_imapAction == nsIImapUrl::nsImapOnlineMove) { + if (HandlingMultipleMessages(messageIdString)) + ProgressEventFunctionUsingNameWithString("imapMovingMessages", + destinationMailbox); + else + ProgressEventFunctionUsingNameWithString("imapMovingMessage", + destinationMailbox); + } else { + if (HandlingMultipleMessages(messageIdString)) + ProgressEventFunctionUsingNameWithString("imapCopyingMessages", + destinationMailbox); + else + ProgressEventFunctionUsingNameWithString("imapCopyingMessage", + destinationMailbox); + } + Copy(messageIdString.get(), destinationMailbox, bMessageIdsAreUids); + PR_FREEIF(destinationMailbox); + ImapOnlineCopyState copyState; + if (DeathSignalReceived()) + copyState = ImapOnlineCopyStateType::kInterruptedState; + else + copyState = GetServerStateParser().LastCommandSuccessful() + ? (ImapOnlineCopyState) + ImapOnlineCopyStateType::kSuccessfulCopy + : (ImapOnlineCopyState) + ImapOnlineCopyStateType::kFailedCopy; + if (m_imapMailFolderSink) + m_imapMailFolderSink->OnlineCopyCompleted(this, copyState); + // Don't mark message 'Deleted' for AOL servers or standard imap + // servers that support MOVE since we already issued an 'xaol-move' + // or 'move' command. + if (GetServerStateParser().LastCommandSuccessful() && + (m_imapAction == nsIImapUrl::nsImapOnlineMove) && + !(GetServerStateParser().ServerIsAOLServer() || + GetServerStateParser().GetCapabilityFlag() & + kHasMoveCapability)) { + // Simulate MOVE for servers that don't support MOVE: do + // COPY-DELETE-EXPUNGE. + Store(messageIdString, "+FLAGS (\\Deleted \\Seen)", + bMessageIdsAreUids); + bool storeSuccessful = + GetServerStateParser().LastCommandSuccessful(); + if (storeSuccessful) { + if (gExpungeAfterDelete) { + // This will expunge all emails marked as deleted in mailbox, + // not just the ones marked as deleted above. + Expunge(); + } else { + // Check if UIDPLUS capable so we can just expunge emails we + // just copied and marked as deleted. This prevents expunging + // emails that other clients may have marked as deleted in the + // mailbox and don't want them to disappear. Only do + // UidExpunge() when user selected delete method is "Move it + // to this folder" or "Remove it immediately", not when the + // delete method is "Just mark it as deleted". + if (!GetShowDeletedMessages() && + (GetServerStateParser().GetCapabilityFlag() & + kUidplusCapability)) { + UidExpunge(messageIdString); + } + } + } + if (m_imapMailFolderSink) { + copyState = storeSuccessful + ? (ImapOnlineCopyState) + ImapOnlineCopyStateType::kSuccessfulDelete + : (ImapOnlineCopyState) + ImapOnlineCopyStateType::kFailedDelete; + m_imapMailFolderSink->OnlineCopyCompleted(this, copyState); + } + } + } else + HandleMemoryFailure(); + } break; + case nsIImapUrl::nsImapOnlineToOfflineCopy: + case nsIImapUrl::nsImapOnlineToOfflineMove: { + nsCString messageIdString; + nsresult rv = m_runningUrl->GetListOfMessageIds(messageIdString); + if (NS_SUCCEEDED(rv)) { + SetProgressString(IMAP_MESSAGES_STRING_INDEX); + m_progressCurrentNumber[m_stringIndex] = 0; + m_progressExpectedNumber = + CountMessagesInIdString(messageIdString.get()); + + FetchMessage(messageIdString, kEveryThingRFC822Peek); + + SetProgressString(IMAP_EMPTY_STRING_INDEX); + if (m_imapMailFolderSink) { + ImapOnlineCopyState copyStatus; + copyStatus = GetServerStateParser().LastCommandSuccessful() + ? ImapOnlineCopyStateType::kSuccessfulCopy + : ImapOnlineCopyStateType::kFailedCopy; + + m_imapMailFolderSink->OnlineCopyCompleted(this, copyStatus); + if (GetServerStateParser().LastCommandSuccessful() && + (m_imapAction == nsIImapUrl::nsImapOnlineToOfflineMove)) { + Store(messageIdString, "+FLAGS (\\Deleted \\Seen)", + bMessageIdsAreUids); + if (GetServerStateParser().LastCommandSuccessful()) { + copyStatus = ImapOnlineCopyStateType::kSuccessfulDelete; + if (gExpungeAfterDelete) Expunge(); + } else + copyStatus = ImapOnlineCopyStateType::kFailedDelete; + + m_imapMailFolderSink->OnlineCopyCompleted(this, copyStatus); + } + } + } else + HandleMemoryFailure(); + } break; + default: + if (GetServerStateParser().LastCommandSuccessful() && !uidValidityOk) + ProcessMailboxUpdate(false); // handle uidvalidity change + break; + } + } + } else if (!DeathSignalReceived()) + HandleMemoryFailure(); +} + +nsresult nsImapProtocol::BeginMessageDownLoad( + uint32_t total_message_size, // for user, headers and body + const char* content_type) { + nsresult rv = NS_OK; + char* sizeString = PR_smprintf("OPEN Size: %ld", total_message_size); + Log("STREAM", sizeString, "Begin Message Download Stream"); + PR_Free(sizeString); + // start counting how many bytes we see in this message after all + // transformations + m_bytesToChannel = 0; + + if (content_type) { + m_fromHeaderSeen = false; + if (GetServerStateParser().GetDownloadingHeaders()) { + // if we get multiple calls to BeginMessageDownload w/o intervening + // calls to NormalEndMessageDownload or Abort, then we're just + // going to fake a NormalMessageEndDownload. This will most likely + // cause an empty header to get written to the db, and the user + // will have to delete the empty header themselves, which + // should remove the message from the server as well. + if (m_curHdrInfo) NormalMessageEndDownload(); + if (!m_curHdrInfo) m_curHdrInfo = m_hdrDownloadCache->StartNewHdr(); + if (m_curHdrInfo) m_curHdrInfo->SetMsgSize(total_message_size); + return NS_OK; + } + // if we have a mock channel, that means we have a channel listener who + // wants the message. So set up a pipe. We'll write the message into one end + // of the pipe and they will read it out of the other end. + if (m_channelListener) { + // create a pipe to pump the message into...the output will go to whoever + // is consuming the message display + // we create an "infinite" pipe in case we get extremely long lines from + // the imap server, and the consumer is waiting for a whole line + nsCOMPtr<nsIPipe> pipe = do_CreateInstance("@mozilla.org/pipe;1"); + rv = pipe->Init(false, false, 4096, PR_UINT32_MAX); + NS_ENSURE_SUCCESS(rv, rv); + + // These always succeed because the pipe is initialized above. + MOZ_ALWAYS_SUCCEEDS( + pipe->GetInputStream(getter_AddRefs(m_channelInputStream))); + MOZ_ALWAYS_SUCCEEDS( + pipe->GetOutputStream(getter_AddRefs(m_channelOutputStream))); + } + // else, if we are saving the message to disk! + else if (m_imapMessageSink /* && m_imapAction == nsIImapUrl::nsImapSaveMessageToDisk */) + { + // we get here when download the inbox for offline use + nsCOMPtr<nsIFile> file; + bool addDummyEnvelope = true; + nsCOMPtr<nsIMsgMessageUrl> msgurl = do_QueryInterface(m_runningUrl); + msgurl->GetMessageFile(getter_AddRefs(file)); + msgurl->GetAddDummyEnvelope(&addDummyEnvelope); + if (file) + rv = m_imapMessageSink->SetupMsgWriteStream(file, addDummyEnvelope); + } + if (m_imapMailFolderSink && m_runningUrl) { + nsCOMPtr<nsISupports> copyState; + if (m_runningUrl) { + m_runningUrl->GetCopyState(getter_AddRefs(copyState)); + if (copyState) // only need this notification during copy + { + nsCOMPtr<nsIMsgMailNewsUrl> mailurl = do_QueryInterface(m_runningUrl); + m_imapMailFolderSink->StartMessage(mailurl); + } + } + } + + } else + HandleMemoryFailure(); + return rv; +} + +void nsImapProtocol::GetShouldDownloadAllHeaders(bool* aResult) { + if (m_imapMailFolderSink) + m_imapMailFolderSink->GetShouldDownloadAllHeaders(aResult); +} + +void nsImapProtocol::GetArbitraryHeadersToDownload(nsCString& aResult) { + if (m_imapServerSink) m_imapServerSink->GetArbitraryHeaders(aResult); +} + +void nsImapProtocol::AdjustChunkSize() { + int32_t deltaInSeconds; + + m_endTime = PR_Now(); + PRTime2Seconds(m_endTime - m_startTime, &deltaInSeconds); + m_trackingTime = false; + if (deltaInSeconds < 0) return; // bogus for some reason + + if (deltaInSeconds <= m_tooFastTime && m_curFetchSize >= m_chunkSize) { + m_chunkSize += m_chunkAddSize; + m_chunkThreshold = m_chunkSize + (m_chunkSize / 2); + // we used to have a max for the chunk size - I don't think that's needed. + } else if (deltaInSeconds <= m_idealTime) + return; + else { + if (m_chunkSize > m_chunkStartSize) + m_chunkSize = m_chunkStartSize; + else if (m_chunkSize > (m_chunkAddSize * 2)) + m_chunkSize -= m_chunkAddSize; + m_chunkThreshold = m_chunkSize + (m_chunkSize / 2); + } + // remember these new values globally so new connections + // can take advantage of them. + if (gChunkSize != m_chunkSize) { + // will cause chunk size pref to be written in CloseStream. + gChunkSizeDirty = true; + gChunkSize = m_chunkSize; + gChunkThreshold = m_chunkThreshold; + } +} + +// authenticated state commands + +// escape any backslashes or quotes. Backslashes are used a lot with our NT +// server +void nsImapProtocol::CreateEscapedMailboxName(const char* rawName, + nsCString& escapedName) { + escapedName.Assign(rawName); + + for (int32_t strIndex = 0; *rawName; strIndex++) { + char currentChar = *rawName++; + if ((currentChar == '\\') || (currentChar == '\"')) + escapedName.Insert('\\', strIndex++); + } +} +void nsImapProtocol::SelectMailbox(const char* mailboxName) { + ProgressEventFunctionUsingNameWithString("imapStatusSelectingMailbox", + mailboxName); + IncrementCommandTagNumber(); + + m_closeNeededBeforeSelect = false; // initial value + GetServerStateParser().ResetFlagInfo(); + nsCString escapedName; + CreateEscapedMailboxName(mailboxName, escapedName); + nsCString commandBuffer(GetServerCommandTag()); + commandBuffer.AppendLiteral(" select \""); + commandBuffer.Append(escapedName.get()); + commandBuffer.Append('"'); + if (UseCondStore()) commandBuffer.AppendLiteral(" (CONDSTORE)"); + commandBuffer.Append(CRLF); + + nsresult res; + res = SendData(commandBuffer.get()); + if (NS_FAILED(res)) return; + ParseIMAPandCheckForNewMail(); + + // Save the folder sink obtained in SetupSinkProxy() for whatever URL just + // caused this SELECT. Needed so idle and noop responses are using the correct + // folder when detecting changed flags or new messages. + m_imapMailFolderSinkSelected = m_imapMailFolderSink; + MOZ_ASSERT(m_imapMailFolderSinkSelected); + Log("SelectMailbox", nullptr, "got m_imapMailFolderSinkSelected"); + + int32_t numOfMessagesInFlagState = 0; + nsImapAction imapAction; + m_flagState->GetNumberOfMessages(&numOfMessagesInFlagState); + res = m_runningUrl->GetImapAction(&imapAction); + // if we've selected a mailbox, and we're not going to do an update because of + // the url type, but don't have the flags, go get them! + if (GetServerStateParser().LastCommandSuccessful() && NS_SUCCEEDED(res) && + imapAction != nsIImapUrl::nsImapSelectFolder && + imapAction != nsIImapUrl::nsImapExpungeFolder && + imapAction != nsIImapUrl::nsImapLiteSelectFolder && + imapAction != nsIImapUrl::nsImapDeleteAllMsgs && + ((GetServerStateParser().NumberOfMessages() != + numOfMessagesInFlagState) && + (numOfMessagesInFlagState == 0))) { + ProcessMailboxUpdate(false); + } +} + +void nsImapProtocol::FetchMsgAttribute(const nsCString& messageIds, + const nsCString& attribute) { + IncrementCommandTagNumber(); + + nsAutoCString commandString(GetServerCommandTag()); + commandString.AppendLiteral(" UID fetch "); + commandString.Append(messageIds); + commandString.AppendLiteral(" ("); + commandString.Append(attribute); + commandString.AppendLiteral(")" CRLF); + nsresult rv = SendData(commandString.get()); + + if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail(commandString.get()); + GetServerStateParser().SetFetchingFlags(false); + // Always clear this flag after every fetch. + m_fetchingWholeMessage = false; +} + +// this routine is used to fetch a message or messages, or headers for a +// message... + +void nsImapProtocol::FallbackToFetchWholeMsg(const nsCString& messageId, + uint32_t messageSize) { + if (m_imapMessageSink && m_runningUrl) { + bool shouldStoreMsgOffline; + m_runningUrl->GetStoreOfflineOnFallback(&shouldStoreMsgOffline); + m_runningUrl->SetStoreResultsOffline(shouldStoreMsgOffline); + } + FetchTryChunking(messageId, + m_imapAction == nsIImapUrl::nsImapMsgFetchPeek + ? kEveryThingRFC822Peek + : kEveryThingRFC822, + true, nullptr, messageSize, true); +} + +void nsImapProtocol::FetchMessage(const nsCString& messageIds, + nsIMAPeFetchFields whatToFetch, + const char* fetchModifier, uint32_t startByte, + uint32_t numBytes, char* part) { + IncrementCommandTagNumber(); + + nsCString commandString; + commandString = "%s UID fetch"; + + switch (whatToFetch) { + case kEveryThingRFC822: + m_flagChangeCount++; + m_fetchingWholeMessage = true; + if (m_trackingTime) AdjustChunkSize(); // we started another segment + m_startTime = PR_Now(); // save start of download time + m_trackingTime = true; + MOZ_LOG(IMAP, LogLevel::Debug, + ("FetchMessage everything: curFetchSize %u numBytes %u", + m_curFetchSize, numBytes)); + if (numBytes > 0) m_curFetchSize = numBytes; + + if (GetServerStateParser().ServerHasIMAP4Rev1Capability()) { + if (GetServerStateParser().GetCapabilityFlag() & kHasXSenderCapability) + commandString.AppendLiteral(" %s (XSENDER UID RFC822.SIZE BODY[]"); + else + commandString.AppendLiteral(" %s (UID RFC822.SIZE BODY[]"); + } else { + if (GetServerStateParser().GetCapabilityFlag() & kHasXSenderCapability) + commandString.AppendLiteral(" %s (XSENDER UID RFC822.SIZE RFC822"); + else + commandString.AppendLiteral(" %s (UID RFC822.SIZE RFC822"); + } + if (numBytes > 0) { + // if we are retrieving chunks + char* byterangeString = PR_smprintf("<%ld.%ld>", startByte, numBytes); + if (byterangeString) { + commandString.Append(byterangeString); + PR_Free(byterangeString); + } + } + commandString.Append(')'); + + break; + + case kEveryThingRFC822Peek: { + MOZ_LOG(IMAP, LogLevel::Debug, + ("FetchMessage peek: curFetchSize %u numBytes %u", m_curFetchSize, + numBytes)); + if (numBytes > 0) m_curFetchSize = numBytes; + const char* formatString = ""; + eIMAPCapabilityFlags server_capabilityFlags = + GetServerStateParser().GetCapabilityFlag(); + + m_fetchingWholeMessage = true; + if (server_capabilityFlags & kIMAP4rev1Capability) { + // use body[].peek since rfc822.peek is not in IMAP4rev1 + if (server_capabilityFlags & kHasXSenderCapability) + formatString = " %s (XSENDER UID RFC822.SIZE BODY.PEEK[]"; + else + formatString = " %s (UID RFC822.SIZE BODY.PEEK[]"; + } else { + if (server_capabilityFlags & kHasXSenderCapability) + formatString = " %s (XSENDER UID RFC822.SIZE RFC822.peek"; + else + formatString = " %s (UID RFC822.SIZE RFC822.peek"; + } + + commandString.Append(formatString); + if (numBytes > 0) { + // if we are retrieving chunks + char* byterangeString = PR_smprintf("<%ld.%ld>", startByte, numBytes); + if (byterangeString) { + commandString.Append(byterangeString); + PR_Free(byterangeString); + } + } + commandString.Append(')'); + } break; + case kHeadersRFC822andUid: + if (GetServerStateParser().ServerHasIMAP4Rev1Capability()) { + eIMAPCapabilityFlags server_capabilityFlags = + GetServerStateParser().GetCapabilityFlag(); + bool aolImapServer = + ((server_capabilityFlags & kAOLImapCapability) != 0); + bool downloadAllHeaders = false; + // checks if we're filtering on "any header" or running a spam filter + // requiring all headers + GetShouldDownloadAllHeaders(&downloadAllHeaders); + + if (!downloadAllHeaders) // if it's ok -- no filters on any header, + // etc. + { + char* headersToDL = nullptr; + char* what = nullptr; + const char* dbHeaders = + (gUseEnvelopeCmd) ? IMAP_DB_HEADERS : IMAP_ENV_AND_DB_HEADERS; + nsCString arbitraryHeaders; + GetArbitraryHeadersToDownload(arbitraryHeaders); + for (uint32_t i = 0; i < mCustomDBHeaders.Length(); i++) { + if (!FindInReadable(mCustomDBHeaders[i], arbitraryHeaders, + nsCaseInsensitiveCStringComparator)) { + if (!arbitraryHeaders.IsEmpty()) arbitraryHeaders.Append(' '); + arbitraryHeaders.Append(mCustomDBHeaders[i]); + } + } + for (uint32_t i = 0; i < mCustomHeaders.Length(); i++) { + if (!FindInReadable(mCustomHeaders[i], arbitraryHeaders, + nsCaseInsensitiveCStringComparator)) { + if (!arbitraryHeaders.IsEmpty()) arbitraryHeaders.Append(' '); + arbitraryHeaders.Append(mCustomHeaders[i]); + } + } + if (arbitraryHeaders.IsEmpty()) + headersToDL = strdup(dbHeaders); + else + headersToDL = + PR_smprintf("%s %s", dbHeaders, arbitraryHeaders.get()); + + if (gUseEnvelopeCmd) + what = PR_smprintf(" ENVELOPE BODY.PEEK[HEADER.FIELDS (%s)])", + headersToDL); + else + what = PR_smprintf(" BODY.PEEK[HEADER.FIELDS (%s)])", headersToDL); + free(headersToDL); + if (what) { + commandString.AppendLiteral(" %s (UID "); + if (m_isGmailServer) + commandString.AppendLiteral("X-GM-MSGID X-GM-THRID X-GM-LABELS "); + if (aolImapServer) + commandString.AppendLiteral(" XAOL.SIZE"); + else + commandString.AppendLiteral("RFC822.SIZE"); + commandString.AppendLiteral(" FLAGS"); + commandString.Append(what); + PR_Free(what); + } else { + commandString.AppendLiteral( + " %s (UID RFC822.SIZE BODY.PEEK[HEADER] FLAGS)"); + } + } else + commandString.AppendLiteral( + " %s (UID RFC822.SIZE BODY.PEEK[HEADER] FLAGS)"); + } else + commandString.AppendLiteral( + " %s (UID RFC822.SIZE RFC822.HEADER FLAGS)"); + break; + case kUid: + commandString.AppendLiteral(" %s (UID)"); + break; + case kFlags: + GetServerStateParser().SetFetchingFlags(true); + commandString.AppendLiteral(" %s (FLAGS)"); + break; + case kRFC822Size: + commandString.AppendLiteral(" %s (RFC822.SIZE)"); + break; + case kBodyStart: { + int32_t numBytesToFetch; + m_runningUrl->GetNumBytesToFetch(&numBytesToFetch); + + commandString.AppendLiteral( + " %s (UID BODY.PEEK[HEADER.FIELDS (Content-Type " + "Content-Transfer-Encoding)] BODY.PEEK[TEXT]<0."); + commandString.AppendInt(numBytesToFetch); + commandString.AppendLiteral(">)"); + } break; + case kRFC822HeadersOnly: + if (GetServerStateParser().ServerHasIMAP4Rev1Capability()) { + if (part) { + commandString.AppendLiteral(" %s (BODY["); + char* what = PR_smprintf("%s.HEADER])", part); + if (what) { + commandString.Append(what); + PR_Free(what); + } else + HandleMemoryFailure(); + } else { + // headers for the top-level message + commandString.AppendLiteral(" %s (BODY[HEADER])"); + } + } else + commandString.AppendLiteral(" %s (RFC822.HEADER)"); + break; + case kMIMEPart: + commandString.AppendLiteral(" %s (BODY.PEEK[%s]"); + if (numBytes > 0) { + // if we are retrieving chunks + char* byterangeString = PR_smprintf("<%ld.%ld>", startByte, numBytes); + if (byterangeString) { + commandString.Append(byterangeString); + PR_Free(byterangeString); + } + } + commandString.Append(')'); + break; + case kMIMEHeader: + commandString.AppendLiteral(" %s (BODY[%s.MIME])"); + break; + } + + if (fetchModifier) commandString.Append(fetchModifier); + + commandString.Append(CRLF); + + // since messageIds can be infinitely long, use a dynamic buffer rather than + // the fixed one + const char* commandTag = GetServerCommandTag(); + int protocolStringSize = commandString.Length() + messageIds.Length() + + PL_strlen(commandTag) + 1 + + (part ? PL_strlen(part) : 0); + char* protocolString = (char*)PR_CALLOC(protocolStringSize); + + if (protocolString) { + char* cCommandStr = ToNewCString(commandString); + if ((whatToFetch == kMIMEPart) || (whatToFetch == kMIMEHeader)) { + PR_snprintf(protocolString, // string to create + protocolStringSize, // max size + cCommandStr, // format string + commandTag, // command tag + messageIds.get(), part); + } else { + PR_snprintf(protocolString, // string to create + protocolStringSize, // max size + cCommandStr, // format string + commandTag, // command tag + messageIds.get()); + } + + nsresult rv = SendData(protocolString); + + free(cCommandStr); + if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail(protocolString); + PR_Free(protocolString); + GetServerStateParser().SetFetchingFlags(false); + // Always clear this flag after every fetch. + m_fetchingWholeMessage = false; + if (GetServerStateParser().LastCommandSuccessful() && CheckNeeded()) + Check(); + } else + HandleMemoryFailure(); +} + +void nsImapProtocol::FetchTryChunking(const nsCString& messageIds, + nsIMAPeFetchFields whatToFetch, + bool idIsUid, char* part, + uint32_t downloadSize, bool tryChunking) { + GetServerStateParser().SetTotalDownloadSize(downloadSize); + MOZ_LOG(IMAP, LogLevel::Debug, + ("FetchTryChunking: curFetchSize %u", downloadSize)); + MOZ_ASSERT(!part, "fetching a part should no longer occur"); + m_curFetchSize = downloadSize; // we'll change this if chunking. + if (m_fetchByChunks && tryChunking && + GetServerStateParser().ServerHasIMAP4Rev1Capability() && + (downloadSize > (uint32_t)m_chunkThreshold)) { + uint32_t startByte = 0; + m_curFetchSize = m_chunkSize; + GetServerStateParser().ClearLastFetchChunkReceived(); + while (!DeathSignalReceived() && !GetPseudoInterrupted() && + !GetServerStateParser().GetLastFetchChunkReceived() && + GetServerStateParser().ContinueParse()) { + GetServerStateParser().ClearNumBytesFetched(); + // This chunk is a fetch of m_chunkSize bytes. But m_chunkSize can be + // changed inside FetchMessage(). Save the original value of m_chunkSize + // to set the correct offset (startByte) for the next chunk. + int32_t bytesFetched = m_chunkSize; + FetchMessage(messageIds, whatToFetch, nullptr, startByte, bytesFetched, + part); + if (!GetServerStateParser().GetNumBytesFetched()) { + // Fetch returned zero bytes chunk from server. This occurs if the + // message was expunged during the fetch. + MOZ_LOG(IMAP, LogLevel::Info, + ("FetchTryChunking: Zero bytes chunk fetched; message probably " + "expunged")); + break; + } + startByte += bytesFetched; + } + + // Only abort the stream if this is a normal message download + // Otherwise, let the body shell abort the stream. + if ((whatToFetch == kEveryThingRFC822) && + ((startByte > 0 && (startByte < downloadSize) && + (DeathSignalReceived() || GetPseudoInterrupted())) || + !GetServerStateParser().ContinueParse())) { + AbortMessageDownLoad(); + PseudoInterrupt(false); + } + } else { + // small message, or (we're not chunking and not doing bodystructure), + // or the server is not rev1. + // Just fetch the whole thing. + FetchMessage(messageIds, whatToFetch, nullptr, 0, 0, part); + } +} + +void nsImapProtocol::PostLineDownLoadEvent(const char* line, + uint32_t uidOfMessage) { + if (!GetServerStateParser().GetDownloadingHeaders()) { + uint32_t byteCount = PL_strlen(line); + bool echoLineToMessageSink = false; + // if we have a channel listener, then just spool the message + // directly to the listener + if (m_channelListener) { + uint32_t count = 0; + if (m_channelOutputStream) { + nsresult rv = m_channelOutputStream->Write(line, byteCount, &count); + NS_ASSERTION(count == byteCount, + "IMAP channel pipe couldn't buffer entire write"); + if (NS_SUCCEEDED(rv)) { + m_channelListener->OnDataAvailable(m_mockChannel, + m_channelInputStream, 0, count); + } + // else some sort of explosion? + } + } + if (m_runningUrl) + m_runningUrl->GetStoreResultsOffline(&echoLineToMessageSink); + + m_bytesToChannel += byteCount; + if (m_imapMessageSink && line && echoLineToMessageSink && + !GetPseudoInterrupted()) + m_imapMessageSink->ParseAdoptedMsgLine(line, uidOfMessage, m_runningUrl); + } + // ***** We need to handle the pseudo interrupt here ***** +} + +// Handle a line seen by the parser. +// * The argument |lineCopy| must be nullptr or should contain the same string +// as |line|. |lineCopy| will be modified. +// * A line may be passed by parts, e.g., "part1 part2\r\n" may be passed as +// HandleMessageDownLoadLine("part 1 ", 1); +// HandleMessageDownLoadLine("part 2\r\n", 0); +// However, it is assumed that a CRLF or a CRCRLF is never split (i.e., this +// is ensured *before* invoking this method). +void nsImapProtocol::HandleMessageDownLoadLine(const char* line, + bool isPartialLine, + char* lineCopy) { + NS_ENSURE_TRUE_VOID(line); + NS_ASSERTION(lineCopy == nullptr || !PL_strcmp(line, lineCopy), + "line and lineCopy must contain the same string"); + const char* messageLine = line; + uint32_t lineLength = strlen(messageLine); + const char* cEndOfLine = messageLine + lineLength; + char* localMessageLine = nullptr; + + // If we obtain a partial line (due to fetching by chunks), we do not + // add/modify the end-of-line terminator. + if (!isPartialLine) { + // Change this line to native line termination, duplicate if necessary. + // Do not assume that the line really ends in CRLF + // to start with, even though it is supposed to be RFC822 + + // normalize line endings to CRLF unless we are saving the message to disk + bool canonicalLineEnding = true; + nsCOMPtr<nsIMsgMessageUrl> msgUrl = do_QueryInterface(m_runningUrl); + + if (m_imapAction == nsIImapUrl::nsImapSaveMessageToDisk && msgUrl) + msgUrl->GetCanonicalLineEnding(&canonicalLineEnding); + + NS_ASSERTION(MSG_LINEBREAK_LEN == 1 || (MSG_LINEBREAK_LEN == 2 && + !PL_strcmp(CRLF, MSG_LINEBREAK)), + "violated assumptions on MSG_LINEBREAK"); + if (MSG_LINEBREAK_LEN == 1 && !canonicalLineEnding) { + bool lineEndsWithCRorLF = + lineLength >= 1 && (cEndOfLine[-1] == '\r' || cEndOfLine[-1] == '\n'); + char* endOfLine; + if (lineCopy && lineEndsWithCRorLF) // true for most lines + { + endOfLine = lineCopy + lineLength; + messageLine = lineCopy; + } else { + // leave enough room for one more char, MSG_LINEBREAK[0] + localMessageLine = (char*)PR_MALLOC(lineLength + 2); + if (!localMessageLine) // memory failure + return; + PL_strcpy(localMessageLine, line); + endOfLine = localMessageLine + lineLength; + messageLine = localMessageLine; + } + + if (lineLength >= 2 && endOfLine[-2] == '\r' && endOfLine[-1] == '\n') { + if (lineLength >= 3 && endOfLine[-3] == '\r') // CRCRLF + { + endOfLine--; + lineLength--; + } + /* CRLF -> CR or LF */ + endOfLine[-2] = MSG_LINEBREAK[0]; + endOfLine[-1] = '\0'; + lineLength--; + } else if (lineLength >= 1 && + ((endOfLine[-1] == '\r') || (endOfLine[-1] == '\n'))) { + /* CR -> LF or LF -> CR */ + endOfLine[-1] = MSG_LINEBREAK[0]; + } else // no eol characters at all + { + endOfLine[0] = MSG_LINEBREAK[0]; // CR or LF + endOfLine[1] = '\0'; + lineLength++; + } + } else // enforce canonical CRLF linebreaks + { + if (lineLength == 0 || (lineLength == 1 && cEndOfLine[-1] == '\n')) { + messageLine = CRLF; + lineLength = 2; + } else if (cEndOfLine[-1] != '\n' || cEndOfLine[-2] != '\r' || + (lineLength >= 3 && cEndOfLine[-3] == '\r')) { + // The line does not end in CRLF (or it ends in CRCRLF). + // Copy line and leave enough room for two more chars (CR and LF). + localMessageLine = (char*)PR_MALLOC(lineLength + 3); + if (!localMessageLine) // memory failure + return; + PL_strcpy(localMessageLine, line); + char* endOfLine = localMessageLine + lineLength; + messageLine = localMessageLine; + + if (lineLength >= 3 && endOfLine[-1] == '\n' && endOfLine[-2] == '\r') { + // CRCRLF -> CRLF + endOfLine[-2] = '\n'; + endOfLine[-1] = '\0'; + lineLength--; + } else if ((endOfLine[-1] == '\r') || (endOfLine[-1] == '\n')) { + // LF -> CRLF or CR -> CRLF + endOfLine[-1] = '\r'; + endOfLine[0] = '\n'; + endOfLine[1] = '\0'; + lineLength++; + } else // no eol characters at all + { + endOfLine[0] = '\r'; + endOfLine[1] = '\n'; + endOfLine[2] = '\0'; + lineLength += 2; + } + } + } + } + NS_ASSERTION(lineLength == PL_strlen(messageLine), "lineLength not accurate"); + + // check if sender obtained via XSENDER server extension matches "From:" field + const char* xSenderInfo = GetServerStateParser().GetXSenderInfo(); + if (xSenderInfo && *xSenderInfo && !m_fromHeaderSeen) { + if (!PL_strncmp("From: ", messageLine, 6)) { + m_fromHeaderSeen = true; + if (PL_strstr(messageLine, xSenderInfo) != NULL) + // Adding a X-Mozilla-Status line here is not very elegant but it + // works. Another X-Mozilla-Status line is added to the message when + // downloading to a local folder; this new line will also contain the + // 'authed' flag we are adding here. (If the message is again + // uploaded to the server, this flag is lost.) + // 0x0200 == nsMsgMessageFlags::SenderAuthed + HandleMessageDownLoadLine("X-Mozilla-Status: 0200\r\n", false); + GetServerStateParser().FreeXSenderInfo(); + } + } + + if (GetServerStateParser().GetDownloadingHeaders()) { + if (!m_curHdrInfo) + BeginMessageDownLoad(GetServerStateParser().SizeOfMostRecentMessage(), + MESSAGE_RFC822); + if (m_curHdrInfo) { + if (NS_FAILED(m_curHdrInfo->CacheLine( + messageLine, GetServerStateParser().CurrentResponseUID()))) + NS_ERROR("CacheLine for a header failed"); + } + PR_Free(localMessageLine); + return; + } + // if this line is for a different message, or the incoming line is too big + if (((m_downloadLineCache->CurrentUID() != + GetServerStateParser().CurrentResponseUID()) && + !m_downloadLineCache->CacheEmpty()) || + (m_downloadLineCache->SpaceAvailable() < lineLength + 1)) + FlushDownloadCache(); + + // so now the cache is flushed, but this string might still be too big + if (m_downloadLineCache->SpaceAvailable() < lineLength + 1) + PostLineDownLoadEvent(messageLine, + GetServerStateParser().CurrentResponseUID()); + else { + NS_ASSERTION( + (PL_strlen(messageLine) + 1) <= m_downloadLineCache->SpaceAvailable(), + "Oops... line length greater than space available"); + if (NS_FAILED(m_downloadLineCache->CacheLine( + messageLine, GetServerStateParser().CurrentResponseUID()))) + NS_ERROR("CacheLine for message body failed"); + } + PR_Free(localMessageLine); +} + +void nsImapProtocol::FlushDownloadCache() { + if (!m_downloadLineCache->CacheEmpty()) { + msg_line_info* downloadLine = m_downloadLineCache->GetCurrentLineInfo(); + PostLineDownLoadEvent(downloadLine->adoptedMessageLine, + downloadLine->uidOfMessage); + m_downloadLineCache->ResetCache(); + } +} + +void nsImapProtocol::NormalMessageEndDownload() { + Log("STREAM", "CLOSE", "Normal Message End Download Stream"); + + if (m_trackingTime) AdjustChunkSize(); + if (m_imapMailFolderSink && m_curHdrInfo && + GetServerStateParser().GetDownloadingHeaders()) { + m_curHdrInfo->SetMsgSize(GetServerStateParser().SizeOfMostRecentMessage()); + m_curHdrInfo->SetMsgUid(GetServerStateParser().CurrentResponseUID()); + m_hdrDownloadCache->FinishCurrentHdr(); + int32_t numHdrsCached; + m_hdrDownloadCache->GetNumHeaders(&numHdrsCached); + if (numHdrsCached == kNumHdrsToXfer) { + m_imapMailFolderSink->ParseMsgHdrs(this, m_hdrDownloadCache); + m_hdrDownloadCache->ResetAll(); + } + } + FlushDownloadCache(); + + if (!GetServerStateParser().GetDownloadingHeaders()) { + int32_t updatedMessageSize = -1; + if (m_fetchingWholeMessage) { + updatedMessageSize = m_bytesToChannel; + if (m_bytesToChannel != + GetServerStateParser().SizeOfMostRecentMessage()) { + MOZ_LOG(IMAP, LogLevel::Debug, + ("STREAM:CLOSE Server's RFC822.SIZE %u, actual size %u", + GetServerStateParser().SizeOfMostRecentMessage(), + m_bytesToChannel)); + } + } + // need to know if we're downloading for display or not. We'll use action == + // nsImapMsgFetch for now + nsImapAction imapAction = + nsIImapUrl::nsImapSelectFolder; // just set it to some legal value + if (m_runningUrl) m_runningUrl->GetImapAction(&imapAction); + + if (m_imapMessageSink) { + if (m_mockChannel) { + // Have a mock channel, tell channel that write to cache is done. + m_mockChannel->SetWritingToCache(false); + MOZ_LOG(IMAP, LogLevel::Debug, ("%s: End cache write", __func__)); + } + m_imapMessageSink->NormalEndMsgWriteStream( + m_downloadLineCache->CurrentUID(), + imapAction == nsIImapUrl::nsImapMsgFetch, m_runningUrl, + updatedMessageSize); + } + + if (m_runningUrl && m_imapMailFolderSink) { + nsCOMPtr<nsISupports> copyState; + m_runningUrl->GetCopyState(getter_AddRefs(copyState)); + if (copyState) // only need this notification during copy + { + nsCOMPtr<nsIMsgMailNewsUrl> mailUrl(do_QueryInterface(m_runningUrl)); + m_imapMailFolderSink->EndMessage(mailUrl, + m_downloadLineCache->CurrentUID()); + } + } + } + m_curHdrInfo = nullptr; +} + +void nsImapProtocol::AbortMessageDownLoad() { + Log("STREAM", "CLOSE", "Abort Message Download Stream"); + + if (m_trackingTime) AdjustChunkSize(); + FlushDownloadCache(); + if (GetServerStateParser().GetDownloadingHeaders()) { + if (m_imapMailFolderSink) + m_imapMailFolderSink->AbortHeaderParseStream(this); + } else if (m_imapMessageSink) + m_imapMessageSink->AbortMsgWriteStream(); + + m_curHdrInfo = nullptr; +} + +void nsImapProtocol::ProcessMailboxUpdate(bool handlePossibleUndo) { + if (DeathSignalReceived()) return; + + // Update quota information + char* boxName; + GetSelectedMailboxName(&boxName); + GetQuotaDataIfSupported(boxName); + PR_Free(boxName); + + // fetch the flags and uids of all existing messages or new ones + if (!DeathSignalReceived() && GetServerStateParser().NumberOfMessages()) { + if (handlePossibleUndo) { + // undo any delete flags we may have asked to + nsCString undoIdsStr; + nsAutoCString undoIds; + + GetCurrentUrl()->GetListOfMessageIds(undoIdsStr); + undoIds.Assign(undoIdsStr); + if (!undoIds.IsEmpty()) { + char firstChar = (char)undoIds.CharAt(0); + undoIds.Cut(0, 1); // remove first character + // if this string started with a '-', then this is an undo of a delete + // if its a '+' its a redo + if (firstChar == '-') + Store(undoIds, "-FLAGS (\\Deleted)", + true); // most servers will fail silently on a failure, deal + // with it? + else if (firstChar == '+') + Store(undoIds, "+FLAGS (\\Deleted)", + true); // most servers will fail silently on a failure, deal + // with it? + else + NS_ASSERTION(false, "bogus undo Id's"); + } + } + + // make the parser record these flags + nsCString fetchStr; + int32_t added = 0, deleted = 0; + + m_flagState->GetNumberOfMessages(&added); + deleted = m_flagState->NumberOfDeletedMessages(); + bool flagStateEmpty = !added; + bool useCS = UseCondStore(); + + // Figure out if we need to do a full sync (UID Fetch Flags 1:*), + // a partial sync using CHANGEDSINCE, or a sync from the previous + // highwater mark. + + // If the folder doesn't know about the highest uid, or the flag state + // is empty, and we're not using CondStore, we definitely need a full sync. + // + // Print to log items affecting needFullFolderSync: + MOZ_LOG(IMAP_CS, LogLevel::Debug, + ("Do full sync?: mFolderHighestUID=%" PRIu32 ", added=%" PRId32 + ", useCS=%s", + mFolderHighestUID, added, useCS ? "true" : "false")); + bool needFullFolderSync = !mFolderHighestUID || (flagStateEmpty && !useCS); + bool needFolderSync = false; + + if (!needFullFolderSync) { + // Figure out if we need to do a non-highwater mark sync. + // Set needFolderSync true when at least 1 of these 3 cases is true: + // 1. Have no uids in flag array or all flag elements are marked deleted + // AND not using CONDSTORE. + // 2. Have no uids in flag array or all flag elements are marked deleted + // AND using "just mark as deleted" and EXISTS response count differs from + // stored message count for folder. + // 3. Using CONDSTORE and highest MODSEQ response is not equal to stored + // mod seq for folder. + + // Print to log items affecting needFolderSync: + // clang-format off + MOZ_LOG(IMAP_CS, LogLevel::Debug, + ("1. Do a sync?: added=%" PRId32 ", deleted=%" PRId32 ", useCS=%s", + added, deleted, useCS ? "true" : "false")); + MOZ_LOG(IMAP_CS, LogLevel::Debug, + ("2. Do a sync?: ShowDeletedMsgs=%s, exists=%" PRId32 + ", mFolderTotalMsgCount=%" PRId32, + GetShowDeletedMessages() ? "true" : "false", + GetServerStateParser().NumberOfMessages(), mFolderTotalMsgCount)); + // clang-format on + MOZ_LOG(IMAP_CS, LogLevel::Debug, + ("3. Do a sync?: fHighestModSeq=%" PRIu64 + ", mFolderLastModSeq=%" PRIu64, + GetServerStateParser().fHighestModSeq, mFolderLastModSeq)); + + needFolderSync = + ((flagStateEmpty || added == deleted) && + (!useCS || (GetShowDeletedMessages() && + GetServerStateParser().NumberOfMessages() != + mFolderTotalMsgCount))) || + (useCS && GetServerStateParser().fHighestModSeq != mFolderLastModSeq); + } + MOZ_LOG(IMAP_CS, LogLevel::Debug, + ("needFullFolderSync=%s, needFolderSync=%s", + needFullFolderSync ? "true" : "false", + needFolderSync ? "true" : "false")); + + if (needFullFolderSync || needFolderSync) { + nsCString idsToFetch("1:*"); + char fetchModifier[40] = ""; + if (!needFullFolderSync && !GetShowDeletedMessages() && useCS) { + m_flagState->StartCapture(); + MOZ_LOG(IMAP_CS, LogLevel::Debug, + ("Doing UID fetch 1:* (CHANGEDSINCE %" PRIu64 ")", + mFolderLastModSeq)); + PR_snprintf(fetchModifier, sizeof(fetchModifier), + " (CHANGEDSINCE %llu)", mFolderLastModSeq); + } else + m_flagState->SetPartialUIDFetch(false); + + FetchMessage(idsToFetch, kFlags, fetchModifier); + // lets see if we should expunge during a full sync of flags. + if (GetServerStateParser().LastCommandSuccessful()) { + // if we did a CHANGEDSINCE fetch, do a sanity check on the msg counts + // to see if some other client may have done an expunge. + if (m_flagState->GetPartialUIDFetch()) { + uint32_t numExists = GetServerStateParser().NumberOfMessages(); + uint32_t numPrevExists = mFolderTotalMsgCount; + MOZ_LOG(IMAP_CS, LogLevel::Debug, + ("Sanity, deleted=%" PRId32 ", numPrevExists=%" PRIu32 + ", numExists=%" PRIu32, + m_flagState->NumberOfDeletedMessages(), numPrevExists, + numExists)); + // Determine the number of new UIDs just fetched that are greater than + // the saved highest UID for the folder. numToCheck will contain the + // number of UIDs just fetched and, of course, not all are new. + uint32_t numNewUIDs = 0; + uint32_t numToCheck = m_flagState->GetNumAdded(); + bool flagChangeDetected = false; + bool expungeHappened = false; + MOZ_LOG(IMAP_CS, LogLevel::Debug, + ("numToCheck=%" PRIu32, numToCheck)); + if (numToCheck && mFolderHighestUID) { + uint32_t uid; + int32_t topIndex; + m_flagState->GetNumberOfMessages(&topIndex); + MOZ_LOG( + IMAP_CS, LogLevel::Debug, + ("Partial fetching. Number of UIDs stored=%" PRId32, topIndex)); + do { + topIndex--; + // Check for potential infinite loop here. This has happened but + // don't know why. If topIndex is negative at this point, set + // expungeHappened true to recover by doing a full flag fetch. + if (topIndex < 0) { + expungeHappened = true; + MOZ_LOG(IMAP_CS, LogLevel::Error, + ("Zero or negative number of UIDs stored, do full flag " + "fetch")); + break; + } + m_flagState->GetUidOfMessage(topIndex, &uid); + if (uid && uid != nsMsgKey_None) { + if (uid > mFolderHighestUID) { + numNewUIDs++; + MOZ_LOG(IMAP_CS, LogLevel::Debug, + ("numNewUIDs=%" PRIu32 ", Added new UID=%" PRIu32, + numNewUIDs, uid)); + numToCheck--; + } else { + // Just a flag change on an existing UID. No more new UIDs + // will be found. This does not detect an expunged message. + flagChangeDetected = true; + MOZ_LOG(IMAP_CS, LogLevel::Debug, + ("Not new uid=%" PRIu32, uid)); + break; + } + } else { + MOZ_LOG(IMAP_CS, LogLevel::Debug, + ("UID is 0 or a gap, uid=0x%" PRIx32, uid)); + break; + } + } while (numToCheck); + } + + // Another client expunged at least one message if the number of new + // UIDs is not equal to the observed change in the number of messages + // existing in the folder. + expungeHappened = + expungeHappened || numNewUIDs != (numExists - numPrevExists); + if (expungeHappened) { + // Sanity check failed - need full fetch to remove expunged msgs. + MOZ_LOG(IMAP_CS, LogLevel::Debug, + ("Other client expunged msgs, do full fetch to remove " + "expunged msgs")); + m_flagState->Reset(); + m_flagState->SetPartialUIDFetch(false); + FetchMessage("1:*"_ns, kFlags); + } else if (numNewUIDs == 0) { + // Nothing has been expunged and no new UIDs, so if just a flag + // change on existing message(s), avoid unneeded fetch of flags for + // messages with UIDs at and above uid (see var uid above) when + // "highwater mark" fetch occurs below. + if (mFolderHighestUID && flagChangeDetected) { + MOZ_LOG(IMAP_CS, LogLevel::Debug, + ("Avoid unneeded fetches after just flag changes")); + GetServerStateParser().ResetHighestRecordedUID(); + } + } + } + int32_t numDeleted = m_flagState->NumberOfDeletedMessages(); + // Don't do expunge when we are lite selecting folder (because we + // could be doing undo) or if gExpungeOption is kAutoExpungeNever. + // Expunge if we're always expunging, or the number of deleted messages + // is over the threshold, and we're either always respecting the + // threshold, or we're expunging based on the delete model, and the + // delete model is not "just mark it as deleted" (imap delete model). + if (m_imapAction != nsIImapUrl::nsImapLiteSelectFolder && + gExpungeOption != kAutoExpungeNever && + (gExpungeOption == kAutoExpungeAlways || + (numDeleted >= gExpungeThreshold && + (gExpungeOption == kAutoExpungeOnThreshold || + (gExpungeOption == kAutoExpungeDeleteModel && + !GetShowDeletedMessages()))))) + Expunge(); + } + } else { + // Obtain the highest (highwater mark) UID seen since the last UIDVALIDITY + // response occurred (associated with the most recent SELECT for the + // folder). + uint32_t highestRecordedUID = GetServerStateParser().HighestRecordedUID(); + // if we're using CONDSTORE, and the parser hasn't seen any UIDs, use + // the highest UID previously seen and saved for the folder instead. + if (useCS && !highestRecordedUID) highestRecordedUID = mFolderHighestUID; + // clang-format off + MOZ_LOG(IMAP_CS, LogLevel::Debug, + ("Check for new messages above UID=%" PRIu32, highestRecordedUID)); + // clang-format on + AppendUid(fetchStr, highestRecordedUID + 1); + fetchStr.AppendLiteral(":*"); + FetchMessage(fetchStr, kFlags); // only new messages please + } + } else if (GetServerStateParser().LastCommandSuccessful()) { + GetServerStateParser().ResetFlagInfo(); + // the flag state is empty, but not partial. + m_flagState->SetPartialUIDFetch(false); + } + + if (GetServerStateParser().LastCommandSuccessful()) { + nsImapAction imapAction; + nsresult res = m_runningUrl->GetImapAction(&imapAction); + if (NS_SUCCEEDED(res) && imapAction == nsIImapUrl::nsImapLiteSelectFolder) + return; + } + + nsTArray<nsMsgKey> msgIdList; + + if (GetServerStateParser().LastCommandSuccessful()) { + ReentrantMonitorAutoEnter mon(m_waitForBodyIdsMonitor); + RefPtr<nsImapMailboxSpec> new_spec = + GetServerStateParser().CreateCurrentMailboxSpec(); + nsImapAction imapAction; + nsresult res = m_runningUrl->GetImapAction(&imapAction); + if (NS_SUCCEEDED(res) && imapAction == nsIImapUrl::nsImapExpungeFolder) + new_spec->mBoxFlags |= kJustExpunged; + + if (m_imapMailFolderSink) { + bool more; + m_imapMailFolderSink->UpdateImapMailboxInfo(this, new_spec); + m_imapMailFolderSink->GetMsgHdrsToDownload( + &more, &m_progressExpectedNumber, msgIdList); + // Assert that either it's empty string OR it must be header string. + MOZ_ASSERT((m_stringIndex == IMAP_EMPTY_STRING_INDEX) || + (m_stringIndex == IMAP_HEADERS_STRING_INDEX)); + m_progressCurrentNumber[m_stringIndex] = 0; + m_runningUrl->SetMoreHeadersToDownload(more); + // We're going to be re-running this url if there are more headers. + if (more) m_runningUrl->SetRerunningUrl(true); + } + } + + if (GetServerStateParser().LastCommandSuccessful()) { + if (msgIdList.Length() > 0) { + FolderHeaderDump(msgIdList.Elements(), msgIdList.Length()); + } + HeaderFetchCompleted(); + // this might be bogus, how are we going to do pane notification and stuff + // when we fetch bodies without headers! + } + + // wait for a list of bodies to fetch. + if (GetServerStateParser().LastCommandSuccessful()) { + nsTArray<nsMsgKey> msgIds; + WaitForPotentialListOfBodysToFetch(msgIds); + if (msgIds.Length() > 0 && GetServerStateParser().LastCommandSuccessful()) { + // Tell the url that it should store the msg fetch results offline, + // while we're dumping the messages, and then restore the setting. + bool wasStoringOffline; + m_runningUrl->GetStoreResultsOffline(&wasStoringOffline); + m_runningUrl->SetStoreResultsOffline(true); + // Assert that either it's empty string OR it must be message string. + MOZ_ASSERT((m_stringIndex == IMAP_EMPTY_STRING_INDEX) || + (m_stringIndex == IMAP_MESSAGES_STRING_INDEX)); + m_progressCurrentNumber[m_stringIndex] = 0; + m_progressExpectedNumber = msgIds.Length(); + FolderMsgDump(msgIds.Elements(), msgIds.Length(), kEveryThingRFC822Peek); + m_runningUrl->SetStoreResultsOffline(wasStoringOffline); + } + } + if (!GetServerStateParser().LastCommandSuccessful()) + GetServerStateParser().ResetFlagInfo(); +} + +void nsImapProtocol::FolderHeaderDump(uint32_t* msgUids, uint32_t msgCount) { + FolderMsgDump(msgUids, msgCount, kHeadersRFC822andUid); +} + +void nsImapProtocol::FolderMsgDump(uint32_t* msgUids, uint32_t msgCount, + nsIMAPeFetchFields fields) { + // lets worry about this progress stuff later. + switch (fields) { + case kHeadersRFC822andUid: + SetProgressString(IMAP_HEADERS_STRING_INDEX); + break; + case kFlags: + SetProgressString(IMAP_FLAGS_STRING_INDEX); + break; + default: + SetProgressString(IMAP_MESSAGES_STRING_INDEX); + break; + } + + FolderMsgDumpLoop(msgUids, msgCount, fields); + + SetProgressString(IMAP_EMPTY_STRING_INDEX); +} + +void nsImapProtocol::WaitForPotentialListOfBodysToFetch( + nsTArray<nsMsgKey>& msgIdList) { + PRIntervalTime sleepTime = kImapSleepTime; + + ReentrantMonitorAutoEnter fetchListMon(m_fetchBodyListMonitor); + while (!m_fetchBodyListIsNew && !DeathSignalReceived()) + fetchListMon.Wait(sleepTime); + m_fetchBodyListIsNew = false; + + msgIdList = m_fetchBodyIdList.Clone(); +} + +// libmsg uses this to notify a running imap url about message bodies it should +// download. why not just have libmsg explicitly download the message bodies? +NS_IMETHODIMP nsImapProtocol::NotifyBodysToDownload( + const nsTArray<nsMsgKey>& keys) { + ReentrantMonitorAutoEnter fetchListMon(m_fetchBodyListMonitor); + m_fetchBodyIdList = keys.Clone(); + m_fetchBodyListIsNew = true; + fetchListMon.Notify(); + return NS_OK; +} + +NS_IMETHODIMP nsImapProtocol::GetFlagsForUID(uint32_t uid, bool* foundIt, + imapMessageFlagsType* resultFlags, + char** customFlags) { + int32_t i; + + imapMessageFlagsType flags = + m_flagState->GetMessageFlagsFromUID(uid, foundIt, &i); + if (*foundIt) { + *resultFlags = flags; + if ((flags & kImapMsgCustomKeywordFlag) && customFlags) + m_flagState->GetCustomFlags(uid, customFlags); + } + return NS_OK; +} + +NS_IMETHODIMP nsImapProtocol::GetFlagAndUidState( + nsIImapFlagAndUidState** aFlagState) { + NS_ENSURE_ARG_POINTER(aFlagState); + NS_IF_ADDREF(*aFlagState = m_flagState); + return NS_OK; +} + +NS_IMETHODIMP nsImapProtocol::GetSupportedUserFlags(uint16_t* supportedFlags) { + if (!supportedFlags) return NS_ERROR_NULL_POINTER; + + *supportedFlags = m_flagState->GetSupportedUserFlags(); + return NS_OK; +} +void nsImapProtocol::FolderMsgDumpLoop(uint32_t* msgUids, uint32_t msgCount, + nsIMAPeFetchFields fields) { + int32_t msgCountLeft = msgCount; + uint32_t msgsDownloaded = 0; + do { + nsCString idString; + uint32_t msgsToDownload = msgCountLeft; + AllocateImapUidString(msgUids + msgsDownloaded, msgsToDownload, m_flagState, + idString); // 20 * 200 + FetchMessage(idString, fields); + msgsDownloaded += msgsToDownload; + msgCountLeft -= msgsToDownload; + } while (msgCountLeft > 0 && !DeathSignalReceived()); +} + +void nsImapProtocol::HeaderFetchCompleted() { + if (m_imapMailFolderSink) + m_imapMailFolderSink->ParseMsgHdrs(this, m_hdrDownloadCache); + m_hdrDownloadCache->ReleaseAll(); + + if (m_imapMailFolderSink) m_imapMailFolderSink->HeaderFetchCompleted(this); +} + +// Use the noop to tell the server we are still here, and therefore we are +// willing to receive status updates. The recent or exists response from the +// server could tell us that there is more mail waiting for us, but we need to +// check the flags of the mail and the high water mark to make sure that we do +// not tell the user that there is new mail when perhaps they have already read +// it in another machine. + +void nsImapProtocol::PeriodicBiff() { + nsMsgBiffState startingState = m_currentBiffState; + + if (GetServerStateParser().GetIMAPstate() == + nsImapServerResponseParser::kFolderSelected) { + Noop(); // check the latest number of messages + int32_t numMessages = 0; + m_flagState->GetNumberOfMessages(&numMessages); + if (GetServerStateParser().NumberOfMessages() != numMessages) { + uint32_t id = GetServerStateParser().HighestRecordedUID() + 1; + nsCString fetchStr; // only update flags + uint32_t added = 0, deleted = 0; + + deleted = m_flagState->NumberOfDeletedMessages(); + added = numMessages; + if (!added || (added == deleted)) // empty keys, get them all + id = 1; + + // sprintf(fetchStr, "%ld:%ld", id, id + + // GetServerStateParser().NumberOfMessages() - + // fFlagState->GetNumberOfMessages()); + AppendUid(fetchStr, id); + fetchStr.AppendLiteral(":*"); + FetchMessage(fetchStr, kFlags); + if (((uint32_t)m_flagState->GetHighestNonDeletedUID() >= id) && + m_flagState->IsLastMessageUnseen()) + m_currentBiffState = nsIMsgFolder::nsMsgBiffState_NewMail; + else + m_currentBiffState = nsIMsgFolder::nsMsgBiffState_NoMail; + } else + m_currentBiffState = nsIMsgFolder::nsMsgBiffState_NoMail; + } else + m_currentBiffState = nsIMsgFolder::nsMsgBiffState_Unknown; + + if (startingState != m_currentBiffState) + SendSetBiffIndicatorEvent(m_currentBiffState); +} + +void nsImapProtocol::SendSetBiffIndicatorEvent(nsMsgBiffState newState) { + if (m_imapMailFolderSink) + m_imapMailFolderSink->SetBiffStateAndUpdate(newState); +} + +/* static */ void nsImapProtocol::LogImapUrl(const char* logMsg, + nsIImapUrl* imapUrl) { + if (MOZ_LOG_TEST(IMAP, LogLevel::Info)) { + nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(imapUrl); + if (mailnewsUrl) { + nsAutoCString urlSpec, unescapedUrlSpec; + nsresult rv = mailnewsUrl->GetSpec(urlSpec); + if (NS_FAILED(rv)) return; + MsgUnescapeString(urlSpec, 0, unescapedUrlSpec); + MOZ_LOG(IMAP, LogLevel::Info, ("%s:%s", logMsg, unescapedUrlSpec.get())); + } + } +} + +// log info including current state... +void nsImapProtocol::Log(const char* logSubName, const char* extraInfo, + const char* logData) { + if (MOZ_LOG_TEST(IMAP, LogLevel::Info)) { + static const char nonAuthStateName[] = "NA"; + static const char authStateName[] = "A"; + static const char selectedStateName[] = "S"; + const nsCString& hostName = + GetImapHostName(); // initialize to empty string + + int32_t logDataLen = PL_strlen(logData); // PL_strlen checks for null + nsCString logDataLines; + const char* logDataToLog; + int32_t lastLineEnd; + + // nspr line length is 512, and we allow some space for the log preamble. + const int kLogDataChunkSize = 400; + + // break up buffers > 400 bytes on line boundaries. + if (logDataLen > kLogDataChunkSize) { + logDataLines.Assign(logData); + lastLineEnd = logDataLines.RFindChar('\n', kLogDataChunkSize); + // null terminate the last line + if (lastLineEnd == kNotFound) lastLineEnd = kLogDataChunkSize - 1; + + logDataLines.Insert('\0', lastLineEnd + 1); + logDataToLog = logDataLines.get(); + } else { + logDataToLog = logData; + lastLineEnd = logDataLen; + } + switch (GetServerStateParser().GetIMAPstate()) { + case nsImapServerResponseParser::kFolderSelected: + if (extraInfo) + MOZ_LOG(IMAP, LogLevel::Info, + ("%p:%s:%s-%s:%s:%s: %.400s", this, hostName.get(), + selectedStateName, + GetServerStateParser().GetSelectedMailboxName(), logSubName, + extraInfo, logDataToLog)); + else + MOZ_LOG(IMAP, LogLevel::Info, + ("%p:%s:%s-%s:%s: %.400s", this, hostName.get(), + selectedStateName, + GetServerStateParser().GetSelectedMailboxName(), logSubName, + logDataToLog)); + break; + case nsImapServerResponseParser::kNonAuthenticated: + case nsImapServerResponseParser::kAuthenticated: { + const char* stateName = (GetServerStateParser().GetIMAPstate() == + nsImapServerResponseParser::kNonAuthenticated) + ? nonAuthStateName + : authStateName; + if (extraInfo) + MOZ_LOG(IMAP, LogLevel::Info, + ("%p:%s:%s:%s:%s: %.400s", this, hostName.get(), stateName, + logSubName, extraInfo, logDataToLog)); + else + MOZ_LOG(IMAP, LogLevel::Info, + ("%p:%s:%s:%s: %.400s", this, hostName.get(), stateName, + logSubName, logDataToLog)); + } + } + + // dump the rest of the string in < 400 byte chunks + while (logDataLen > kLogDataChunkSize) { + logDataLines.Cut( + 0, + lastLineEnd + 2); // + 2 to account for the LF and the '\0' we added + logDataLen = logDataLines.Length(); + lastLineEnd = (logDataLen > kLogDataChunkSize) + ? logDataLines.RFindChar('\n', kLogDataChunkSize) + : kNotFound; + // null terminate the last line + if (lastLineEnd == kNotFound) lastLineEnd = kLogDataChunkSize - 1; + logDataLines.Insert('\0', lastLineEnd + 1); + logDataToLog = logDataLines.get(); + MOZ_LOG(IMAP, LogLevel::Info, ("%.400s", logDataToLog)); + } + } +} + +// In 4.5, this posted an event back to libmsg and blocked until it got a +// response. We may still have to do this.It would be nice if we could preflight +// this value, but we may not always know when we'll need it. +uint32_t nsImapProtocol::GetMessageSize(const nsACString& messageId) { + uint32_t size = 0; + if (m_imapMessageSink) + m_imapMessageSink->GetMessageSizeFromDB(PromiseFlatCString(messageId).get(), + &size); + if (DeathSignalReceived()) size = 0; + return size; +} + +// message id string utility functions +/* static */ bool nsImapProtocol::HandlingMultipleMessages( + const nsCString& messageIdString) { + return (MsgFindCharInSet(messageIdString, ",:") != kNotFound); +} + +uint32_t nsImapProtocol::CountMessagesInIdString(const char* idString) { + uint32_t numberOfMessages = 0; + char* uidString = PL_strdup(idString); + + if (uidString) { + // This is in the form <id>,<id>, or <id1>:<id2> + char curChar = *uidString; + bool isRange = false; + int32_t curToken; + int32_t saveStartToken = 0; + + for (char* curCharPtr = uidString; curChar && *curCharPtr;) { + char* currentKeyToken = curCharPtr; + curChar = *curCharPtr; + while (curChar != ':' && curChar != ',' && curChar != '\0') + curChar = *curCharPtr++; + *(curCharPtr - 1) = '\0'; + curToken = atol(currentKeyToken); + if (isRange) { + while (saveStartToken < curToken) { + numberOfMessages++; + saveStartToken++; + } + } + + numberOfMessages++; + isRange = (curChar == ':'); + if (isRange) saveStartToken = curToken + 1; + } + PR_Free(uidString); + } + return numberOfMessages; +} + +// It would be really nice not to have to use this method nearly as much as we +// did in 4.5 - we need to think about this some. Some of it may just go away in +// the new world order +bool nsImapProtocol::DeathSignalReceived() { + // ignore mock channel status if we've been pseudo interrupted + // ### need to make sure we clear pseudo interrupted status appropriately. + if (!GetPseudoInterrupted() && m_mockChannel) { + nsresult returnValue; + m_mockChannel->GetStatus(&returnValue); + if (NS_FAILED(returnValue)) return false; + } + + // Check the other way of cancelling. + ReentrantMonitorAutoEnter threadDeathMon(m_threadDeathMonitor); + return m_threadShouldDie; +} + +NS_IMETHODIMP nsImapProtocol::ResetToAuthenticatedState() { + GetServerStateParser().PreauthSetAuthenticatedState(); + return NS_OK; +} + +NS_IMETHODIMP nsImapProtocol::GetSelectedMailboxName(char** folderName) { + if (!folderName) return NS_ERROR_NULL_POINTER; + if (GetServerStateParser().GetSelectedMailboxName()) + *folderName = PL_strdup((GetServerStateParser().GetSelectedMailboxName())); + return NS_OK; +} + +bool nsImapProtocol::GetPseudoInterrupted() { + ReentrantMonitorAutoEnter pseudoInterruptMon(m_pseudoInterruptMonitor); + return m_pseudoInterrupted; +} + +NS_IMETHODIMP +nsImapProtocol::PseudoInterrupt(bool interrupt) { + ReentrantMonitorAutoEnter pseudoInterruptMon(m_pseudoInterruptMonitor); + m_pseudoInterrupted = interrupt; + if (interrupt) Log("CONTROL", NULL, "PSEUDO-Interrupted"); + return NS_OK; +} + +void nsImapProtocol::SetActive(bool active) { + ReentrantMonitorAutoEnter dataMemberMon(m_dataMemberMonitor); + m_active = active; +} + +bool nsImapProtocol::GetActive() { + ReentrantMonitorAutoEnter dataMemberMon(m_dataMemberMonitor); + return m_active; +} + +bool nsImapProtocol::GetShowAttachmentsInline() { + bool showAttachmentsInline = true; + if (m_imapServerSink) + m_imapServerSink->GetShowAttachmentsInline(&showAttachmentsInline); + return showAttachmentsInline; +} + +// Adds a set of rights for a given user on a given mailbox on the current host. +// if userName is NULL, it means "me," or MYRIGHTS. +void nsImapProtocol::AddFolderRightsForUser(const char* mailboxName, + const char* userName, + const char* rights) { + if (!userName) userName = ""; + if (m_imapServerSink) + m_imapServerSink->AddFolderRights(nsDependentCString(mailboxName), + nsDependentCString(userName), + nsDependentCString(rights)); +} + +void nsImapProtocol::SetCopyResponseUid(const char* msgIdString) { + if (m_imapMailFolderSink) + m_imapMailFolderSink->SetCopyResponseUid(msgIdString, m_runningUrl); +} + +void nsImapProtocol::CommitNamespacesForHostEvent() { + if (m_imapServerSink) m_imapServerSink->CommitNamespaces(); +} + +// notifies libmsg that we have new capability data for the current host +void nsImapProtocol::CommitCapability() { + if (m_imapServerSink) { + m_imapServerSink->SetCapability(GetServerStateParser().GetCapabilityFlag()); + } +} + +// rights is a single string of rights, as specified by RFC2086, the IMAP ACL +// extension. Clears all rights for a given folder, for all users. +void nsImapProtocol::ClearAllFolderRights() { + if (m_imapMailFolderSink) m_imapMailFolderSink->ClearFolderRights(); +} + +// Reads a line from the socket. +// Upon failure, the thread will be flagged for shutdown, and +// m_connectionStatus will be set to a failing code. +// Remember that some socket errors are deferred until the first read +// attempt, so this function could be the first place we hear about +// connection issues (e.g. bad certificates for SSL). +char* nsImapProtocol::CreateNewLineFromSocket() { + bool needMoreData = false; + char* newLine = nullptr; + uint32_t numBytesInLine = 0; + nsresult rv = NS_OK; + // we hold a ref to the input stream in case we get cancelled from the + // ui thread, which releases our ref to the input stream, and can + // cause the pipe to get deleted before the monitor the read is + // blocked on gets notified. When that happens, the imap thread + // will stay blocked. + nsCOMPtr<nsIInputStream> kungFuGrip = m_inputStream; + + if (m_mockChannel) { + nsImapMockChannel* imapChannel = + static_cast<nsImapMockChannel*>(m_mockChannel.get()); + + mozilla::MonitorAutoLock lock(imapChannel->mSuspendedMonitor); + + bool suspended = imapChannel->mSuspended; + if (suspended) + MOZ_LOG(IMAP, LogLevel::Debug, + ("Waiting until [imapChannel=%p] is resumed.", imapChannel)); + while (imapChannel->mSuspended) { + lock.Wait(); + } + if (suspended) + MOZ_LOG( + IMAP, LogLevel::Debug, + ("Done waiting, [imapChannel=%p] has been resumed.", imapChannel)); + } + + do { + newLine = m_inputStreamBuffer->ReadNextLine(m_inputStream, numBytesInLine, + needMoreData, &rv); + MOZ_LOG(IMAP, LogLevel::Verbose, + ("ReadNextLine [rv=0x%" PRIx32 " stream=%p nb=%u needmore=%u]", + static_cast<uint32_t>(rv), m_inputStream.get(), numBytesInLine, + needMoreData)); + + } while (!newLine && NS_SUCCEEDED(rv) && + !DeathSignalReceived()); // until we get the next line and haven't + // been interrupted + + kungFuGrip = nullptr; + + if (NS_FAILED(rv)) { + switch (rv) { + case NS_ERROR_UNKNOWN_HOST: + case NS_ERROR_UNKNOWN_PROXY_HOST: + AlertUserEventUsingName("imapUnknownHostError"); + break; + case NS_ERROR_CONNECTION_REFUSED: + case NS_ERROR_PROXY_CONNECTION_REFUSED: + AlertUserEventUsingName("imapConnectionRefusedError"); + break; + case NS_ERROR_NET_TIMEOUT: + case NS_ERROR_NET_RESET: + case NS_BASE_STREAM_CLOSED: + case NS_ERROR_NET_INTERRUPT: + // we should retry on RESET, especially for SSL... + if ((TestFlag(IMAP_RECEIVED_GREETING) || rv == NS_ERROR_NET_RESET) && + m_runningUrl && !m_retryUrlOnError) { + bool rerunningUrl; + nsImapAction imapAction; + m_runningUrl->GetRerunningUrl(&rerunningUrl); + m_runningUrl->GetImapAction(&imapAction); + // don't rerun if we already were rerunning. And don't rerun + // online move/copies that timeout. + if (!rerunningUrl && (rv != NS_ERROR_NET_TIMEOUT || + (imapAction != nsIImapUrl::nsImapOnlineCopy && + imapAction != nsIImapUrl::nsImapOnlineMove))) { + m_runningUrl->SetRerunningUrl(true); + m_retryUrlOnError = true; + break; + } + } + if (rv == NS_ERROR_NET_TIMEOUT) + AlertUserEventUsingName("imapNetTimeoutError"); + else + AlertUserEventUsingName(TestFlag(IMAP_RECEIVED_GREETING) + ? "imapServerDisconnected" + : "imapServerDroppedConnection"); + break; + default: + // This is probably a TLS error. Usually TLS errors won't show up until + // we do ReadNextLine() above. Since we're in the IMAP thread we can't + // call NSSErrorsService::GetErrorClass() to determine if the error + // should result in an non-fatal override dialog (usually certificate + // issues) or if it's a fatal protocol error that the user must be + // alerted to. Instead, we use some publicly-accessible macros and a + // function to determine this. + if (NS_ERROR_GET_MODULE(rv) == NS_ERROR_MODULE_SECURITY && + NS_ERROR_GET_SEVERITY(rv) == NS_ERROR_SEVERITY_ERROR) { + // It's an error of class 21 (SSL/TLS/Security), e.g., overridable + // SSL_ERROR_BAD_CERT_DOMAIN from security/nss/lib/ssl/sslerr.h + // rv = 0x80000000 + 0x00450000 + 0x00150000 + 0x00002ff4 = 0x805A2ff4 + int32_t sec_error = -1 * NS_ERROR_GET_CODE(rv); // = 0xFFFFD00C + if (!mozilla::psm::ErrorIsOverridable(sec_error)) { + AlertUserEventUsingName("imapTlsError"); + } + + // Stash the socket transport securityInfo on the URL so it will be + // available in nsIUrlListener OnStopRunningUrl() callbacks to trigger + // the override dialog or a security related error message. + // Currently this is only used to trigger the override dialog. + if (m_runningUrl) { + MOZ_ASSERT(!NS_IsMainThread()); + nsCOMPtr<nsIMsgMailNewsUrl> mailNewsUrl = + do_QueryInterface(m_runningUrl); + if (mailNewsUrl) { + nsCOMPtr<nsITransportSecurityInfo> securityInfo; + GetTransportSecurityInfo(getter_AddRefs(securityInfo)); + if (securityInfo) { + nsAutoCString logMsg("Security error - error code="); + nsAutoString errorCodeString; + securityInfo->GetErrorCodeString(errorCodeString); + logMsg.Append(NS_ConvertUTF16toUTF8(errorCodeString)); + Log("CreateNewLineFromSocket", nullptr, logMsg.get()); + + mailNewsUrl->SetFailedSecInfo(securityInfo); + } + } + } + } + break; + } + + nsAutoCString logMsg("clearing IMAP_CONNECTION_IS_OPEN - rv = "); + logMsg.AppendInt(static_cast<uint32_t>(rv), 16); + Log("CreateNewLineFromSocket", nullptr, logMsg.get()); + ClearFlag(IMAP_CONNECTION_IS_OPEN); + TellThreadToDie(); + } + Log("CreateNewLineFromSocket", nullptr, newLine); + SetConnectionStatus(newLine && numBytesInLine + ? NS_OK + : rv); // set > 0 if string is not null or empty + return newLine; +} + +nsresult nsImapProtocol::GetConnectionStatus() { return m_connectionStatus; } + +void nsImapProtocol::SetConnectionStatus(nsresult status) { + // Log failure at Debug level, otherwise use Verbose to avoid huge logs + MOZ_LOG( + IMAP, NS_SUCCEEDED(status) ? LogLevel::Verbose : LogLevel::Debug, + ("SetConnectionStatus(0x%" PRIx32 ")", static_cast<uint32_t>(status))); + m_connectionStatus = status; +} + +void nsImapProtocol::NotifyMessageFlags(imapMessageFlagsType flags, + const nsACString& keywords, + nsMsgKey key, uint64_t highestModSeq) { + if (m_imapMessageSink) { + // if we're selecting the folder, don't need to report the flags; we've + // already fetched them. + if (m_imapAction != nsIImapUrl::nsImapSelectFolder) + m_imapMessageSink->NotifyMessageFlags(flags, keywords, key, + highestModSeq); + } +} + +void nsImapProtocol::NotifySearchHit(const char* hitLine) { + nsresult rv; + nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = + do_QueryInterface(m_runningUrl, &rv); + if (m_imapMailFolderSink) + m_imapMailFolderSink->NotifySearchHit(mailnewsUrl, hitLine); +} + +void nsImapProtocol::SetMailboxDiscoveryStatus(EMailboxDiscoverStatus status) { + ReentrantMonitorAutoEnter mon(m_dataMemberMonitor); + m_discoveryStatus = status; +} + +EMailboxDiscoverStatus nsImapProtocol::GetMailboxDiscoveryStatus() { + ReentrantMonitorAutoEnter mon(m_dataMemberMonitor); + return m_discoveryStatus; +} + +bool nsImapProtocol::GetSubscribingNow() { + // ***** code me ***** + return false; // ***** for now +} + +void nsImapProtocol::DiscoverMailboxSpec(nsImapMailboxSpec* adoptedBoxSpec) { + nsImapNamespace* ns = nullptr; + + NS_ASSERTION(m_hostSessionList, "fatal null host session list"); + if (!m_hostSessionList) return; + + m_hostSessionList->GetDefaultNamespaceOfTypeForHost(GetImapServerKey(), + kPersonalNamespace, ns); + const char* nsPrefix = ns ? ns->GetPrefix() : 0; + + if (m_specialXListMailboxes.Count() > 0) { + nsCString strHashKey(adoptedBoxSpec->mAllocatedPathName); + int32_t hashValue = m_specialXListMailboxes.Get(strHashKey); + adoptedBoxSpec->mBoxFlags |= hashValue; + } + + switch (m_hierarchyNameState) { + case kXListing: + if (adoptedBoxSpec->mBoxFlags & + (kImapXListTrash | kImapAllMail | kImapInbox | kImapSent | kImapSpam | + kImapDrafts)) { + nsCString mailboxName(adoptedBoxSpec->mAllocatedPathName); + m_specialXListMailboxes.InsertOrUpdate(mailboxName, + adoptedBoxSpec->mBoxFlags); + // Remember hierarchy delimiter in case this is the first time we've + // connected to the server and we need it to be correct for the + // two-level XLIST we send (INBOX is guaranteed to be in the first + // response). + if (adoptedBoxSpec->mBoxFlags & kImapInbox) + m_runningUrl->SetOnlineSubDirSeparator( + adoptedBoxSpec->mHierarchySeparator); + } + break; + case kListingForFolderFlags: { + // store mailbox flags from LIST for use by LSUB + nsCString mailboxName(adoptedBoxSpec->mAllocatedPathName); + m_standardListMailboxes.InsertOrUpdate(mailboxName, + adoptedBoxSpec->mBoxFlags); + } break; + case kListingForCreate: + case kNoOperationInProgress: + case kDiscoverTrashFolderInProgress: + case kListingForInfoAndDiscovery: { + // standard mailbox specs are stored in m_standardListMailboxes + // because LSUB does necessarily return all mailbox flags. + // count should be > 0 only when we are looking at response of LSUB + if (m_standardListMailboxes.Count() > 0) { + int32_t hashValue = 0; + nsCString strHashKey(adoptedBoxSpec->mAllocatedPathName); + if (m_standardListMailboxes.Get(strHashKey, &hashValue)) + adoptedBoxSpec->mBoxFlags |= hashValue; + else + // if mailbox is not in hash list, then it is subscribed but does not + // exist, so we make sure it can't be selected + adoptedBoxSpec->mBoxFlags |= kNoselect; + } + if (ns && + nsPrefix) // if no personal namespace, there can be no Trash folder + { + bool onlineTrashFolderExists = false; + if (m_hostSessionList) { + if (adoptedBoxSpec->mBoxFlags & (kImapTrash | kImapXListTrash)) { + m_hostSessionList->SetOnlineTrashFolderExistsForHost( + GetImapServerKey(), true); + onlineTrashFolderExists = true; + } else { + m_hostSessionList->GetOnlineTrashFolderExistsForHost( + GetImapServerKey(), onlineTrashFolderExists); + } + } + + // Don't set the Trash flag if not using the Trash model + if (GetDeleteIsMoveToTrash() && !onlineTrashFolderExists && + FindInReadable(m_trashFolderPath, + adoptedBoxSpec->mAllocatedPathName, + nsCaseInsensitiveCStringComparator)) { + bool trashExists = false; + if (StringBeginsWith(m_trashFolderPath, "INBOX/"_ns, + nsCaseInsensitiveCStringComparator)) { + nsAutoCString pathName(adoptedBoxSpec->mAllocatedPathName.get() + + 6); + trashExists = + StringBeginsWith( + adoptedBoxSpec->mAllocatedPathName, m_trashFolderPath, + nsCaseInsensitiveCStringComparator) && /* "INBOX/" */ + pathName.Equals(Substring(m_trashFolderPath, 6), + nsCaseInsensitiveCStringComparator); + } else + trashExists = adoptedBoxSpec->mAllocatedPathName.Equals( + m_trashFolderPath, nsCaseInsensitiveCStringComparator); + + if (m_hostSessionList) + m_hostSessionList->SetOnlineTrashFolderExistsForHost( + GetImapServerKey(), trashExists); + + if (trashExists) adoptedBoxSpec->mBoxFlags |= kImapTrash; + } + } + + // Discover the folder (shuttle over to libmsg, yay) + // Do this only if the folder name is not empty (i.e. the root) + if (!adoptedBoxSpec->mAllocatedPathName.IsEmpty()) { + if (m_hierarchyNameState == kListingForCreate) + adoptedBoxSpec->mBoxFlags |= kNewlyCreatedFolder; + + if (m_imapServerSink) { + bool newFolder; + + m_imapServerSink->PossibleImapMailbox( + adoptedBoxSpec->mAllocatedPathName, + adoptedBoxSpec->mHierarchySeparator, adoptedBoxSpec->mBoxFlags, + &newFolder); + // if it's a new folder to the server sink, setting discovery status + // to eContinueNew will cause us to get the ACL for the new folder. + if (newFolder) SetMailboxDiscoveryStatus(eContinueNew); + + bool useSubscription = false; + + if (m_hostSessionList) + m_hostSessionList->GetHostIsUsingSubscription(GetImapServerKey(), + useSubscription); + + if ((GetMailboxDiscoveryStatus() != eContinue) && + (GetMailboxDiscoveryStatus() != eContinueNew) && + (GetMailboxDiscoveryStatus() != eListMyChildren)) { + SetConnectionStatus(NS_ERROR_FAILURE); + } else if (!adoptedBoxSpec->mAllocatedPathName.IsEmpty() && + (GetMailboxDiscoveryStatus() == eListMyChildren) && + (!useSubscription || GetSubscribingNow())) { + NS_ASSERTION(false, "we should never get here anymore"); + SetMailboxDiscoveryStatus(eContinue); + } else if (GetMailboxDiscoveryStatus() == eContinueNew) { + if (m_hierarchyNameState == kListingForInfoAndDiscovery && + !adoptedBoxSpec->mAllocatedPathName.IsEmpty() && + !(adoptedBoxSpec->mBoxFlags & kNameSpace)) { + // remember the info here also + nsIMAPMailboxInfo* mb = + new nsIMAPMailboxInfo(adoptedBoxSpec->mAllocatedPathName, + adoptedBoxSpec->mHierarchySeparator); + m_listedMailboxList.AppendElement(mb); + } + SetMailboxDiscoveryStatus(eContinue); + } + } + } + } break; + case kDeleteSubFoldersInProgress: { + NS_ASSERTION(m_deletableChildren, "Oops .. null m_deletableChildren"); + m_deletableChildren->AppendElement(adoptedBoxSpec->mAllocatedPathName); + } break; + case kListingForInfoOnly: { + // UpdateProgressWindowForUpgrade(adoptedBoxSpec->allocatedPathName); + ProgressEventFunctionUsingNameWithString( + "imapDiscoveringMailbox", adoptedBoxSpec->mAllocatedPathName.get()); + nsIMAPMailboxInfo* mb = + new nsIMAPMailboxInfo(adoptedBoxSpec->mAllocatedPathName, + adoptedBoxSpec->mHierarchySeparator); + m_listedMailboxList.AppendElement(mb); + } break; + case kDiscoveringNamespacesOnly: { + } break; + default: + NS_ASSERTION(false, "we aren't supposed to be here"); + break; + } +} + +void nsImapProtocol::AlertUserEventUsingName(const char* aMessageName) { + if (m_imapServerSink) { + bool suppressErrorMsg = false; + + nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_runningUrl); + if (mailnewsUrl) mailnewsUrl->GetSuppressErrorMsgs(&suppressErrorMsg); + + if (!suppressErrorMsg) + m_imapServerSink->FEAlertWithName(aMessageName, mailnewsUrl); + } +} + +void nsImapProtocol::AlertUserEvent(const char* message) { + if (m_imapServerSink) { + bool suppressErrorMsg = false; + + nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_runningUrl); + if (mailnewsUrl) mailnewsUrl->GetSuppressErrorMsgs(&suppressErrorMsg); + + if (!suppressErrorMsg) + m_imapServerSink->FEAlert(NS_ConvertASCIItoUTF16(message), mailnewsUrl); + } +} + +void nsImapProtocol::AlertUserEventFromServer(const char* aServerEvent, + bool aForIdle) { + if (aServerEvent) { + // If called due to BAD/NO imap IDLE response, the server sink and running + // url are typically null when IDLE command is sent. So use the stored + // latest values for these so that the error alert notification occurs. + if (aForIdle && !m_imapServerSink && !m_runningUrl && + m_imapServerSinkLatest) { + nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = + do_QueryInterface(m_runningUrlLatest); + m_imapServerSinkLatest->FEAlertFromServer( + nsDependentCString(aServerEvent), mailnewsUrl); + } else if (m_imapServerSink) { + nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_runningUrl); + m_imapServerSink->FEAlertFromServer(nsDependentCString(aServerEvent), + mailnewsUrl); + } + } +} + +void nsImapProtocol::ResetProgressInfo() { + m_lastProgressTime = 0; + m_lastPercent = -1; + m_lastProgressStringName.Truncate(); +} + +void nsImapProtocol::SetProgressString(uint32_t aStringIndex) { + m_stringIndex = aStringIndex; + MOZ_ASSERT(m_stringIndex <= IMAP_EMPTY_STRING_INDEX); + switch (m_stringIndex) { + case IMAP_HEADERS_STRING_INDEX: + m_progressStringName = "imapReceivingMessageHeaders3"; + break; + case IMAP_MESSAGES_STRING_INDEX: + m_progressStringName = "imapFolderReceivingMessageOf3"; + break; + case IMAP_FLAGS_STRING_INDEX: + m_progressStringName = "imapReceivingMessageFlags3"; + break; + case IMAP_EMPTY_STRING_INDEX: + default: + break; + } +} + +void nsImapProtocol::ShowProgress() { + if (m_imapServerSink && (m_stringIndex != IMAP_EMPTY_STRING_INDEX)) { + nsString progressString; + const char* mailboxName = GetServerStateParser().GetSelectedMailboxName(); + nsString unicodeMailboxName; + nsresult rv = CopyFolderNameToUTF16(nsDependentCString(mailboxName), + unicodeMailboxName); + NS_ENSURE_SUCCESS_VOID(rv); + + int32_t progressCurrentNumber = ++m_progressCurrentNumber[m_stringIndex]; + + PercentProgressUpdateEvent(m_progressStringName, unicodeMailboxName, + progressCurrentNumber, m_progressExpectedNumber); + } +} + +void nsImapProtocol::ProgressEventFunctionUsingName(const char* aMsgName) { + if (m_imapMailFolderSink && !m_lastProgressStringName.Equals(aMsgName)) { + m_imapMailFolderSink->ProgressStatusString(this, aMsgName, nullptr); + m_lastProgressStringName.Assign(aMsgName); + // who's going to free this? Does ProgressStatusString complete + // synchronously? + } +} + +void nsImapProtocol::ProgressEventFunctionUsingNameWithString( + const char* aMsgName, const char* aExtraInfo) { + if (m_imapMailFolderSink) { + nsString unicodeStr; + nsresult rv = + CopyFolderNameToUTF16(nsDependentCString(aExtraInfo), unicodeStr); + if (NS_SUCCEEDED(rv)) + m_imapMailFolderSink->ProgressStatusString(this, aMsgName, + unicodeStr.get()); + } +} + +void nsImapProtocol::PercentProgressUpdateEvent(nsACString const& fmtStringName, + nsAString const& mailbox, + int64_t currentProgress, + int64_t maxProgress) { + int64_t nowMS = 0; + int32_t percent = (100 * currentProgress) / maxProgress; + if (percent == m_lastPercent) + return; // hasn't changed, right? So just return. Do we need to clear this + // anywhere? + + if (percent < 100) // always need to do 100% + { + nowMS = PR_IntervalToMilliseconds(PR_IntervalNow()); + if (nowMS - m_lastProgressTime < 750) return; + } + + m_lastPercent = percent; + m_lastProgressTime = nowMS; + + // set our max progress on the running URL + if (m_runningUrl) { + nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl(do_QueryInterface(m_runningUrl)); + mailnewsUrl->SetMaxProgress(maxProgress); + } + + if (m_imapMailFolderSink) { + m_imapMailFolderSink->PercentProgress(this, fmtStringName, mailbox, + currentProgress, maxProgress); + } +} + +// imap commands issued by the parser +void nsImapProtocol::Store(const nsCString& messageList, + const char* messageData, bool idsAreUid) { + // turn messageList back into key array and then back into a message id list, + // but use the flag state to handle ranges correctly. + nsCString messageIdList; + nsTArray<nsMsgKey> msgKeys; + if (idsAreUid) ParseUidString(messageList.get(), msgKeys); + + int32_t msgCountLeft = msgKeys.Length(); + uint32_t msgsHandled = 0; + do { + nsCString idString; + + uint32_t msgsToHandle = msgCountLeft; + if (idsAreUid) + AllocateImapUidString(msgKeys.Elements() + msgsHandled, msgsToHandle, + m_flagState, idString); // 20 * 200 + else + idString.Assign(messageList); + + msgsHandled += msgsToHandle; + msgCountLeft -= msgsToHandle; + + IncrementCommandTagNumber(); + const char* formatString; + if (idsAreUid) + formatString = "%s uid store %s %s\015\012"; + else + formatString = "%s store %s %s\015\012"; + + // we might need to close this mailbox after this + m_closeNeededBeforeSelect = + GetDeleteIsMoveToTrash() && (PL_strcasestr(messageData, "\\Deleted")); + + const char* commandTag = GetServerCommandTag(); + int protocolStringSize = PL_strlen(formatString) + messageList.Length() + + PL_strlen(messageData) + PL_strlen(commandTag) + 1; + char* protocolString = (char*)PR_CALLOC(protocolStringSize); + + if (protocolString) { + PR_snprintf(protocolString, // string to create + protocolStringSize, // max size + formatString, // format string + commandTag, // command tag + idString.get(), messageData); + + nsresult rv = SendData(protocolString); + if (NS_SUCCEEDED(rv)) { + m_flagChangeCount++; + ParseIMAPandCheckForNewMail(protocolString); + if (GetServerStateParser().LastCommandSuccessful() && CheckNeeded()) + Check(); + } + PR_Free(protocolString); + } else + HandleMemoryFailure(); + } while (msgCountLeft > 0 && !DeathSignalReceived()); +} + +void nsImapProtocol::IssueUserDefinedMsgCommand(const char* command, + const char* messageList) { + IncrementCommandTagNumber(); + + const char* formatString; + formatString = "%s uid %s %s\015\012"; + + const char* commandTag = GetServerCommandTag(); + int protocolStringSize = PL_strlen(formatString) + PL_strlen(messageList) + + PL_strlen(command) + PL_strlen(commandTag) + 1; + char* protocolString = (char*)PR_CALLOC(protocolStringSize); + + if (protocolString) { + PR_snprintf(protocolString, // string to create + protocolStringSize, // max size + formatString, // format string + commandTag, // command tag + command, messageList); + + nsresult rv = SendData(protocolString); + if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail(protocolString); + PR_Free(protocolString); + } else + HandleMemoryFailure(); +} + +void nsImapProtocol::UidExpunge(const nsCString& messageSet) { + IncrementCommandTagNumber(); + nsCString command(GetServerCommandTag()); + command.AppendLiteral(" uid expunge "); + command.Append(messageSet); + command.Append(CRLF); + nsresult rv = SendData(command.get()); + if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail(); +} + +void nsImapProtocol::Expunge() { + uint32_t aclFlags = 0; + if (GetServerStateParser().ServerHasACLCapability() && m_imapMailFolderSink) + m_imapMailFolderSink->GetAclFlags(&aclFlags); + + if (aclFlags && !(aclFlags & IMAP_ACL_EXPUNGE_FLAG)) return; + ProgressEventFunctionUsingName("imapStatusExpungingMailbox"); + + if (gCheckDeletedBeforeExpunge) { + GetServerStateParser().ResetSearchResultSequence(); + Search("SEARCH DELETED", false, false); + if (GetServerStateParser().LastCommandSuccessful()) { + nsImapSearchResultIterator* search = + GetServerStateParser().CreateSearchResultIterator(); + nsMsgKey key = search->GetNextMessageNumber(); + delete search; + if (key == 0) return; // no deleted messages to expunge (bug 235004) + } + } + + IncrementCommandTagNumber(); + nsAutoCString command(GetServerCommandTag()); + command.AppendLiteral(" expunge" CRLF); + + nsresult rv = SendData(command.get()); + if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail(); +} + +void nsImapProtocol::HandleMemoryFailure() { + PR_CEnterMonitor(this); + // **** jefft fix me!!!!!! ****** + // m_imapThreadIsRunning = false; + // SetConnectionStatus(-1); + PR_CExitMonitor(this); +} + +void nsImapProtocol::HandleCurrentUrlError() { + // This is to handle a move/copy failing, especially because the user + // cancelled the password prompt. + (void)m_runningUrl->GetImapAction(&m_imapAction); + if (m_imapAction == nsIImapUrl::nsImapOfflineToOnlineMove || + m_imapAction == nsIImapUrl::nsImapAppendMsgFromFile || + m_imapAction == nsIImapUrl::nsImapAppendDraftFromFile) { + if (m_imapMailFolderSink) + m_imapMailFolderSink->OnlineCopyCompleted( + this, ImapOnlineCopyStateType::kFailedCopy); + } +} + +void nsImapProtocol::StartTLS() { + IncrementCommandTagNumber(); + nsCString tag(GetServerCommandTag()); + nsCString command(tag); + + command.AppendLiteral(" STARTTLS" CRLF); + nsresult rv = SendData(command.get()); + bool ok = false; + if (NS_SUCCEEDED(rv)) { + nsCString expectOkResponse = tag + " OK "_ns; + char* serverResponse = nullptr; + do { + // This reads and discards lines not starting with "<tag> OK " or + // "<tag> BAD " and exits when when either are found. Otherwise, this + // exits on timeout when all lines in the buffer are read causing + // serverResponse to be set null. Usually just "<tag> OK " is present. + serverResponse = CreateNewLineFromSocket(); + ok = serverResponse && + !PL_strncasecmp(serverResponse, expectOkResponse.get(), + expectOkResponse.Length()); + if (!ok && serverResponse) { + // Check for possible BAD response, e.g., server not STARTTLS capable. + nsCString expectBadResponse = tag + " BAD "_ns; + if (!PL_strncasecmp(serverResponse, expectBadResponse.get(), + expectBadResponse.Length())) { + PR_Free(serverResponse); + break; + } + } + PR_Free(serverResponse); + } while (serverResponse && !ok); + } + // ok == false implies a "<tag> BAD " response or time out on socket read. + // It could also be due to failure on SendData() above. + GetServerStateParser().SetCommandFailed(!ok); +} + +void nsImapProtocol::Capability() { + ProgressEventFunctionUsingName("imapStatusCheckCompat"); + IncrementCommandTagNumber(); + nsCString command(GetServerCommandTag()); + + command.AppendLiteral(" capability" CRLF); + + nsresult rv = SendData(command.get()); + if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail(); +} + +void nsImapProtocol::ID() { + if (!gAppName[0]) return; + IncrementCommandTagNumber(); + nsCString command(GetServerCommandTag()); + command.AppendLiteral(" ID (\"name\" \""); + command.Append(gAppName); + command.AppendLiteral("\" \"version\" \""); + command.Append(gAppVersion); + command.AppendLiteral("\")" CRLF); + + nsresult rv = SendData(command.get()); + if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail(); +} + +void nsImapProtocol::EnableUTF8Accept() { + IncrementCommandTagNumber(); + nsCString command(GetServerCommandTag()); + command.AppendLiteral(" ENABLE UTF8=ACCEPT" CRLF); + + nsresult rv = SendData(command.get()); + if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail(); +} + +void nsImapProtocol::EnableCondStore() { + IncrementCommandTagNumber(); + nsCString command(GetServerCommandTag()); + + command.AppendLiteral(" ENABLE CONDSTORE" CRLF); + + nsresult rv = SendData(command.get()); + if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail(); +} + +void nsImapProtocol::StartCompressDeflate() { + // only issue a compression request if we haven't already + if (!TestFlag(IMAP_ISSUED_COMPRESS_REQUEST)) { + SetFlag(IMAP_ISSUED_COMPRESS_REQUEST); + IncrementCommandTagNumber(); + nsCString command(GetServerCommandTag()); + + command.AppendLiteral(" COMPRESS DEFLATE" CRLF); + + nsresult rv = SendData(command.get()); + if (NS_SUCCEEDED(rv)) { + ParseIMAPandCheckForNewMail(); + if (GetServerStateParser().LastCommandSuccessful()) { + rv = BeginCompressing(); + if (NS_FAILED(rv)) { + Log("CompressDeflate", nullptr, "failed to enable compression"); + // we can't use this connection without compression any more, so die + ClearFlag(IMAP_CONNECTION_IS_OPEN); + TellThreadToDie(); + SetConnectionStatus(rv); + return; + } + } + } + } +} + +nsresult nsImapProtocol::BeginCompressing() { + // wrap the streams in compression layers that compress or decompress + // all traffic. + RefPtr<nsMsgCompressIStream> new_in = new nsMsgCompressIStream(); + if (!new_in) return NS_ERROR_OUT_OF_MEMORY; + + nsresult rv = new_in->InitInputStream(m_inputStream); + NS_ENSURE_SUCCESS(rv, rv); + + m_inputStream = new_in; + + RefPtr<nsMsgCompressOStream> new_out = new nsMsgCompressOStream(); + if (!new_out) return NS_ERROR_OUT_OF_MEMORY; + + rv = new_out->InitOutputStream(m_outputStream); + NS_ENSURE_SUCCESS(rv, rv); + + m_outputStream = new_out; + return rv; +} + +void nsImapProtocol::Language() { + // only issue the language request if we haven't done so already... + if (!TestFlag(IMAP_ISSUED_LANGUAGE_REQUEST)) { + SetFlag(IMAP_ISSUED_LANGUAGE_REQUEST); + ProgressEventFunctionUsingName("imapStatusCheckCompat"); + IncrementCommandTagNumber(); + nsCString command(GetServerCommandTag()); + + // extract the desired language attribute from prefs + nsresult rv = NS_OK; + + // we need to parse out the first language out of this comma separated + // list.... i.e if we have en,ja we only want to send en to the server. + if (mAcceptLanguages.get()) { + nsAutoCString extractedLanguage; + LossyCopyUTF16toASCII(mAcceptLanguages, extractedLanguage); + int32_t pos = extractedLanguage.FindChar(','); + if (pos > 0) // we have a comma separated list of languages... + extractedLanguage.SetLength(pos); // truncate everything after the + // first comma (including the comma) + + if (extractedLanguage.IsEmpty()) return; + + command.AppendLiteral(" LANGUAGE "); + command.Append(extractedLanguage); + command.Append(CRLF); + + rv = SendData(command.get()); + if (NS_SUCCEEDED(rv)) + ParseIMAPandCheckForNewMail(nullptr, true /* ignore bad or no result from the server for this command */); + } + } +} + +void nsImapProtocol::EscapeUserNamePasswordString(const char* strToEscape, + nsCString* resultStr) { + if (strToEscape) { + uint32_t i = 0; + uint32_t escapeStrlen = strlen(strToEscape); + for (i = 0; i < escapeStrlen; i++) { + if (strToEscape[i] == '\\' || strToEscape[i] == '\"') { + resultStr->Append('\\'); + } + resultStr->Append(strToEscape[i]); + } + } +} + +void nsImapProtocol::InitPrefAuthMethods(int32_t authMethodPrefValue, + nsIMsgIncomingServer* aServer) { + // for m_prefAuthMethods, using the same flags as server capabilities. + switch (authMethodPrefValue) { + case nsMsgAuthMethod::none: + m_prefAuthMethods = kHasAuthNoneCapability; + break; + case nsMsgAuthMethod::old: + m_prefAuthMethods = kHasAuthOldLoginCapability; + break; + case nsMsgAuthMethod::passwordCleartext: + m_prefAuthMethods = kHasAuthOldLoginCapability | kHasAuthLoginCapability | + kHasAuthPlainCapability; + break; + case nsMsgAuthMethod::passwordEncrypted: + m_prefAuthMethods = kHasCRAMCapability; + break; + case nsMsgAuthMethod::NTLM: + m_prefAuthMethods = kHasAuthNTLMCapability | kHasAuthMSNCapability; + break; + case nsMsgAuthMethod::GSSAPI: + m_prefAuthMethods = kHasAuthGssApiCapability; + break; + case nsMsgAuthMethod::External: + m_prefAuthMethods = kHasAuthExternalCapability; + break; + case nsMsgAuthMethod::secure: + m_prefAuthMethods = kHasCRAMCapability | kHasAuthGssApiCapability | + kHasAuthNTLMCapability | kHasAuthMSNCapability; + break; + case nsMsgAuthMethod::OAuth2: + m_prefAuthMethods = kHasXOAuth2Capability; + break; + default: + NS_ASSERTION(false, "IMAP: authMethod pref invalid"); + MOZ_LOG(IMAP, LogLevel::Error, + ("IMAP: bad pref authMethod = %d", authMethodPrefValue)); + // fall to any + [[fallthrough]]; + case nsMsgAuthMethod::anything: + m_prefAuthMethods = kHasAuthOldLoginCapability | kHasAuthLoginCapability | + kHasAuthPlainCapability | kHasCRAMCapability | + kHasAuthGssApiCapability | kHasAuthNTLMCapability | + kHasAuthMSNCapability | kHasAuthExternalCapability | + kHasXOAuth2Capability; + break; + } + + if (m_prefAuthMethods & kHasXOAuth2Capability) { + mOAuth2Support = new mozilla::mailnews::OAuth2ThreadHelper(aServer); + if (!mOAuth2Support || !mOAuth2Support->SupportsOAuth2()) { + // Disable OAuth2 support if we don't have the prefs installed. + m_prefAuthMethods &= ~kHasXOAuth2Capability; + mOAuth2Support = nullptr; + MOZ_LOG(IMAP, LogLevel::Warning, + ("IMAP: no OAuth2 support for this server.")); + } + } +} + +/** + * Changes m_currentAuthMethod to pick the best remaining one + * which is allowed by server and prefs and not marked failed. + * The order of preference and trying of auth methods is encoded here. + */ +nsresult nsImapProtocol::ChooseAuthMethod() { + eIMAPCapabilityFlags serverCaps = GetServerStateParser().GetCapabilityFlag(); + eIMAPCapabilityFlags availCaps = + serverCaps & m_prefAuthMethods & ~m_failedAuthMethods; + + MOZ_LOG(IMAP, LogLevel::Debug, + ("IMAP auth: server caps 0x%" PRIx64 ", pref 0x%" PRIx64 + ", failed 0x%" PRIx64 ", avail caps 0x%" PRIx64, + serverCaps, m_prefAuthMethods, m_failedAuthMethods, availCaps)); + // clang-format off + MOZ_LOG(IMAP, LogLevel::Debug, + ("(GSSAPI = 0x%" PRIx64 ", CRAM = 0x%" PRIx64 ", NTLM = 0x%" PRIx64 + ", MSN = 0x%" PRIx64 ", PLAIN = 0x%" PRIx64 ", LOGIN = 0x%" PRIx64 + ", old-style IMAP login = 0x%" PRIx64 + ", auth external IMAP login = 0x%" PRIx64 ", OAUTH2 = 0x%" PRIx64 ")", + kHasAuthGssApiCapability, kHasCRAMCapability, kHasAuthNTLMCapability, + kHasAuthMSNCapability, kHasAuthPlainCapability, kHasAuthLoginCapability, + kHasAuthOldLoginCapability, kHasAuthExternalCapability, + kHasXOAuth2Capability)); + // clang-format on + + if (kHasAuthExternalCapability & availCaps) + m_currentAuthMethod = kHasAuthExternalCapability; + else if (kHasAuthGssApiCapability & availCaps) + m_currentAuthMethod = kHasAuthGssApiCapability; + else if (kHasCRAMCapability & availCaps) + m_currentAuthMethod = kHasCRAMCapability; + else if (kHasAuthNTLMCapability & availCaps) + m_currentAuthMethod = kHasAuthNTLMCapability; + else if (kHasAuthMSNCapability & availCaps) + m_currentAuthMethod = kHasAuthMSNCapability; + else if (kHasXOAuth2Capability & availCaps) + m_currentAuthMethod = kHasXOAuth2Capability; + else if (kHasAuthPlainCapability & availCaps) + m_currentAuthMethod = kHasAuthPlainCapability; + else if (kHasAuthLoginCapability & availCaps) + m_currentAuthMethod = kHasAuthLoginCapability; + else if (kHasAuthOldLoginCapability & availCaps) + m_currentAuthMethod = kHasAuthOldLoginCapability; + else { + MOZ_LOG(IMAP, LogLevel::Debug, ("No remaining auth method")); + m_currentAuthMethod = kCapabilityUndefined; + return NS_ERROR_FAILURE; + } + MOZ_LOG(IMAP, LogLevel::Debug, + ("Trying auth method 0x%" PRIx64, m_currentAuthMethod)); + return NS_OK; +} + +void nsImapProtocol::MarkAuthMethodAsFailed( + eIMAPCapabilityFlags failedAuthMethod) { + MOZ_LOG(IMAP, LogLevel::Debug, + ("Marking auth method 0x%" PRIx64 " failed", failedAuthMethod)); + m_failedAuthMethods |= failedAuthMethod; +} + +/** + * Start over, trying all auth methods again + */ +void nsImapProtocol::ResetAuthMethods() { + MOZ_LOG(IMAP, LogLevel::Debug, ("resetting (failed) auth methods")); + m_currentAuthMethod = kCapabilityUndefined; + m_failedAuthMethods = 0; +} + +nsresult nsImapProtocol::SendDataParseIMAPandCheckForNewMail( + const char* aData, const char* aCommand) { + nsresult rv; + bool isResend = false; + while (true) { + // Send authentication string (true: suppress logging the string). + rv = SendData(aData, true); + if (NS_FAILED(rv)) break; + ParseIMAPandCheckForNewMail(aCommand); + if (!GetServerStateParser().WaitingForMoreClientInput()) break; + + // The server is asking for the authentication string again. So we send + // the same string again although we know that it might be rejected again. + // We do that to get a firm authentication failure instead of a resend + // request. That keeps things in order before failing authentication and + // trying another method if capable. + if (isResend) { + rv = NS_ERROR_FAILURE; + break; + } + isResend = true; + } + + return rv; +} + +nsresult nsImapProtocol::ClientID() { + IncrementCommandTagNumber(); + nsCString command(GetServerCommandTag()); + command += " CLIENTID UUID "; + command += m_clientId; + command += CRLF; + nsresult rv = SendDataParseIMAPandCheckForNewMail(command.get(), nullptr); + NS_ENSURE_SUCCESS(rv, rv); + if (!GetServerStateParser().LastCommandSuccessful()) { + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +nsresult nsImapProtocol::AuthLogin(const char* userName, + const nsString& aPassword, + eIMAPCapabilityFlag flag) { + ProgressEventFunctionUsingName("imapStatusSendingAuthLogin"); + IncrementCommandTagNumber(); + + char* currentCommand = nullptr; + nsresult rv; + NS_ConvertUTF16toUTF8 password(aPassword); + MOZ_LOG(IMAP, LogLevel::Debug, + ("IMAP: trying auth method 0x%" PRIx64, m_currentAuthMethod)); + + if (flag & kHasAuthExternalCapability) { + char* base64UserName = PL_Base64Encode(userName, strlen(userName), nullptr); + nsAutoCString command(GetServerCommandTag()); + command.AppendLiteral(" authenticate EXTERNAL "); + command.Append(base64UserName); + command.Append(CRLF); + PR_Free(base64UserName); + rv = SendData(command.get()); + ParseIMAPandCheckForNewMail(); + nsImapServerResponseParser& parser = GetServerStateParser(); + if (parser.LastCommandSuccessful()) return NS_OK; + parser.SetCapabilityFlag(parser.GetCapabilityFlag() & + ~kHasAuthExternalCapability); + } else if (flag & kHasCRAMCapability) { + NS_ENSURE_TRUE(m_imapServerSink, NS_ERROR_NULL_POINTER); + MOZ_LOG(IMAP, LogLevel::Debug, ("MD5 auth")); + // inform the server that we want to begin a CRAM authentication + // procedure... + nsAutoCString command(GetServerCommandTag()); + command.AppendLiteral(" authenticate CRAM-MD5" CRLF); + rv = SendData(command.get()); + NS_ENSURE_SUCCESS(rv, rv); + ParseIMAPandCheckForNewMail(); + if (GetServerStateParser().LastCommandSuccessful()) { + char* digest = nullptr; + char* cramDigest = GetServerStateParser().fAuthChallenge; + char* decodedChallenge = + PL_Base64Decode(cramDigest, strlen(cramDigest), nullptr); + rv = m_imapServerSink->CramMD5Hash(decodedChallenge, password.get(), + &digest); + PR_Free(decodedChallenge); + NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_TRUE(digest, NS_ERROR_NULL_POINTER); + // The encoded digest is the hexadecimal representation of + // DIGEST_LENGTH characters, so it will be twice that length. + nsAutoCStringN<2 * DIGEST_LENGTH> encodedDigest; + + for (uint32_t j = 0; j < DIGEST_LENGTH; j++) { + char hexVal[3]; + PR_snprintf(hexVal, 3, "%.2x", 0x0ff & (unsigned short)(digest[j])); + encodedDigest.Append(hexVal); + } + + PR_snprintf(m_dataOutputBuf, OUTPUT_BUFFER_SIZE, "%.255s %s", userName, + encodedDigest.get()); + char* base64Str = + PL_Base64Encode(m_dataOutputBuf, strlen(m_dataOutputBuf), nullptr); + PR_snprintf(m_dataOutputBuf, OUTPUT_BUFFER_SIZE, "%s" CRLF, base64Str); + PR_Free(base64Str); + PR_Free(digest); + rv = SendData(m_dataOutputBuf); + NS_ENSURE_SUCCESS(rv, rv); + ParseIMAPandCheckForNewMail(command.get()); + } + } // if CRAM response was received + else if (flag & kHasAuthGssApiCapability) { + MOZ_LOG(IMAP, LogLevel::Debug, ("MD5 auth")); + + // Only try GSSAPI once - if it fails, its going to be because we don't + // have valid credentials + // MarkAuthMethodAsFailed(kHasAuthGssApiCapability); + + // We do step1 first, so we don't try GSSAPI against a server which + // we can't get credentials for. + nsAutoCString response; + + nsAutoCString service("imap@"); + service.Append(m_hostName); + rv = DoGSSAPIStep1(service, userName, response); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString command(GetServerCommandTag()); + command.AppendLiteral(" authenticate GSSAPI" CRLF); + rv = SendData(command.get()); + NS_ENSURE_SUCCESS(rv, rv); + + ParseIMAPandCheckForNewMail("AUTH GSSAPI"); + if (GetServerStateParser().LastCommandSuccessful()) { + response += CRLF; + rv = SendData(response.get()); + NS_ENSURE_SUCCESS(rv, rv); + ParseIMAPandCheckForNewMail(command.get()); + nsresult gssrv = NS_OK; + + while (GetServerStateParser().LastCommandSuccessful() && + NS_SUCCEEDED(gssrv) && gssrv != NS_SUCCESS_AUTH_FINISHED) { + nsCString challengeStr(GetServerStateParser().fAuthChallenge); + gssrv = DoGSSAPIStep2(challengeStr, response); + if (NS_SUCCEEDED(gssrv)) { + response += CRLF; + rv = SendData(response.get()); + } else + rv = SendData("*" CRLF); + + NS_ENSURE_SUCCESS(rv, rv); + ParseIMAPandCheckForNewMail(command.get()); + } + // TODO: whether it worked or not is shown by LastCommandSuccessful(), not + // gssrv, right? + } + } else if (flag & (kHasAuthNTLMCapability | kHasAuthMSNCapability)) { + MOZ_LOG(IMAP, LogLevel::Debug, ("NTLM auth")); + nsAutoCString command(GetServerCommandTag()); + command.Append((flag & kHasAuthNTLMCapability) ? " authenticate NTLM" CRLF + : " authenticate MSN" CRLF); + rv = SendData(command.get()); + ParseIMAPandCheckForNewMail( + "AUTH NTLM"); // this just waits for ntlm step 1 + if (GetServerStateParser().LastCommandSuccessful()) { + nsAutoCString cmd; + rv = DoNtlmStep1(nsDependentCString(userName), aPassword, cmd); + NS_ENSURE_SUCCESS(rv, rv); + cmd += CRLF; + rv = SendData(cmd.get()); + NS_ENSURE_SUCCESS(rv, rv); + ParseIMAPandCheckForNewMail(command.get()); + if (GetServerStateParser().LastCommandSuccessful()) { + nsCString challengeStr(GetServerStateParser().fAuthChallenge); + nsCString response; + rv = DoNtlmStep2(challengeStr, response); + NS_ENSURE_SUCCESS(rv, rv); + response += CRLF; + rv = SendData(response.get()); + ParseIMAPandCheckForNewMail(command.get()); + } + } + } else if (flag & kHasAuthPlainCapability) { + MOZ_LOG(IMAP, LogLevel::Debug, ("PLAIN auth")); + PR_snprintf(m_dataOutputBuf, OUTPUT_BUFFER_SIZE, + "%s authenticate PLAIN" CRLF, GetServerCommandTag()); + rv = SendData(m_dataOutputBuf); + NS_ENSURE_SUCCESS(rv, rv); + currentCommand = PL_strdup( + m_dataOutputBuf); /* StrAllocCopy(currentCommand, GetOutputBuffer()); */ + ParseIMAPandCheckForNewMail(); + if (GetServerStateParser().LastCommandSuccessful()) { + // RFC 4616 + char plain_string[513]; + memset(plain_string, 0, 513); + PR_snprintf(&plain_string[1], 256, "%.255s", userName); + uint32_t len = std::min<uint32_t>(PL_strlen(userName), 255u) + + 2; // We include two <NUL> characters. + PR_snprintf(&plain_string[len], 256, "%.255s", password.get()); + len += std::min<uint32_t>(password.Length(), 255u); + char* base64Str = PL_Base64Encode(plain_string, len, nullptr); + PR_snprintf(m_dataOutputBuf, OUTPUT_BUFFER_SIZE, "%s" CRLF, base64Str); + PR_Free(base64Str); + + rv = SendDataParseIMAPandCheckForNewMail(m_dataOutputBuf, currentCommand); + } // if the last command succeeded + } // if auth plain capability + else if (flag & kHasAuthLoginCapability) { + MOZ_LOG(IMAP, LogLevel::Debug, ("LOGIN auth")); + PR_snprintf(m_dataOutputBuf, OUTPUT_BUFFER_SIZE, + "%s authenticate LOGIN" CRLF, GetServerCommandTag()); + rv = SendData(m_dataOutputBuf); + NS_ENSURE_SUCCESS(rv, rv); + currentCommand = PL_strdup(m_dataOutputBuf); + ParseIMAPandCheckForNewMail(); + + if (GetServerStateParser().LastCommandSuccessful()) { + char* base64Str = PL_Base64Encode( + userName, std::min<uint32_t>(PL_strlen(userName), 255u), nullptr); + PR_snprintf(m_dataOutputBuf, OUTPUT_BUFFER_SIZE, "%s" CRLF, base64Str); + PR_Free(base64Str); + rv = SendData(m_dataOutputBuf, true /* suppress logging */); + if (NS_SUCCEEDED(rv)) { + ParseIMAPandCheckForNewMail(currentCommand); + if (GetServerStateParser().LastCommandSuccessful()) { + base64Str = PL_Base64Encode( + password.get(), std::min<uint32_t>(password.Length(), 255u), + nullptr); + PR_snprintf(m_dataOutputBuf, OUTPUT_BUFFER_SIZE, "%s" CRLF, + base64Str); + PR_Free(base64Str); + rv = SendData(m_dataOutputBuf, true /* suppress logging */); + if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail(currentCommand); + } // if last command successful + } // if last command successful + } // if last command successful + } // if has auth login capability + else if (flag & kHasAuthOldLoginCapability) { + MOZ_LOG(IMAP, LogLevel::Debug, ("old-style auth")); + ProgressEventFunctionUsingName("imapStatusSendingLogin"); + IncrementCommandTagNumber(); + nsCString command(GetServerCommandTag()); + nsAutoCString escapedUserName; + command.AppendLiteral(" login \""); + EscapeUserNamePasswordString(userName, &escapedUserName); + command.Append(escapedUserName); + command.AppendLiteral("\" \""); + + // if the password contains a \, login will fail + // turn foo\bar into foo\\bar + nsAutoCString correctedPassword; + // We're assuming old style login doesn't want UTF-8 + EscapeUserNamePasswordString(NS_LossyConvertUTF16toASCII(aPassword).get(), + &correctedPassword); + command.Append(correctedPassword); + command.AppendLiteral("\"" CRLF); + rv = SendData(command.get(), true /* suppress logging */); + NS_ENSURE_SUCCESS(rv, rv); + ParseIMAPandCheckForNewMail(); + } else if (flag & kHasXOAuth2Capability) { + MOZ_LOG(IMAP, LogLevel::Debug, ("XOAUTH2 auth")); + + // Get the XOAuth2 base64 string. + NS_ASSERTION(mOAuth2Support, + "What are we doing here without OAuth2 helper?"); + if (!mOAuth2Support) return NS_ERROR_UNEXPECTED; + nsAutoCString base64Str; + mOAuth2Support->GetXOAuth2String(base64Str); + mOAuth2Support = nullptr; // Its purpose has been served. + if (base64Str.IsEmpty()) { + MOZ_LOG(IMAP, LogLevel::Debug, ("OAuth2 failed")); + return NS_ERROR_FAILURE; + } + + // Send the data on the network. + nsAutoCString command(GetServerCommandTag()); + command += " AUTHENTICATE XOAUTH2 "; + command += base64Str; + command += CRLF; + + rv = SendDataParseIMAPandCheckForNewMail(command.get(), nullptr); + } else if (flag & kHasAuthNoneCapability) { + // TODO What to do? "login <username>" like POP? + return NS_ERROR_NOT_IMPLEMENTED; + } else { + MOZ_LOG(IMAP, LogLevel::Error, ("flags param has no auth scheme selected")); + return NS_ERROR_ILLEGAL_VALUE; + } + + PR_Free(currentCommand); + NS_ENSURE_SUCCESS(rv, rv); + return GetServerStateParser().LastCommandSuccessful() ? NS_OK + : NS_ERROR_FAILURE; +} + +void nsImapProtocol::OnLSubFolders() { + // **** use to find out whether Drafts, Sent, & Templates folder + // exists or not even the user didn't subscribe to it + char* mailboxName = OnCreateServerSourceFolderPathString(); + if (mailboxName) { + ProgressEventFunctionUsingName("imapStatusLookingForMailbox"); + IncrementCommandTagNumber(); + PR_snprintf(m_dataOutputBuf, OUTPUT_BUFFER_SIZE, "%s list \"\" \"%s\"" CRLF, + GetServerCommandTag(), mailboxName); + nsresult rv = SendData(m_dataOutputBuf); + if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail(); + PR_Free(mailboxName); + } else { + HandleMemoryFailure(); + } +} + +void nsImapProtocol::OnAppendMsgFromFile() { + nsCOMPtr<nsIFile> file; + nsresult rv = NS_OK; + rv = m_runningUrl->GetMsgFile(getter_AddRefs(file)); + if (NS_SUCCEEDED(rv) && file) { + char* mailboxName = OnCreateServerSourceFolderPathString(); + if (mailboxName) { + imapMessageFlagsType flagsToSet = 0; + uint32_t msgFlags = 0; + PRTime date = 0; + nsCString keywords; + if (m_imapMessageSink) + m_imapMessageSink->GetCurMoveCopyMessageInfo(m_runningUrl, &date, + keywords, &msgFlags); + + if (msgFlags & nsMsgMessageFlags::Read) flagsToSet |= kImapMsgSeenFlag; + if (msgFlags & nsMsgMessageFlags::MDNReportSent) + flagsToSet |= kImapMsgMDNSentFlag; + // convert msg flag label (0xE000000) to imap flag label (0x0E00) + if (msgFlags & nsMsgMessageFlags::Labels) + flagsToSet |= (msgFlags & nsMsgMessageFlags::Labels) >> 16; + if (msgFlags & nsMsgMessageFlags::Marked) + flagsToSet |= kImapMsgFlaggedFlag; + if (msgFlags & nsMsgMessageFlags::Replied) + flagsToSet |= kImapMsgAnsweredFlag; + if (msgFlags & nsMsgMessageFlags::Forwarded) + flagsToSet |= kImapMsgForwardedFlag; + + // If the message copied was a draft, flag it as such + nsImapAction imapAction; + rv = m_runningUrl->GetImapAction(&imapAction); + if (NS_SUCCEEDED(rv) && + (imapAction == nsIImapUrl::nsImapAppendDraftFromFile)) + flagsToSet |= kImapMsgDraftFlag; + UploadMessageFromFile(file, mailboxName, date, flagsToSet, keywords); + PR_Free(mailboxName); + } else { + HandleMemoryFailure(); + } + } +} + +void nsImapProtocol::UploadMessageFromFile(nsIFile* file, + const char* mailboxName, PRTime date, + imapMessageFlagsType flags, + nsCString& keywords) { + if (!file || !mailboxName) return; + IncrementCommandTagNumber(); + + int64_t fileSize = 0; + int64_t totalSize; + uint32_t readCount; + char* dataBuffer = nullptr; + nsCString command(GetServerCommandTag()); + nsCString escapedName; + CreateEscapedMailboxName(mailboxName, escapedName); + nsresult rv; + bool urlOk = false; + nsCString flagString; + + nsCOMPtr<nsIInputStream> fileInputStream; + + if (!escapedName.IsEmpty()) { + command.AppendLiteral(" append \""); + command.Append(escapedName); + command.Append('"'); + if (flags || keywords.Length()) { + command.AppendLiteral(" ("); + + if (flags) { + SetupMessageFlagsString(flagString, flags, + GetServerStateParser().SupportsUserFlags()); + command.Append(flagString); + } + if (keywords.Length()) { + if (flags) command.Append(' '); + command.Append(keywords); + } + command.Append(')'); + } + + // date should never be 0, but just in case... + if (date) { + /* Use PR_FormatTimeUSEnglish() to format the date in US English format, + then figure out what our local GMT offset is, and append it (since + PR_FormatTimeUSEnglish() can't do that.) Generate four digit years as + per RFC 1123 (superseding RFC 822.) + */ + char szDateTime[64]; + char dateStr[100]; + PRExplodedTime exploded; + PR_ExplodeTime(date, PR_LocalTimeParameters, &exploded); + PR_FormatTimeUSEnglish(szDateTime, sizeof(szDateTime), + "%d-%b-%Y %H:%M:%S", &exploded); + PRExplodedTime now; + PR_ExplodeTime(PR_Now(), PR_LocalTimeParameters, &now); + int gmtoffset = + (now.tm_params.tp_gmt_offset + now.tm_params.tp_dst_offset) / 60; + PR_snprintf(dateStr, sizeof(dateStr), " \"%s %c%02d%02d\"", szDateTime, + (gmtoffset >= 0 ? '+' : '-'), + ((gmtoffset >= 0 ? gmtoffset : -gmtoffset) / 60), + ((gmtoffset >= 0 ? gmtoffset : -gmtoffset) % 60)); + + command.Append(dateStr); + } + if (m_allowUTF8Accept) + command.AppendLiteral(" UTF8 (~{"); + else + command.AppendLiteral(" {"); + + dataBuffer = (char*)PR_CALLOC(COPY_BUFFER_SIZE + 1); + if (!dataBuffer) goto done; + rv = file->GetFileSize(&fileSize); + NS_ASSERTION(fileSize, "got empty file in UploadMessageFromFile"); + if (NS_FAILED(rv) || !fileSize) goto done; + rv = NS_NewLocalFileInputStream(getter_AddRefs(fileInputStream), file); + if (NS_FAILED(rv) || !fileInputStream) goto done; + command.AppendInt((int32_t)fileSize); + + // Set useLiteralPlus to true if server has capability LITERAL+ and + // LITERAL+ usage is enabled in the config editor, + // i.e., "mail.imap.use_literal_plus" = true. + bool useLiteralPlus = + (GetServerStateParser().GetCapabilityFlag() & kLiteralPlusCapability) && + gUseLiteralPlus; + if (useLiteralPlus) + command.AppendLiteral("+}" CRLF); + else + command.AppendLiteral("}" CRLF); + + rv = SendData(command.get()); + if (NS_FAILED(rv)) goto done; + + if (!useLiteralPlus) { + ParseIMAPandCheckForNewMail(); + if (!GetServerStateParser().LastCommandSuccessful()) goto done; + } + + totalSize = fileSize; + readCount = 0; + while (NS_SUCCEEDED(rv) && totalSize > 0) { + if (DeathSignalReceived()) goto done; + rv = fileInputStream->Read(dataBuffer, COPY_BUFFER_SIZE, &readCount); + if (NS_SUCCEEDED(rv) && !readCount) rv = NS_ERROR_FAILURE; + + if (NS_SUCCEEDED(rv)) { + NS_ASSERTION(readCount <= (uint32_t)totalSize, + "got more bytes than there should be"); + dataBuffer[readCount] = 0; + rv = SendData(dataBuffer); + totalSize -= readCount; + PercentProgressUpdateEvent(""_ns, u""_ns, fileSize - totalSize, + fileSize); + if (!totalSize) { + // The full message has been queued for sending, but the actual send + // is just now starting and can still potentially fail. From this + // point the progress cannot be determined, so just set the progress + // to "indeterminate" so that the user does not see an incorrect 100% + // complete on the progress bar while waiting for the retry dialog to + // appear if the send should fail. + m_lastProgressTime = 0; // Force progress bar update + m_lastPercent = -1; // Force progress bar update + PercentProgressUpdateEvent(""_ns, u""_ns, 0, -1); // Indeterminate + } + } + } // end while appending chunks + + if (NS_SUCCEEDED(rv)) { // complete the append + if (m_allowUTF8Accept) + rv = SendData(")" CRLF); + else + rv = SendData(CRLF); + if (NS_FAILED(rv)) goto done; + + ParseIMAPandCheckForNewMail(command.get()); + if (!GetServerStateParser().LastCommandSuccessful()) goto done; + + // If reached, the append completed without error. No more goto's! + // May still find problems in imap responses below so urlOk may still + // become false. + urlOk = true; + + nsImapAction imapAction; + m_runningUrl->GetImapAction(&imapAction); + + if (imapAction == nsIImapUrl::nsImapAppendDraftFromFile || + imapAction == nsIImapUrl::nsImapAppendMsgFromFile) { + if (GetServerStateParser().GetCapabilityFlag() & kUidplusCapability) { + nsMsgKey newKey = GetServerStateParser().CurrentResponseUID(); + if (m_imapMailFolderSink) + m_imapMailFolderSink->SetAppendMsgUid(newKey, m_runningUrl); + + // Courier imap server seems to have problems with recently + // appended messages. Noop seems to clear its confusion. + if (FolderIsSelected(mailboxName)) Noop(); + + nsCString oldMsgId; + rv = m_runningUrl->GetListOfMessageIds(oldMsgId); + if (NS_SUCCEEDED(rv) && !oldMsgId.IsEmpty()) { + bool idsAreUids = true; + m_runningUrl->MessageIdsAreUids(&idsAreUids); + Store(oldMsgId, "+FLAGS (\\Deleted)", idsAreUids); + UidExpunge(oldMsgId); + } + // Only checks the last imap command in sequence above. + if (!GetServerStateParser().LastCommandSuccessful()) urlOk = false; + } + // for non UIDPLUS servers this code used to check for + // imapAction==nsIImapUrl::nsImapAppendMsgFromFile, which meant we'd get + // into this code whenever sending a message, as well as when copying + // messages to an imap folder from local folders or an other imap + // server. This made sending a message slow when there was a large sent + // folder. I don't believe this code worked anyway. + // *** code me to search for the newly appended message + else if (m_imapMailFolderSink && + imapAction == nsIImapUrl::nsImapAppendDraftFromFile) { + // go to selected state + nsCString messageId; + rv = m_imapMailFolderSink->GetMessageId(m_runningUrl, messageId); + if (NS_SUCCEEDED(rv) && !messageId.IsEmpty()) { + // if the appended to folder isn't selected in the connection, + // select it. + if (!FolderIsSelected(mailboxName)) + SelectMailbox(mailboxName); + else + Noop(); // See if this makes SEARCH work on the newly appended + // msg. + + if (GetServerStateParser().LastCommandSuccessful()) { + command = "SEARCH UNDELETED HEADER Message-ID "; + command.Append(messageId); + + // Clean up result sequence before issuing the cmd. + GetServerStateParser().ResetSearchResultSequence(); + + Search(command.get(), true, false); + if (GetServerStateParser().LastCommandSuccessful()) { + nsMsgKey newkey = nsMsgKey_None; + nsImapSearchResultIterator* searchResult = + GetServerStateParser().CreateSearchResultIterator(); + newkey = searchResult->GetNextMessageNumber(); + delete searchResult; + if (newkey != nsMsgKey_None) + m_imapMailFolderSink->SetAppendMsgUid(newkey, m_runningUrl); + } else + urlOk = false; + } else + urlOk = false; + } + } + } + } + } +done: + // If imap command fails or network goes down, make sure URL sees the failure. + if (!urlOk) GetServerStateParser().SetCommandFailed(true); + + PR_Free(dataBuffer); + if (fileInputStream) fileInputStream->Close(); +} + +// caller must free using PR_Free +char* nsImapProtocol::OnCreateServerSourceFolderPathString() { + char* sourceMailbox = nullptr; + char hierarchyDelimiter = 0; + char onlineDelimiter = 0; + m_runningUrl->GetOnlineSubDirSeparator(&hierarchyDelimiter); + if (m_imapMailFolderSink) + m_imapMailFolderSink->GetOnlineDelimiter(&onlineDelimiter); + + if (onlineDelimiter != kOnlineHierarchySeparatorUnknown && + onlineDelimiter != hierarchyDelimiter) + m_runningUrl->SetOnlineSubDirSeparator(onlineDelimiter); + + m_runningUrl->CreateServerSourceFolderPathString(&sourceMailbox); + + return sourceMailbox; +} + +// caller must free using PR_Free, safe to call from ui thread +char* nsImapProtocol::GetFolderPathString() { + char* sourceMailbox = nullptr; + char onlineSubDirDelimiter = 0; + char hierarchyDelimiter = 0; + nsCOMPtr<nsIMsgFolder> msgFolder; + + m_runningUrl->GetOnlineSubDirSeparator(&onlineSubDirDelimiter); + nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_runningUrl); + mailnewsUrl->GetFolder(getter_AddRefs(msgFolder)); + if (msgFolder) { + nsCOMPtr<nsIMsgImapMailFolder> imapFolder = do_QueryInterface(msgFolder); + if (imapFolder) { + imapFolder->GetHierarchyDelimiter(&hierarchyDelimiter); + if (hierarchyDelimiter != kOnlineHierarchySeparatorUnknown && + onlineSubDirDelimiter != hierarchyDelimiter) + m_runningUrl->SetOnlineSubDirSeparator(hierarchyDelimiter); + } + } + m_runningUrl->CreateServerSourceFolderPathString(&sourceMailbox); + + return sourceMailbox; +} + +nsresult nsImapProtocol::CreateServerSourceFolderPathString(char** result) { + NS_ENSURE_ARG(result); + *result = OnCreateServerSourceFolderPathString(); + return (*result) ? NS_OK : NS_ERROR_OUT_OF_MEMORY; +} + +// caller must free using PR_Free +char* nsImapProtocol::OnCreateServerDestinationFolderPathString() { + char* destinationMailbox = nullptr; + char hierarchyDelimiter = 0; + char onlineDelimiter = 0; + m_runningUrl->GetOnlineSubDirSeparator(&hierarchyDelimiter); + if (m_imapMailFolderSink) + m_imapMailFolderSink->GetOnlineDelimiter(&onlineDelimiter); + if (onlineDelimiter != kOnlineHierarchySeparatorUnknown && + onlineDelimiter != hierarchyDelimiter) + m_runningUrl->SetOnlineSubDirSeparator(onlineDelimiter); + + m_runningUrl->CreateServerDestinationFolderPathString(&destinationMailbox); + + return destinationMailbox; +} + +void nsImapProtocol::OnCreateFolder(const char* aSourceMailbox) { + bool created = CreateMailboxRespectingSubscriptions(aSourceMailbox); + if (created) { + m_hierarchyNameState = kListingForCreate; + nsCString mailboxWODelim(aSourceMailbox); + RemoveHierarchyDelimiter(mailboxWODelim); + List(mailboxWODelim.get(), false); + m_hierarchyNameState = kNoOperationInProgress; + } else + FolderNotCreated(aSourceMailbox); +} + +void nsImapProtocol::OnEnsureExistsFolder(const char* aSourceMailbox) { + // We need to handle the following edge case where the destination server + // wasn't authenticated when the folder name was encoded in + // `EnsureFolderExists()'. In this case we always get MUTF-7. Here we are + // authenticated and can rely on `m_allowUTF8Accept'. If the folder appears + // to be MUTF-7 and we need UTF-8, we re-encode it. If it's not ASCII, it + // must be already correct in UTF-8. And if it was ASCII to start with, it + // doesn't matter that we MUTF-7 decode and UTF-8 re-encode. + + // `aSourceMailbox' is a path with hierarchy delimiters possibly. To determine + // if the edge case is in effect, we only want to check the leaf node for + // ASCII and this is only necessary when `m_allowUTF8Accept' is true. + + // `fullPath' is modified below if leaf re-encoding is necessary and it must + // be defined here at top level so it stays in scope. + nsAutoCString fullPath(aSourceMailbox); + + if (m_allowUTF8Accept) { + char onlineDirSeparator = kOnlineHierarchySeparatorUnknown; + m_runningUrl->GetOnlineSubDirSeparator(&onlineDirSeparator); + + int32_t leafStart = fullPath.RFindChar(onlineDirSeparator); + nsAutoCString leafName; + if (leafStart == kNotFound) { + // This is a root level mailbox + leafName = fullPath; + fullPath.SetLength(0); + } else { + leafName = Substring(fullPath, leafStart + 1); + fullPath.SetLength(leafStart + 1); + } + + if (NS_IsAscii(leafName.get())) { + MOZ_LOG(IMAP, LogLevel::Debug, + ("re-encode leaf of mailbox %s to UTF-8", aSourceMailbox)); + nsAutoString utf16LeafName; + CopyMUTF7toUTF16(leafName, utf16LeafName); + + // Convert UTF-16 to UTF-8 to create the folder. + nsAutoCString utf8LeafName; + CopyUTF16toUTF8(utf16LeafName, utf8LeafName); + fullPath.Append(utf8LeafName); + aSourceMailbox = fullPath.get(); + MOZ_LOG(IMAP, LogLevel::Debug, + ("re-encoded leaf of mailbox %s to UTF-8", aSourceMailbox)); + } + } + List(aSourceMailbox, false); // how to tell if that succeeded? + // If List() produces OK tagged response and an untagged "* LIST" response + // then the folder exists on the server. For simplicity, just look for + // a general untagged response. + bool folderExists = false; + if (GetServerStateParser().LastCommandSuccessful()) + folderExists = GetServerStateParser().UntaggedResponse(); + + // try converting aSourceMailbox to canonical format + nsImapNamespace* nsForMailbox = nullptr; + m_hostSessionList->GetNamespaceForMailboxForHost( + GetImapServerKey(), aSourceMailbox, nsForMailbox); + // NS_ASSERTION (nsForMailbox, "Oops .. null nsForMailbox"); + + nsCString name; + + if (nsForMailbox) + m_runningUrl->AllocateCanonicalPath( + aSourceMailbox, nsForMailbox->GetDelimiter(), getter_Copies(name)); + else + m_runningUrl->AllocateCanonicalPath( + aSourceMailbox, kOnlineHierarchySeparatorUnknown, getter_Copies(name)); + + // Also check that the folder has been verified to exist in server sink. + bool verifiedExists = false; + if (folderExists && m_imapServerSink) + m_imapServerSink->FolderVerifiedOnline(name, &verifiedExists); + + // If folder exists on server and is verified and known to exists in server + // sink, just subscribe the folder. Otherwise, create a new folder and + // then subscribe and do list again to make sure it's created. + if (folderExists && verifiedExists) { + Subscribe(aSourceMailbox); + } else { + bool created = CreateMailboxRespectingSubscriptions(aSourceMailbox); + if (created) { + List(aSourceMailbox, false); + // Check that we see an untagged response indicating folder now exists. + folderExists = GetServerStateParser().UntaggedResponse(); + } + } + if (!GetServerStateParser().LastCommandSuccessful() || !folderExists) + FolderNotCreated(aSourceMailbox); +} + +void nsImapProtocol::OnSubscribe(const char* sourceMailbox) { + Subscribe(sourceMailbox); +} + +void nsImapProtocol::OnUnsubscribe(const char* sourceMailbox) { + // When we try to auto-unsubscribe from \Noselect folders, + // some servers report errors if we were already unsubscribed + // from them. + bool lastReportingErrors = GetServerStateParser().GetReportingErrors(); + GetServerStateParser().SetReportingErrors(false); + Unsubscribe(sourceMailbox); + GetServerStateParser().SetReportingErrors(lastReportingErrors); +} + +void nsImapProtocol::RefreshACLForFolderIfNecessary(const char* mailboxName) { + if (GetServerStateParser().ServerHasACLCapability()) { + if (!m_folderNeedsACLRefreshed && m_imapMailFolderSink) + m_imapMailFolderSink->GetFolderNeedsACLListed(&m_folderNeedsACLRefreshed); + if (m_folderNeedsACLRefreshed) { + RefreshACLForFolder(mailboxName); + m_folderNeedsACLRefreshed = false; + } + } +} + +void nsImapProtocol::RefreshACLForFolder(const char* mailboxName) { + nsImapNamespace* ns = nullptr; + m_hostSessionList->GetNamespaceForMailboxForHost(GetImapServerKey(), + mailboxName, ns); + if (ns) { + switch (ns->GetType()) { + case kPersonalNamespace: + // It's a personal folder, most likely. + // I find it hard to imagine a server that supports ACL that doesn't + // support NAMESPACE, so most likely we KNOW that this is a personal, + // rather than the default, namespace. + + // First, clear what we have. + ClearAllFolderRights(); + // Now, get the new one. + GetMyRightsForFolder(mailboxName); + if (m_imapMailFolderSink) { + uint32_t aclFlags = 0; + if (NS_SUCCEEDED(m_imapMailFolderSink->GetAclFlags(&aclFlags)) && + aclFlags & IMAP_ACL_ADMINISTER_FLAG) + GetACLForFolder(mailboxName); + } + + // We're all done, refresh the icon/flags for this folder + RefreshFolderACLView(mailboxName, ns); + break; + default: + // We know it's a public folder or other user's folder. + // We only want our own rights + + // First, clear what we have + ClearAllFolderRights(); + // Now, get the new one. + GetMyRightsForFolder(mailboxName); + // We're all done, refresh the icon/flags for this folder + RefreshFolderACLView(mailboxName, ns); + break; + } + } else { + // no namespace, not even default... can this happen? + NS_ASSERTION(false, "couldn't get namespace"); + } +} + +void nsImapProtocol::RefreshFolderACLView(const char* mailboxName, + nsImapNamespace* nsForMailbox) { + nsCString canonicalMailboxName; + + if (nsForMailbox) + m_runningUrl->AllocateCanonicalPath(mailboxName, + nsForMailbox->GetDelimiter(), + getter_Copies(canonicalMailboxName)); + else + m_runningUrl->AllocateCanonicalPath(mailboxName, + kOnlineHierarchySeparatorUnknown, + getter_Copies(canonicalMailboxName)); + + if (m_imapServerSink) + m_imapServerSink->RefreshFolderRights(canonicalMailboxName); +} + +void nsImapProtocol::GetACLForFolder(const char* mailboxName) { + IncrementCommandTagNumber(); + + nsCString command(GetServerCommandTag()); + nsCString escapedName; + CreateEscapedMailboxName(mailboxName, escapedName); + command.AppendLiteral(" getacl \""); + command.Append(escapedName); + command.AppendLiteral("\"" CRLF); + + nsresult rv = SendData(command.get()); + if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail(); +} + +void nsImapProtocol::OnRefreshAllACLs() { + m_hierarchyNameState = kListingForInfoOnly; + nsIMAPMailboxInfo* mb = NULL; + + // This will fill in the list + List("*", true); + + int32_t total = m_listedMailboxList.Length(), count = 0; + GetServerStateParser().SetReportingErrors(false); + for (int32_t i = 0; i < total; i++) { + mb = m_listedMailboxList.ElementAt(i); + if (mb) // paranoia + { + char* onlineName = nullptr; + m_runningUrl->AllocateServerPath( + PromiseFlatCString(mb->GetMailboxName()).get(), mb->GetDelimiter(), + &onlineName); + if (onlineName) { + RefreshACLForFolder(onlineName); + free(onlineName); + } + PercentProgressUpdateEvent(""_ns, u""_ns, count, total); + delete mb; + count++; + } + } + m_listedMailboxList.Clear(); + + PercentProgressUpdateEvent(""_ns, u""_ns, 100, 100); + GetServerStateParser().SetReportingErrors(true); + m_hierarchyNameState = kNoOperationInProgress; +} + +// any state commands +void nsImapProtocol::Logout(bool shuttingDown /* = false */, + bool waitForResponse /* = true */) { + if (!shuttingDown) ProgressEventFunctionUsingName("imapStatusLoggingOut"); + + /****************************************************************** + * due to the undo functionality we cannot issue ImapClose when logout; there + * is no way to do an undo if the message has been permanently expunge + * jt - 07/12/1999 + + bool closeNeeded = GetServerStateParser().GetIMAPstate() == + nsImapServerResponseParser::kFolderSelected; + + if (closeNeeded && GetDeleteIsMoveToTrash()) + ImapClose(); + ********************/ + + IncrementCommandTagNumber(); + + nsCString command(GetServerCommandTag()); + + command.AppendLiteral(" logout" CRLF); + + nsresult rv = SendData(command.get()); + if (m_transport && shuttingDown) + m_transport->SetTimeout(nsISocketTransport::TIMEOUT_READ_WRITE, 5); + // the socket may be dead before we read the response, so drop it. + if (NS_SUCCEEDED(rv) && waitForResponse) ParseIMAPandCheckForNewMail(); +} + +void nsImapProtocol::Noop() { + // ProgressUpdateEvent("noop..."); + IncrementCommandTagNumber(); + nsCString command(GetServerCommandTag()); + + command.AppendLiteral(" noop" CRLF); + + nsresult rv = SendData(command.get()); + if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail(); +} + +void nsImapProtocol::XServerInfo() { + ProgressEventFunctionUsingName("imapGettingServerInfo"); + IncrementCommandTagNumber(); + nsCString command(GetServerCommandTag()); + + command.AppendLiteral( + " XSERVERINFO MANAGEACCOUNTURL MANAGELISTSURL MANAGEFILTERSURL" CRLF); + + nsresult rv = SendData(command.get()); + if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail(); +} + +void nsImapProtocol::Netscape() { + ProgressEventFunctionUsingName("imapGettingServerInfo"); + IncrementCommandTagNumber(); + + nsCString command(GetServerCommandTag()); + + command.AppendLiteral(" netscape" CRLF); + + nsresult rv = SendData(command.get()); + if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail(); +} + +void nsImapProtocol::XMailboxInfo(const char* mailboxName) { + ProgressEventFunctionUsingName("imapGettingMailboxInfo"); + IncrementCommandTagNumber(); + nsCString command(GetServerCommandTag()); + + command.AppendLiteral(" XMAILBOXINFO \""); + command.Append(mailboxName); + command.AppendLiteral("\" MANAGEURL POSTURL" CRLF); + + nsresult rv = SendData(command.get()); + if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail(); +} + +void nsImapProtocol::Namespace() { + IncrementCommandTagNumber(); + + nsCString command(GetServerCommandTag()); + command.AppendLiteral(" namespace" CRLF); + + nsresult rv = SendData(command.get()); + if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail(); +} + +void nsImapProtocol::MailboxData() { + IncrementCommandTagNumber(); + + nsCString command(GetServerCommandTag()); + command.AppendLiteral(" mailboxdata" CRLF); + + nsresult rv = SendData(command.get()); + if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail(); +} + +void nsImapProtocol::GetMyRightsForFolder(const char* mailboxName) { + IncrementCommandTagNumber(); + + nsCString command(GetServerCommandTag()); + nsCString escapedName; + CreateEscapedMailboxName(mailboxName, escapedName); + + if (MailboxIsNoSelectMailbox(escapedName.get())) + return; // Don't issue myrights on Noselect folder + + command.AppendLiteral(" myrights \""); + command.Append(escapedName); + command.AppendLiteral("\"" CRLF); + + nsresult rv = SendData(command.get()); + if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail(); +} + +bool nsImapProtocol::FolderIsSelected(const char* mailboxName) { + return (GetServerStateParser().GetIMAPstate() == + nsImapServerResponseParser::kFolderSelected && + GetServerStateParser().GetSelectedMailboxName() && + PL_strcmp(GetServerStateParser().GetSelectedMailboxName(), + mailboxName) == 0); +} + +void nsImapProtocol::OnStatusForFolder(const char* mailboxName) { + bool untaggedResponse; + // RFC 3501 says: + // "the STATUS command SHOULD NOT be used on the currently selected mailbox", + // so use NOOP instead if mailboxName is the selected folder on this + // connection. + if (FolderIsSelected(mailboxName)) { + Noop(); + // Did untagged responses occur during the NOOP response? If so, this + // indicates new mail or other changes in the mailbox. Handle this like an + // IDLE response which will cause a folder update. + if ((untaggedResponse = GetServerStateParser().UntaggedResponse()) && + m_imapMailFolderSinkSelected) { + Log("OnStatusForFolder", nullptr, + "mailbox change on selected folder during noop"); + m_imapMailFolderSinkSelected->OnNewIdleMessages(); + } + mailboxName = nullptr; // for new_spec below. Obtain SELECTed mailbox data. + } else { + // Imap connection is not in selected state or imap connection is selected + // on a mailbox other than than the mailbox folderstatus URL is requesting + // status for. + untaggedResponse = true; // STATUS always produces an untagged response + IncrementCommandTagNumber(); + + nsAutoCString command(GetServerCommandTag()); + nsCString escapedName; + CreateEscapedMailboxName(mailboxName, escapedName); + + command.AppendLiteral(" STATUS \""); + command.Append(escapedName); + command.AppendLiteral("\" (UIDNEXT MESSAGES UNSEEN RECENT)" CRLF); + + int32_t prevNumMessages = GetServerStateParser().NumberOfMessages(); + nsresult rv = SendData(command.get()); + if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail(); + + // Respond to possible untagged responses EXISTS and RECENT for the SELECTed + // folder. Handle as though this were an IDLE response. Can't check for any + // untagged as for Noop() above since STATUS always produces an untagged + // response for the target mailbox and possibly also for the SELECTed box. + // Of cource, this won't occur if imap connection is not in selected state. + if (GetServerStateParser().GetIMAPstate() == + nsImapServerResponseParser::kFolderSelected && + m_imapMailFolderSinkSelected && + (GetServerStateParser().NumberOfRecentMessages() || + prevNumMessages != GetServerStateParser().NumberOfMessages())) { + Log("OnStatusForFolder", nullptr, + "new mail on selected folder during status"); + m_imapMailFolderSinkSelected->OnNewIdleMessages(); + } + MOZ_ASSERT(m_imapMailFolderSink != m_imapMailFolderSinkSelected); + } + + // Do this to ensure autosync detects changes in server counts and thus + // triggers a full body fetch for when NOOP or STATUS is sent above. + // But if NOOP didn't produce an untagged response, no need to do this. + // Note: For SELECTed noop() above, "folder sink" and "folder sink selected" + // both reference the same folder but are not always equal. So OK to use + // m_imapMailFolderSink below since it is correct for NOOP and STATUS cases. + if (untaggedResponse && GetServerStateParser().LastCommandSuccessful()) { + RefPtr<nsImapMailboxSpec> new_spec = + GetServerStateParser().CreateCurrentMailboxSpec(mailboxName); + if (new_spec && m_imapMailFolderSink) + m_imapMailFolderSink->UpdateImapMailboxStatus(this, new_spec); + } +} + +void nsImapProtocol::OnListFolder(const char* aSourceMailbox, bool aBool) { + List(aSourceMailbox, aBool); +} + +// Returns true if the mailbox is a NoSelect mailbox. +// If we don't know about it, returns false. +bool nsImapProtocol::MailboxIsNoSelectMailbox(const char* mailboxName) { + bool rv = false; + + nsImapNamespace* nsForMailbox = nullptr; + m_hostSessionList->GetNamespaceForMailboxForHost(GetImapServerKey(), + mailboxName, nsForMailbox); + // NS_ASSERTION (nsForMailbox, "Oops .. null nsForMailbox"); + + nsCString name; + + if (nsForMailbox) + m_runningUrl->AllocateCanonicalPath( + mailboxName, nsForMailbox->GetDelimiter(), getter_Copies(name)); + else + m_runningUrl->AllocateCanonicalPath( + mailboxName, kOnlineHierarchySeparatorUnknown, getter_Copies(name)); + + if (name.IsEmpty()) return false; + + NS_ASSERTION(m_imapServerSink, + "unexpected, no imap server sink, see bug #194335"); + if (m_imapServerSink) m_imapServerSink->FolderIsNoSelect(name, &rv); + return rv; +} + +nsresult nsImapProtocol::SetFolderAdminUrl(const char* mailboxName) { + nsresult rv = + NS_ERROR_NULL_POINTER; // if m_imapServerSink is null, rv will be this. + + nsImapNamespace* nsForMailbox = nullptr; + m_hostSessionList->GetNamespaceForMailboxForHost(GetImapServerKey(), + mailboxName, nsForMailbox); + + nsCString name; + + if (nsForMailbox) + m_runningUrl->AllocateCanonicalPath( + mailboxName, nsForMailbox->GetDelimiter(), getter_Copies(name)); + else + m_runningUrl->AllocateCanonicalPath( + mailboxName, kOnlineHierarchySeparatorUnknown, getter_Copies(name)); + + if (m_imapServerSink) + rv = m_imapServerSink->SetFolderAdminURL( + name, nsDependentCString(GetServerStateParser().GetManageFolderUrl())); + return rv; +} +// returns true is the delete succeeded (regardless of subscription changes) +bool nsImapProtocol::DeleteMailboxRespectingSubscriptions( + const char* mailboxName) { + bool rv = true; + if (!MailboxIsNoSelectMailbox(mailboxName)) { + // Only try to delete it if it really exists + DeleteMailbox(mailboxName); + rv = GetServerStateParser().LastCommandSuccessful(); + } + + // We can unsubscribe even if the mailbox doesn't exist. + if (rv && m_autoUnsubscribe) // auto-unsubscribe is on + { + bool reportingErrors = GetServerStateParser().GetReportingErrors(); + GetServerStateParser().SetReportingErrors(false); + Unsubscribe(mailboxName); + GetServerStateParser().SetReportingErrors(reportingErrors); + } + return (rv); +} + +// returns true is the rename succeeded (regardless of subscription changes) +// reallyRename tells us if we should really do the rename (true) or if we +// should just move subscriptions (false) +bool nsImapProtocol::RenameMailboxRespectingSubscriptions( + const char* existingName, const char* newName, bool reallyRename) { + bool rv = true; + if (reallyRename && !MailboxIsNoSelectMailbox(existingName)) { + RenameMailbox(existingName, newName); + rv = GetServerStateParser().LastCommandSuccessful(); + } + + if (rv) { + if (m_autoSubscribe) // if auto-subscribe is on + { + bool reportingErrors = GetServerStateParser().GetReportingErrors(); + GetServerStateParser().SetReportingErrors(false); + Subscribe(newName); + GetServerStateParser().SetReportingErrors(reportingErrors); + } + if (m_autoUnsubscribe) // if auto-unsubscribe is on + { + bool reportingErrors = GetServerStateParser().GetReportingErrors(); + GetServerStateParser().SetReportingErrors(false); + Unsubscribe(existingName); + GetServerStateParser().SetReportingErrors(reportingErrors); + } + } + return (rv); +} + +bool nsImapProtocol::RenameHierarchyByHand(const char* oldParentMailboxName, + const char* newParentMailboxName) { + bool renameSucceeded = true; + char onlineDirSeparator = kOnlineHierarchySeparatorUnknown; + m_deletableChildren = new nsTArray<nsCString>(); + + bool nonHierarchicalRename = + ((GetServerStateParser().GetCapabilityFlag() & kNoHierarchyRename) || + MailboxIsNoSelectMailbox(oldParentMailboxName)); + + m_hierarchyNameState = kDeleteSubFoldersInProgress; + nsImapNamespace* ns = nullptr; + m_hostSessionList->GetNamespaceForMailboxForHost(GetImapServerKey(), + oldParentMailboxName, + ns); // for delimiter + if (!ns) { + if (!PL_strcasecmp(oldParentMailboxName, "INBOX")) + m_hostSessionList->GetDefaultNamespaceOfTypeForHost( + GetImapServerKey(), kPersonalNamespace, ns); + } + if (ns) { + nsCString pattern(oldParentMailboxName); + pattern += ns->GetDelimiter(); + pattern += "*"; + bool isUsingSubscription = false; + m_hostSessionList->GetHostIsUsingSubscription(GetImapServerKey(), + isUsingSubscription); + + if (isUsingSubscription) + Lsub(pattern.get(), false); + else + List(pattern.get(), false); + } + m_hierarchyNameState = kNoOperationInProgress; + + if (GetServerStateParser().LastCommandSuccessful()) + renameSucceeded = // rename this, and move subscriptions + RenameMailboxRespectingSubscriptions(oldParentMailboxName, + newParentMailboxName, true); + + size_t numberToDelete = m_deletableChildren->Length(); + + for (size_t childIndex = 0; (childIndex < numberToDelete) && renameSucceeded; + childIndex++) { + nsCString name = m_deletableChildren->ElementAt(childIndex); + char* serverName = nullptr; + m_runningUrl->AllocateServerPath(name.get(), onlineDirSeparator, + &serverName); + if (!serverName) { + renameSucceeded = false; + break; + } + char* currentName = serverName; + + // calculate the new name and do the rename + nsCString newChildName(newParentMailboxName); + newChildName += (currentName + PL_strlen(oldParentMailboxName)); + // Pass in 'nonHierarchicalRename' to determine if we should really + // rename, or just move subscriptions. + renameSucceeded = RenameMailboxRespectingSubscriptions( + currentName, newChildName.get(), nonHierarchicalRename); + PR_FREEIF(currentName); + } + + delete m_deletableChildren; + m_deletableChildren = nullptr; + + return renameSucceeded; +} + +bool nsImapProtocol::DeleteSubFolders(const char* selectedMailbox, + bool& aDeleteSelf) { + bool deleteSucceeded = true; + m_deletableChildren = new nsTArray<nsCString>; + + bool folderDeleted = false; + + m_hierarchyNameState = kDeleteSubFoldersInProgress; + nsCString pattern(selectedMailbox); + char onlineDirSeparator = kOnlineHierarchySeparatorUnknown; + m_runningUrl->GetOnlineSubDirSeparator(&onlineDirSeparator); + pattern.Append(onlineDirSeparator); + pattern.Append('*'); + + if (!pattern.IsEmpty()) { + List(pattern.get(), false); + } + m_hierarchyNameState = kNoOperationInProgress; + + // this should be a short list so perform a sequential search for the + // longest name mailbox. Deleting the longest first will hopefully + // prevent the server from having problems about deleting parents + // ** jt - why? I don't understand this. + size_t numberToDelete = m_deletableChildren->Length(); + size_t outerIndex, innerIndex; + + // intelligently decide if myself(either plain format or following the + // dir-separator) is in the sub-folder list + bool folderInSubfolderList = false; // For Performance + char* selectedMailboxDir = nullptr; + { + int32_t length = strlen(selectedMailbox); + selectedMailboxDir = (char*)PR_MALLOC(length + 2); + if (selectedMailboxDir) // only do the intelligent test if there is + // enough memory + { + strcpy(selectedMailboxDir, selectedMailbox); + selectedMailboxDir[length] = onlineDirSeparator; + selectedMailboxDir[length + 1] = '\0'; + size_t i; + for (i = 0; i < numberToDelete && !folderInSubfolderList; i++) { + const char* currentName = m_deletableChildren->ElementAt(i).get(); + if (!strcmp(currentName, selectedMailbox) || + !strcmp(currentName, selectedMailboxDir)) + folderInSubfolderList = true; + } + } + } + + deleteSucceeded = GetServerStateParser().LastCommandSuccessful(); + for (outerIndex = 0; (outerIndex < numberToDelete) && deleteSucceeded; + outerIndex++) { + char* longestName = nullptr; + size_t longestIndex = 0; // fix bogus warning by initializing + for (innerIndex = 0; innerIndex < m_deletableChildren->Length(); + innerIndex++) { + const char* currentName = + m_deletableChildren->ElementAt(innerIndex).get(); + if (!longestName || strlen(longestName) < strlen(currentName)) { + longestName = (char*)currentName; + longestIndex = innerIndex; + } + } + if (longestName) { + char* serverName = nullptr; + m_runningUrl->AllocateServerPath(longestName, onlineDirSeparator, + &serverName); + m_deletableChildren->RemoveElementAt(longestIndex); + longestName = serverName; + } + + // some imap servers include the selectedMailbox in the list of + // subfolders of the selectedMailbox. Check for this so we don't + // delete the selectedMailbox (usually the trash and doing an + // empty trash) + // The Cyrus imap server ignores the "INBOX.Trash" constraining + // string passed to the list command. Be defensive and make sure + // we only delete children of the trash + if (longestName && strcmp(selectedMailbox, longestName) && + !strncmp(selectedMailbox, longestName, strlen(selectedMailbox))) { + if (selectedMailboxDir && + !strcmp(selectedMailboxDir, longestName)) // just myself + { + if (aDeleteSelf) { + bool deleted = DeleteMailboxRespectingSubscriptions(longestName); + if (deleted) FolderDeleted(longestName); + folderDeleted = deleted; + deleteSucceeded = deleted; + } + } else { + if (m_imapServerSink) + m_imapServerSink->ResetServerConnection( + nsDependentCString(longestName)); + bool deleted = false; + if (folderInSubfolderList) // for performance + { + nsTArray<nsCString>* pDeletableChildren = m_deletableChildren; + m_deletableChildren = nullptr; + bool folderDeleted = true; + deleted = DeleteSubFolders(longestName, folderDeleted); + // longestName may have subfolder list including itself + if (!folderDeleted) { + if (deleted) + deleted = DeleteMailboxRespectingSubscriptions(longestName); + if (deleted) FolderDeleted(longestName); + } + m_deletableChildren = pDeletableChildren; + } else { + deleted = DeleteMailboxRespectingSubscriptions(longestName); + if (deleted) FolderDeleted(longestName); + } + deleteSucceeded = deleted; + } + } + PR_FREEIF(longestName); + } + + aDeleteSelf = folderDeleted; // feedback if myself is deleted + PR_Free(selectedMailboxDir); + + delete m_deletableChildren; + m_deletableChildren = nullptr; + + return deleteSucceeded; +} + +void nsImapProtocol::FolderDeleted(const char* mailboxName) { + char onlineDelimiter = kOnlineHierarchySeparatorUnknown; + nsCString orphanedMailboxName; + + if (mailboxName) { + m_runningUrl->AllocateCanonicalPath(mailboxName, onlineDelimiter, + getter_Copies(orphanedMailboxName)); + if (m_imapServerSink) + m_imapServerSink->OnlineFolderDelete(orphanedMailboxName); + } +} + +void nsImapProtocol::FolderNotCreated(const char* folderName) { + if (folderName && m_imapServerSink) + m_imapServerSink->OnlineFolderCreateFailed(nsDependentCString(folderName)); +} + +void nsImapProtocol::FolderRenamed(const char* oldName, const char* newName) { + char onlineDelimiter = kOnlineHierarchySeparatorUnknown; + + if ((m_hierarchyNameState == kNoOperationInProgress) || + (m_hierarchyNameState == kListingForInfoAndDiscovery)) + + { + nsCString canonicalOldName, canonicalNewName; + m_runningUrl->AllocateCanonicalPath(oldName, onlineDelimiter, + getter_Copies(canonicalOldName)); + m_runningUrl->AllocateCanonicalPath(newName, onlineDelimiter, + getter_Copies(canonicalNewName)); + AutoProxyReleaseMsgWindow msgWindow; + GetMsgWindow(getter_AddRefs(msgWindow)); + m_imapServerSink->OnlineFolderRename(msgWindow, canonicalOldName, + canonicalNewName); + } +} + +void nsImapProtocol::OnDeleteFolder(const char* sourceMailbox) { + // intelligently delete the folder + bool folderDeleted = true; + bool deleted = DeleteSubFolders(sourceMailbox, folderDeleted); + if (!folderDeleted) { + if (deleted) deleted = DeleteMailboxRespectingSubscriptions(sourceMailbox); + if (deleted) FolderDeleted(sourceMailbox); + } +} + +void nsImapProtocol::RemoveMsgsAndExpunge() { + uint32_t numberOfMessages = GetServerStateParser().NumberOfMessages(); + if (numberOfMessages) { + // Remove all msgs and expunge the folder (ie, compact it). + Store("1:*"_ns, "+FLAGS.SILENT (\\Deleted)", + false); // use sequence #'s + if (GetServerStateParser().LastCommandSuccessful()) Expunge(); + } +} + +void nsImapProtocol::DeleteFolderAndMsgs(const char* sourceMailbox) { + RemoveMsgsAndExpunge(); + if (GetServerStateParser().LastCommandSuccessful()) { + // All msgs are deleted successfully - let's remove the folder itself. + bool reportingErrors = GetServerStateParser().GetReportingErrors(); + GetServerStateParser().SetReportingErrors(false); + OnDeleteFolder(sourceMailbox); + GetServerStateParser().SetReportingErrors(reportingErrors); + } +} + +void nsImapProtocol::OnRenameFolder(const char* sourceMailbox) { + char* destinationMailbox = OnCreateServerDestinationFolderPathString(); + + if (destinationMailbox) { + bool renamed = RenameHierarchyByHand(sourceMailbox, destinationMailbox); + if (renamed) FolderRenamed(sourceMailbox, destinationMailbox); + + // Cause a LIST and re-discovery when slash and/or ^ are escaped. Also + // needed when folder renamed to non-ASCII UTF-8 when UTF8=ACCEPT in + // effect. + m_hierarchyNameState = kListingForCreate; + nsCString mailboxWODelim(destinationMailbox); + RemoveHierarchyDelimiter(mailboxWODelim); + List(mailboxWODelim.get(), false); + m_hierarchyNameState = kNoOperationInProgress; + + PR_Free(destinationMailbox); + } else + HandleMemoryFailure(); +} + +void nsImapProtocol::OnMoveFolderHierarchy(const char* sourceMailbox) { + char* destinationMailbox = OnCreateServerDestinationFolderPathString(); + + if (destinationMailbox) { + nsCString newBoxName; + newBoxName.Adopt(destinationMailbox); + + char onlineDirSeparator = kOnlineHierarchySeparatorUnknown; + m_runningUrl->GetOnlineSubDirSeparator(&onlineDirSeparator); + + nsCString oldBoxName(sourceMailbox); + int32_t leafStart = oldBoxName.RFindChar(onlineDirSeparator); + nsCString leafName; + + if (-1 == leafStart) + leafName = oldBoxName; // this is a root level box + else + leafName = Substring(oldBoxName, leafStart + 1); + + if (!newBoxName.IsEmpty()) newBoxName.Append(onlineDirSeparator); + newBoxName.Append(leafName); + bool renamed = RenameHierarchyByHand(sourceMailbox, newBoxName.get()); + if (renamed) FolderRenamed(sourceMailbox, newBoxName.get()); + } else + HandleMemoryFailure(); +} + +// This is called to do mailbox discovery if discovery not already complete +// for the "host" (i.e., server or account). Discovery still only occurs if +// the imap action is appropriate and if discovery is not in progress due to +// a running "discoverallboxes" URL. +void nsImapProtocol::FindMailboxesIfNecessary() { + // biff should not discover mailboxes + nsImapAction imapAction; + (void)m_runningUrl->GetImapAction(&imapAction); + if ((imapAction != nsIImapUrl::nsImapBiff) && + (imapAction != nsIImapUrl::nsImapVerifylogon) && + (imapAction != nsIImapUrl::nsImapDiscoverAllBoxesUrl) && + (imapAction != nsIImapUrl::nsImapUpgradeToSubscription) && + !GetSubscribingNow()) { + // If discovery in progress, don't kick-off another discovery. + bool discoveryInProgress = false; + m_hostSessionList->GetDiscoveryForHostInProgress(GetImapServerKey(), + discoveryInProgress); + if (!discoveryInProgress) { + m_hostSessionList->SetDiscoveryForHostInProgress(GetImapServerKey(), + true); + DiscoverMailboxList(); + } + } +} + +void nsImapProtocol::DiscoverAllAndSubscribedBoxes() { + // used for subscribe pane + // iterate through all namespaces + uint32_t count = 0; + m_hostSessionList->GetNumberOfNamespacesForHost(GetImapServerKey(), count); + + for (uint32_t i = 0; i < count; i++) { + nsImapNamespace* ns = nullptr; + m_hostSessionList->GetNamespaceNumberForHost(GetImapServerKey(), i, ns); + if (!ns) { + continue; + } + if ((gHideOtherUsersFromList && (ns->GetType() != kOtherUsersNamespace)) || + !gHideOtherUsersFromList) { + const char* prefix = ns->GetPrefix(); + if (prefix) { + nsAutoCString inboxNameWithDelim("INBOX"); + inboxNameWithDelim.Append(ns->GetDelimiter()); + + // Only do it for non-empty namespace prefixes. + if (!gHideUnusedNamespaces && *prefix && + PL_strcasecmp(prefix, inboxNameWithDelim.get())) { + // Explicitly discover each Namespace, just so they're + // there in the subscribe UI + RefPtr<nsImapMailboxSpec> boxSpec = new nsImapMailboxSpec; + boxSpec->mFolderSelected = false; + boxSpec->mHostName.Assign(GetImapHostName()); + boxSpec->mConnection = this; + boxSpec->mFlagState = nullptr; + boxSpec->mDiscoveredFromLsub = true; + boxSpec->mOnlineVerified = true; + boxSpec->mBoxFlags = kNoselect; + boxSpec->mHierarchySeparator = ns->GetDelimiter(); + + m_runningUrl->AllocateCanonicalPath( + ns->GetPrefix(), ns->GetDelimiter(), + getter_Copies(boxSpec->mAllocatedPathName)); + boxSpec->mNamespaceForFolder = ns; + boxSpec->mBoxFlags |= kNameSpace; + + switch (ns->GetType()) { + case kPersonalNamespace: + boxSpec->mBoxFlags |= kPersonalMailbox; + break; + case kPublicNamespace: + boxSpec->mBoxFlags |= kPublicMailbox; + break; + case kOtherUsersNamespace: + boxSpec->mBoxFlags |= kOtherUsersMailbox; + break; + default: // (kUnknownNamespace) + break; + } + + DiscoverMailboxSpec(boxSpec); + } + + nsAutoCString allPattern(prefix); + allPattern += '*'; + + if (!m_imapServerSink) return; + + m_imapServerSink->SetServerDoingLsub(true); + Lsub(allPattern.get(), true); // LSUB all the subscribed + + m_imapServerSink->SetServerDoingLsub(false); + List(allPattern.get(), true); // LIST all folders + } + } + } +} + +// DiscoverMailboxList() is used to actually do the discovery of folders +// for a host. This is used both when we initially start up (and re-sync) +// and also when the user manually requests a re-sync, by collapsing and +// expanding a host in the folder pane. This is not used for the subscribe +// pane. +// DiscoverMailboxList() also gets the ACLs for each newly discovered folder +void nsImapProtocol::DiscoverMailboxList() { + bool usingSubscription = false; + + m_hostSessionList->GetHostIsUsingSubscription(GetImapServerKey(), + usingSubscription); + // Pretend that the Trash folder doesn't exist, so we will rediscover it if we + // need to. + m_hostSessionList->SetOnlineTrashFolderExistsForHost(GetImapServerKey(), + false); + + // should we check a pref here, to be able to turn off XList? + bool hasXLIST = + GetServerStateParser().GetCapabilityFlag() & kHasXListCapability; + if (hasXLIST && usingSubscription) { + m_hierarchyNameState = kXListing; + nsAutoCString pattern("%"); + List("%", true, true); + // We list the first and second levels since special folders are unlikely + // to be more than 2 levels deep. + char separator = 0; + m_runningUrl->GetOnlineSubDirSeparator(&separator); + pattern.Append(separator); + pattern += '%'; + List(pattern.get(), true, true); + } + + SetMailboxDiscoveryStatus(eContinue); + if (GetServerStateParser().ServerHasACLCapability()) + m_hierarchyNameState = kListingForInfoAndDiscovery; + else + m_hierarchyNameState = kNoOperationInProgress; + + // iterate through all namespaces and LSUB them. + uint32_t count = 0; + m_hostSessionList->GetNumberOfNamespacesForHost(GetImapServerKey(), count); + for (uint32_t i = 0; i < count; i++) { + nsImapNamespace* ns = nullptr; + m_hostSessionList->GetNamespaceNumberForHost(GetImapServerKey(), i, ns); + if (ns) { + const char* prefix = ns->GetPrefix(); + if (prefix) { + nsAutoCString inboxNameWithDelim("INBOX"); + inboxNameWithDelim.Append(ns->GetDelimiter()); + + // static bool gHideUnusedNamespaces = true; + // mscott -> WARNING!!! i where are we going to get this + // global variable for unused name spaces from??? + // dmb - we should get this from a per-host preference, + // I'd say. But for now, just make it true. + // Only do it for non-empty namespace prefixes, and for non-INBOX prefix + if (!gHideUnusedNamespaces && *prefix && + PL_strcasecmp(prefix, inboxNameWithDelim.get())) { + // Explicitly discover each Namespace, so that we can + // create subfolders of them, + RefPtr<nsImapMailboxSpec> boxSpec = new nsImapMailboxSpec; + boxSpec->mFolderSelected = false; + boxSpec->mHostName = GetImapHostName(); + boxSpec->mConnection = this; + boxSpec->mFlagState = nullptr; + boxSpec->mDiscoveredFromLsub = true; + boxSpec->mOnlineVerified = true; + boxSpec->mBoxFlags = kNoselect; + boxSpec->mHierarchySeparator = ns->GetDelimiter(); + // Until |AllocateCanonicalPath()| gets updated: + m_runningUrl->AllocateCanonicalPath( + ns->GetPrefix(), ns->GetDelimiter(), + getter_Copies(boxSpec->mAllocatedPathName)); + boxSpec->mNamespaceForFolder = ns; + boxSpec->mBoxFlags |= kNameSpace; + + switch (ns->GetType()) { + case kPersonalNamespace: + boxSpec->mBoxFlags |= kPersonalMailbox; + break; + case kPublicNamespace: + boxSpec->mBoxFlags |= kPublicMailbox; + break; + case kOtherUsersNamespace: + boxSpec->mBoxFlags |= kOtherUsersMailbox; + break; + default: // (kUnknownNamespace) + break; + } + + DiscoverMailboxSpec(boxSpec); + } + + // now do the folders within this namespace + nsCString pattern; + nsCString pattern2; + if (usingSubscription) { + pattern.Append(prefix); + pattern.Append('*'); + } else { + pattern.Append(prefix); + pattern.Append('%'); // mscott just need one percent right? + // pattern = PR_smprintf("%s%%", prefix); + char delimiter = ns->GetDelimiter(); + if (delimiter) { + // delimiter might be NIL, in which case there's no hierarchy anyway + pattern2 = prefix; + pattern2 += "%"; + pattern2 += delimiter; + pattern2 += "%"; + // pattern2 = PR_smprintf("%s%%%c%%", prefix, delimiter); + } + } + // Note: It is important to make sure we are respecting the + // server_sub_directory preference when calling List and Lsub (2nd arg = + // true), otherwise we end up with performance issues or even crashes + // when connecting to servers that expose the users entire home + // directory (like UW-IMAP). + if (usingSubscription) { // && !GetSubscribingNow()) should never get + // here from subscribe pane + if (GetServerStateParser().GetCapabilityFlag() & + kHasListExtendedCapability) + Lsub(pattern.get(), true); // do LIST (SUBSCRIBED) + else { + // store mailbox flags from LIST + EMailboxHierarchyNameState currentState = m_hierarchyNameState; + m_hierarchyNameState = kListingForFolderFlags; + List(pattern.get(), true); + m_hierarchyNameState = currentState; + // then do LSUB using stored flags + Lsub(pattern.get(), true); + m_standardListMailboxes.Clear(); + } + } else { + List(pattern.get(), true, hasXLIST); + List(pattern2.get(), true, hasXLIST); + } + } + } + } + + // explicitly LIST the INBOX if (a) we're not using subscription, or (b) we + // are using subscription and the user wants us to always show the INBOX. + bool listInboxForHost = false; + m_hostSessionList->GetShouldAlwaysListInboxForHost(GetImapServerKey(), + listInboxForHost); + if (!usingSubscription || listInboxForHost) List("INBOX", true); + + m_hierarchyNameState = kNoOperationInProgress; + + MailboxDiscoveryFinished(); + + // Get the ACLs for newly discovered folders + if (GetServerStateParser().ServerHasACLCapability()) { + int32_t total = m_listedMailboxList.Length(), cnt = 0; + // Let's not turn this off here, since we don't turn it on after + // GetServerStateParser().SetReportingErrors(false); + if (total) { + ProgressEventFunctionUsingName("imapGettingACLForFolder"); + nsIMAPMailboxInfo* mb = nullptr; + do { + if (m_listedMailboxList.Length() == 0) break; + + mb = m_listedMailboxList[0]; // get top element + m_listedMailboxList.RemoveElementAt( + 0); // XP_ListRemoveTopObject(fListedMailboxList); + if (mb) { + if (FolderNeedsACLInitialized( + PromiseFlatCString(mb->GetMailboxName()).get())) { + char* onlineName = nullptr; + m_runningUrl->AllocateServerPath( + PromiseFlatCString(mb->GetMailboxName()).get(), + mb->GetDelimiter(), &onlineName); + if (onlineName) { + RefreshACLForFolder(onlineName); + PR_Free(onlineName); + } + } + PercentProgressUpdateEvent(""_ns, u""_ns, cnt, total); + delete mb; // this is the last time we're using the list, so delete + // the entries here + cnt++; + } + } while (mb && !DeathSignalReceived()); + } + } +} + +bool nsImapProtocol::FolderNeedsACLInitialized(const char* folderName) { + bool rv = false; + m_imapServerSink->FolderNeedsACLInitialized(nsDependentCString(folderName), + &rv); + return rv; +} + +void nsImapProtocol::MailboxDiscoveryFinished() { + if (!DeathSignalReceived() && !GetSubscribingNow() && + ((m_hierarchyNameState == kNoOperationInProgress) || + (m_hierarchyNameState == kListingForInfoAndDiscovery))) { + nsImapNamespace* ns = nullptr; + m_hostSessionList->GetDefaultNamespaceOfTypeForHost(GetImapServerKey(), + kPersonalNamespace, ns); + const char* personalDir = ns ? ns->GetPrefix() : 0; + + bool trashFolderExists = false; + bool usingSubscription = false; + m_hostSessionList->GetOnlineTrashFolderExistsForHost(GetImapServerKey(), + trashFolderExists); + m_hostSessionList->GetHostIsUsingSubscription(GetImapServerKey(), + usingSubscription); + if (!trashFolderExists && GetDeleteIsMoveToTrash() && usingSubscription) { + // maybe we're not subscribed to the Trash folder + if (personalDir) { + m_hierarchyNameState = kDiscoverTrashFolderInProgress; + List(m_trashFolderPath.get(), true); + m_hierarchyNameState = kNoOperationInProgress; + } + } + + // There is no Trash folder (either LIST'd or LSUB'd), and we're using the + // Delete-is-move-to-Trash model, and there is a personal namespace + if (!trashFolderExists && GetDeleteIsMoveToTrash() && ns) { + nsCString onlineTrashName; + m_runningUrl->AllocateServerPath(m_trashFolderPath.get(), + ns->GetDelimiter(), + getter_Copies(onlineTrashName)); + + GetServerStateParser().SetReportingErrors(false); + bool created = + CreateMailboxRespectingSubscriptions(onlineTrashName.get()); + GetServerStateParser().SetReportingErrors(true); + + // force discovery of new trash folder. + if (created) { + m_hierarchyNameState = kDiscoverTrashFolderInProgress; + List(onlineTrashName.get(), false); + m_hierarchyNameState = kNoOperationInProgress; + } else + m_hostSessionList->SetOnlineTrashFolderExistsForHost(GetImapServerKey(), + true); + } // if trash folder doesn't exist + m_hostSessionList->SetHaveWeEverDiscoveredFoldersForHost(GetImapServerKey(), + true); + // notify front end that folder discovery is complete.... + if (m_imapServerSink) m_imapServerSink->DiscoveryDone(); + + // Clear the discovery in progress flag. + m_hostSessionList->SetDiscoveryForHostInProgress(GetImapServerKey(), false); + } +} + +// returns the mailboxName with the IMAP delimiter removed from the tail end +void nsImapProtocol::RemoveHierarchyDelimiter(nsCString& mailboxName) { + char onlineDelimiter[2] = {0, 0}; + if (m_imapMailFolderSink) + m_imapMailFolderSink->GetOnlineDelimiter(&onlineDelimiter[0]); + // take the hierarchy delimiter off the end, if any. + if (onlineDelimiter[0]) mailboxName.Trim(onlineDelimiter, false, true); +} + +// returns true is the create succeeded (regardless of subscription changes) +bool nsImapProtocol::CreateMailboxRespectingSubscriptions( + const char* mailboxName) { + CreateMailbox(mailboxName); + bool rv = GetServerStateParser().LastCommandSuccessful(); + if (rv && m_autoSubscribe) // auto-subscribe is on + { + // create succeeded - let's subscribe to it + bool reportingErrors = GetServerStateParser().GetReportingErrors(); + GetServerStateParser().SetReportingErrors(false); + nsCString mailboxWODelim(mailboxName); + RemoveHierarchyDelimiter(mailboxWODelim); + OnSubscribe(mailboxWODelim.get()); + GetServerStateParser().SetReportingErrors(reportingErrors); + } + return rv; +} + +void nsImapProtocol::CreateMailbox(const char* mailboxName) { + ProgressEventFunctionUsingName("imapStatusCreatingMailbox"); + + IncrementCommandTagNumber(); + + nsCString escapedName; + CreateEscapedMailboxName(mailboxName, escapedName); + nsCString command(GetServerCommandTag()); + command += " create \""; + command += escapedName; + command += "\"" CRLF; + + nsresult rv = SendData(command.get()); + if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail(); + // If that failed, let's list the parent folder to see if + // it allows inferiors, so we won't try to create sub-folders + // of the parent folder again in the current session. + if (GetServerStateParser().CommandFailed()) { + // Figure out parent folder name. + nsCString parentName(mailboxName); + char hierarchyDelimiter; + m_runningUrl->GetOnlineSubDirSeparator(&hierarchyDelimiter); + int32_t leafPos = parentName.RFindChar(hierarchyDelimiter); + if (leafPos > 0) { + parentName.SetLength(leafPos); + List(parentName.get(), false); + // We still want the caller to know the create failed, so restore that. + GetServerStateParser().SetCommandFailed(true); + } + } +} + +void nsImapProtocol::DeleteMailbox(const char* mailboxName) { + // check if this connection currently has the folder to be deleted selected. + // If so, we should close it because at least some UW servers don't like you + // deleting a folder you have open. + if (FolderIsSelected(mailboxName)) ImapClose(); + + ProgressEventFunctionUsingNameWithString("imapStatusDeletingMailbox", + mailboxName); + + IncrementCommandTagNumber(); + + nsCString escapedName; + CreateEscapedMailboxName(mailboxName, escapedName); + nsCString command(GetServerCommandTag()); + command += " delete \""; + command += escapedName; + command += "\"" CRLF; + + nsresult rv = SendData(command.get()); + if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail(); +} + +void nsImapProtocol::RenameMailbox(const char* existingName, + const char* newName) { + // just like DeleteMailbox; Some UW servers don't like it. + if (FolderIsSelected(existingName)) ImapClose(); + + ProgressEventFunctionUsingNameWithString("imapStatusRenamingMailbox", + existingName); + + IncrementCommandTagNumber(); + + nsCString escapedExistingName; + nsCString escapedNewName; + CreateEscapedMailboxName(existingName, escapedExistingName); + CreateEscapedMailboxName(newName, escapedNewName); + nsCString command(GetServerCommandTag()); + command += " rename \""; + command += escapedExistingName; + command += "\" \""; + command += escapedNewName; + command += "\"" CRLF; + + nsresult rv = SendData(command.get()); + if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail(); +} + +bool nsImapProtocol::GetListSubscribedIsBrokenOnServer() { + // This is a workaround for an issue with LIST(SUBSCRIBED) crashing older + // versions of Zimbra + if (FindInReadable("\"NAME\" \"Zimbra\""_ns, + GetServerStateParser().GetServerID(), + nsCaseInsensitiveCStringComparator)) { + nsCString serverID(GetServerStateParser().GetServerID()); + int start = serverID.LowerCaseFindASCII("\"version\" \"") + 11; + int length = serverID.LowerCaseFindASCII("\" ", start); + const nsDependentCSubstring serverVersionSubstring = + Substring(serverID, start, length); + nsCString serverVersionStr(serverVersionSubstring); + Version serverVersion(serverVersionStr.get()); + Version sevenTwoThree("7.2.3_"); + Version eightZeroZero("8.0.0_"); + Version eightZeroThree("8.0.3_"); + if ((serverVersion < sevenTwoThree) || + ((serverVersion >= eightZeroZero) && (serverVersion < eightZeroThree))) + return true; + } + return false; +} + +void nsImapProtocol::Lsub(const char* mailboxPattern, + bool addDirectoryIfNecessary) { + ProgressEventFunctionUsingName("imapStatusLookingForMailbox"); + + IncrementCommandTagNumber(); + + char* boxnameWithOnlineDirectory = nullptr; + if (addDirectoryIfNecessary) + m_runningUrl->AddOnlineDirectoryIfNecessary(mailboxPattern, + &boxnameWithOnlineDirectory); + + nsCString escapedPattern; + CreateEscapedMailboxName( + boxnameWithOnlineDirectory ? boxnameWithOnlineDirectory : mailboxPattern, + escapedPattern); + + nsCString command(GetServerCommandTag()); + eIMAPCapabilityFlags flag = GetServerStateParser().GetCapabilityFlag(); + bool useListSubscribed = (flag & kHasListExtendedCapability) && + !GetListSubscribedIsBrokenOnServer(); + if (useListSubscribed) + command += " list (subscribed)"; + else + command += " lsub"; + command += " \"\" \""; + command += escapedPattern; + if (useListSubscribed && (flag & kHasSpecialUseCapability)) + command += "\" return (special-use)" CRLF; + else + command += "\"" CRLF; + + PR_Free(boxnameWithOnlineDirectory); + + nsresult rv = SendData(command.get()); + if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail(command.get(), true); +} + +void nsImapProtocol::List(const char* mailboxPattern, + bool addDirectoryIfNecessary, bool useXLIST) { + ProgressEventFunctionUsingName("imapStatusLookingForMailbox"); + + IncrementCommandTagNumber(); + + char* boxnameWithOnlineDirectory = nullptr; + if (addDirectoryIfNecessary) + m_runningUrl->AddOnlineDirectoryIfNecessary(mailboxPattern, + &boxnameWithOnlineDirectory); + + nsCString escapedPattern; + CreateEscapedMailboxName( + boxnameWithOnlineDirectory ? boxnameWithOnlineDirectory : mailboxPattern, + escapedPattern); + + nsCString command(GetServerCommandTag()); + command += useXLIST ? " xlist \"\" \"" : " list \"\" \""; + command += escapedPattern; + command += "\"" CRLF; + + PR_Free(boxnameWithOnlineDirectory); + + nsresult rv = SendData(command.get()); + if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail(command.get(), true); +} + +void nsImapProtocol::Subscribe(const char* mailboxName) { + ProgressEventFunctionUsingNameWithString("imapStatusSubscribeToMailbox", + mailboxName); + + IncrementCommandTagNumber(); + + nsCString escapedName; + CreateEscapedMailboxName(mailboxName, escapedName); + + nsCString command(GetServerCommandTag()); + command += " subscribe \""; + command += escapedName; + command += "\"" CRLF; + + nsresult rv = SendData(command.get()); + if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail(); +} + +void nsImapProtocol::Unsubscribe(const char* mailboxName) { + ProgressEventFunctionUsingNameWithString("imapStatusUnsubscribeMailbox", + mailboxName); + IncrementCommandTagNumber(); + + nsCString escapedName; + CreateEscapedMailboxName(mailboxName, escapedName); + + nsCString command(GetServerCommandTag()); + command += " unsubscribe \""; + command += escapedName; + command += "\"" CRLF; + + nsresult rv = SendData(command.get()); + if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail(); +} + +void nsImapProtocol::Idle() { + IncrementCommandTagNumber(); + + if (m_urlInProgress) return; + nsAutoCString command(GetServerCommandTag()); + command += " IDLE" CRLF; + nsresult rv = SendData(command.get()); + if (NS_SUCCEEDED(rv)) { + // Typically, we'll just get back only a continuation char on IDLE response, + // "+ idling". However, it is possible untagged responses will occur before + // and/or after the '+' which we treat the same as later untagged responses + // signaled by the socket thread. If untagged responses occur on IDLE, + // HandleIdleResponses() will trigger a select URL which will exit idle mode + // and update the selected folder. Finally, if IDLE responds with tagged BAD + // or NO, HandleIdleResponses() will return false. + m_idle = HandleIdleResponses(); + } +} + +// until we can fix the hang on shutdown waiting for server +// responses, we need to not wait for the server response +// on shutdown. +void nsImapProtocol::EndIdle(bool waitForResponse /* = true */) { + // clear the async wait - otherwise, we have trouble doing a blocking read + // below. + nsCOMPtr<nsIAsyncInputStream> asyncInputStream = + do_QueryInterface(m_inputStream); + if (asyncInputStream) asyncInputStream->AsyncWait(nullptr, 0, 0, nullptr); + nsresult rv = SendData("DONE" CRLF); + // set a short timeout if we don't want to wait for a response + if (m_transport && !waitForResponse) + m_transport->SetTimeout(nsISocketTransport::TIMEOUT_READ_WRITE, 5); + if (NS_SUCCEEDED(rv)) { + m_idle = false; + ParseIMAPandCheckForNewMail(); + // If waiting for response (i.e., not shutting down), check for IDLE + // untagged response(s) occurring after DONE is sent, which can occur and is + // mentioned in the IDLE rfc as a possibility. This is similar to the checks + // done in OnStatusForFolder(). + if (waitForResponse && m_imapMailFolderSinkSelected && + GetServerStateParser().UntaggedResponse()) { + Log("EndIdle", nullptr, "idle response after idle DONE"); + m_imapMailFolderSinkSelected->OnNewIdleMessages(); + } + } + // Set m_imapMailFolderSink null only if shutting down or if DONE succeeds. + // We need to keep m_imapMailFolderSink if DONE fails or times out when not + // shutting down so the URL that is attempting to run on this connection can + // retry or signal a failed status when SetUrlState is called in + // ProcessCurrentUrl to invoke nsIUrlListener.onStopRunningUrl. + if (!waitForResponse || GetServerStateParser().LastCommandSuccessful()) + m_imapMailFolderSink = nullptr; +} + +void nsImapProtocol::Search(const char* searchCriteria, bool useUID, + bool notifyHit /* true */) { + m_notifySearchHit = notifyHit; + ProgressEventFunctionUsingName("imapStatusSearchMailbox"); + IncrementCommandTagNumber(); + + nsCString protocolString(GetServerCommandTag()); + // the searchCriteria string contains the 'search ....' string + if (useUID) protocolString.AppendLiteral(" uid"); + protocolString.Append(' '); + protocolString.Append(searchCriteria); + // the search criteria can contain string literals, which means we + // need to break up the protocol string by CRLF's, and after sending CRLF, + // wait for the server to respond OK before sending more data + nsresult rv; + int32_t crlfIndex; + while ((crlfIndex = protocolString.Find(CRLF)) != kNotFound && + !DeathSignalReceived()) { + nsAutoCString tempProtocolString; + tempProtocolString = StringHead(protocolString, crlfIndex + 2); + rv = SendData(tempProtocolString.get()); + if (NS_FAILED(rv)) return; + ParseIMAPandCheckForNewMail(); + protocolString.Cut(0, crlfIndex + 2); + } + protocolString.Append(CRLF); + + rv = SendData(protocolString.get()); + if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail(); +} + +void nsImapProtocol::Copy(const char* messageList, + const char* destinationMailbox, bool idsAreUid) { + IncrementCommandTagNumber(); + + nsCString escapedDestination; + CreateEscapedMailboxName(destinationMailbox, escapedDestination); + + // turn messageList back into key array and then back into a message id list, + // but use the flag state to handle ranges correctly. + nsCString messageIdList; + nsTArray<nsMsgKey> msgKeys; + if (idsAreUid) ParseUidString(messageList, msgKeys); + + int32_t msgCountLeft = msgKeys.Length(); + uint32_t msgsHandled = 0; + + do { + nsCString idString; + + uint32_t msgsToHandle = msgCountLeft; + if (idsAreUid) + AllocateImapUidString(msgKeys.Elements() + msgsHandled, msgsToHandle, + m_flagState, idString); + else + idString.Assign(messageList); + + msgsHandled += msgsToHandle; + msgCountLeft -= msgsToHandle; + + IncrementCommandTagNumber(); + nsAutoCString protocolString(GetServerCommandTag()); + if (idsAreUid) protocolString.AppendLiteral(" uid"); + // If it's a MOVE operation on aol servers then use 'xaol-move' cmd. + if ((m_imapAction == nsIImapUrl::nsImapOnlineMove) && + GetServerStateParser().ServerIsAOLServer()) + protocolString.AppendLiteral(" xaol-move "); + else if ((m_imapAction == nsIImapUrl::nsImapOnlineMove) && + GetServerStateParser().GetCapabilityFlag() & kHasMoveCapability) + protocolString.AppendLiteral(" move "); + else + protocolString.AppendLiteral(" copy "); + + protocolString.Append(idString); + protocolString.AppendLiteral(" \""); + protocolString.Append(escapedDestination); + protocolString.AppendLiteral("\"" CRLF); + + nsresult rv = SendData(protocolString.get()); + if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail(protocolString.get()); + } while (msgCountLeft > 0 && !DeathSignalReceived()); +} + +void nsImapProtocol::NthLevelChildList(const char* onlineMailboxPrefix, + int32_t depth) { + NS_ASSERTION(depth >= 0, "Oops ... depth must be equal or greater than 0"); + if (depth < 0) return; + + nsCString truncatedPrefix(onlineMailboxPrefix); + char16_t slash = '/'; + if (truncatedPrefix.Last() == slash) + truncatedPrefix.SetLength(truncatedPrefix.Length() - 1); + + nsAutoCString pattern(truncatedPrefix); + nsAutoCString suffix; + int count = 0; + char separator = 0; + m_runningUrl->GetOnlineSubDirSeparator(&separator); + suffix.Assign(separator); + suffix += '%'; + + while (count < depth) { + pattern += suffix; + count++; + List(pattern.get(), false); + } +} + +/** + * ProcessAuthenticatedStateURL() is a helper for ProcessCurrentURL() which + * handles running URLs which require the connection to be in the + * Authenticated state. + */ +void nsImapProtocol::ProcessAuthenticatedStateURL() { + nsImapAction imapAction; + char* sourceMailbox = nullptr; + m_runningUrl->GetImapAction(&imapAction); + + // switch off of the imap url action and take an appropriate action + switch (imapAction) { + case nsIImapUrl::nsImapLsubFolders: + OnLSubFolders(); + break; + case nsIImapUrl::nsImapAppendMsgFromFile: + OnAppendMsgFromFile(); + break; + case nsIImapUrl::nsImapDiscoverAllBoxesUrl: + NS_ASSERTION(!GetSubscribingNow(), + "Oops ... should not get here from subscribe UI"); + DiscoverMailboxList(); + break; + case nsIImapUrl::nsImapDiscoverAllAndSubscribedBoxesUrl: + DiscoverAllAndSubscribedBoxes(); + break; + case nsIImapUrl::nsImapCreateFolder: + sourceMailbox = OnCreateServerSourceFolderPathString(); + OnCreateFolder(sourceMailbox); + break; + case nsIImapUrl::nsImapEnsureExistsFolder: + sourceMailbox = OnCreateServerSourceFolderPathString(); + OnEnsureExistsFolder(sourceMailbox); + break; + case nsIImapUrl::nsImapDiscoverChildrenUrl: { + char* canonicalParent = nullptr; + m_runningUrl->CreateServerSourceFolderPathString(&canonicalParent); + if (canonicalParent) { + NthLevelChildList(canonicalParent, 2); + PR_Free(canonicalParent); + } + break; + } + case nsIImapUrl::nsImapSubscribe: + sourceMailbox = OnCreateServerSourceFolderPathString(); + OnSubscribe(sourceMailbox); // used to be called subscribe + + if (GetServerStateParser().LastCommandSuccessful()) { + bool shouldList; + // if url is an external click url, then we should list the folder + // after subscribing to it, so we can select it. + m_runningUrl->GetExternalLinkUrl(&shouldList); + if (shouldList) OnListFolder(sourceMailbox, true); + } + break; + case nsIImapUrl::nsImapUnsubscribe: + sourceMailbox = OnCreateServerSourceFolderPathString(); + OnUnsubscribe(sourceMailbox); + break; + case nsIImapUrl::nsImapRefreshACL: + sourceMailbox = OnCreateServerSourceFolderPathString(); + RefreshACLForFolder(sourceMailbox); + break; + case nsIImapUrl::nsImapRefreshAllACLs: + OnRefreshAllACLs(); + break; + case nsIImapUrl::nsImapListFolder: + sourceMailbox = OnCreateServerSourceFolderPathString(); + OnListFolder(sourceMailbox, false); + break; + case nsIImapUrl::nsImapFolderStatus: + sourceMailbox = OnCreateServerSourceFolderPathString(); + OnStatusForFolder(sourceMailbox); + break; + case nsIImapUrl::nsImapRefreshFolderUrls: + sourceMailbox = OnCreateServerSourceFolderPathString(); + XMailboxInfo(sourceMailbox); + if (GetServerStateParser().LastCommandSuccessful()) + SetFolderAdminUrl(sourceMailbox); + break; + case nsIImapUrl::nsImapDeleteFolder: + sourceMailbox = OnCreateServerSourceFolderPathString(); + OnDeleteFolder(sourceMailbox); + break; + case nsIImapUrl::nsImapRenameFolder: + sourceMailbox = OnCreateServerSourceFolderPathString(); + OnRenameFolder(sourceMailbox); + break; + case nsIImapUrl::nsImapMoveFolderHierarchy: + sourceMailbox = OnCreateServerSourceFolderPathString(); + OnMoveFolderHierarchy(sourceMailbox); + break; + case nsIImapUrl::nsImapVerifylogon: + break; + default: + break; + } + PR_Free(sourceMailbox); +} + +void nsImapProtocol::ProcessAfterAuthenticated() { + // if we're a netscape server, and we haven't got the admin url, get it + bool hasAdminUrl = true; + + // If a capability response didn't occur during authentication, request + // the capabilities again to ensure the full capability set is known. + if (!m_capabilityResponseOccurred) Capability(); + + if (NS_SUCCEEDED(m_hostSessionList->GetHostHasAdminURL(GetImapServerKey(), + hasAdminUrl)) && + !hasAdminUrl) { + if (GetServerStateParser().ServerHasServerInfo()) { + XServerInfo(); + if (GetServerStateParser().LastCommandSuccessful() && m_imapServerSink) { + m_imapServerSink->SetMailServerUrls( + GetServerStateParser().GetMailAccountUrl(), + GetServerStateParser().GetManageListsUrl(), + GetServerStateParser().GetManageFiltersUrl()); + // we've tried to ask for it, so don't try again this session. + m_hostSessionList->SetHostHasAdminURL(GetImapServerKey(), true); + } + } else if (GetServerStateParser().ServerIsNetscape3xServer()) { + Netscape(); + if (GetServerStateParser().LastCommandSuccessful() && m_imapServerSink) + m_imapServerSink->SetMailServerUrls( + GetServerStateParser().GetMailAccountUrl(), EmptyCString(), + EmptyCString()); + } + } + + if (GetServerStateParser().ServerHasNamespaceCapability()) { + bool nameSpacesOverridable = false; + bool haveNameSpacesForHost = false; + m_hostSessionList->GetNamespacesOverridableForHost(GetImapServerKey(), + nameSpacesOverridable); + m_hostSessionList->GetGotNamespacesForHost(GetImapServerKey(), + haveNameSpacesForHost); + + // mscott: VERIFY THIS CLAUSE!!!!!!! + if (nameSpacesOverridable && !haveNameSpacesForHost) Namespace(); + } + + // If the server supports compression, turn it on now. + // Choosing this spot (after login has finished) because + // many proxies (e.g. perdition, nginx) talk IMAP to the + // client until login is finished, then hand off to the + // backend. If we enable compression early the proxy + // will be confused. + if (UseCompressDeflate()) StartCompressDeflate(); + + if ((GetServerStateParser().GetCapabilityFlag() & kHasEnableCapability) && + UseCondStore()) + EnableCondStore(); + + if ((GetServerStateParser().GetCapabilityFlag() & kHasIDCapability) && + m_sendID) { + ID(); + if (m_imapServerSink && !GetServerStateParser().GetServerID().IsEmpty()) + m_imapServerSink->SetServerID(GetServerStateParser().GetServerID()); + } + + bool utf8AcceptAllowed = m_allowUTF8Accept; + m_allowUTF8Accept = false; + if (utf8AcceptAllowed && + ((GetServerStateParser().GetCapabilityFlag() & + (kHasEnableCapability | kHasUTF8AcceptCapability)) == + (kHasEnableCapability | kHasUTF8AcceptCapability))) { + if (m_imapServerSink) { + EnableUTF8Accept(); + m_allowUTF8Accept = GetServerStateParser().fUtf8AcceptEnabled; + // m_allowUTF8Accept affects imap append handling. See + // UploadMessageFromFile(). + m_imapServerSink->SetServerUtf8AcceptEnabled(m_allowUTF8Accept); + GetServerStateParser().fUtf8AcceptEnabled = false; + } else { + NS_WARNING("UTF8=ACCEPT not enabled due to null m_imapServerSink"); + } + } +} + +void nsImapProtocol::SetupMessageFlagsString(nsCString& flagString, + imapMessageFlagsType flags, + uint16_t userFlags) { + if (flags & kImapMsgSeenFlag) flagString.AppendLiteral("\\Seen "); + if (flags & kImapMsgAnsweredFlag) flagString.AppendLiteral("\\Answered "); + if (flags & kImapMsgFlaggedFlag) flagString.AppendLiteral("\\Flagged "); + if (flags & kImapMsgDeletedFlag) flagString.AppendLiteral("\\Deleted "); + if (flags & kImapMsgDraftFlag) flagString.AppendLiteral("\\Draft "); + if (flags & kImapMsgRecentFlag) flagString.AppendLiteral("\\Recent "); + if ((flags & kImapMsgForwardedFlag) && + (userFlags & kImapMsgSupportForwardedFlag)) + flagString.AppendLiteral("$Forwarded "); // Not always available + if ((flags & kImapMsgMDNSentFlag) && (userFlags & kImapMsgSupportMDNSentFlag)) + flagString.AppendLiteral("$MDNSent "); // Not always available + + // eat the last space + if (!flagString.IsEmpty()) flagString.SetLength(flagString.Length() - 1); +} + +void nsImapProtocol::ProcessStoreFlags(const nsCString& messageIdsString, + bool idsAreUids, + imapMessageFlagsType flags, + bool addFlags) { + nsCString flagString; + + uint16_t userFlags = GetServerStateParser().SupportsUserFlags(); + uint16_t settableFlags = GetServerStateParser().SettablePermanentFlags(); + + if (!addFlags && (flags & userFlags) && !(flags & settableFlags)) { + if (m_runningUrl) + m_runningUrl->SetExtraStatus(nsIImapUrl::ImapStatusFlagsNotSettable); + return; // if cannot set any of the flags bail out + } + + if (addFlags) + flagString = "+Flags ("; + else + flagString = "-Flags ("; + + if (flags & kImapMsgSeenFlag && kImapMsgSeenFlag & settableFlags) + flagString.AppendLiteral("\\Seen "); + if (flags & kImapMsgAnsweredFlag && kImapMsgAnsweredFlag & settableFlags) + flagString.AppendLiteral("\\Answered "); + if (flags & kImapMsgFlaggedFlag && kImapMsgFlaggedFlag & settableFlags) + flagString.AppendLiteral("\\Flagged "); + if (flags & kImapMsgDeletedFlag && kImapMsgDeletedFlag & settableFlags) + flagString.AppendLiteral("\\Deleted "); + if (flags & kImapMsgDraftFlag && kImapMsgDraftFlag & settableFlags) + flagString.AppendLiteral("\\Draft "); + if (flags & kImapMsgForwardedFlag && kImapMsgSupportForwardedFlag & userFlags) + flagString.AppendLiteral("$Forwarded "); // if supported + if (flags & kImapMsgMDNSentFlag && kImapMsgSupportMDNSentFlag & userFlags) + flagString.AppendLiteral("$MDNSent "); // if supported + + if (flagString.Length() > 8) // if more than "+Flags (" + { + // replace the final space with ')' + flagString.SetCharAt(')', flagString.Length() - 1); + + Store(messageIdsString, flagString.get(), idsAreUids); + if (m_runningUrl && idsAreUids) { + nsCString messageIdString; + m_runningUrl->GetListOfMessageIds(messageIdString); + nsTArray<nsMsgKey> msgKeys; + ParseUidString(messageIdString.get(), msgKeys); + + int32_t msgCount = msgKeys.Length(); + for (int32_t i = 0; i < msgCount; i++) { + bool found; + imapMessageFlagsType resultFlags; + // check if the flags were added/removed, and if the uid really exists. + nsresult rv = GetFlagsForUID(msgKeys[i], &found, &resultFlags, nullptr); + if (NS_FAILED(rv) || !found || + (addFlags && ((flags & resultFlags) != flags)) || + (!addFlags && ((flags & resultFlags) != 0))) { + m_runningUrl->SetExtraStatus(nsIImapUrl::ImapStatusFlagChangeFailed); + break; + } + } + } + } +} + +/** + * This will cause all messages marked deleted to be expunged with no untagged + * response so it can cause unexpected data loss if used improperly. + */ +void nsImapProtocol::ImapClose(bool shuttingDown /* = false */, + bool waitForResponse /* = true */) { + IncrementCommandTagNumber(); + + nsCString command(GetServerCommandTag()); + command.AppendLiteral(" close" CRLF); + + if (!shuttingDown) ProgressEventFunctionUsingName("imapStatusCloseMailbox"); + + GetServerStateParser().ResetFlagInfo(); + + nsresult rv = SendData(command.get()); + if (m_transport && shuttingDown) + m_transport->SetTimeout(nsISocketTransport::TIMEOUT_READ_WRITE, 5); + + if (NS_SUCCEEDED(rv) && waitForResponse) ParseIMAPandCheckForNewMail(); +} + +void nsImapProtocol::XAOL_Option(const char* option) { + IncrementCommandTagNumber(); + + nsCString command(GetServerCommandTag()); + command.AppendLiteral(" XAOL-OPTION "); + command.Append(option); + command.Append(CRLF); + + nsresult rv = SendData(command.get()); + if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail(); +} + +void nsImapProtocol::Check() { + // ProgressUpdateEvent("Checking mailbox..."); + + IncrementCommandTagNumber(); + + nsCString command(GetServerCommandTag()); + command.AppendLiteral(" check" CRLF); + + nsresult rv = SendData(command.get()); + if (NS_SUCCEEDED(rv)) { + m_flagChangeCount = 0; + m_lastCheckTime = PR_Now(); + ParseIMAPandCheckForNewMail(); + } +} + +nsresult nsImapProtocol::GetMsgWindow(nsIMsgWindow** aMsgWindow) { + nsresult rv; + *aMsgWindow = nullptr; + nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = + do_QueryInterface(m_runningUrl, &rv); + NS_ENSURE_SUCCESS(rv, rv); + if (!m_imapProtocolSink) return NS_ERROR_FAILURE; + return m_imapProtocolSink->GetUrlWindow(mailnewsUrl, aMsgWindow); +} + +/** + * Get password from RAM, disk (password manager) or user (dialog) + * @return NS_MSG_PASSWORD_PROMPT_CANCELLED + * (which is NS_SUCCEEDED!) when user cancelled + * NS_FAILED(rv) for other errors + */ +nsresult nsImapProtocol::GetPassword(nsString& password, + bool newPasswordRequested) { + // we are in the imap thread so *NEVER* try to extract the password with UI + NS_ENSURE_TRUE(m_imapServerSink, NS_ERROR_NULL_POINTER); + NS_ENSURE_TRUE(m_server, NS_ERROR_NULL_POINTER); + nsresult rv; + + password = nsString(); + // Get the password already stored in mem + rv = m_imapServerSink->GetServerPassword(password); + if (NS_FAILED(rv) || password.IsEmpty()) { + // First see if there's an associated window. We don't want to produce a + // password prompt if there is no window, e.g., during biff. + AutoProxyReleaseMsgWindow msgWindow; + GetMsgWindow(getter_AddRefs(msgWindow)); + if (msgWindow) { + m_passwordStatus = NS_OK; + m_passwordObtained = false; + + // Get the password from pw manager (harddisk) or user (dialog) + rv = m_imapServerSink->AsyncGetPassword(this, newPasswordRequested, + password); + + if (NS_SUCCEEDED(rv)) { + while (password.IsEmpty()) { + bool shuttingDown = false; + (void)m_imapServerSink->GetServerShuttingDown(&shuttingDown); + if (shuttingDown) { + // Note: If we fix bug 1783573 this check could be ditched. + rv = NS_ERROR_FAILURE; + break; + } + + ReentrantMonitorAutoEnter mon(m_passwordReadyMonitor); + if (!m_passwordObtained && !NS_FAILED(m_passwordStatus) && + m_passwordStatus != NS_MSG_PASSWORD_PROMPT_CANCELLED && + !DeathSignalReceived()) { + mon.Wait(PR_MillisecondsToInterval(1000)); + } + + if (NS_FAILED(m_passwordStatus) || + m_passwordStatus == NS_MSG_PASSWORD_PROMPT_CANCELLED) { + rv = m_passwordStatus; + break; + } + + if (DeathSignalReceived()) { + rv = NS_ERROR_FAILURE; + break; + } + + if (m_passwordObtained) { + rv = m_passwordStatus; + password = m_password; + break; + } + } // end while + } + } else { + // If no msgWindow (i.e., unattended operation like biff, filtering or + // autosync) try to get the password directly from login mgr. If it's not + // there, will return NS_ERROR_NOT_AVAILABLE and the connection will fail + // with only the IMAP log message: `password prompt failed or user + // canceled it'. No password prompt occurs. + rv = m_imapServerSink->SyncGetPassword(password); + } + } + if (!password.IsEmpty()) m_lastPasswordSent = password; + return rv; +} + +NS_IMETHODIMP nsImapProtocol::OnPromptStartAsync( + nsIMsgAsyncPromptCallback* aCallback) { + bool result = false; + OnPromptStart(&result); + return aCallback->OnAuthResult(result); +} + +// This is called from the UI thread. +NS_IMETHODIMP +nsImapProtocol::OnPromptStart(bool* aResult) { + nsresult rv; + nsCOMPtr<nsIImapIncomingServer> imapServer = do_QueryReferent(m_server, &rv); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr<nsIMsgWindow> msgWindow; + + *aResult = false; + GetMsgWindow(getter_AddRefs(msgWindow)); + nsString password = m_lastPasswordSent; + rv = imapServer->PromptPassword(msgWindow, password); + + ReentrantMonitorAutoEnter passwordMon(m_passwordReadyMonitor); + + m_password = password; + m_passwordStatus = rv; + if (!m_password.IsEmpty()) *aResult = true; + + // Notify the imap thread that we have a password. + m_passwordObtained = true; + passwordMon.Notify(); + return rv; +} + +NS_IMETHODIMP +nsImapProtocol::OnPromptAuthAvailable() { + nsresult rv; + nsCOMPtr<nsIMsgIncomingServer> imapServer = do_QueryReferent(m_server, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsresult status = imapServer->GetPassword(m_password); + + ReentrantMonitorAutoEnter passwordMon(m_passwordReadyMonitor); + + m_passwordStatus = status; + // Notify the imap thread that we have a password. + m_passwordObtained = true; + passwordMon.Notify(); + return m_passwordStatus; +} + +NS_IMETHODIMP +nsImapProtocol::OnPromptCanceled() { + // A prompt was cancelled, so notify the imap thread. + ReentrantMonitorAutoEnter passwordMon(m_passwordReadyMonitor); + m_passwordStatus = NS_MSG_PASSWORD_PROMPT_CANCELLED; + passwordMon.Notify(); + return NS_OK; +} + +// Called when capability response is parsed. +void nsImapProtocol::SetCapabilityResponseOccurred() { + m_capabilityResponseOccurred = true; +} + +bool nsImapProtocol::TryToLogon() { + MOZ_LOG(IMAP, LogLevel::Debug, ("Try to log in")); + NS_ENSURE_TRUE(m_imapServerSink, false); + bool loginSucceeded = false; + bool skipLoop = false; + nsAutoString password; + nsAutoCString userName; + + // If remains false when authentication is complete it means that a + // capability response didn't occur within the authentication response so + // capabilities will be requested explicitly. + m_capabilityResponseOccurred = false; + + nsresult rv = ChooseAuthMethod(); + if (NS_FAILED(rv)) // all methods failed + { + // are there any matching login schemes at all? + if (!(GetServerStateParser().GetCapabilityFlag() & m_prefAuthMethods)) { + // Pref doesn't match server. Now, find an appropriate error msg. + + // pref has plaintext pw & server claims to support encrypted pw + if (m_prefAuthMethods == + (kHasAuthOldLoginCapability | kHasAuthLoginCapability | + kHasAuthPlainCapability) && + GetServerStateParser().GetCapabilityFlag() & kHasCRAMCapability) + // tell user to change to encrypted pw + AlertUserEventUsingName("imapAuthChangePlainToEncrypt"); + // pref has encrypted pw & server claims to support plaintext pw + else if (m_prefAuthMethods == kHasCRAMCapability && + GetServerStateParser().GetCapabilityFlag() & + (kHasAuthOldLoginCapability | kHasAuthLoginCapability | + kHasAuthPlainCapability)) { + // have SSL + if (m_socketType == nsMsgSocketType::SSL || + m_socketType == nsMsgSocketType::alwaysSTARTTLS) + // tell user to change to plaintext pw + AlertUserEventUsingName("imapAuthChangeEncryptToPlainSSL"); + else + // tell user to change to plaintext pw, with big warning + AlertUserEventUsingName("imapAuthChangeEncryptToPlainNoSSL"); + } else + // just "change auth method" + AlertUserEventUsingName("imapAuthMechNotSupported"); + + skipLoop = true; + } else { + // try to reset failed methods and try them again + ResetAuthMethods(); + rv = ChooseAuthMethod(); + if (NS_FAILED(rv)) // all methods failed + { + MOZ_LOG(IMAP, LogLevel::Error, + ("huch? there are auth methods, and we reset failed ones, but " + "ChooseAuthMethod still fails.")); + return false; + } + } + } + + // Check the uri host for localhost indicators to see if we + // should bypass the SSL check for clientid. + // Unfortunately we cannot call IsOriginPotentiallyTrustworthy + // here because it can only be called from the main thread. + bool isLocalhostConnection = false; + if (m_mockChannel) { + nsCOMPtr<nsIURI> uri; + m_mockChannel->GetURI(getter_AddRefs(uri)); + if (uri) { + nsCString uriHost; + uri->GetHost(uriHost); + if (uriHost.Equals("127.0.0.1") || uriHost.Equals("::1") || + uriHost.Equals("localhost")) { + isLocalhostConnection = true; + } + } + } + + // Whether our connection can be considered 'secure' and whether + // we should allow the CLIENTID to be sent over this channel. + bool isSecureConnection = + (m_connectionType.EqualsLiteral("starttls") || + m_connectionType.EqualsLiteral("ssl") || isLocalhostConnection); + + // Before running the ClientID command we check for clientid + // support by checking the server capability flags for the + // flag kHasClientIDCapability. + // We check that the m_clientId string is not empty, and + // we ensure the connection can be considered secure. + if ((GetServerStateParser().GetCapabilityFlag() & kHasClientIDCapability) && + !m_clientId.IsEmpty() && isSecureConnection) { + rv = ClientID(); + if (NS_FAILED(rv)) { + MOZ_LOG(IMAP, LogLevel::Error, + ("TryToLogon: Could not issue CLIENTID command")); + skipLoop = true; + } + } + + // Get username, either the stored one or from user + rv = m_imapServerSink->GetLoginUsername(userName); + if (NS_FAILED(rv) || userName.IsEmpty()) { + // The user hit "Cancel" on the dialog box + skipLoop = true; + } + + // clang-format off + /* + * Login can fail for various reasons: + * 1. Server claims to support GSSAPI, but it really doesn't. + * Or the client doesn't support GSSAPI, or is not logged in yet. + * (GSSAPI is a mechanism without password in apps). + * 2. Server claims to support CRAM-MD5, but it's broken and will fail despite correct password. + * 2.1. Some servers say they support CRAM but are so badly broken that trying it causes + * all subsequent login attempts to fail during this connection (bug 231303). + * So we use CRAM/NTLM/MSN only if enabled in prefs. + * Update: if it affects only some ISPs, we can maybe use the ISP DB + * and disable CRAM specifically for these. + * 3. Prefs are set to require auth methods which the server doesn't support + * (per CAPS or we tried and they failed). + * 4. User provided wrong password. + * 5. We tried too often and the server shut us down, so even a correct attempt + * will now (currently) fail. + * The above problems may overlap, e.g. 3. with 1. and 2., and we can't differentiate + * between 2. and 4., which is really unfortunate. + */ + // clang-format on + + bool newPasswordRequested = false; + // remember the msgWindow before we start trying to logon, because if the + // server drops the connection on errors, TellThreadToDie will null out the + // protocolsink and we won't be able to get the msgWindow. + AutoProxyReleaseMsgWindow msgWindow; + GetMsgWindow(getter_AddRefs(msgWindow)); + + // This loops over 1) auth methods (only one per loop) and 2) password tries + // (with UI) + while (!loginSucceeded && !skipLoop && !DeathSignalReceived()) { + // Get password + if (m_currentAuthMethod != + kHasAuthGssApiCapability && // GSSAPI uses no pw in apps + m_currentAuthMethod != kHasAuthExternalCapability && + m_currentAuthMethod != kHasXOAuth2Capability && + m_currentAuthMethod != kHasAuthNoneCapability) { + rv = GetPassword(password, newPasswordRequested); + newPasswordRequested = false; + if (rv == NS_MSG_PASSWORD_PROMPT_CANCELLED || NS_FAILED(rv)) { + MOZ_LOG(IMAP, LogLevel::Error, + ("IMAP: password prompt failed or user canceled it")); + break; + } + MOZ_LOG(IMAP, LogLevel::Debug, ("got new password")); + } + + bool lastReportingErrors = GetServerStateParser().GetReportingErrors(); + GetServerStateParser().SetReportingErrors( + false); // turn off errors - we'll put up our own. + + rv = AuthLogin(userName.get(), password, m_currentAuthMethod); + + GetServerStateParser().SetReportingErrors( + lastReportingErrors); // restore error reports + loginSucceeded = NS_SUCCEEDED(rv); + + if (!loginSucceeded) { + MOZ_LOG(IMAP, LogLevel::Debug, ("authlogin failed")); + MarkAuthMethodAsFailed(m_currentAuthMethod); + rv = ChooseAuthMethod(); // change m_currentAuthMethod to try other one + // next round + + if (NS_FAILED(rv)) // all methods failed + { + if (m_prefAuthMethods == kHasAuthGssApiCapability) { + // GSSAPI failed, and it's the only available method, + // and it's password-less, so nothing left to do. + AlertUserEventUsingName("imapAuthGssapiFailed"); + break; + } + + if (m_prefAuthMethods & kHasXOAuth2Capability) { + // OAuth2 failed. Entering password does not help. + AlertUserEventUsingName("imapOAuth2Error"); + break; + } + + // The reason that we failed might be a wrong password, so + // ask user what to do + MOZ_LOG(IMAP, LogLevel::Warning, + ("IMAP: ask user what to do (after login failed): new " + "passwort, retry, cancel")); + if (!m_imapServerSink) break; + // if there's no msg window, don't forget the password + if (!msgWindow) break; + int32_t buttonPressed = 1; + rv = m_imapServerSink->PromptLoginFailed(msgWindow, &buttonPressed); + if (NS_FAILED(rv)) break; + if (buttonPressed == 2) // 'New password' button + { + MOZ_LOG(IMAP, LogLevel::Warning, ("new password button pressed.")); + // Forget the current password + password.Truncate(); + m_hostSessionList->SetPasswordForHost(GetImapServerKey(), + EmptyString()); + m_imapServerSink->ForgetPassword(); + m_password.Truncate(); + MOZ_LOG(IMAP, LogLevel::Warning, ("password reset (nulled)")); + newPasswordRequested = true; + // Will call GetPassword() in beginning of next loop + + // Try all possible auth methods again with the new password. + ResetAuthMethods(); + } else if (buttonPressed == 0) // Retry button + { + MOZ_LOG(IMAP, LogLevel::Warning, ("retry button pressed")); + // Try all possible auth methods again + ResetAuthMethods(); + } else if (buttonPressed == 1) // Cancel button + { + MOZ_LOG(IMAP, LogLevel::Warning, ("cancel button pressed")); + break; // Abort quickly + } + + // TODO what is this for? When does it get set to != unknown again? + m_currentBiffState = nsIMsgFolder::nsMsgBiffState_Unknown; + SendSetBiffIndicatorEvent(m_currentBiffState); + } // all methods failed + } // login failed + } // while + + if (loginSucceeded) { + MOZ_LOG(IMAP, LogLevel::Debug, ("login succeeded")); + bool passwordAlreadyVerified; + m_hostSessionList->SetPasswordForHost(GetImapServerKey(), password); + rv = m_hostSessionList->GetPasswordVerifiedOnline(GetImapServerKey(), + passwordAlreadyVerified); + if (NS_SUCCEEDED(rv) && !passwordAlreadyVerified) { + // First successful login for this server/host during this session. + m_hostSessionList->SetPasswordVerifiedOnline(GetImapServerKey()); + } + + bool imapPasswordIsNew = !passwordAlreadyVerified; + if (imapPasswordIsNew) { + if (m_currentBiffState == nsIMsgFolder::nsMsgBiffState_Unknown) { + m_currentBiffState = nsIMsgFolder::nsMsgBiffState_NoMail; + SendSetBiffIndicatorEvent(m_currentBiffState); + } + m_imapServerSink->SetUserAuthenticated(true); + } + + nsImapAction imapAction; + m_runningUrl->GetImapAction(&imapAction); + // We don't want to do any more processing if we're just + // verifying the ability to logon because it can leave us in + // a half-constructed state. + if (imapAction != nsIImapUrl::nsImapVerifylogon) + ProcessAfterAuthenticated(); + } else // login failed + { + MOZ_LOG(IMAP, LogLevel::Error, ("login failed entirely")); + m_currentBiffState = nsIMsgFolder::nsMsgBiffState_Unknown; + SendSetBiffIndicatorEvent(m_currentBiffState); + HandleCurrentUrlError(); + SetConnectionStatus(NS_ERROR_FAILURE); // stop netlib + } + + return loginSucceeded; +} + +void nsImapProtocol::UpdateFolderQuotaData(nsImapQuotaAction aAction, + nsCString& aQuotaRoot, + uint64_t aUsed, uint64_t aMax) { + NS_ASSERTION(m_imapMailFolderSink, "m_imapMailFolderSink is null!"); + + m_imapMailFolderSink->SetFolderQuotaData(aAction, aQuotaRoot, aUsed, aMax); +} + +void nsImapProtocol::GetQuotaDataIfSupported(const char* aBoxName) { + // If server doesn't have quota support, don't do anything + if (!(GetServerStateParser().GetCapabilityFlag() & kQuotaCapability)) return; + + nsCString escapedName; + CreateEscapedMailboxName(aBoxName, escapedName); + + IncrementCommandTagNumber(); + + nsAutoCString quotacommand(GetServerCommandTag()); + quotacommand.AppendLiteral(" getquotaroot \""); + quotacommand.Append(escapedName); + quotacommand.AppendLiteral("\"" CRLF); + + NS_ASSERTION(m_imapMailFolderSink, "m_imapMailFolderSink is null!"); + if (m_imapMailFolderSink) + m_imapMailFolderSink->SetFolderQuotaCommandIssued(true); + + nsresult quotarv = SendData(quotacommand.get()); + if (NS_SUCCEEDED(quotarv)) + ParseIMAPandCheckForNewMail(nullptr, true); // don't display errors. +} + +bool nsImapProtocol::GetDeleteIsMoveToTrash() { + bool rv = false; + NS_ASSERTION(m_hostSessionList, "fatal... null host session list"); + if (m_hostSessionList) + m_hostSessionList->GetDeleteIsMoveToTrashForHost(GetImapServerKey(), rv); + return rv; +} + +bool nsImapProtocol::GetShowDeletedMessages() { + bool rv = false; + if (m_hostSessionList) + m_hostSessionList->GetShowDeletedMessagesForHost(GetImapServerKey(), rv); + return rv; +} + +bool nsImapProtocol::CheckNeeded() { + if (m_flagChangeCount >= kFlagChangesBeforeCheck) return true; + + int32_t deltaInSeconds; + + PRTime2Seconds(PR_Now() - m_lastCheckTime, &deltaInSeconds); + + return (deltaInSeconds >= kMaxSecondsBeforeCheck); +} + +bool nsImapProtocol::UseCondStore() { + // Check that the server is capable of cond store, and the user + // hasn't disabled the use of constore for this server. + return m_useCondStore && + GetServerStateParser().GetCapabilityFlag() & kHasCondStoreCapability && + GetServerStateParser().fUseModSeq; +} + +bool nsImapProtocol::UseCompressDeflate() { + // Check that the server is capable of compression, and the user + // hasn't disabled the use of compression for this server. + return m_useCompressDeflate && GetServerStateParser().GetCapabilityFlag() & + kHasCompressDeflateCapability; +} + +////////////////////////////////////////////////////////////////////////////////////////////// +// The following is the implementation of nsImapMockChannel and an intermediary +// imap steam listener. The stream listener is used to make a clean binding +// between the imap mock channel and the memory cache channel (if we are reading +// from the cache) +// Used by both offline storage "cache" and by the system cache called "cache2". +////////////////////////////////////////////////////////////////////////////////////////////// + +// WARNING: the cache stream listener is intended to be accessed from the UI +// thread! it will NOT create another proxy for the stream listener that gets +// passed in... +class nsImapCacheStreamListener : public nsIStreamListener { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIREQUESTOBSERVER + NS_DECL_NSISTREAMLISTENER + + nsImapCacheStreamListener(); + + nsresult Init(nsIStreamListener* aStreamListener, + nsIImapMockChannel* aMockChannelToUse, bool cache2 = false); + + protected: + virtual ~nsImapCacheStreamListener(); + nsCOMPtr<nsIImapMockChannel> mChannelToUse; + nsCOMPtr<nsIStreamListener> mListener; + bool mCache2; // Initialized for cache2 usage + bool mStarting; // Used with cache2. Indicates 1st data segment is read. + + private: + static bool mGoodCache2; + static const uint32_t kPeekBufSize; + static nsresult Peeker(nsIInputStream* aInStr, void* aClosure, + const char* aBuffer, uint32_t aOffset, uint32_t aCount, + uint32_t* aCountWritten); +}; + +NS_IMPL_ADDREF(nsImapCacheStreamListener) +NS_IMPL_RELEASE(nsImapCacheStreamListener) + +NS_INTERFACE_MAP_BEGIN(nsImapCacheStreamListener) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIStreamListener) + NS_INTERFACE_MAP_ENTRY(nsIRequestObserver) + NS_INTERFACE_MAP_ENTRY(nsIStreamListener) +NS_INTERFACE_MAP_END + +nsImapCacheStreamListener::nsImapCacheStreamListener() {} +bool nsImapCacheStreamListener::mGoodCache2 = false; +const uint32_t nsImapCacheStreamListener::kPeekBufSize = 101; + +nsImapCacheStreamListener::~nsImapCacheStreamListener() { mStarting = true; } + +nsresult nsImapCacheStreamListener::Init(nsIStreamListener* aStreamListener, + nsIImapMockChannel* aMockChannelToUse, + bool aCache2 /*false*/) { + NS_ENSURE_ARG(aStreamListener); + NS_ENSURE_ARG(aMockChannelToUse); + + mChannelToUse = aMockChannelToUse; + mListener = aStreamListener; + mCache2 = aCache2; + mStarting = true; + + return NS_OK; +} + +NS_IMETHODIMP +nsImapCacheStreamListener::OnStartRequest(nsIRequest* request) { + if (!mChannelToUse) { + NS_ERROR("OnStartRequest called after OnStopRequest"); + return NS_ERROR_NULL_POINTER; + } + if (!mCache2 || !mStarting) { + return mListener->OnStartRequest(mChannelToUse); + } + return NS_OK; +} + +NS_IMETHODIMP +nsImapCacheStreamListener::OnStopRequest(nsIRequest* request, + nsresult aStatus) { + if (!mListener) { + NS_ERROR("OnStopRequest called twice"); + return NS_ERROR_NULL_POINTER; + } + + nsresult rv = NS_OK; + if (!mCache2 || !mStarting) { + rv = mListener->OnStopRequest(mChannelToUse, aStatus); + + mListener = nullptr; + mChannelToUse->Close(); + mChannelToUse = nullptr; + } + return rv; +} + +/* + * Called when cache2 is in effect on first available data segment returned + * to check that cache entry looks like it it a valid email header. With + * cache2 memory cache this could be done synchronously. But with disk cache + * it can only be done asynchronously like this. + * Note: If NS_OK returned, the peeked at bytes are consumed here and not passed + * on to the listener so a special return value is used. + */ +nsresult nsImapCacheStreamListener::Peeker(nsIInputStream* aInStr, + void* aClosure, const char* aBuffer, + uint32_t aOffset, uint32_t aCount, + uint32_t* aCountWritten) { + char peekBuf[kPeekBufSize]; + aCount = aCount > sizeof peekBuf ? sizeof peekBuf : aCount; + memcpy(peekBuf, aBuffer, aCount); + peekBuf[aCount] = 0; // Null terminate the starting header data. + int32_t findPos = MsgFindCharInSet(nsDependentCString(peekBuf), ":\n\r", 0); + // Check that the first line is a header line, i.e., with a ':' in it + // Or that it begins with "From " because some IMAP servers allow that, + // even though it's technically invalid. + mGoodCache2 = ((findPos != -1 && peekBuf[findPos] == ':') || + !(strncmp(peekBuf, "From ", 5))); + return NS_BASE_STREAM_WOULD_BLOCK; // So stream buffer not "consumed" +} + +NS_IMETHODIMP +nsImapCacheStreamListener::OnDataAvailable(nsIRequest* request, + nsIInputStream* aInStream, + uint64_t aSourceOffset, + uint32_t aCount) { + if (mCache2 && mStarting) { + // Peeker() does check of leading bytes and sets mGoodCache2. + uint32_t numRead; + aInStream->ReadSegments(Peeker, nullptr, kPeekBufSize - 1, &numRead); + MOZ_LOG(IMAPCache, LogLevel::Debug, + ("%s: mGoodCache2=%d(bool)", __func__, mGoodCache2)); + + if (mGoodCache2) { + // Do deferred setup of loadGroup and OnStartRequest and then forward + // the verified first segment to the actual listener. + mStarting = false; + mListener->OnStartRequest(mChannelToUse); + } else { + MOZ_LOG(IMAPCache, LogLevel::Error, + ("%s: cache entry bad so just read imap here", __func__)); + mChannelToUse->ReadFromImapConnection(); + return NS_ERROR_FAILURE; // no more starts, one more stop occurs + } + } + // Forward the segment to the actual listener. + return mListener->OnDataAvailable(mChannelToUse, aInStream, aSourceOffset, + aCount); +} + +NS_IMPL_ISUPPORTS_INHERITED(nsImapMockChannel, nsHashPropertyBag, + nsIImapMockChannel, nsIMailChannel, nsIChannel, + nsIRequest, nsICacheEntryOpenCallback, + nsITransportEventSink, nsISupportsWeakReference) + +nsImapMockChannel::nsImapMockChannel() + : mSuspendedMonitor("nsImapMockChannel"), mSuspended(false) { + m_cancelStatus = NS_OK; + mLoadFlags = 0; + mChannelClosed = false; + mReadingFromCache = false; + mContentLength = mozilla::dom::InternalResponse::UNKNOWN_BODY_SIZE; + mContentDisposition = nsIChannel::DISPOSITION_INLINE; + mWritingToCache = false; +} + +nsImapMockChannel::~nsImapMockChannel() { + // if we're offline, we may not get to close the channel correctly. + // we need to do this to send the url state change notification in + // the case of mem and disk cache reads. + MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread(), + "should only access mock channel on ui thread"); + if (!mChannelClosed) Close(); +} + +nsresult nsImapMockChannel::NotifyStartEndReadFromCache(bool start) { + nsresult rv = NS_OK; + mReadingFromCache = start; + nsCOMPtr<nsIImapUrl> imapUrl = do_QueryInterface(m_url, &rv); + nsCOMPtr<nsIImapProtocol> imapProtocol = do_QueryReferent(mProtocol); + if (imapUrl) { + nsCOMPtr<nsIImapMailFolderSink> folderSink; + rv = imapUrl->GetImapMailFolderSink(getter_AddRefs(folderSink)); + if (folderSink) { + nsCOMPtr<nsIMsgMailNewsUrl> mailUrl = do_QueryInterface(m_url); + rv = folderSink->SetUrlState(nullptr /* we don't know the protocol */, + mailUrl, start, false, m_cancelStatus); + + // Required for killing ImapProtocol thread + if (NS_FAILED(m_cancelStatus) && imapProtocol) + imapProtocol->TellThreadToDie(false); + } + } + return rv; +} + +NS_IMETHODIMP nsImapMockChannel::Close() { + if (mReadingFromCache) + NotifyStartEndReadFromCache(false); + else { + nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_url); + if (mailnewsUrl) { + nsCOMPtr<nsICacheEntry> cacheEntry; + mailnewsUrl->GetMemCacheEntry(getter_AddRefs(cacheEntry)); + if (cacheEntry) cacheEntry->MarkValid(); + // remove the channel from the load group + nsCOMPtr<nsILoadGroup> loadGroup; + GetLoadGroup(getter_AddRefs(loadGroup)); + // if the mock channel wasn't initialized with a load group then + // use our load group (they may differ) + if (!loadGroup) mailnewsUrl->GetLoadGroup(getter_AddRefs(loadGroup)); + if (loadGroup) + loadGroup->RemoveRequest((nsIRequest*)this, nullptr, NS_OK); + } + } + + m_channelListener = nullptr; + mChannelClosed = true; + return NS_OK; +} + +NS_IMETHODIMP nsImapMockChannel::GetProgressEventSink( + nsIProgressEventSink** aProgressEventSink) { + NS_IF_ADDREF(*aProgressEventSink = mProgressEventSink); + return NS_OK; +} + +NS_IMETHODIMP nsImapMockChannel::SetProgressEventSink( + nsIProgressEventSink* aProgressEventSink) { + mProgressEventSink = aProgressEventSink; + return NS_OK; +} + +NS_IMETHODIMP nsImapMockChannel::GetChannelListener( + nsIStreamListener** aChannelListener) { + NS_IF_ADDREF(*aChannelListener = m_channelListener); + return NS_OK; +} + +// now implement our mock implementation of the channel interface...we forward +// all calls to the real channel if we have one...otherwise we return something +// bogus... + +NS_IMETHODIMP nsImapMockChannel::SetLoadGroup(nsILoadGroup* aLoadGroup) { + m_loadGroup = aLoadGroup; + return NS_OK; +} + +NS_IMETHODIMP nsImapMockChannel::GetLoadGroup(nsILoadGroup** aLoadGroup) { + NS_IF_ADDREF(*aLoadGroup = m_loadGroup); + return NS_OK; +} + +NS_IMETHODIMP nsImapMockChannel::GetTRRMode(nsIRequest::TRRMode* aTRRMode) { + return GetTRRModeImpl(aTRRMode); +} + +NS_IMETHODIMP nsImapMockChannel::SetTRRMode(nsIRequest::TRRMode aTRRMode) { + return SetTRRModeImpl(aTRRMode); +} + +NS_IMETHODIMP nsImapMockChannel::GetLoadInfo(nsILoadInfo** aLoadInfo) { + NS_IF_ADDREF(*aLoadInfo = m_loadInfo); + return NS_OK; +} + +NS_IMETHODIMP nsImapMockChannel::SetLoadInfo(nsILoadInfo* aLoadInfo) { + m_loadInfo = aLoadInfo; + return NS_OK; +} + +NS_IMETHODIMP nsImapMockChannel::GetOriginalURI(nsIURI** aURI) { + // IMap does not seem to have the notion of an original URI :-( + // *aURI = m_originalUrl ? m_originalUrl : m_url; + NS_IF_ADDREF(*aURI = m_url); + return NS_OK; +} + +NS_IMETHODIMP nsImapMockChannel::SetOriginalURI(nsIURI* aURI) { + // IMap does not seem to have the notion of an original URI :-( + // MOZ_ASSERT_UNREACHABLE("nsImapMockChannel::SetOriginalURI"); + // return NS_ERROR_NOT_IMPLEMENTED; + return NS_OK; // ignore +} + +NS_IMETHODIMP nsImapMockChannel::GetURI(nsIURI** aURI) { + NS_IF_ADDREF(*aURI = m_url); + return NS_OK; +} + +NS_IMETHODIMP nsImapMockChannel::SetURI(nsIURI* aURI) { + m_url = aURI; +#ifdef DEBUG_bienvenu + if (!aURI) printf("Clearing URI\n"); +#endif + if (m_url) { + // if we don't have a progress event sink yet, get it from the url for + // now... + nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_url); + if (mailnewsUrl && !mProgressEventSink) { + nsCOMPtr<nsIMsgStatusFeedback> statusFeedback; + mailnewsUrl->GetStatusFeedback(getter_AddRefs(statusFeedback)); + mProgressEventSink = do_QueryInterface(statusFeedback); + } + // If this is a fetch URL and we can, get the message size from the message + // header and set it to be the content length. + // Note that for an attachment URL, this will set the content length to be + // equal to the size of the entire message. + nsCOMPtr<nsIImapUrl> imapUrl(do_QueryInterface(m_url)); + nsImapAction imapAction; + imapUrl->GetImapAction(&imapAction); + if (imapAction == nsIImapUrl::nsImapMsgFetch) { + nsCOMPtr<nsIMsgMessageUrl> msgUrl(do_QueryInterface(m_url)); + if (msgUrl) { + nsCOMPtr<nsIMsgDBHdr> msgHdr; + // A failure to get a message header isn't an error + msgUrl->GetMessageHeader(getter_AddRefs(msgHdr)); + if (msgHdr) { + uint32_t messageSize; + if (NS_SUCCEEDED(msgHdr->GetMessageSize(&messageSize))) + SetContentLength(messageSize); + } + } + } + } + return NS_OK; +} + +NS_IMETHODIMP nsImapMockChannel::Open(nsIInputStream** _retval) { + nsCOMPtr<nsIStreamListener> listener; + nsresult rv = + nsContentSecurityManager::doContentSecurityCheck(this, listener); + NS_ENSURE_SUCCESS(rv, rv); + if (m_url) { + nsCOMPtr<nsIImapUrl> imapUrl(do_QueryInterface(m_url, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + nsImapAction imapAction; + imapUrl->GetImapAction(&imapAction); + // If we're shutting down, and not running the kinds of urls we run at + // shutdown, then this should fail because running urls during + // shutdown will very likely fail and potentially hang. + nsCOMPtr<nsIMsgAccountManager> accountMgr = + do_GetService("@mozilla.org/messenger/account-manager;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + bool shuttingDown = false; + (void)accountMgr->GetShutdownInProgress(&shuttingDown); + if (shuttingDown && imapAction != nsIImapUrl::nsImapExpungeFolder && + imapAction != nsIImapUrl::nsImapDeleteAllMsgs && + imapAction != nsIImapUrl::nsImapDeleteFolder) + return NS_ERROR_FAILURE; + } + return NS_ImplementChannelOpen(this, _retval); +} + +NS_IMETHODIMP +nsImapMockChannel::OnCacheEntryAvailable(nsICacheEntry* entry, bool aNew, + nsresult status) { + if (MOZ_LOG_TEST(IMAPCache, LogLevel::Debug)) { + MOZ_LOG(IMAPCache, LogLevel::Debug, + ("%s: Create/write new cache entry=%s", __func__, + aNew ? "true" : "false")); + if (NS_SUCCEEDED(status)) { + nsAutoCString key; + entry->GetKey(key); + MOZ_LOG(IMAPCache, LogLevel::Debug, + ("%s: Cache entry key = |%s|", __func__, key.get())); + } + } + + // make sure we didn't close the channel before the async call back came in... + // hmmm....if we had write access and we canceled this mock channel then I + // wonder if we should be invalidating the cache entry before kicking out... + if (mChannelClosed) { + if (NS_SUCCEEDED(status)) { + entry->AsyncDoom(nullptr); + } + return NS_OK; + } + + if (!m_url) { + // Something has gone terribly wrong. + NS_WARNING("m_url is null in OnCacheEntryAvailable"); + return Cancel(NS_ERROR_UNEXPECTED); + } + + do { + // For "normal" read/write access we always see status == NS_OK here. aNew + // indicates whether the cache entry is new and needs to be written, or not + // new and can be read. If AsyncOpenURI() was called with access read-only, + // status == NS_ERROR_CACHE_KEY_NOT_FOUND can be received here and we just + // read the data directly from imap. + if (NS_FAILED(status)) { + MOZ_LOG(IMAPCache, LogLevel::Debug, + ("%s: status parameter bad, preference " + "browser.cache.memory.enable not true?", + __func__)); + break; + } + + nsresult rv = NS_OK; + nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_url, &rv); + mailnewsUrl->SetMemCacheEntry(entry); + + if (aNew) { + // Writing cache so insert a "stream listener Tee" into the stream from + // the imap fetch to direct the message into the cache and to our current + // channel listener. But first get the size of the message to be fetched. + // If message too big to fit in cache, the message just goes to the + // stream listener. If unable to get the size, messageSize remains 0 so + // assume it fits in cache, right or wrong. + uint32_t messageSize = 0; + nsCOMPtr<nsIMsgMessageUrl> msgUrl(do_QueryInterface(m_url)); + if (msgUrl) { + nsCOMPtr<nsIMsgDBHdr> msgHdr; + msgUrl->GetMessageHeader(getter_AddRefs(msgHdr)); + if (msgHdr) { + msgHdr->GetMessageSize(&messageSize); + MOZ_LOG(IMAPCache, LogLevel::Debug, + ("%s: messageSize=%d", __func__, messageSize)); + } else + MOZ_LOG(IMAPCache, LogLevel::Debug, + ("%s: Can't get msgHdr", __func__)); + } + // Check if message fits in a cache entry. If too big, or if unable to + // create or initialize the tee or open the stream to the entry, will + // fall thought and only do ReadFromImapConnection() called below and the + // message will not be cached. + bool tooBig = + net::CacheObserver::EntryIsTooBig(messageSize, gUseDiskCache2); + if (!tooBig) { + // Message fits in cache. Create the tee. + nsCOMPtr<nsIStreamListenerTee> tee = + do_CreateInstance(NS_STREAMLISTENERTEE_CONTRACTID, &rv); + if (NS_SUCCEEDED(rv)) { + nsCOMPtr<nsIOutputStream> out; + rv = entry->OpenOutputStream(0, -1, getter_AddRefs(out)); + if (NS_SUCCEEDED(rv)) { + rv = tee->Init(m_channelListener, out, nullptr); + m_channelListener = tee; + } else + NS_WARNING( + "IMAP Protocol failed to open output stream to Necko cache"); + } + } + if (tooBig || NS_FAILED(rv)) { + // Need this so next OpenCacheEntry() triggers OnCacheEntryAvailable() + // since nothing was actually written to cache. Without this there is no + // response to next OpenCacheEntry call. + entry->AsyncDoom(nullptr); + MOZ_LOG(IMAPCache, LogLevel::Debug, + ("%s: Not writing to cache, msg too big or other errors", + __func__)); + } else { + mWritingToCache = true; + MOZ_LOG(IMAPCache, LogLevel::Debug, + ("%s: Begin cache WRITE", __func__)); + } + } else { + // We are reading cache (!aNew) + mWritingToCache = false; + if (MOZ_LOG_TEST(IMAPCache, LogLevel::Debug)) { + int64_t size = 0; + rv = entry->GetDataSize(&size); + if (rv == NS_ERROR_IN_PROGRESS) + MOZ_LOG(IMAPCache, LogLevel::Debug, + ("%s: Concurrent cache READ, no size available", __func__)); + MOZ_LOG(IMAPCache, LogLevel::Debug, + ("%s: Begin cache READ, size=%" PRIi64, __func__, size)); + } + rv = ReadFromCache2(entry); + if (NS_SUCCEEDED(rv)) { + NotifyStartEndReadFromCache(true); + entry->MarkValid(); + return NS_OK; // Return here since reading from the cache succeeded. + } + entry->AsyncDoom(nullptr); // Doom entry if we failed to read from cache. + mailnewsUrl->SetMemCacheEntry( + nullptr); // We aren't going to be reading from the cache. + } + } while (false); + + // If reading from the cache failed or if we are writing into the cache, or if + // or message is too big for cache or other errors occur, do + // ReadFromImapConnection to fetch message from imap server. + if (!aNew) + MOZ_LOG(IMAPCache, LogLevel::Debug, + ("%s: Cache READ failed so read from imap", __func__)); + return ReadFromImapConnection(); +} + +NS_IMETHODIMP +nsImapMockChannel::OnCacheEntryCheck(nsICacheEntry* entry, uint32_t* aResult) { + *aResult = nsICacheEntryOpenCallback::ENTRY_WANTED; + + // Check concurrent read: We can't read concurrently since we don't know + // that the entry will ever be written successfully. It may be aborted + // due to a size limitation. If reading concurrently, the following function + // will return NS_ERROR_IN_PROGRESS. Then we tell the cache to wait until + // the write is finished. + int64_t size = 0; + nsresult rv = entry->GetDataSize(&size); + if (rv == NS_ERROR_IN_PROGRESS) { + *aResult = nsICacheEntryOpenCallback::RECHECK_AFTER_WRITE_FINISHED; + MOZ_LOG(IMAPCache, LogLevel::Debug, + ("OnCacheEntryCheck(): Attempted cache write while reading, will " + "try again")); + } + return NS_OK; +} + +nsresult nsImapMockChannel::OpenCacheEntry() { + nsresult rv; + if (!gCache2Storage) { + // Only need to do this once since cache2 is used by all accounts and + // folders. Get the cache storage object from the imap service. + nsCOMPtr<nsIImapService> imapService = + do_GetService("@mozilla.org/messenger/imapservice;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + // Obtain the cache storage object used by all channels in this session. + // This will return disk cache (default) or memory cache as determined by + // the boolean pref "mail.imap.use_disk_cache2" + rv = imapService->GetCacheStorage(getter_AddRefs(gCache2Storage)); + NS_ENSURE_SUCCESS(rv, rv); + MOZ_LOG(IMAPCache, LogLevel::Debug, + ("%s: Obtained storage obj for |%s| cache2", __func__, + gUseDiskCache2 ? "disk" : "mem")); + } + + int32_t uidValidity = -1; + uint32_t cacheAccess = nsICacheStorage::OPEN_NORMALLY; + + nsCOMPtr<nsIImapUrl> imapUrl = do_QueryInterface(m_url, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIImapMailFolderSink> folderSink; + rv = imapUrl->GetImapMailFolderSink(getter_AddRefs(folderSink)); + if (folderSink) folderSink->GetUidValidity(&uidValidity); + + // If we're storing the message in the offline store, don't + // write/save to cache2 cache. (Not sure if this even happens!) + bool storeResultsOffline; + imapUrl->GetStoreResultsOffline(&storeResultsOffline); + if (storeResultsOffline) cacheAccess = nsICacheStorage::OPEN_READONLY; + + // clang-format off + MOZ_LOG(IMAPCache, LogLevel::Debug, + ("%s: For URL = |%s|", __func__, m_url->GetSpecOrDefault().get())); + // clang-format on + + // Use the uid validity as part of the cache key, so that if the uid validity + // changes, we won't re-use the wrong cache entries. + nsAutoCString extension; + extension.AppendInt(uidValidity, 16); + + // Open a cache entry where the key is the potentially modified URL. + nsAutoCString path; + m_url->GetPathQueryRef(path); + + // First we need to "normalise" the URL by extracting ?part= and &filename. + // The path should only contain: ?part=x.y&filename=file.ext + // These are seen in the wild: + // /;section=2?part=1.2&filename=A01.JPG + // ?section=2?part=1.2&filename=A01.JPG&type=image/jpeg&filename=A01.JPG + // ?part=1.2&type=image/jpeg&filename=IMG_C0030.jpg + // ?header=quotebody&part=1.2&filename=lijbmghmkilicioj.png + nsCString partQuery = MsgExtractQueryPart(path, "?part="); + if (partQuery.IsEmpty()) { + partQuery = MsgExtractQueryPart(path, "&part="); + if (!partQuery.IsEmpty()) { + // ? indicates a part query, so set the first character to that. + partQuery.SetCharAt('?', 0); + } + } + nsCString filenameQuery = MsgExtractQueryPart(path, "&filename="); + MOZ_LOG(IMAPCache, LogLevel::Debug, + ("%s: part = |%s|, filename = |%s|", __func__, partQuery.get(), + filenameQuery.get())); + + // Truncate path at either /; or ? + MsgRemoveQueryPart(path); + + nsCOMPtr<nsIURI> newUri; + rv = NS_MutateURI(m_url).SetPathQueryRef(path).Finalize(newUri); + NS_ENSURE_SUCCESS(rv, rv); + if (partQuery.IsEmpty()) { + // Not accessing a part but the whole message. + MOZ_LOG(IMAPCache, LogLevel::Debug, + ("%s: Call AsyncOpenURI on entire message", __func__)); + } else { + // Access just a part. Set up part extraction and read in the part from the + // whole cached message. Note: Parts are now never individually written to + // or read from cache. + SetupPartExtractorListener(imapUrl, m_channelListener); + MOZ_LOG(IMAPCache, LogLevel::Debug, + ("%s: Call AsyncOpenURI to read part from entire message cache", + __func__)); + } + return gCache2Storage->AsyncOpenURI(newUri, extension, cacheAccess, this); +} + +// Pumps content of cache2 entry to channel listener. If a part was +// requested in the original URL seen in OpenCacheEntry(), it will be extracted +// from the whole message by the channel listener. So to obtain a single part +// always requires reading the complete message from cache. +nsresult nsImapMockChannel::ReadFromCache2(nsICacheEntry* entry) { + NS_ENSURE_ARG(entry); + + bool useCacheEntry = true; + nsresult rv; + nsAutoCString entryKey; + + entry->GetKey(entryKey); + + // Compare cache entry size with message size. Init to an invalid value. + int64_t entrySize = -1; + + // We don't expect concurrent read here, so this call should always work. + rv = entry->GetDataSize(&entrySize); + + nsCOMPtr<nsIMsgMessageUrl> msgUrl(do_QueryInterface(m_url)); + if (msgUrl && NS_SUCCEEDED(rv)) { + nsCOMPtr<nsIMsgDBHdr> msgHdr; + // A failure to get a message header isn't an automatic error + msgUrl->GetMessageHeader(getter_AddRefs(msgHdr)); + if (msgHdr) { + uint32_t messageSize; + if (NS_SUCCEEDED(rv = msgHdr->GetMessageSize(&messageSize)) && + messageSize != entrySize) { + // clang-format off + MOZ_LOG(IMAP, LogLevel::Warning, + ("%s: Size mismatch for %s: message %" PRIu32 + ", cache %" PRIi64, + __func__, entryKey.get(), messageSize, entrySize)); + MOZ_LOG(IMAPCache, LogLevel::Debug, + ("%s: Size mismatch for %s: message %" PRIu32 + ", cache %" PRIi64, + __func__, entryKey.get(), messageSize, entrySize)); + // clang-format on + useCacheEntry = false; + } + } + } + // Cache entry is invalid if GetDataSize() or GetMessageSize failed or if + // otherwise unable to obtain the cache entry size. (Not sure if it's possible + // to have a 0 length cache entry but negative is definitely invalid.) + if (NS_FAILED(rv) || entrySize < 1) useCacheEntry = false; + + nsCOMPtr<nsIInputStream> ins; + if (useCacheEntry) { + if (NS_SUCCEEDED(rv = entry->OpenInputStream(0, getter_AddRefs(ins)))) { + uint64_t bytesAvailable = 0; + rv = ins->Available(&bytesAvailable); + // Note: bytesAvailable will usually be zero (at least for disk cache + // since only async access occurs) so don't check it. + if (NS_FAILED(rv)) { + MOZ_LOG(IMAPCache, LogLevel::Debug, + ("%s: Input stream for disk cache not useable", __func__)); + useCacheEntry = false; + } + } + } + + if (NS_SUCCEEDED(rv) && useCacheEntry) { + nsCOMPtr<nsIInputStreamPump> pump; + if (NS_SUCCEEDED( + rv = NS_NewInputStreamPump(getter_AddRefs(pump), ins.forget()))) { + // Create and use a cache listener object. + RefPtr<nsImapCacheStreamListener> cacheListener = + new nsImapCacheStreamListener(); + + cacheListener->Init(m_channelListener, this, true); + rv = pump->AsyncRead(cacheListener); + if (NS_SUCCEEDED(rv)) { + nsCOMPtr<nsIImapUrl> imapUrl = do_QueryInterface(m_url); + imapUrl->SetMsgLoadingFromCache(true); + // Set the cache entry's security info status as our security + // info status... + nsCOMPtr<nsITransportSecurityInfo> securityInfo; + entry->GetSecurityInfo(getter_AddRefs(securityInfo)); + SetSecurityInfo(securityInfo); + MOZ_LOG(IMAPCache, LogLevel::Debug, + ("%s: Cache entry accepted and being read", __func__)); + } // if AsyncRead succeeded. + } // new pump + } // if useCacheEntry + + if (!useCacheEntry || NS_FAILED(rv)) { + // Cache entry appears to be unusable. Return an error so will still attempt + // to read the data via just an imap fetch (the "old fashioned" way). + if (NS_SUCCEEDED(rv)) rv = NS_ERROR_FAILURE; + MOZ_LOG(IMAPCache, LogLevel::Debug, + ("%s: Cache entry rejected, returning error %" PRIx32, __func__, + static_cast<uint32_t>(rv))); + } + return rv; +} + +class nsReadFromImapConnectionFailure : public mozilla::Runnable { + public: + explicit nsReadFromImapConnectionFailure(nsImapMockChannel* aChannel) + : mozilla::Runnable("nsReadFromImapConnectionFailure"), + mImapMockChannel(aChannel) {} + + NS_IMETHOD Run() { + if (mImapMockChannel) { + mImapMockChannel->RunOnStopRequestFailure(); + } + return NS_OK; + } + + private: + RefPtr<nsImapMockChannel> mImapMockChannel; +}; + +nsresult nsImapMockChannel::RunOnStopRequestFailure() { + if (m_channelListener) { + m_channelListener->OnStopRequest(this, NS_MSG_ERROR_MSG_NOT_OFFLINE); + } + return NS_OK; +} + +// This is called when the message requested by the url isn't yet in offline +// store or not yet in cache. It is also called if the storage is corrupt. This +// creates an imap connection to process the url. This is usually called from +// the mock channel or possibly from nsImapCacheStreamListener::OnDataAvailable. +NS_IMETHODIMP nsImapMockChannel::ReadFromImapConnection() { + nsresult rv = NS_OK; + nsCOMPtr<nsIImapUrl> imapUrl = do_QueryInterface(m_url); + nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_url); + + bool localOnly = false; + imapUrl->GetLocalFetchOnly(&localOnly); + if (localOnly) { + // This will cause an OnStartRunningUrl, and the subsequent close + // will then cause an OnStopRunningUrl with the cancel status. + NotifyStartEndReadFromCache(true); + Cancel(NS_MSG_ERROR_MSG_NOT_OFFLINE); + + // Dispatch error notification, so ReadFromImapConnection() returns *before* + // the error is sent to the listener's OnStopRequest(). This avoids + // endless recursion where the caller relies on async execution. + nsCOMPtr<nsIRunnable> event = new nsReadFromImapConnectionFailure(this); + NS_DispatchToCurrentThread(event); + return NS_MSG_ERROR_MSG_NOT_OFFLINE; + } + + nsCOMPtr<nsILoadGroup> loadGroup; + GetLoadGroup(getter_AddRefs(loadGroup)); + if (!loadGroup) // if we don't have one, the url will snag one from the msg + // window... + mailnewsUrl->GetLoadGroup(getter_AddRefs(loadGroup)); + + // okay, add the mock channel to the load group.. + if (loadGroup) + loadGroup->AddRequest((nsIRequest*)this, nullptr /* context isupports */); + + // loading the url consists of asking the server to add the url to it's imap + // event queue.... + nsCOMPtr<nsIMsgIncomingServer> server; + rv = mailnewsUrl->GetServer(getter_AddRefs(server)); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr<nsIImapIncomingServer> imapServer(do_QueryInterface(server, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + // Assume AsyncRead is always called from the UI thread..... + return imapServer->GetImapConnectionAndLoadUrl(imapUrl, m_channelListener); +} + +// for messages stored in our offline cache, we have special code to handle +// that... If it's in the local cache, we return true and we can abort the +// download because this method does the rest of the work. +bool nsImapMockChannel::ReadFromLocalCache() { + nsresult rv = NS_OK; + + nsCOMPtr<nsIImapUrl> imapUrl = do_QueryInterface(m_url); + nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_url, &rv); + + bool useLocalCache = false; + mailnewsUrl->GetMsgIsInLocalCache(&useLocalCache); + if (!useLocalCache) { + return false; + } + + nsAutoCString messageIdString; + + SetupPartExtractorListener(imapUrl, m_channelListener); + + imapUrl->GetListOfMessageIds(messageIdString); + nsCOMPtr<nsIMsgFolder> folder; + rv = mailnewsUrl->GetFolder(getter_AddRefs(folder)); + NS_ENSURE_SUCCESS(rv, false); + if (!folder) { + return false; + } + // we want to create a file channel and read the msg from there. + nsMsgKey msgKey = strtoul(messageIdString.get(), nullptr, 10); + nsCOMPtr<nsIMsgDBHdr> hdr; + rv = folder->GetMessageHeader(msgKey, getter_AddRefs(hdr)); + NS_ENSURE_SUCCESS(rv, false); + nsCOMPtr<nsIInputStream> msgStream; + rv = folder->GetLocalMsgStream(hdr, getter_AddRefs(msgStream)); + NS_ENSURE_SUCCESS(rv, false); + // dougt - This may break the ablity to "cancel" a read from offline + // mail reading. fileChannel->SetLoadGroup(m_loadGroup); + RefPtr<nsImapCacheStreamListener> cacheListener = + new nsImapCacheStreamListener(); + cacheListener->Init(m_channelListener, this); + + // create a stream pump that will async read the message. + nsCOMPtr<nsIInputStreamPump> pump; + rv = NS_NewInputStreamPump(getter_AddRefs(pump), msgStream.forget()); + NS_ENSURE_SUCCESS(rv, false); + rv = pump->AsyncRead(cacheListener); + NS_ENSURE_SUCCESS(rv, false); + + // if the msg is unread, we should mark it read on the server. This lets + // the code running this url know we're loading from the cache, if it cares. + imapUrl->SetMsgLoadingFromCache(true); + return true; +} + +NS_IMETHODIMP nsImapMockChannel::AsyncOpen(nsIStreamListener* aListener) { + nsCOMPtr<nsIStreamListener> listener = aListener; + nsresult rv = + nsContentSecurityManager::doContentSecurityCheck(this, listener); + NS_ENSURE_SUCCESS(rv, rv); + + int32_t port; + if (!m_url) return NS_ERROR_NULL_POINTER; + rv = m_url->GetPort(&port); + if (NS_FAILED(rv)) return rv; + + rv = NS_CheckPortSafety(port, "imap"); + if (NS_FAILED(rv)) return rv; + + // set the stream listener and then load the url + NS_ASSERTION(!m_channelListener, "shouldn't already have a listener"); + m_channelListener = listener; + nsCOMPtr<nsIImapUrl> imapUrl(do_QueryInterface(m_url)); + + nsImapAction imapAction; + imapUrl->GetImapAction(&imapAction); + + bool externalLink = true; + imapUrl->GetExternalLinkUrl(&externalLink); + + if (externalLink) { + // for security purposes, only allow imap urls originating from external + // sources perform a limited set of actions. Currently the allowed set + // includes: 1) folder selection 2) message fetch 3) message part fetch + + if (!(imapAction == nsIImapUrl::nsImapSelectFolder || + imapAction == nsIImapUrl::nsImapMsgFetch || + imapAction == nsIImapUrl::nsImapOpenMimePart || + imapAction == nsIImapUrl::nsImapMsgFetchPeek)) + return NS_ERROR_FAILURE; // abort the running of this url....it failed a + // security check + } + + if (ReadFromLocalCache()) { + (void)NotifyStartEndReadFromCache(true); + return NS_OK; + } + + // okay, it's not in the local cache, now check the memory cache... + // but we can't download for offline use from the memory cache + if (imapAction != nsIImapUrl::nsImapMsgDownloadForOffline) { + rv = OpenCacheEntry(); + if (NS_SUCCEEDED(rv)) return rv; + } + + SetupPartExtractorListener(imapUrl, m_channelListener); + // if for some reason open cache entry failed then just default to opening an + // imap connection for the url + return ReadFromImapConnection(); +} + +nsresult nsImapMockChannel::SetupPartExtractorListener( + nsIImapUrl* aUrl, nsIStreamListener* aConsumer) { + // if the url we are loading refers to a specific part then we need + // libmime to extract that part from the message for us. + bool refersToPart = false; + aUrl->GetMimePartSelectorDetected(&refersToPart); + if (refersToPart) { + nsCOMPtr<nsIStreamConverterService> converter = + do_GetService("@mozilla.org/streamConverters;1"); + if (converter && aConsumer) { + nsCOMPtr<nsIStreamListener> newConsumer; + converter->AsyncConvertData("message/rfc822", "*/*", aConsumer, + static_cast<nsIChannel*>(this), + getter_AddRefs(newConsumer)); + if (newConsumer) m_channelListener = newConsumer; + } + } + + return NS_OK; +} + +NS_IMETHODIMP nsImapMockChannel::GetLoadFlags(nsLoadFlags* aLoadFlags) { + //*aLoadFlags = nsIRequest::LOAD_NORMAL; + *aLoadFlags = mLoadFlags; + return NS_OK; +} + +NS_IMETHODIMP nsImapMockChannel::SetLoadFlags(nsLoadFlags aLoadFlags) { + mLoadFlags = aLoadFlags; + return NS_OK; // don't fail when trying to set this +} + +NS_IMETHODIMP nsImapMockChannel::GetContentType(nsACString& aContentType) { + if (mContentType.IsEmpty()) { + nsImapAction imapAction = 0; + if (m_url) { + nsCOMPtr<nsIImapUrl> imapUrl = do_QueryInterface(m_url); + if (imapUrl) { + imapUrl->GetImapAction(&imapAction); + } + } + if (imapAction == nsIImapUrl::nsImapSelectFolder) + aContentType.AssignLiteral("x-application-imapfolder"); + else + aContentType.AssignLiteral("message/rfc822"); + } else + aContentType = mContentType; + return NS_OK; +} + +NS_IMETHODIMP nsImapMockChannel::SetContentType( + const nsACString& aContentType) { + nsAutoCString charset; + nsresult rv = + NS_ParseResponseContentType(aContentType, mContentType, charset); + if (NS_FAILED(rv) || mContentType.IsEmpty()) + mContentType.AssignLiteral(UNKNOWN_CONTENT_TYPE); + return rv; +} + +NS_IMETHODIMP nsImapMockChannel::GetContentCharset( + nsACString& aContentCharset) { + aContentCharset.Assign(mCharset); + return NS_OK; +} + +NS_IMETHODIMP nsImapMockChannel::SetContentCharset( + const nsACString& aContentCharset) { + mCharset.Assign(aContentCharset); + return NS_OK; +} + +NS_IMETHODIMP +nsImapMockChannel::GetContentDisposition(uint32_t* aContentDisposition) { + *aContentDisposition = mContentDisposition; + return NS_OK; +} + +NS_IMETHODIMP +nsImapMockChannel::SetContentDisposition(uint32_t aContentDisposition) { + mContentDisposition = aContentDisposition; + return NS_OK; +} + +NS_IMETHODIMP +nsImapMockChannel::GetContentDispositionFilename( + nsAString& aContentDispositionFilename) { + return NS_ERROR_NOT_AVAILABLE; +} + +NS_IMETHODIMP +nsImapMockChannel::SetContentDispositionFilename( + const nsAString& aContentDispositionFilename) { + return NS_ERROR_NOT_AVAILABLE; +} + +NS_IMETHODIMP +nsImapMockChannel::GetContentDispositionHeader( + nsACString& aContentDispositionHeader) { + return NS_ERROR_NOT_AVAILABLE; +} + +NS_IMETHODIMP nsImapMockChannel::GetContentLength(int64_t* aContentLength) { + *aContentLength = mContentLength; + return NS_OK; +} + +NS_IMETHODIMP +nsImapMockChannel::SetContentLength(int64_t aContentLength) { + mContentLength = aContentLength; + return NS_OK; +} + +NS_IMETHODIMP nsImapMockChannel::GetOwner(nsISupports** aPrincipal) { + NS_IF_ADDREF(*aPrincipal = mOwner); + return NS_OK; +} + +NS_IMETHODIMP nsImapMockChannel::SetOwner(nsISupports* aPrincipal) { + mOwner = aPrincipal; + return NS_OK; +} + +NS_IMETHODIMP nsImapMockChannel::GetSecurityInfo( + nsITransportSecurityInfo** aSecurityInfo) { + NS_IF_ADDREF(*aSecurityInfo = mSecurityInfo); + return NS_OK; +} + +NS_IMETHODIMP nsImapMockChannel::SetSecurityInfo( + nsITransportSecurityInfo* aSecurityInfo) { + mSecurityInfo = aSecurityInfo; + return NS_OK; +} + +NS_IMETHODIMP +nsImapMockChannel::GetIsDocument(bool* aIsDocument) { + return NS_GetIsDocumentChannel(this, aIsDocument); +} + +//////////////////////////////////////////////////////////////////////////////// +// From nsIRequest +//////////////////////////////////////////////////////////////////////////////// + +NS_IMETHODIMP nsImapMockChannel::GetName(nsACString& result) { + if (m_url) return m_url->GetSpec(result); + result.Truncate(); + return NS_OK; +} + +NS_IMETHODIMP nsImapMockChannel::IsPending(bool* result) { + *result = m_channelListener != nullptr; + return NS_OK; +} + +NS_IMETHODIMP nsImapMockChannel::GetStatus(nsresult* status) { + *status = m_cancelStatus; + return NS_OK; +} + +NS_IMETHODIMP nsImapMockChannel::SetWritingToCache(bool aWriting) { + mWritingToCache = aWriting; + return NS_OK; +} + +NS_IMETHODIMP nsImapMockChannel::GetWritingToCache(bool* result) { + *result = mWritingToCache; + return NS_OK; +} + +NS_IMETHODIMP nsImapMockChannel::SetImapProtocol(nsIImapProtocol* aProtocol) { + mProtocol = do_GetWeakReference(aProtocol); + return NS_OK; +} + +NS_IMETHODIMP nsImapMockChannel::SetCanceledReason(const nsACString& aReason) { + return SetCanceledReasonImpl(aReason); +} + +NS_IMETHODIMP nsImapMockChannel::GetCanceledReason(nsACString& aReason) { + return GetCanceledReasonImpl(aReason); +} + +NS_IMETHODIMP nsImapMockChannel::CancelWithReason(nsresult aStatus, + const nsACString& aReason) { + return CancelWithReasonImpl(aStatus, aReason); +} + +NS_IMETHODIMP nsImapMockChannel::Cancel(nsresult status) { + MOZ_DIAGNOSTIC_ASSERT( + NS_IsMainThread(), + "nsImapMockChannel::Cancel should only be called from UI thread"); + MOZ_LOG(IMAPCache, LogLevel::Debug, + ("nsImapMockChannel::%s: entering", __func__)); + m_cancelStatus = status; + nsCOMPtr<nsIImapProtocol> imapProtocol = do_QueryReferent(mProtocol); + + // if we aren't reading from the cache and we get canceled...doom our cache + // entry if write is still in progress... + if (m_url) { + nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_url); + MOZ_LOG(IMAPCache, LogLevel::Debug, + ("%s: Doom cache entry only if writing=%d(bool), url=%s", __func__, + mWritingToCache, m_url->GetSpecOrDefault().get())); + if (mWritingToCache) DoomCacheEntry(mailnewsUrl); + } + + // The associated ImapProtocol thread must be unblocked before being killed. + // Otherwise, it will be deadlocked. + ResumeAndNotifyOne(); + + // Required for killing ImapProtocol thread + if (imapProtocol) imapProtocol->TellThreadToDie(false); + + return NS_OK; +} + +NS_IMETHODIMP nsImapMockChannel::GetCanceled(bool* aCanceled) { + nsresult status = NS_ERROR_FAILURE; + GetStatus(&status); + *aCanceled = NS_FAILED(status); + return NS_OK; +} + +/** + * Suspends the current request. This may have the effect of closing + * any underlying transport (in order to free up resources), although + * any open streams remain logically opened and will continue delivering + * data when the transport is resumed. + * + * Calling cancel() on a suspended request must not send any + * notifications (such as onstopRequest) until the request is resumed. + * + * NOTE: some implementations are unable to immediately suspend, and + * may continue to deliver events already posted to an event queue. In + * general, callers should be capable of handling events even after + * suspending a request. + */ +NS_IMETHODIMP nsImapMockChannel::Suspend() { + MOZ_LOG(IMAP, LogLevel::Debug, ("Suspending [this=%p].", this)); + + mozilla::MonitorAutoLock lock(mSuspendedMonitor); + NS_ENSURE_TRUE(!mSuspended, NS_ERROR_NOT_AVAILABLE); + mSuspended = true; + + MOZ_LOG(IMAP, LogLevel::Debug, ("Suspended [this=%p].", this)); + + return NS_OK; +} + +/** + * Resumes the current request. This may have the effect of re-opening + * any underlying transport and will resume the delivery of data to + * any open streams. + */ +NS_IMETHODIMP nsImapMockChannel::Resume() { + MOZ_LOG(IMAP, LogLevel::Debug, ("Resuming [this=%p].", this)); + + nsresult rv = ResumeAndNotifyOne(); + + MOZ_LOG(IMAP, LogLevel::Debug, ("Resumed [this=%p].", this)); + + return rv; +} + +nsresult nsImapMockChannel::ResumeAndNotifyOne() { + mozilla::MonitorAutoLock lock(mSuspendedMonitor); + NS_ENSURE_TRUE(mSuspended, NS_ERROR_NOT_AVAILABLE); + mSuspended = false; + lock.Notify(); + + return NS_OK; +} + +NS_IMETHODIMP +nsImapMockChannel::GetNotificationCallbacks( + nsIInterfaceRequestor** aNotificationCallbacks) { + NS_IF_ADDREF(*aNotificationCallbacks = mCallbacks.get()); + return NS_OK; +} + +NS_IMETHODIMP +nsImapMockChannel::SetNotificationCallbacks( + nsIInterfaceRequestor* aNotificationCallbacks) { + mCallbacks = aNotificationCallbacks; + return NS_OK; +} + +NS_IMETHODIMP +nsImapMockChannel::OnTransportStatus(nsITransport* transport, nsresult status, + int64_t progress, int64_t progressMax) { + if (NS_FAILED(m_cancelStatus) || (mLoadFlags & LOAD_BACKGROUND) || !m_url) + return NS_OK; + + // these transport events should not generate any status messages + if (status == NS_NET_STATUS_RECEIVING_FROM || + status == NS_NET_STATUS_SENDING_TO) + return NS_OK; + + if (!mProgressEventSink) { + NS_QueryNotificationCallbacks(mCallbacks, m_loadGroup, mProgressEventSink); + if (!mProgressEventSink) return NS_OK; + } + + nsAutoCString host; + m_url->GetHost(host); + + nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_url); + if (mailnewsUrl) { + nsCOMPtr<nsIMsgIncomingServer> server; + mailnewsUrl->GetServer(getter_AddRefs(server)); + if (server) server->GetHostName(host); + } + mProgressEventSink->OnStatus(this, status, NS_ConvertUTF8toUTF16(host).get()); + + return NS_OK; +} + +nsIMAPMailboxInfo::nsIMAPMailboxInfo(const nsACString& aName, char aDelimiter) { + mMailboxName.Assign(aName); + mDelimiter = aDelimiter; + mChildrenListed = false; +} + +nsIMAPMailboxInfo::~nsIMAPMailboxInfo() {} + +void nsIMAPMailboxInfo::SetChildrenListed(bool childrenListed) { + mChildrenListed = childrenListed; +} + +bool nsIMAPMailboxInfo::GetChildrenListed() { return mChildrenListed; } + +const nsACString& nsIMAPMailboxInfo::GetMailboxName() { return mMailboxName; } + +char nsIMAPMailboxInfo::GetDelimiter() { return mDelimiter; } |