Dactyloidae/mailnews/imap/src/nsImapProtocol.cpp

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;
}