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