/* -*- 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))
  {
    // PREAUTH greeting received. We've been pre-authenticated by the server.
    // We can skip sending a password and transition right into the
    // kAuthenticated state; but we won't if the user has configured STARTTLS.
    // (STARTTLS can only occur with the server in non-authenticated state.)
    if (!(m_socketType == nsMsgSocketType::alwaysSTARTTLS ||
          m_socketType == nsMsgSocketType::trySTARTTLS)) {
      GetServerStateParser().PreauthSetAuthenticatedState();

      if (GetServerStateParser().GetCapabilityFlag() == kCapabilityUndefined)
        Capability();

      if (!(GetServerStateParser().GetCapabilityFlag() &
            (kIMAP4Capability | kIMAP4rev1Capability | kIMAP4other))) {
        // AlertUserEventUsingId(MK_MSG_IMAP_SERVER_NOT_IMAP4);
        SetConnectionStatus(NS_ERROR_FAILURE);  // stop netlib
      } else {
        // let's record the user as authenticated.
        m_imapServerSink->SetUserAuthenticated(true);

        ProcessAfterAuthenticated();
        // the connection was a success
        SetConnectionStatus(NS_OK);
      }
    } else {
      // STARTTLS is configured so don't transition to authenticated state. Just
      // alert the user, log the error and drop the connection. This may
      // indicate a man-in-the middle attack if the user is not expecting
      // PREAUTH. The user must change the connection security setting to other
      // than STARTTLS to allow PREAUTH to be accepted on subsequent IMAP
      // connections.
      AlertUserEventUsingName("imapServerDisconnected");
      const nsCString &hostName = GetImapHostName();
      MOZ_LOG(
          IMAP, LogLevel::Error,
          ("PREAUTH received from IMAP server %s because STARTTLS selected. "
           "Connection dropped",
           hostName.get()));
      SetConnectionStatus(NS_ERROR_FAILURE);        // stop netlib
     }
  }

  PR_Free(serverResponse); // we don't care about the greeting yet...

#undef ESC_LENGTH
#undef ESC_OK
#undef ESC_OK_LEN
#undef ESC_PREAUTH
#undef ESC_PREAUTH_LEN
#undef ESC_CAPABILITY_STAR
#undef ESC_CAPABILITY_STAR_LEN
#undef ESC_CAPABILITY_OK
#undef ESC_CAPABILITY_OK_LEN
#undef ESC_CAPABILITY_GREETING
#undef ESC_CAPABILITY_GREETING_LEN
}

// This can get called from the UI thread or an imap thread.
// It makes sure we don't get left with partial messages in
// the memory cache.
static void DoomCacheEntry(nsIMsgMailNewsUrl *url)
{
  bool readingFromMemCache = false;
  nsCOMPtr<nsIImapUrl> imapUrl = do_QueryInterface(url);
  imapUrl->GetMsgLoadingFromCache(&readingFromMemCache);
  if (!readingFromMemCache)
  {
    nsCOMPtr<nsICacheEntry> cacheEntry;
    url->GetMemCacheEntry(getter_AddRefs(cacheEntry));
    if (cacheEntry)
      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::SendDataParseIMAPandCheckForNewMail(const char *aData, const char *aCommand)
{
  nsresult rv;
  bool isResend = false;
  while (true)
  {
    // Send authentication string (true: suppress logging the string).
    rv = SendData(aData, true);
    if (NS_FAILED(rv))
      break;
    ParseIMAPandCheckForNewMail(aCommand);
    if (!GetServerStateParser().WaitingForMoreClientInput())
      break;

    // The server is asking for the authentication string again. So we send
    // the same string again although we know that it might be rejected again.
    // We do that to get a firm authentication failure instead of a resend
    // request. That keeps things in order before failing authentication and
    // trying another method if capable.
    if (isResend)
    {
      rv = NS_ERROR_FAILURE;
      break;
    }
    isResend = true;
  }

  return rv;
}

nsresult nsImapProtocol::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);

      rv = SendDataParseIMAPandCheckForNewMail(m_dataOutputBuf, currentCommand);
    } // if the last command succeeded
  } // if auth plain capability
  else if (flag & kHasAuthLoginCapability)
  {
    MOZ_LOG(IMAP, LogLevel::Debug, ("LOGIN auth"));
    PR_snprintf(m_dataOutputBuf, OUTPUT_BUFFER_SIZE, "%s authenticate LOGIN" CRLF, GetServerCommandTag());
    rv = SendData(m_dataOutputBuf);
    NS_ENSURE_SUCCESS(rv, rv);
    currentCommand = PL_strdup(m_dataOutputBuf);
    ParseIMAPandCheckForNewMail();

    if (GetServerStateParser().LastCommandSuccessful())
    {
      char *base64Str = PL_Base64Encode(userName, 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 = SendDataParseIMAPandCheckForNewMail(command.get(), nullptr);
  }
#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;
}