/* -*- 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 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 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 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 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(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(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(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(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 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 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 prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID)); NS_ASSERTION(prefBranch, "FAILED to create the preference service"); // read in the accept languages preference if (prefBranch) { if (!gInitialized) GlobalInitialization(prefBranch); nsCOMPtr prefString; prefBranch->GetComplexValue("intl.accept_languages", NS_GET_IID(nsIPrefLocalizedString), getter_AddRefs(prefString)); if (prefString) prefString->ToString(getter_Copies(mAcceptLanguages)); nsCString customDBHeaders; prefBranch->GetCharPref("mailnews.customDBHeaders", 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(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(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 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 imapThread; nsresult rv = NS_NewNamedThread("IMAP", getter_AddRefs(imapThread)); if (NS_FAILED(rv)) { NS_ASSERTION(imapThread, "Unable to create imap thread."); return rv; } RefPtr 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 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 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 aImapMessageSink; (void)m_runningUrl->GetImapMessageSink(getter_AddRefs(aImapMessageSink)); if (aImapMessageSink) { m_imapMessageSink = new ImapMessageSinkProxy(aImapMessageSink); } else { return NS_ERROR_ILLEGAL_VALUE; } } if (!m_imapServerSink) { nsCOMPtr aImapServerSink; res = m_runningUrl->GetImapServerSink(getter_AddRefs(aImapServerSink)); if (aImapServerSink) { m_imapServerSink = new ImapServerSinkProxy(aImapServerSink); m_imapServerSinkLatest = m_imapServerSink; } else { return NS_ERROR_ILLEGAL_VALUE; } } if (!m_imapProtocolSink) { nsCOMPtr anImapProxyHelper( do_QueryInterface(NS_ISUPPORTS_CAST(nsIImapProtocolSink*, this), &res)); m_imapProtocolSink = new ImapProtocolSinkProxy(anImapProxyHelper); } return NS_OK; } static void SetSecurityCallbacksFromChannel(nsISocketTransport* aTrans, nsIChannel* aChannel) { nsCOMPtr callbacks; aChannel->GetNotificationCallbacks(getter_AddRefs(callbacks)); nsCOMPtr loadGroup; aChannel->GetLoadGroup(getter_AddRefs(loadGroup)); nsCOMPtr securityCallbacks; 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 imapURL = do_QueryInterface(aURL, &rv); NS_ENSURE_SUCCESS(rv, rv); m_runningUrl = imapURL; m_runningUrlLatest = m_runningUrl; nsCOMPtr mailnewsUrl = do_QueryInterface(m_runningUrl); nsCOMPtr server = do_QueryReferent(m_server); if (!server) { rv = mailnewsUrl->GetServer(getter_AddRefs(server)); NS_ENSURE_SUCCESS(rv, rv); m_server = do_GetWeakReference(server); } nsCOMPtr folder; mailnewsUrl->GetFolder(getter_AddRefs(folder)); mFolderLastModSeq = 0; mFolderTotalMsgCount = 0; mFolderHighestUID = 0; m_uidValidity = kUidUnknown; if (folder) { nsCOMPtr folderDB; nsCOMPtr folderInfo; folder->GetDBFolderInfoAndDB(getter_AddRefs(folderInfo), getter_AddRefs(folderDB)); if (folderInfo) { nsCString modSeqStr; folderInfo->GetCharProperty(kModSeqPropertyName, modSeqStr); mFolderLastModSeq = ParseUint64Str(modSeqStr.get()); folderInfo->GetNumMessages(&mFolderTotalMsgCount); folderInfo->GetUint32Property(kHighestRecordedUIDPropertyName, 0, &mFolderHighestUID); folderInfo->GetImapUidValidity(&m_uidValidity); } } nsCOMPtr imapServer = do_QueryInterface(server); nsCOMPtr aRealStreamListener = do_QueryInterface(aConsumer); m_runningUrl->GetMockChannel(getter_AddRefs(m_mockChannel)); imapServer->GetIsGMailServer(&m_isGmailServer); if (!m_mockChannel) { nsCOMPtr nullPrincipal = 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 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 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 channelListener; m_mockChannel->GetChannelListener(getter_AddRefs(channelListener)); if (channelListener) // only over-ride if we have a non null channel // listener aRealStreamListener = channelListener; nsCOMPtr 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 docShell; msgWindow->GetMessageWindowDocShell(getter_AddRefs(docShell)); nsCOMPtr ir(do_QueryInterface(docShell)); nsCOMPtr interfaceRequestor; msgWindow->GetNotificationCallbacks(getter_AddRefs(interfaceRequestor)); nsCOMPtr 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 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 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 uri = do_QueryInterface(m_runningUrl, &rv); if (NS_FAILED(rv)) return rv; uri->GetPort(&port); AutoTArray 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 mailnewsurl; nsCOMPtr saveFolderSink; { MutexAutoLock mon(mLock); if (m_runningUrl) { mailnewsurl = do_QueryInterface(m_runningUrl); // It is unclear what 'saveFolderSink' is used for, most likely to hold // a reference for a little longer. See bug 1324893 and bug 391259. saveFolderSink = m_imapMailFolderSink; m_runningUrl = nullptr; // force us to release our last reference on the url m_urlInProgress = false; } } // Need to null this out whether we have an m_runningUrl or not m_imapMailFolderSink = nullptr; // we want to make sure the imap protocol's last reference to the url gets // released back on the UI thread. This ensures that the objects the imap url // hangs on to properly get released back on the UI thread. if (mailnewsurl) { NS_ReleaseOnMainThread("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 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 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 me_server = do_QueryReferent(m_server); if (me_server) { nsresult result; nsCOMPtr aImapServer( do_QueryInterface(me_server, &result)); if (NS_SUCCEEDED(result)) aImapServer->RemoveConnection(this); me_server = nullptr; } m_server = nullptr; // take this opportunity of being on the UI thread to // persist chunk prefs if they've changed if (gChunkSizeDirty) { nsCOMPtr prefBranch( do_GetService(NS_PREFSERVICE_CONTRACTID)); if (prefBranch) { prefBranch->SetIntPref("mail.imap.chunk_size", gChunkSize); prefBranch->SetIntPref("mail.imap.min_chunk_size_threshold", gChunkThreshold); gChunkSizeDirty = false; } } return NS_OK; } NS_IMETHODIMP nsImapProtocol::GetUrlWindow(nsIMsgMailNewsUrl* aUrl, nsIMsgWindow** aMsgWindow) { NS_ENSURE_ARG_POINTER(aUrl); NS_ENSURE_ARG_POINTER(aMsgWindow); return aUrl->GetMsgWindow(aMsgWindow); } NS_IMETHODIMP nsImapProtocol::SetupMainThreadProxies() { return SetupSinkProxy(); } NS_IMETHODIMP nsImapProtocol::OnInputStreamReady(nsIAsyncInputStream* inStr) { // should we check if it's a close vs. data available? if (m_idle) { uint64_t bytesAvailable = 0; (void)inStr->Available(&bytesAvailable); // check if data available - might be a close if (bytesAvailable != 0) { ReentrantMonitorAutoEnter mon(m_urlReadyToRunMonitor); m_lastActiveTime = PR_Now(); m_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 me_server = do_QueryReferent(m_server); if (me_server) { nsresult rv; nsCOMPtr aImapServer( do_QueryInterface(me_server, &rv)); if (NS_SUCCEEDED(rv)) aImapServer->RemoveConnection(this); m_server = nullptr; me_server = nullptr; } { ReentrantMonitorAutoEnter deathMon(m_threadDeathMonitor); m_safeToCloseConnection = aIsSafeToClose; m_threadShouldDie = true; } ReentrantMonitorAutoEnter readyMon(m_urlReadyToRunMonitor); m_nextUrlReadyToRun = true; readyMon.Notify(); return NS_OK; } /** * 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 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 tlsSocketControl; if (m_transport && NS_SUCCEEDED( m_transport->GetTlsSocketControl(getter_AddRefs(tlsSocketControl))) && tlsSocketControl) { auto CallStartTLS = [sockCon = nsCOMPtr{tlsSocketControl}, &rv]() mutable { rv = sockCon->StartTLS(); }; nsCOMPtr 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 socketThread( do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID)); nsCOMPtr tlsSocketControl; if (socketThread && NS_SUCCEEDED( m_transport->GetTlsSocketControl(getter_AddRefs(tlsSocketControl))) && tlsSocketControl) { if (socketThread) { nsCOMPtr 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 runningImapURL; rv = GetRunningImapURL(getter_AddRefs(runningImapURL)); if (NS_SUCCEEDED(rv) && runningImapURL) { nsCOMPtr runningImapFolder; nsCOMPtr msgWindow; nsCOMPtr mailnewsUrl = do_QueryInterface(runningImapURL); mailnewsUrl->GetMsgWindow(getter_AddRefs(msgWindow)); mailnewsUrl->GetFolder(getter_AddRefs(runningImapFolder)); if (aImapFolder == runningImapFolder && msgWindow == aMsgWindow) { 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 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 imapUrl = do_QueryInterface(url); imapUrl->GetMsgLoadingFromCache(&readingFromMemCache); if (!readingFromMemCache) { nsCOMPtr 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 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 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(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(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 copyState; if (m_runningUrl) m_runningUrl->GetCopyState(getter_AddRefs(copyState)); // this is so hokey...we MUST clear any local references to the url // BEFORE calling ReleaseUrlState mailnewsurl = nullptr; if (suspendUrl) m_imapServerSink->SuspendUrl(m_runningUrl); // save the imap folder sink since we need it to do the CopyNextStreamMessage RefPtr imapMailFolderSink = m_imapMailFolderSink; // release the url as we are done with it... ReleaseUrlState(false); ResetProgressInfo(); ClearFlag(IMAP_CLEAN_UP_URL_STATE); if (imapMailFolderSink) { if (copyState) { rv = imapMailFolderSink->CopyNextStreamMessage( GetServerStateParser().LastCommandSuccessful() && NS_SUCCEEDED(GetConnectionStatus()), copyState); if (NS_FAILED(rv)) MOZ_LOG(IMAP, LogLevel::Info, ("CopyNextStreamMessage failed: %" PRIx32, static_cast(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 kungFuGripImapUrl = m_runningUrl; nsCOMPtr saveMockChannel; // the mock channel might be null - that's OK. if (m_imapServerSink) (void)m_imapServerSink->PrepareToRetryUrl(kungFuGripImapUrl, getter_AddRefs(saveMockChannel)); ReleaseUrlState(true); if (m_imapServerSink) { m_imapServerSink->RemoveServerConnection(this); m_imapServerSink->RetryUrl(kungFuGripImapUrl, saveMockChannel); } // 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 folder; mUrl->GetFolder(getter_AddRefs(folder)); NS_ENSURE_TRUE(folder, NS_OK); nsCOMPtr folderSink(do_QueryInterface(folder)); // This causes the url listener to get OnStart and Stop notifications. folderSink->SetUrlState(mProtocol, mUrl, true, false, NS_OK); folderSink->SetUrlState(mProtocol, mUrl, false, false, NS_OK); } return NS_OK; } private: nsCOMPtr mUrl; nsCOMPtr mProtocol; }; bool nsImapProtocol::TryToRunUrlLocally(nsIURI* aURL, nsISupports* aConsumer) { nsresult rv; nsCOMPtr imapUrl(do_QueryInterface(aURL, &rv)); NS_ENSURE_SUCCESS(rv, false); nsCOMPtr mailnewsUrl = do_QueryInterface(aURL); nsCString messageIdString; imapUrl->GetListOfMessageIds(messageIdString); bool useLocalCache = false; if (!messageIdString.IsEmpty() && !HandlingMultipleMessages(messageIdString)) { nsImapAction action; imapUrl->GetImapAction(&action); nsCOMPtr folder; mailnewsUrl->GetFolder(getter_AddRefs(folder)); NS_ENSURE_TRUE(folder, false); folder->HasMsgOffline(strtoul(messageIdString.get(), nullptr, 10), &useLocalCache); mailnewsUrl->SetMsgIsInLocalCache(useLocalCache); // We're downloading a single message for offline use, and it's // already offline. So we shouldn't do anything, but we do // need to notify the url listener. if (useLocalCache && action == nsIImapUrl::nsImapMsgDownloadForOffline) { nsCOMPtr event = new UrlListenerNotifierEvent(mailnewsUrl, this); // Post this as an event because it can lead to re-entrant calls to // LoadNextQueuedUrl if the listener runs a new url. if (event) NS_DispatchToCurrentThread(event); return true; } } if (!useLocalCache) return false; nsCOMPtr mockChannel; imapUrl->GetMockChannel(getter_AddRefs(mockChannel)); if (!mockChannel) return false; nsImapMockChannel* imapChannel = static_cast(mockChannel.get()); if (!imapChannel) return false; nsCOMPtr loadGroup; imapChannel->GetLoadGroup(getter_AddRefs(loadGroup)); if (!loadGroup) // if we don't have one, the url will snag one from the msg // window... mailnewsUrl->GetLoadGroup(getter_AddRefs(loadGroup)); if (loadGroup) loadGroup->RemoveRequest((nsIRequest*)mockChannel, nullptr /* context isupports */, NS_OK); if (imapChannel->ReadFromLocalCache()) { (void)imapChannel->NotifyStartEndReadFromCache(true); return true; } return false; } // LoadImapUrl takes a url, initializes all of our url specific data by calling // SetupUrl. 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 sinkMC = do_QueryInterface(m_mockChannel); if (sinkMC) { nsCOMPtr thread = do_GetMainThread(); RefPtr 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 mailnewsUrl = do_QueryInterface(m_runningUrl); if (mailnewsUrl && m_securityInfo) { nsCOMPtr 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 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 msgUrl = do_QueryInterface(aImapUrl); nsCOMPtr server; rv = msgUrl->GetServer(getter_AddRefs(server)); if (NS_SUCCEEDED(rv)) { // compare host/user between url and connection. nsCString urlHostName; nsCString urlUserName; rv = server->GetHostName(urlHostName); NS_ENSURE_SUCCESS(rv, rv); rv = server->GetUsername(urlUserName); NS_ENSURE_SUCCESS(rv, rv); if ((GetImapHostName().IsEmpty() || urlHostName.Equals(GetImapHostName(), nsCaseInsensitiveCStringComparator)) && (GetImapUserName().IsEmpty() || urlUserName.Equals(GetImapUserName(), nsCaseInsensitiveCStringComparator))) { if (isSelectedStateUrl) { if (inSelectedState) { // *** jt - in selected state can only run url with // matching foldername char* folderNameForProposedUrl = nullptr; rv = aImapUrl->CreateServerSourceFolderPathString( &folderNameForProposedUrl); if (NS_SUCCEEDED(rv) && folderNameForProposedUrl) { bool isInbox = PL_strcasecmp("Inbox", folderNameForProposedUrl) == 0; if (!curSelectedUrlFolderName.IsEmpty() || !pendingUrlFolderName.IsEmpty()) { bool matched = isInbox ? PL_strcasecmp(curSelectedUrlFolderName.get(), folderNameForProposedUrl) == 0 : PL_strcmp(curSelectedUrlFolderName.get(), folderNameForProposedUrl) == 0; if (!matched && !pendingUrlFolderName.IsEmpty()) { matched = isInbox ? PL_strcasecmp(pendingUrlFolderName.get(), folderNameForProposedUrl) == 0 : PL_strcmp(pendingUrlFolderName.get(), folderNameForProposedUrl) == 0; } if (matched) { if (isBusy) *hasToWait = true; else *aCanRunUrl = true; } } } MOZ_LOG(IMAP, LogLevel::Debug, ("proposed url = %s folder for connection %s has To Wait = " "%s can run = %s", folderNameForProposedUrl, curSelectedUrlFolderName.get(), (*hasToWait) ? "true" : "false", (*aCanRunUrl) ? "true" : "false")); PR_FREEIF(folderNameForProposedUrl); } } else // *** jt - an authenticated state url can be run in either // authenticated or selected state { nsImapAction actionForRunningUrl; // If proposed url is subscription related, and we are currently running // a subscription url, then we want to queue the proposed url after the // current url. Otherwise, we can run this url if we're not busy. If we // never find a running subscription-related url, the caller will just // use whatever free connection it can find, which is what we want. if (IS_SUBSCRIPTION_RELATED_ACTION(actionForProposedUrl)) { if (isBusy && m_runningUrl) { m_runningUrl->GetImapAction(&actionForRunningUrl); if (IS_SUBSCRIPTION_RELATED_ACTION(actionForRunningUrl)) { *aCanRunUrl = false; *hasToWait = true; } } } else { if (!isBusy) *aCanRunUrl = true; } } } } return rv; } // Command tag handling stuff. // 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 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 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 pipe = do_CreateInstance("@mozilla.org/pipe;1"); rv = pipe->Init(false, false, 4096, PR_UINT32_MAX); NS_ENSURE_SUCCESS(rv, rv); // These always succeed because the pipe is initialized above. MOZ_ALWAYS_SUCCEEDS( pipe->GetInputStream(getter_AddRefs(m_channelInputStream))); MOZ_ALWAYS_SUCCEEDS( pipe->GetOutputStream(getter_AddRefs(m_channelOutputStream))); } // else, if we are saving the message to disk! else if (m_imapMessageSink /* && m_imapAction == nsIImapUrl::nsImapSaveMessageToDisk */) { // we get here when download the inbox for offline use nsCOMPtr file; bool addDummyEnvelope = true; nsCOMPtr msgurl = do_QueryInterface(m_runningUrl); msgurl->GetMessageFile(getter_AddRefs(file)); msgurl->GetAddDummyEnvelope(&addDummyEnvelope); if (file) rv = m_imapMessageSink->SetupMsgWriteStream(file, addDummyEnvelope); } if (m_imapMailFolderSink && m_runningUrl) { nsCOMPtr copyState; if (m_runningUrl) { m_runningUrl->GetCopyState(getter_AddRefs(copyState)); if (copyState) // only need this notification during copy { nsCOMPtr mailurl = do_QueryInterface(m_runningUrl); m_imapMailFolderSink->StartMessage(mailurl); } } } } else HandleMemoryFailure(); return rv; } void nsImapProtocol::GetShouldDownloadAllHeaders(bool* aResult) { if (m_imapMailFolderSink) m_imapMailFolderSink->GetShouldDownloadAllHeaders(aResult); } void nsImapProtocol::GetArbitraryHeadersToDownload(nsCString& aResult) { if (m_imapServerSink) m_imapServerSink->GetArbitraryHeaders(aResult); } void nsImapProtocol::AdjustChunkSize() { int32_t deltaInSeconds; 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 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 copyState; m_runningUrl->GetCopyState(getter_AddRefs(copyState)); if (copyState) // only need this notification during copy { nsCOMPtr mailUrl(do_QueryInterface(m_runningUrl)); m_imapMailFolderSink->EndMessage(mailUrl, m_downloadLineCache->CurrentUID()); } } } m_curHdrInfo = nullptr; } void nsImapProtocol::AbortMessageDownLoad() { Log("STREAM", "CLOSE", "Abort Message Download Stream"); if (m_trackingTime) AdjustChunkSize(); FlushDownloadCache(); if (GetServerStateParser().GetDownloadingHeaders()) { if (m_imapMailFolderSink) m_imapMailFolderSink->AbortHeaderParseStream(this); } else if (m_imapMessageSink) m_imapMessageSink->AbortMsgWriteStream(); m_curHdrInfo = nullptr; } void nsImapProtocol::ProcessMailboxUpdate(bool handlePossibleUndo) { if (DeathSignalReceived()) return; // Update quota information char* boxName; GetSelectedMailboxName(&boxName); GetQuotaDataIfSupported(boxName); PR_Free(boxName); // fetch the flags and uids of all existing messages or new ones if (!DeathSignalReceived() && GetServerStateParser().NumberOfMessages()) { if (handlePossibleUndo) { // undo any delete flags we may have asked to nsCString undoIdsStr; nsAutoCString undoIds; GetCurrentUrl()->GetListOfMessageIds(undoIdsStr); undoIds.Assign(undoIdsStr); if (!undoIds.IsEmpty()) { char firstChar = (char)undoIds.CharAt(0); undoIds.Cut(0, 1); // remove first character // if this string started with a '-', then this is an undo of a delete // if its a '+' its a redo if (firstChar == '-') Store(undoIds, "-FLAGS (\\Deleted)", true); // most servers will fail silently on a failure, deal // with it? else if (firstChar == '+') Store(undoIds, "+FLAGS (\\Deleted)", true); // most servers will fail silently on a failure, deal // with it? else NS_ASSERTION(false, "bogus undo Id's"); } } // make the parser record these flags nsCString fetchStr; int32_t added = 0, deleted = 0; m_flagState->GetNumberOfMessages(&added); deleted = m_flagState->NumberOfDeletedMessages(); bool flagStateEmpty = !added; 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 msgIdList; if (GetServerStateParser().LastCommandSuccessful()) { ReentrantMonitorAutoEnter mon(m_waitForBodyIdsMonitor); RefPtr 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 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& 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& 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 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 ,, or : char curChar = *uidString; bool isRange = false; int32_t curToken; int32_t saveStartToken = 0; for (char* curCharPtr = uidString; curChar && *curCharPtr;) { char* currentKeyToken = curCharPtr; curChar = *curCharPtr; while (curChar != ':' && curChar != ',' && curChar != '\0') curChar = *curCharPtr++; *(curCharPtr - 1) = '\0'; curToken = atol(currentKeyToken); if (isRange) { while (saveStartToken < curToken) { numberOfMessages++; saveStartToken++; } } numberOfMessages++; isRange = (curChar == ':'); if (isRange) saveStartToken = curToken + 1; } PR_Free(uidString); } return numberOfMessages; } // It would be really nice not to have to use this method nearly as much as we // did in 4.5 - we need to think about this some. Some of it may just go away in // the new world order bool nsImapProtocol::DeathSignalReceived() { // ignore mock channel status if we've been pseudo interrupted // ### need to make sure we clear pseudo interrupted status appropriately. if (!GetPseudoInterrupted() && m_mockChannel) { 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 kungFuGrip = m_inputStream; if (m_mockChannel) { nsImapMockChannel* imapChannel = static_cast(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(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 mailNewsUrl = do_QueryInterface(m_runningUrl); if (mailNewsUrl) { nsCOMPtr 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(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(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 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 mailnewsUrl = do_QueryInterface(m_runningUrl); if (mailnewsUrl) mailnewsUrl->GetSuppressErrorMsgs(&suppressErrorMsg); if (!suppressErrorMsg) m_imapServerSink->FEAlertWithName(aMessageName, mailnewsUrl); } } void nsImapProtocol::AlertUserEvent(const char* message) { if (m_imapServerSink) { bool suppressErrorMsg = false; nsCOMPtr mailnewsUrl = do_QueryInterface(m_runningUrl); if (mailnewsUrl) mailnewsUrl->GetSuppressErrorMsgs(&suppressErrorMsg); if (!suppressErrorMsg) m_imapServerSink->FEAlert(NS_ConvertASCIItoUTF16(message), mailnewsUrl); } } void nsImapProtocol::AlertUserEventFromServer(const char* aServerEvent, 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 mailnewsUrl = do_QueryInterface(m_runningUrlLatest); m_imapServerSinkLatest->FEAlertFromServer( nsDependentCString(aServerEvent), mailnewsUrl); } else if (m_imapServerSink) { nsCOMPtr mailnewsUrl = do_QueryInterface(m_runningUrl); m_imapServerSink->FEAlertFromServer(nsDependentCString(aServerEvent), mailnewsUrl); } } } void nsImapProtocol::ResetProgressInfo() { m_lastProgressTime = 0; m_lastPercent = -1; m_lastProgressStringName.Truncate(); } void nsImapProtocol::SetProgressString(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 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 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 " OK " or // " 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 " 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 " 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 new_in = new nsMsgCompressIStream(); if (!new_in) return NS_ERROR_OUT_OF_MEMORY; nsresult rv = new_in->InitInputStream(m_inputStream); NS_ENSURE_SUCCESS(rv, rv); m_inputStream = new_in; RefPtr new_out = new nsMsgCompressOStream(); if (!new_out) return NS_ERROR_OUT_OF_MEMORY; rv = new_out->InitOutputStream(m_outputStream); NS_ENSURE_SUCCESS(rv, rv); m_outputStream = new_out; return rv; } void nsImapProtocol::Language() { // only issue the language request if we haven't done so already... if (!TestFlag(IMAP_ISSUED_LANGUAGE_REQUEST)) { SetFlag(IMAP_ISSUED_LANGUAGE_REQUEST); ProgressEventFunctionUsingName("imapStatusCheckCompat"); IncrementCommandTagNumber(); nsCString command(GetServerCommandTag()); // extract the desired language attribute from prefs nsresult rv = NS_OK; // we need to parse out the first language out of this comma separated // list.... i.e if we have en,ja we only want to send en to the server. if (mAcceptLanguages.get()) { nsAutoCString extractedLanguage; LossyCopyUTF16toASCII(mAcceptLanguages, extractedLanguage); int32_t pos = extractedLanguage.FindChar(','); if (pos > 0) // we have a comma separated list of languages... extractedLanguage.SetLength(pos); // truncate everything after the // first comma (including the comma) if (extractedLanguage.IsEmpty()) return; command.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(PL_strlen(userName), 255u) + 2; // We include two characters. PR_snprintf(&plain_string[len], 256, "%.255s", password.get()); len += std::min(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(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(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 " like POP? return NS_ERROR_NOT_IMPLEMENTED; } else { MOZ_LOG(IMAP, LogLevel::Error, ("flags param has no auth scheme selected")); return NS_ERROR_ILLEGAL_VALUE; } PR_Free(currentCommand); NS_ENSURE_SUCCESS(rv, rv); return GetServerStateParser().LastCommandSuccessful() ? NS_OK : NS_ERROR_FAILURE; } void nsImapProtocol::OnLSubFolders() { // **** use to find out whether Drafts, Sent, & Templates folder // exists or not even the user didn't subscribe to it char* mailboxName = OnCreateServerSourceFolderPathString(); if (mailboxName) { ProgressEventFunctionUsingName("imapStatusLookingForMailbox"); IncrementCommandTagNumber(); PR_snprintf(m_dataOutputBuf, OUTPUT_BUFFER_SIZE, "%s list \"\" \"%s\"" CRLF, GetServerCommandTag(), mailboxName); nsresult rv = SendData(m_dataOutputBuf); if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail(); PR_Free(mailboxName); } else { HandleMemoryFailure(); } } void nsImapProtocol::OnAppendMsgFromFile() { nsCOMPtr file; nsresult rv = NS_OK; rv = m_runningUrl->GetMsgFile(getter_AddRefs(file)); if (NS_SUCCEEDED(rv) && file) { char* mailboxName = OnCreateServerSourceFolderPathString(); if (mailboxName) { imapMessageFlagsType flagsToSet = 0; uint32_t msgFlags = 0; PRTime date = 0; nsCString keywords; if (m_imapMessageSink) m_imapMessageSink->GetCurMoveCopyMessageInfo(m_runningUrl, &date, keywords, &msgFlags); if (msgFlags & nsMsgMessageFlags::Read) flagsToSet |= kImapMsgSeenFlag; if (msgFlags & nsMsgMessageFlags::MDNReportSent) flagsToSet |= kImapMsgMDNSentFlag; // convert msg flag label (0xE000000) to imap flag label (0x0E00) if (msgFlags & nsMsgMessageFlags::Labels) flagsToSet |= (msgFlags & nsMsgMessageFlags::Labels) >> 16; if (msgFlags & nsMsgMessageFlags::Marked) flagsToSet |= kImapMsgFlaggedFlag; if (msgFlags & nsMsgMessageFlags::Replied) flagsToSet |= kImapMsgAnsweredFlag; if (msgFlags & nsMsgMessageFlags::Forwarded) flagsToSet |= kImapMsgForwardedFlag; // If the message copied was a draft, flag it as such nsImapAction imapAction; rv = m_runningUrl->GetImapAction(&imapAction); if (NS_SUCCEEDED(rv) && (imapAction == nsIImapUrl::nsImapAppendDraftFromFile)) flagsToSet |= kImapMsgDraftFlag; UploadMessageFromFile(file, mailboxName, date, flagsToSet, keywords); PR_Free(mailboxName); } else { HandleMemoryFailure(); } } } void nsImapProtocol::UploadMessageFromFile(nsIFile* file, const char* mailboxName, PRTime date, imapMessageFlagsType flags, nsCString& keywords) { if (!file || !mailboxName) return; IncrementCommandTagNumber(); int64_t fileSize = 0; int64_t totalSize; uint32_t readCount; char* dataBuffer = nullptr; nsCString command(GetServerCommandTag()); nsCString escapedName; CreateEscapedMailboxName(mailboxName, escapedName); nsresult rv; bool urlOk = false; nsCString flagString; nsCOMPtr 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 msgFolder; m_runningUrl->GetOnlineSubDirSeparator(&onlineSubDirDelimiter); nsCOMPtr mailnewsUrl = do_QueryInterface(m_runningUrl); mailnewsUrl->GetFolder(getter_AddRefs(msgFolder)); if (msgFolder) { nsCOMPtr imapFolder = do_QueryInterface(msgFolder); if (imapFolder) { imapFolder->GetHierarchyDelimiter(&hierarchyDelimiter); if (hierarchyDelimiter != kOnlineHierarchySeparatorUnknown && onlineSubDirDelimiter != hierarchyDelimiter) m_runningUrl->SetOnlineSubDirSeparator(hierarchyDelimiter); } } m_runningUrl->CreateServerSourceFolderPathString(&sourceMailbox); return sourceMailbox; } nsresult nsImapProtocol::CreateServerSourceFolderPathString(char** result) { NS_ENSURE_ARG(result); *result = OnCreateServerSourceFolderPathString(); return (*result) ? NS_OK : NS_ERROR_OUT_OF_MEMORY; } // caller must free using PR_Free char* nsImapProtocol::OnCreateServerDestinationFolderPathString() { char* destinationMailbox = nullptr; char hierarchyDelimiter = 0; char onlineDelimiter = 0; m_runningUrl->GetOnlineSubDirSeparator(&hierarchyDelimiter); if (m_imapMailFolderSink) m_imapMailFolderSink->GetOnlineDelimiter(&onlineDelimiter); if (onlineDelimiter != kOnlineHierarchySeparatorUnknown && onlineDelimiter != hierarchyDelimiter) m_runningUrl->SetOnlineSubDirSeparator(onlineDelimiter); m_runningUrl->CreateServerDestinationFolderPathString(&destinationMailbox); return destinationMailbox; } void nsImapProtocol::OnCreateFolder(const char* aSourceMailbox) { bool created = CreateMailboxRespectingSubscriptions(aSourceMailbox); if (created) { m_hierarchyNameState = kListingForCreate; nsCString mailboxWODelim(aSourceMailbox); RemoveHierarchyDelimiter(mailboxWODelim); List(mailboxWODelim.get(), false); m_hierarchyNameState = kNoOperationInProgress; } else FolderNotCreated(aSourceMailbox); } void nsImapProtocol::OnEnsureExistsFolder(const char* aSourceMailbox) { // 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 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(); 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; 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* 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 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 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 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 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 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 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 imapServer = do_QueryReferent(m_server, &rv); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr 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 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 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 mChannelToUse; nsCOMPtr 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 imapUrl = do_QueryInterface(m_url, &rv); nsCOMPtr imapProtocol = do_QueryReferent(mProtocol); if (imapUrl) { nsCOMPtr folderSink; rv = imapUrl->GetImapMailFolderSink(getter_AddRefs(folderSink)); if (folderSink) { nsCOMPtr mailUrl = do_QueryInterface(m_url); rv = folderSink->SetUrlState(nullptr /* we don't know the protocol */, mailUrl, start, false, m_cancelStatus); // Required for killing ImapProtocol thread if (NS_FAILED(m_cancelStatus) && imapProtocol) imapProtocol->TellThreadToDie(false); } } return rv; } NS_IMETHODIMP nsImapMockChannel::Close() { if (mReadingFromCache) NotifyStartEndReadFromCache(false); else { nsCOMPtr mailnewsUrl = do_QueryInterface(m_url); if (mailnewsUrl) { nsCOMPtr cacheEntry; mailnewsUrl->GetMemCacheEntry(getter_AddRefs(cacheEntry)); if (cacheEntry) cacheEntry->MarkValid(); // remove the channel from the load group nsCOMPtr loadGroup; GetLoadGroup(getter_AddRefs(loadGroup)); // if the mock channel wasn't initialized with a load group then // use our load group (they may differ) if (!loadGroup) mailnewsUrl->GetLoadGroup(getter_AddRefs(loadGroup)); if (loadGroup) loadGroup->RemoveRequest((nsIRequest*)this, nullptr, NS_OK); } } m_channelListener = nullptr; 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 mailnewsUrl = do_QueryInterface(m_url); if (mailnewsUrl && !mProgressEventSink) { nsCOMPtr statusFeedback; mailnewsUrl->GetStatusFeedback(getter_AddRefs(statusFeedback)); mProgressEventSink = do_QueryInterface(statusFeedback); } // If this is a fetch URL and we can, get the message size from the message // header and set it to be the content length. // Note that for an attachment URL, this will set the content length to be // equal to the size of the entire message. nsCOMPtr imapUrl(do_QueryInterface(m_url)); nsImapAction imapAction; imapUrl->GetImapAction(&imapAction); if (imapAction == nsIImapUrl::nsImapMsgFetch) { nsCOMPtr msgUrl(do_QueryInterface(m_url)); if (msgUrl) { nsCOMPtr msgHdr; // A failure to get a message header isn't an error msgUrl->GetMessageHeader(getter_AddRefs(msgHdr)); if (msgHdr) { uint32_t messageSize; if (NS_SUCCEEDED(msgHdr->GetMessageSize(&messageSize))) SetContentLength(messageSize); } } } } return NS_OK; } NS_IMETHODIMP nsImapMockChannel::Open(nsIInputStream** _retval) { nsCOMPtr listener; nsresult rv = nsContentSecurityManager::doContentSecurityCheck(this, listener); NS_ENSURE_SUCCESS(rv, rv); if (m_url) { nsCOMPtr 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 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 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 msgUrl(do_QueryInterface(m_url)); if (msgUrl) { nsCOMPtr 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 tee = do_CreateInstance(NS_STREAMLISTENERTEE_CONTRACTID, &rv); if (NS_SUCCEEDED(rv)) { nsCOMPtr 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 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 imapUrl = do_QueryInterface(m_url, &rv); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr 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 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 msgUrl(do_QueryInterface(m_url)); if (msgUrl && NS_SUCCEEDED(rv)) { nsCOMPtr 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 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 pump; if (NS_SUCCEEDED( rv = NS_NewInputStreamPump(getter_AddRefs(pump), ins.forget()))) { // Create and use a cache listener object. RefPtr cacheListener = new nsImapCacheStreamListener(); cacheListener->Init(m_channelListener, this, true); rv = pump->AsyncRead(cacheListener); if (NS_SUCCEEDED(rv)) { nsCOMPtr imapUrl = do_QueryInterface(m_url); imapUrl->SetMsgLoadingFromCache(true); // Set the cache entry's security info status as our security // info status... nsCOMPtr 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(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 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 imapUrl = do_QueryInterface(m_url); nsCOMPtr mailnewsUrl = do_QueryInterface(m_url); bool localOnly = false; imapUrl->GetLocalFetchOnly(&localOnly); if (localOnly) { // This will cause an OnStartRunningUrl, and the subsequent close // will then cause an OnStopRunningUrl with the cancel status. NotifyStartEndReadFromCache(true); Cancel(NS_MSG_ERROR_MSG_NOT_OFFLINE); // Dispatch error notification, so ReadFromImapConnection() returns *before* // the error is sent to the listener's OnStopRequest(). This avoids // endless recursion where the caller relies on async execution. nsCOMPtr event = new nsReadFromImapConnectionFailure(this); NS_DispatchToCurrentThread(event); return NS_MSG_ERROR_MSG_NOT_OFFLINE; } nsCOMPtr loadGroup; GetLoadGroup(getter_AddRefs(loadGroup)); if (!loadGroup) // if we don't have one, the url will snag one from the msg // window... mailnewsUrl->GetLoadGroup(getter_AddRefs(loadGroup)); // okay, add the mock channel to the load group.. if (loadGroup) loadGroup->AddRequest((nsIRequest*)this, nullptr /* context isupports */); // loading the url consists of asking the server to add the url to it's imap // event queue.... nsCOMPtr server; rv = mailnewsUrl->GetServer(getter_AddRefs(server)); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr imapServer(do_QueryInterface(server, &rv)); NS_ENSURE_SUCCESS(rv, rv); // Assume AsyncRead is always called from the UI thread..... return imapServer->GetImapConnectionAndLoadUrl(imapUrl, m_channelListener); } // for messages stored in our offline cache, we have special code to handle // that... If it's in the local cache, we return true and we can abort the // download because this method does the rest of the work. bool nsImapMockChannel::ReadFromLocalCache() { nsresult rv = NS_OK; nsCOMPtr imapUrl = do_QueryInterface(m_url); nsCOMPtr mailnewsUrl = do_QueryInterface(m_url, &rv); bool useLocalCache = false; mailnewsUrl->GetMsgIsInLocalCache(&useLocalCache); if (!useLocalCache) { return false; } nsAutoCString messageIdString; SetupPartExtractorListener(imapUrl, m_channelListener); imapUrl->GetListOfMessageIds(messageIdString); nsCOMPtr 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 hdr; rv = folder->GetMessageHeader(msgKey, getter_AddRefs(hdr)); NS_ENSURE_SUCCESS(rv, false); nsCOMPtr 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 cacheListener = new nsImapCacheStreamListener(); cacheListener->Init(m_channelListener, this); // create a stream pump that will async read the message. nsCOMPtr 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 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 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 converter = do_GetService("@mozilla.org/streamConverters;1"); if (converter && aConsumer) { nsCOMPtr newConsumer; converter->AsyncConvertData("message/rfc822", "*/*", aConsumer, static_cast(this), getter_AddRefs(newConsumer)); if (newConsumer) m_channelListener = newConsumer; } } return NS_OK; } NS_IMETHODIMP nsImapMockChannel::GetLoadFlags(nsLoadFlags* aLoadFlags) { //*aLoadFlags = nsIRequest::LOAD_NORMAL; *aLoadFlags = mLoadFlags; return NS_OK; } NS_IMETHODIMP nsImapMockChannel::SetLoadFlags(nsLoadFlags aLoadFlags) { mLoadFlags = aLoadFlags; return NS_OK; // don't fail when trying to set this } NS_IMETHODIMP nsImapMockChannel::GetContentType(nsACString& aContentType) { if (mContentType.IsEmpty()) { nsImapAction imapAction = 0; if (m_url) { nsCOMPtr imapUrl = do_QueryInterface(m_url); if (imapUrl) { imapUrl->GetImapAction(&imapAction); } } if (imapAction == nsIImapUrl::nsImapSelectFolder) aContentType.AssignLiteral("x-application-imapfolder"); else aContentType.AssignLiteral("message/rfc822"); } else aContentType = mContentType; return NS_OK; } NS_IMETHODIMP nsImapMockChannel::SetContentType( const nsACString& aContentType) { nsAutoCString charset; nsresult rv = NS_ParseResponseContentType(aContentType, mContentType, charset); if (NS_FAILED(rv) || mContentType.IsEmpty()) mContentType.AssignLiteral(UNKNOWN_CONTENT_TYPE); return rv; } NS_IMETHODIMP nsImapMockChannel::GetContentCharset( nsACString& aContentCharset) { aContentCharset.Assign(mCharset); return NS_OK; } NS_IMETHODIMP nsImapMockChannel::SetContentCharset( const nsACString& aContentCharset) { mCharset.Assign(aContentCharset); return NS_OK; } NS_IMETHODIMP nsImapMockChannel::GetContentDisposition(uint32_t* aContentDisposition) { *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 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 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 mailnewsUrl = do_QueryInterface(m_url); if (mailnewsUrl) { nsCOMPtr 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; }