/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// as does this
#include "msgCore.h"  // for pre-compiled headers
#include "nsMsgUtils.h"

#include "nsImapStringBundle.h"
#include "nsVersionComparator.h"

#include "nsThreadUtils.h"
#include "nsIMsgStatusFeedback.h"
#include "nsImapCore.h"
#include "nsImapProtocol.h"
#include "nsIMsgMailNewsUrl.h"
#include "../public/nsIImapHostSessionList.h"
#include "nsImapMailFolder.h"
#include "nsIMsgAccountManager.h"
#include "nsImapServerResponseParser.h"
#include "nspr.h"
#include "plbase64.h"
#include "nsIEventTarget.h"
#include "nsIImapService.h"
#include "nsISocketTransportService.h"
#include "nsIStreamListenerTee.h"
#include "nsIInputStreamPump.h"
#include "nsNetUtil.h"
#include "nsIDBFolderInfo.h"
#include "nsIPipe.h"
#include "nsIMsgFolder.h"
#include "nsMsgMessageFlags.h"
#include "nsTextFormatter.h"
#include "nsTransportUtils.h"
#include "nsIMsgHdr.h"
#include "nsMsgI18N.h"
// for the memory cache...
#include "nsICacheEntry.h"
#include "nsICacheStorage.h"
#include "nsICacheEntryOpenCallback.h"
#include "CacheObserver.h"
#include "nsIURIMutator.h"

#include "nsIDocShell.h"
#include "nsILoadInfo.h"
#include "nsCOMPtr.h"
#include "nsMimeTypes.h"
#include "nsIInterfaceRequestor.h"
#include "nsXPCOMCIDInternal.h"
#include "nsIXULAppInfo.h"
#include "nsSocketTransportService2.h"
#include "nsSyncRunnableHelpers.h"
#include "nsICancelable.h"

// netlib required files
#include "nsIStreamListener.h"
#include "nsIMsgIncomingServer.h"
#include "nsIImapIncomingServer.h"
#include "nsIPrefBranch.h"
#include "nsIPrefService.h"
#include "nsIPrefLocalizedString.h"
#include "nsImapUtils.h"
#include "nsIStreamConverterService.h"
#include "nsIProxyInfo.h"
#include "nsITLSSocketControl.h"
#include "nsITransportSecurityInfo.h"
#include "nsProxyRelease.h"
#include "nsDebug.h"
#include "nsMsgCompressIStream.h"
#include "nsMsgCompressOStream.h"
#include "mozilla/Logging.h"
#include "mozilla/Attributes.h"
#include "mozilla/SlicedInputStream.h"
#include "nsIPrincipal.h"
#include "nsContentSecurityManager.h"

// imap event sinks
#include "nsIImapMailFolderSink.h"
#include "nsIImapServerSink.h"
#include "nsIImapMessageSink.h"

#include "mozilla/dom/InternalResponse.h"
#include "mozilla/NullPrincipal.h"

// TLS alerts
#include "NSSErrorsService.h"

#include "mozilla/SyncRunnable.h"

using namespace mozilla;

LazyLogModule IMAP("IMAP");
LazyLogModule IMAP_CS("IMAP_CS");
LazyLogModule IMAPCache("IMAPCache");

#define ONE_SECOND ((uint32_t)1000)  // one second

#define OUTPUT_BUFFER_SIZE (4096 * 2)

#define IMAP_ENV_HEADERS "From To Cc Bcc Subject Date Message-ID "
#define IMAP_DB_HEADERS                                                 \
  "Priority X-Priority References Newsgroups In-Reply-To Content-Type " \
  "Reply-To"
#define IMAP_ENV_AND_DB_HEADERS IMAP_ENV_HEADERS IMAP_DB_HEADERS
static const PRIntervalTime kImapSleepTime = PR_MillisecondsToInterval(60000);
static int32_t gPromoteNoopToCheckCount = 0;
static const uint32_t kFlagChangesBeforeCheck = 10;
static const int32_t kMaxSecondsBeforeCheck = 600;

class AutoProxyReleaseMsgWindow {
 public:
  AutoProxyReleaseMsgWindow() : mMsgWindow() {}
  ~AutoProxyReleaseMsgWindow() {
    NS_ReleaseOnMainThread("AutoProxyReleaseMsgWindow::mMsgWindow",
                           dont_AddRef(mMsgWindow));
  }
  nsIMsgWindow** StartAssignment() {
    MOZ_ASSERT(!mMsgWindow);
    return &mMsgWindow;
  }
  operator nsIMsgWindow*() { return mMsgWindow; }

 private:
  nsIMsgWindow* mMsgWindow;
};

nsIMsgWindow** getter_AddRefs(AutoProxyReleaseMsgWindow& aSmartPtr) {
  return aSmartPtr.StartAssignment();
}

NS_IMPL_ISUPPORTS(nsMsgImapHdrXferInfo, nsIImapHeaderXferInfo)

nsMsgImapHdrXferInfo::nsMsgImapHdrXferInfo() : m_hdrInfos(kNumHdrsToXfer) {
  m_nextFreeHdrInfo = 0;
}

nsMsgImapHdrXferInfo::~nsMsgImapHdrXferInfo() {}

NS_IMETHODIMP nsMsgImapHdrXferInfo::GetNumHeaders(int32_t* aNumHeaders) {
  *aNumHeaders = m_nextFreeHdrInfo;
  return NS_OK;
}

NS_IMETHODIMP nsMsgImapHdrXferInfo::GetHeader(int32_t hdrIndex,
                                              nsIImapHeaderInfo** aResult) {
  // If the header index is more than (or equal to) our next free pointer, then
  // its a header we haven't really got and the caller has done something
  // wrong.
  NS_ENSURE_TRUE(hdrIndex < m_nextFreeHdrInfo, NS_ERROR_NULL_POINTER);

  NS_IF_ADDREF(*aResult = m_hdrInfos.SafeObjectAt(hdrIndex));
  if (!*aResult) return NS_ERROR_NULL_POINTER;
  return NS_OK;
}

static const int32_t kInitLineHdrCacheSize = 512;  // should be about right

nsIImapHeaderInfo* nsMsgImapHdrXferInfo::StartNewHdr() {
  if (m_nextFreeHdrInfo >= kNumHdrsToXfer) return nullptr;

  nsIImapHeaderInfo* result = m_hdrInfos.SafeObjectAt(m_nextFreeHdrInfo++);
  if (result) return result;

  nsMsgImapLineDownloadCache* lineCache = new nsMsgImapLineDownloadCache();
  if (!lineCache) return nullptr;

  lineCache->GrowBuffer(kInitLineHdrCacheSize);

  m_hdrInfos.AppendObject(lineCache);

  return lineCache;
}

// maybe not needed...
void nsMsgImapHdrXferInfo::FinishCurrentHdr() {
  // nothing to do?
}

void nsMsgImapHdrXferInfo::ResetAll() {
  int32_t count = m_hdrInfos.Count();
  for (int32_t i = 0; i < count; i++) {
    nsIImapHeaderInfo* hdrInfo = m_hdrInfos[i];
    if (hdrInfo) hdrInfo->ResetCache();
  }
  m_nextFreeHdrInfo = 0;
}

void nsMsgImapHdrXferInfo::ReleaseAll() {
  m_hdrInfos.Clear();
  m_nextFreeHdrInfo = 0;
}

NS_IMPL_ISUPPORTS(nsMsgImapLineDownloadCache, nsIImapHeaderInfo)

// **** helper class for downloading line ****
nsMsgImapLineDownloadCache::nsMsgImapLineDownloadCache() {
  fLineInfo = (msg_line_info*)PR_CALLOC(sizeof(msg_line_info));
  fLineInfo->uidOfMessage = nsMsgKey_None;
  m_msgSize = 0;
}

nsMsgImapLineDownloadCache::~nsMsgImapLineDownloadCache() {
  PR_Free(fLineInfo);
}

uint32_t nsMsgImapLineDownloadCache::CurrentUID() {
  return fLineInfo->uidOfMessage;
}

uint32_t nsMsgImapLineDownloadCache::SpaceAvailable() {
  MOZ_ASSERT(kDownLoadCacheSize >= m_bufferPos);
  if (kDownLoadCacheSize <= m_bufferPos) return 0;
  return kDownLoadCacheSize - m_bufferPos;
}

msg_line_info* nsMsgImapLineDownloadCache::GetCurrentLineInfo() {
  AppendBuffer("", 1);  // null terminate the buffer
  fLineInfo->adoptedMessageLine = GetBuffer();
  return fLineInfo;
}

NS_IMETHODIMP nsMsgImapLineDownloadCache::ResetCache() {
  ResetWritePos();
  return NS_OK;
}

bool nsMsgImapLineDownloadCache::CacheEmpty() { return m_bufferPos == 0; }

NS_IMETHODIMP nsMsgImapLineDownloadCache::CacheLine(const char* line,
                                                    uint32_t uid) {
  fLineInfo->uidOfMessage = uid;
  return AppendString(line);
}

/* attribute nsMsgKey msgUid; */
NS_IMETHODIMP nsMsgImapLineDownloadCache::GetMsgUid(nsMsgKey* aMsgUid) {
  *aMsgUid = fLineInfo->uidOfMessage;
  return NS_OK;
}
NS_IMETHODIMP nsMsgImapLineDownloadCache::SetMsgUid(nsMsgKey aMsgUid) {
  fLineInfo->uidOfMessage = aMsgUid;
  return NS_OK;
}

/* attribute long msgSize; */
NS_IMETHODIMP nsMsgImapLineDownloadCache::GetMsgSize(int32_t* aMsgSize) {
  *aMsgSize = m_msgSize;
  return NS_OK;
}

NS_IMETHODIMP nsMsgImapLineDownloadCache::SetMsgSize(int32_t aMsgSize) {
  m_msgSize = aMsgSize;
  return NS_OK;
}

/* readonly attribute ACString msgHdrs; */
NS_IMETHODIMP nsMsgImapLineDownloadCache::GetMsgHdrs(nsACString& aMsgHdrs) {
  AppendBuffer("", 1);  // null terminate the buffer
  aMsgHdrs.Assign(GetBuffer());
  return NS_OK;
}

// The following macros actually implement addref, release and query interface
// for our component.
NS_IMPL_ADDREF_INHERITED(nsImapProtocol, nsMsgProtocol)
NS_IMPL_RELEASE_INHERITED(nsImapProtocol, nsMsgProtocol)

NS_INTERFACE_MAP_BEGIN(nsImapProtocol)
  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIImapProtocol)
  NS_INTERFACE_MAP_ENTRY(nsIImapProtocol)
  NS_INTERFACE_MAP_ENTRY(nsIProtocolProxyCallback)
  NS_INTERFACE_MAP_ENTRY(nsIInputStreamCallback)
  NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
  NS_INTERFACE_MAP_ENTRY(nsIImapProtocolSink)
  NS_INTERFACE_MAP_ENTRY(nsIMsgAsyncPromptListener)
NS_INTERFACE_MAP_END

static int32_t gTooFastTime = 2;
static int32_t gIdealTime = 4;
static int32_t gChunkAddSize = 16384;
static int32_t gChunkSize = 250000;
static int32_t gChunkThreshold = gChunkSize + gChunkSize / 2;
static bool gChunkSizeDirty = false;
static bool gFetchByChunks = true;
static bool gInitialized = false;
static bool gHideUnusedNamespaces = true;
static bool gHideOtherUsersFromList = false;
static bool gUseEnvelopeCmd = false;
static bool gUseLiteralPlus = true;
static bool gExpungeAfterDelete = false;
static bool gCheckDeletedBeforeExpunge = false;  // bug 235004
static int32_t gResponseTimeout = 100;
static int32_t gAppendTimeout = gResponseTimeout / 5;
static nsImapProtocol::TCPKeepalive gTCPKeepalive;
static bool gUseDiskCache2 = true;  // Use disk cache instead of memory cache
static nsCOMPtr<nsICacheStorage> gCache2Storage;

// let delete model control expunging, i.e., don't ever expunge when the
// user chooses the imap delete model, otherwise, expunge when over the
// threshold. This is the normal TB behavior.
static const int32_t kAutoExpungeDeleteModel = 0;  // default
// Expunge whenever the folder is opened regardless of delete model or number
// of marked deleted messages present in the folder.
static const int32_t kAutoExpungeAlways = 1;
// Expunge when over the threshold, independent of the delete model.
static const int32_t kAutoExpungeOnThreshold = 2;
// Set mail.imap.expunge_option to kAutoExpungeNever to NEVER do an auto-
// expunge. This is useful when doing a bulk transfer of folders and messages
// between imap servers.
static const int32_t kAutoExpungeNever = 3;

static int32_t gExpungeOption = kAutoExpungeDeleteModel;
static int32_t gExpungeThreshold = 20;

const int32_t kAppBufSize = 100;
// can't use static nsCString because it shows up as a leak.
static char gAppName[kAppBufSize];
static char gAppVersion[kAppBufSize];

nsresult nsImapProtocol::GlobalInitialization(nsIPrefBranch* aPrefBranch) {
  gInitialized = true;

  aPrefBranch->GetIntPref("mail.imap.chunk_fast",
                          &gTooFastTime);  // secs we read too little too fast
  aPrefBranch->GetIntPref("mail.imap.chunk_ideal",
                          &gIdealTime);  // secs we read enough in good time
  aPrefBranch->GetIntPref(
      "mail.imap.chunk_add",
      &gChunkAddSize);  // buffer size to add when wasting time
  aPrefBranch->GetIntPref("mail.imap.chunk_size", &gChunkSize);
  aPrefBranch->GetIntPref("mail.imap.min_chunk_size_threshold",
                          &gChunkThreshold);
  aPrefBranch->GetBoolPref("mail.imap.hide_other_users",
                           &gHideOtherUsersFromList);
  aPrefBranch->GetBoolPref("mail.imap.hide_unused_namespaces",
                           &gHideUnusedNamespaces);
  aPrefBranch->GetIntPref("mail.imap.noop_check_count",
                          &gPromoteNoopToCheckCount);
  aPrefBranch->GetBoolPref("mail.imap.use_envelope_cmd", &gUseEnvelopeCmd);
  aPrefBranch->GetBoolPref("mail.imap.use_literal_plus", &gUseLiteralPlus);
  aPrefBranch->GetBoolPref("mail.imap.expunge_after_delete",
                           &gExpungeAfterDelete);
  aPrefBranch->GetBoolPref("mail.imap.use_disk_cache2", &gUseDiskCache2);
  aPrefBranch->GetBoolPref("mail.imap.check_deleted_before_expunge",
                           &gCheckDeletedBeforeExpunge);
  aPrefBranch->GetIntPref("mail.imap.expunge_option", &gExpungeOption);
  aPrefBranch->GetIntPref("mail.imap.expunge_threshold_number",
                          &gExpungeThreshold);
  aPrefBranch->GetIntPref("mailnews.tcptimeout", &gResponseTimeout);
  gAppendTimeout = gResponseTimeout / 5;

  gTCPKeepalive.enabled.store(false, std::memory_order_relaxed);
  gTCPKeepalive.idleTimeS.store(-1, std::memory_order_relaxed);
  gTCPKeepalive.retryIntervalS.store(-1, std::memory_order_relaxed);

  nsCOMPtr<nsIXULAppInfo> appInfo(do_GetService(XULAPPINFO_SERVICE_CONTRACTID));

  if (appInfo) {
    nsCString appName, appVersion;
    appInfo->GetName(appName);
    appInfo->GetVersion(appVersion);
    PL_strncpyz(gAppName, appName.get(), kAppBufSize);
    PL_strncpyz(gAppVersion, appVersion.get(), kAppBufSize);
  }
  return NS_OK;
}

class nsImapTransportEventSink final : public nsITransportEventSink {
 public:
  NS_DECL_THREADSAFE_ISUPPORTS
  NS_DECL_NSITRANSPORTEVENTSINK

 private:
  friend class nsImapProtocol;

  virtual ~nsImapTransportEventSink() = default;
  nsresult ApplyTCPKeepalive(nsISocketTransport* aTransport);

  nsCOMPtr<nsITransportEventSink> m_proxy;
};

NS_IMPL_ISUPPORTS(nsImapTransportEventSink, nsITransportEventSink)

NS_IMETHODIMP
nsImapTransportEventSink::OnTransportStatus(nsITransport* aTransport,
                                            nsresult aStatus, int64_t aProgress,
                                            int64_t aProgressMax) {
  if (aStatus == NS_NET_STATUS_CONNECTED_TO) {
    nsCOMPtr<nsISocketTransport> sockTrans(do_QueryInterface(aTransport));
    if (!NS_WARN_IF(!sockTrans)) ApplyTCPKeepalive(sockTrans);
  }

  if (NS_WARN_IF(!m_proxy)) return NS_OK;

  return m_proxy->OnTransportStatus(aTransport, aStatus, aProgress,
                                    aProgressMax);
}

nsresult nsImapTransportEventSink::ApplyTCPKeepalive(
    nsISocketTransport* aTransport) {
  nsresult rv;

  bool kaEnabled = gTCPKeepalive.enabled.load(std::memory_order_relaxed);
  if (kaEnabled) {
    // TCP keepalive idle time, don't mistake with IMAP IDLE.
    int32_t kaIdleTime =
        gTCPKeepalive.idleTimeS.load(std::memory_order_relaxed);
    int32_t kaRetryInterval =
        gTCPKeepalive.retryIntervalS.load(std::memory_order_relaxed);

    if (kaIdleTime < 0 || kaRetryInterval < 0) {
      if (NS_WARN_IF(!net::gSocketTransportService))
        return NS_ERROR_NOT_INITIALIZED;
    }
    if (kaIdleTime < 0) {
      rv = net::gSocketTransportService->GetKeepaliveIdleTime(&kaIdleTime);
      if (NS_FAILED(rv)) {
        MOZ_LOG(IMAP, LogLevel::Error,
                ("GetKeepaliveIdleTime() failed, %" PRIx32,
                 static_cast<uint32_t>(rv)));
        return rv;
      }
    }
    if (kaRetryInterval < 0) {
      rv = net::gSocketTransportService->GetKeepaliveRetryInterval(
          &kaRetryInterval);
      if (NS_FAILED(rv)) {
        MOZ_LOG(IMAP, LogLevel::Error,
                ("GetKeepaliveRetryInterval() failed, %" PRIx32,
                 static_cast<uint32_t>(rv)));
        return rv;
      }
    }

    MOZ_ASSERT(kaIdleTime > 0);
    MOZ_ASSERT(kaRetryInterval > 0);
    rv = aTransport->SetKeepaliveVals(kaIdleTime, kaRetryInterval);
    if (NS_FAILED(rv)) {
      MOZ_LOG(IMAP, LogLevel::Error,
              ("SetKeepaliveVals(%" PRId32 ", %" PRId32 ") failed, %" PRIx32,
               kaIdleTime, kaRetryInterval, static_cast<uint32_t>(rv)));
      return rv;
    }
  }

  rv = aTransport->SetKeepaliveEnabled(kaEnabled);
  if (NS_FAILED(rv)) {
    MOZ_LOG(IMAP, LogLevel::Error,
            ("SetKeepaliveEnabled(%s) failed, %" PRIx32,
             kaEnabled ? "true" : "false", static_cast<uint32_t>(rv)));
    return rv;
  }
  return NS_OK;
}

// This runnable runs on IMAP thread.
class nsImapProtocolMainLoopRunnable final : public mozilla::Runnable {
 public:
  explicit nsImapProtocolMainLoopRunnable(nsImapProtocol* aProtocol)
      : mozilla::Runnable("nsImapProtocolEventLoopRunnable"),
        mProtocol(aProtocol) {}

  NS_IMETHOD Run() {
    MOZ_ASSERT(!NS_IsMainThread());

    if (!mProtocol->RunImapThreadMainLoop()) {
      // We already run another IMAP event loop.
      return NS_OK;
    }

    // Release protocol object on the main thread to avoid destruction of
    // nsImapProtocol on the IMAP thread, which causes grief for weak
    // references.
    NS_ReleaseOnMainThread("nsImapProtocol::this", mProtocol.forget());

    // shutdown this thread, but do it from the main thread
    nsCOMPtr<nsIThread> imapThread(do_GetCurrentThread());
    if (NS_FAILED(NS_DispatchToMainThread(
            NS_NewRunnableFunction("nsImapProtorolMainLoopRunnable::Run",
                                   [imapThread = std::move(imapThread)]() {
                                     imapThread->Shutdown();
                                   })))) {
      NS_WARNING("Failed to dispatch nsImapThreadShutdownEvent");
    }
    return NS_OK;
  }

 private:
  RefPtr<nsImapProtocol> mProtocol;
};

nsImapProtocol::nsImapProtocol()
    : nsMsgProtocol(nullptr),
      m_dataAvailableMonitor("imapDataAvailable"),
      m_urlReadyToRunMonitor("imapUrlReadyToRun"),
      m_pseudoInterruptMonitor("imapPseudoInterrupt"),
      m_dataMemberMonitor("imapDataMember"),
      m_threadDeathMonitor("imapThreadDeath"),
      m_waitForBodyIdsMonitor("imapWaitForBodyIds"),
      m_fetchBodyListMonitor("imapFetchBodyList"),
      m_passwordReadyMonitor("imapPasswordReady"),
      mLock("nsImapProtocol.mLock"),
      m_parser(*this) {
  m_urlInProgress = false;
  m_idle = false;
  m_retryUrlOnError = false;
  m_useIdle = true;  // by default, use it
  m_useCondStore = true;
  m_useCompressDeflate = true;
  m_ignoreExpunges = false;
  m_prefAuthMethods = kCapabilityUndefined;
  m_failedAuthMethods = 0;
  m_currentAuthMethod = kCapabilityUndefined;
  m_socketType = nsMsgSocketType::trySTARTTLS;
  m_connectionStatus = NS_OK;
  m_safeToCloseConnection = false;
  m_hostSessionList = nullptr;
  m_isGmailServer = false;
  m_fetchingWholeMessage = false;
  m_allowUTF8Accept = false;

  nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID));
  NS_ASSERTION(prefBranch, "FAILED to create the preference service");

  // read in the accept languages preference
  if (prefBranch) {
    if (!gInitialized) GlobalInitialization(prefBranch);

    nsCOMPtr<nsIPrefLocalizedString> prefString;
    prefBranch->GetComplexValue("intl.accept_languages",
                                NS_GET_IID(nsIPrefLocalizedString),
                                getter_AddRefs(prefString));
    if (prefString) prefString->ToString(getter_Copies(mAcceptLanguages));

    nsCString customDBHeaders;
    prefBranch->GetCharPref("mailnews.customDBHeaders", customDBHeaders);

    ParseString(customDBHeaders, ' ', mCustomDBHeaders);
    prefBranch->GetBoolPref("mailnews.display.prefer_plaintext",
                            &m_preferPlainText);

    nsAutoCString customHeaders;
    prefBranch->GetCharPref("mailnews.customHeaders", customHeaders);
    customHeaders.StripWhitespace();
    ParseString(customHeaders, ':', mCustomHeaders);

    nsresult rv;
    bool bVal = false;
    rv = prefBranch->GetBoolPref("mail.imap.tcp_keepalive.enabled", &bVal);
    if (NS_SUCCEEDED(rv))
      gTCPKeepalive.enabled.store(bVal, std::memory_order_relaxed);

    if (bVal) {
      int32_t val;
      // TCP keepalive idle time, don't mistake with IMAP IDLE.
      rv = prefBranch->GetIntPref("mail.imap.tcp_keepalive.idle_time", &val);
      if (NS_SUCCEEDED(rv) && val >= 0)
        gTCPKeepalive.idleTimeS.store(
            std::min<int32_t>(std::max(val, 1), net::kMaxTCPKeepIdle),
            std::memory_order_relaxed);

      rv = prefBranch->GetIntPref("mail.imap.tcp_keepalive.retry_interval",
                                  &val);
      if (NS_SUCCEEDED(rv) && val >= 0)
        gTCPKeepalive.retryIntervalS.store(
            std::min<int32_t>(std::max(val, 1), net::kMaxTCPKeepIntvl),
            std::memory_order_relaxed);
    }
  }

  // ***** Thread support *****
  m_thread = nullptr;
  m_imapThreadIsRunning = false;
  m_currentServerCommandTagNumber = 0;
  m_active = false;
  m_folderNeedsSubscribing = false;
  m_folderNeedsACLRefreshed = false;
  m_threadShouldDie = false;
  m_inThreadShouldDie = false;
  m_pseudoInterrupted = false;
  m_nextUrlReadyToRun = false;
  m_idleResponseReadyToHandle = false;
  m_trackingTime = false;
  m_curFetchSize = 0;
  m_startTime = 0;
  m_endTime = 0;
  m_lastActiveTime = 0;
  m_lastProgressTime = 0;
  ResetProgressInfo();

  m_tooFastTime = 0;
  m_idealTime = 0;
  m_chunkAddSize = 0;
  m_chunkStartSize = 0;
  m_fetchByChunks = true;
  m_sendID = true;
  m_chunkSize = 0;
  m_chunkThreshold = 0;
  m_fromHeaderSeen = false;
  m_closeNeededBeforeSelect = false;
  m_needNoop = false;
  m_noopCount = 0;
  m_fetchBodyListIsNew = false;
  m_flagChangeCount = 0;
  m_lastCheckTime = PR_Now();

  m_hierarchyNameState = kNoOperationInProgress;
  m_discoveryStatus = eContinue;

  // m_dataOutputBuf is used by Send Data
  m_dataOutputBuf = (char*)PR_CALLOC(sizeof(char) * OUTPUT_BUFFER_SIZE);

  // used to buffer incoming data by ReadNextLine
  m_inputStreamBuffer = new nsMsgLineStreamBuffer(
      OUTPUT_BUFFER_SIZE, true /* allocate new lines */,
      false /* leave CRLFs on the returned string */);
  m_currentBiffState = nsIMsgFolder::nsMsgBiffState_Unknown;
  m_progressStringName.Truncate();
  m_stringIndex = IMAP_EMPTY_STRING_INDEX;
  m_progressExpectedNumber = 0;
  memset(m_progressCurrentNumber, 0, sizeof m_progressCurrentNumber);

  // since these are embedded in the nsImapProtocol object, but passed
  // through proxied xpcom methods, just AddRef them here.
  m_hdrDownloadCache = new nsMsgImapHdrXferInfo();
  m_downloadLineCache = new nsMsgImapLineDownloadCache();

  // subscription
  m_autoSubscribe = true;
  m_autoUnsubscribe = true;
  m_autoSubscribeOnOpen = true;
  m_deletableChildren = nullptr;

  mFolderLastModSeq = 0;

  Configure(gTooFastTime, gIdealTime, gChunkAddSize, gChunkSize,
            gChunkThreshold, gFetchByChunks);
  m_forceSelect = false;
  m_capabilityResponseOccurred = true;

  m_imapAction = 0;
  m_bytesToChannel = 0;
  m_passwordStatus = NS_OK;
  m_passwordObtained = false;
  mFolderTotalMsgCount = 0;
  mFolderHighestUID = 0;
  m_notifySearchHit = false;
  m_preferPlainText = false;
  m_uidValidity = kUidUnknown;
}

nsresult nsImapProtocol::Configure(int32_t TooFastTime, int32_t IdealTime,
                                   int32_t ChunkAddSize, int32_t ChunkSize,
                                   int32_t ChunkThreshold, bool FetchByChunks) {
  m_tooFastTime = TooFastTime;    // secs we read too little too fast
  m_idealTime = IdealTime;        // secs we read enough in good time
  m_chunkAddSize = ChunkAddSize;  // buffer size to add when wasting time
  m_chunkStartSize = m_chunkSize = ChunkSize;
  m_chunkThreshold = ChunkThreshold;
  m_fetchByChunks = FetchByChunks;

  return NS_OK;
}

NS_IMETHODIMP
nsImapProtocol::Initialize(nsIImapHostSessionList* aHostSessionList,
                           nsIImapIncomingServer* aServer) {
  NS_ASSERTION(
      aHostSessionList && aServer,
      "oops...trying to initialize with a null host session list or server!");
  if (!aHostSessionList || !aServer) return NS_ERROR_NULL_POINTER;

  nsresult rv = m_downloadLineCache->GrowBuffer(kDownLoadCacheSize);
  NS_ENSURE_SUCCESS(rv, rv);

  m_flagState = new nsImapFlagAndUidState(kImapFlagAndUidStateSize);
  if (!m_flagState) return NS_ERROR_OUT_OF_MEMORY;

  aServer->GetUseIdle(&m_useIdle);
  aServer->GetForceSelect(&m_forceSelect);
  aServer->GetUseCondStore(&m_useCondStore);
  aServer->GetUseCompressDeflate(&m_useCompressDeflate);
  aServer->GetAllowUTF8Accept(&m_allowUTF8Accept);

  m_hostSessionList = aHostSessionList;
  m_parser.SetHostSessionList(aHostSessionList);
  m_parser.SetFlagState(m_flagState);

  // Initialize the empty mime part string on the main thread.
  nsCOMPtr<nsIStringBundle> bundle;
  rv = IMAPGetStringBundle(getter_AddRefs(bundle));
  NS_ENSURE_SUCCESS(rv, rv);

  rv = bundle->GetStringFromName("imapEmptyMimePart", m_emptyMimePartString);
  NS_ENSURE_SUCCESS(rv, rv);

  // Now initialize the thread for the connection
  if (m_thread == nullptr) {
    nsCOMPtr<nsIThread> imapThread;
    nsresult rv = NS_NewNamedThread("IMAP", getter_AddRefs(imapThread));
    if (NS_FAILED(rv)) {
      NS_ASSERTION(imapThread, "Unable to create imap thread.");
      return rv;
    }
    RefPtr<nsImapProtocolMainLoopRunnable> runnable =
        new nsImapProtocolMainLoopRunnable(this);
    imapThread->Dispatch(runnable.forget(), NS_DISPATCH_NORMAL);
    imapThread->GetPRThread(&m_thread);
  }
  return NS_OK;
}

nsImapProtocol::~nsImapProtocol() {
  PR_Free(m_dataOutputBuf);

  // **** We must be out of the thread main loop function
  NS_ASSERTION(!m_imapThreadIsRunning, "Oops, thread is still running.");
  MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread());
}

const nsCString& nsImapProtocol::GetImapHostName() {
  if (m_runningUrl && m_hostName.IsEmpty()) {
    nsCOMPtr<nsIURI> url = do_QueryInterface(m_runningUrl);
    url->GetAsciiHost(m_hostName);
  }

  return m_hostName;
}

const nsCString& nsImapProtocol::GetImapUserName() {
  if (m_userName.IsEmpty() && m_imapServerSink) {
    m_imapServerSink->GetOriginalUsername(m_userName);
  }
  return m_userName;
}

const char* nsImapProtocol::GetImapServerKey() {
  if (m_serverKey.IsEmpty() && m_imapServerSink) {
    m_imapServerSink->GetServerKey(m_serverKey);
  }
  return m_serverKey.get();
}

nsresult nsImapProtocol::SetupSinkProxy() {
  if (!m_runningUrl) return NS_OK;
  nsresult res;
  bool newFolderSink = false;
  if (!m_imapMailFolderSink) {
    nsCOMPtr<nsIImapMailFolderSink> aImapMailFolderSink;
    (void)m_runningUrl->GetImapMailFolderSink(
        getter_AddRefs(aImapMailFolderSink));
    if (aImapMailFolderSink) {
      m_imapMailFolderSink = new ImapMailFolderSinkProxy(aImapMailFolderSink);
      newFolderSink = true;
    }
  }
  if (newFolderSink) Log("SetupSinkProxy", nullptr, "got m_imapMailFolderSink");

  if (!m_imapMessageSink) {
    nsCOMPtr<nsIImapMessageSink> aImapMessageSink;
    (void)m_runningUrl->GetImapMessageSink(getter_AddRefs(aImapMessageSink));
    if (aImapMessageSink) {
      m_imapMessageSink = new ImapMessageSinkProxy(aImapMessageSink);
    } else {
      return NS_ERROR_ILLEGAL_VALUE;
    }
  }
  if (!m_imapServerSink) {
    nsCOMPtr<nsIImapServerSink> aImapServerSink;
    res = m_runningUrl->GetImapServerSink(getter_AddRefs(aImapServerSink));
    if (aImapServerSink) {
      m_imapServerSink = new ImapServerSinkProxy(aImapServerSink);
      m_imapServerSinkLatest = m_imapServerSink;
    } else {
      return NS_ERROR_ILLEGAL_VALUE;
    }
  }
  if (!m_imapProtocolSink) {
    nsCOMPtr<nsIImapProtocolSink> anImapProxyHelper(
        do_QueryInterface(NS_ISUPPORTS_CAST(nsIImapProtocolSink*, this), &res));
    m_imapProtocolSink = new ImapProtocolSinkProxy(anImapProxyHelper);
  }
  return NS_OK;
}

static void SetSecurityCallbacksFromChannel(nsISocketTransport* aTrans,
                                            nsIChannel* aChannel) {
  nsCOMPtr<nsIInterfaceRequestor> callbacks;
  aChannel->GetNotificationCallbacks(getter_AddRefs(callbacks));

  nsCOMPtr<nsILoadGroup> loadGroup;
  aChannel->GetLoadGroup(getter_AddRefs(loadGroup));

  nsCOMPtr<nsIInterfaceRequestor> securityCallbacks;
  NS_NewNotificationCallbacksAggregation(callbacks, loadGroup,
                                         getter_AddRefs(securityCallbacks));
  if (securityCallbacks) aTrans->SetSecurityCallbacks(securityCallbacks);
}

// Setup With Url is intended to set up data which is held on a PER URL basis
// and not a per connection basis. If you have data which is independent of the
// url we are currently running, then you should put it in Initialize(). This is
// only ever called from the UI thread. It is called from LoadImapUrl, right
// before the url gets run - i.e., the url is next in line to run.
// See also ReleaseUrlState(), which frees a bunch of the things set up in here.
nsresult nsImapProtocol::SetupWithUrl(nsIURI* aURL, nsISupports* aConsumer) {
  nsresult rv = NS_ERROR_FAILURE;
  NS_ASSERTION(aURL, "null URL passed into Imap Protocol");
  if (aURL) {
    nsCOMPtr<nsIImapUrl> imapURL = do_QueryInterface(aURL, &rv);
    NS_ENSURE_SUCCESS(rv, rv);
    m_runningUrl = imapURL;
    m_runningUrlLatest = m_runningUrl;

    nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_runningUrl);
    nsCOMPtr<nsIMsgIncomingServer> server = do_QueryReferent(m_server);
    if (!server) {
      rv = mailnewsUrl->GetServer(getter_AddRefs(server));
      NS_ENSURE_SUCCESS(rv, rv);
      m_server = do_GetWeakReference(server);
    }
    nsCOMPtr<nsIMsgFolder> folder;
    mailnewsUrl->GetFolder(getter_AddRefs(folder));
    mFolderLastModSeq = 0;
    mFolderTotalMsgCount = 0;
    mFolderHighestUID = 0;
    m_uidValidity = kUidUnknown;
    if (folder) {
      nsCOMPtr<nsIMsgDatabase> folderDB;
      nsCOMPtr<nsIDBFolderInfo> folderInfo;
      folder->GetDBFolderInfoAndDB(getter_AddRefs(folderInfo),
                                   getter_AddRefs(folderDB));
      if (folderInfo) {
        nsCString modSeqStr;
        folderInfo->GetCharProperty(kModSeqPropertyName, modSeqStr);
        mFolderLastModSeq = ParseUint64Str(modSeqStr.get());
        folderInfo->GetNumMessages(&mFolderTotalMsgCount);
        folderInfo->GetUint32Property(kHighestRecordedUIDPropertyName, 0,
                                      &mFolderHighestUID);
        folderInfo->GetImapUidValidity(&m_uidValidity);
      }
    }
    nsCOMPtr<nsIImapIncomingServer> imapServer = do_QueryInterface(server);
    nsCOMPtr<nsIStreamListener> aRealStreamListener =
        do_QueryInterface(aConsumer);
    m_runningUrl->GetMockChannel(getter_AddRefs(m_mockChannel));
    imapServer->GetIsGMailServer(&m_isGmailServer);
    if (!m_mockChannel) {
      nsCOMPtr<nsIPrincipal> nullPrincipal =
          NullPrincipal::CreateWithoutOriginAttributes();

      // there are several imap operations that aren't initiated via a
      // nsIChannel::AsyncOpen call on the mock channel. such as selecting a
      // folder. nsImapProtocol now insists on a mock channel when processing a
      // url.
      nsCOMPtr<nsIChannel> channel;
      rv =
          NS_NewChannel(getter_AddRefs(channel), aURL, nullPrincipal,
                        nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
                        nsIContentPolicy::TYPE_OTHER);
      m_mockChannel = do_QueryInterface(channel);
      NS_ASSERTION(m_mockChannel,
                   "failed to get a mock channel in nsImapProtocol");

      // Certain imap operations (not initiated by the IO Service via AsyncOpen)
      // can be interrupted by  the stop button on the toolbar. We do this by
      // using the loadgroup of the docshell for the message pane. We really
      // shouldn't be doing this.. See the comment in
      // nsMsgMailNewsUrl::GetLoadGroup.
      nsCOMPtr<nsILoadGroup> loadGroup;
      mailnewsUrl->GetLoadGroup(
          getter_AddRefs(loadGroup));  // get the message pane load group
      if (loadGroup)
        loadGroup->AddRequest(m_mockChannel, nullptr /* context isupports */);
    }

    if (m_mockChannel) {
      m_mockChannel->SetImapProtocol(this);
      // if we have a listener from a mock channel, over-ride the consumer that
      // was passed in
      nsCOMPtr<nsIStreamListener> channelListener;
      m_mockChannel->GetChannelListener(getter_AddRefs(channelListener));
      if (channelListener)  // only over-ride if we have a non null channel
                            // listener
        aRealStreamListener = channelListener;
      nsCOMPtr<nsIMsgWindow> msgWindow;
      GetMsgWindow(getter_AddRefs(msgWindow));
      if (!msgWindow) GetTopmostMsgWindow(getter_AddRefs(msgWindow));
      if (msgWindow) {
        // Set up the MockChannel to attempt nsIProgressEventSink callbacks on
        // the messageWindow, with fallback to the docShell (and the
        // loadgroup).
        nsCOMPtr<nsIDocShell> docShell;
        msgWindow->GetMessageWindowDocShell(getter_AddRefs(docShell));
        nsCOMPtr<nsIInterfaceRequestor> ir(do_QueryInterface(docShell));
        nsCOMPtr<nsIInterfaceRequestor> interfaceRequestor;
        msgWindow->GetNotificationCallbacks(getter_AddRefs(interfaceRequestor));
        nsCOMPtr<nsIInterfaceRequestor> aggregateIR;
        NS_NewInterfaceRequestorAggregation(interfaceRequestor, ir,
                                            getter_AddRefs(aggregateIR));
        m_mockChannel->SetNotificationCallbacks(aggregateIR);
      }
    }

    // since we'll be making calls directly from the imap thread to the channel
    // listener, we need to turn it into a proxy object....we'll assume that the
    // listener is on the same thread as the event sink queue
    if (aRealStreamListener) {
      NS_ASSERTION(!m_channelListener,
                   "shouldn't already have a channel listener");
      m_channelListener = new StreamListenerProxy(aRealStreamListener);
    }

    server->GetHostName(m_hostName);
    int32_t authMethod;
    (void)server->GetAuthMethod(&authMethod);
    InitPrefAuthMethods(authMethod, server);
    (void)server->GetSocketType(&m_socketType);
    bool shuttingDown;
    (void)imapServer->GetShuttingDown(&shuttingDown);
    if (!shuttingDown)
      (void)imapServer->GetUseIdle(&m_useIdle);
    else
      m_useIdle = false;
    imapServer->GetFetchByChunks(&m_fetchByChunks);
    imapServer->GetSendID(&m_sendID);

    nsAutoString trashFolderPath;
    if (NS_SUCCEEDED(imapServer->GetTrashFolderName(trashFolderPath))) {
      if (m_allowUTF8Accept)
        CopyUTF16toUTF8(trashFolderPath, m_trashFolderPath);
      else
        CopyUTF16toMUTF7(trashFolderPath, m_trashFolderPath);
    }

    nsCOMPtr<nsIPrefBranch> prefBranch(
        do_GetService(NS_PREFSERVICE_CONTRACTID));
    if (prefBranch) {
      bool preferPlainText;
      prefBranch->GetBoolPref("mailnews.display.prefer_plaintext",
                              &preferPlainText);
      // If the pref has changed since the last time we ran a url,
      // clear the shell cache for this host. (bodyshell no longer exists.)
      if (preferPlainText != m_preferPlainText) {
        m_preferPlainText = preferPlainText;
      }
    }
    // If enabled, retrieve the clientid so that we can use it later.
    bool clientidEnabled = false;
    if (NS_SUCCEEDED(server->GetClientidEnabled(&clientidEnabled)) &&
        clientidEnabled)
      server->GetClientid(m_clientId);
    else {
      m_clientId.Truncate();
    }

    bool proxyCallback = false;
    if (m_runningUrl && !m_transport /* and we don't have a transport yet */) {
      if (m_mockChannel) {
        rv = MsgExamineForProxyAsync(m_mockChannel, this,
                                     getter_AddRefs(m_proxyRequest));
        if (NS_FAILED(rv)) {
          rv = SetupWithUrlCallback(nullptr);
        } else {
          proxyCallback = true;
        }
      }
    }

    if (!proxyCallback) rv = LoadImapUrlInternal();
  }

  return rv;
}

// nsIProtocolProxyCallback
NS_IMETHODIMP
nsImapProtocol::OnProxyAvailable(nsICancelable* aRequest, nsIChannel* aChannel,
                                 nsIProxyInfo* aProxyInfo, nsresult aStatus) {
  // If we're called with NS_BINDING_ABORTED, the IMAP thread already died,
  // so we can't carry on. Otherwise, no checking of 'aStatus' here, see
  // nsHttpChannel::OnProxyAvailable(). Status is non-fatal and we just kick on.
  if (aStatus == NS_BINDING_ABORTED) return NS_ERROR_FAILURE;

  nsresult rv = SetupWithUrlCallback(aProxyInfo);
  if (NS_FAILED(rv)) {
    // Cancel the protocol and be done.
    if (m_mockChannel) m_mockChannel->Cancel(rv);
    return rv;
  }

  rv = LoadImapUrlInternal();
  if (NS_FAILED(rv)) {
    if (m_mockChannel) m_mockChannel->Cancel(rv);
  }

  return rv;
}

nsresult nsImapProtocol::SetupWithUrlCallback(nsIProxyInfo* aProxyInfo) {
  m_proxyRequest = nullptr;

  nsresult rv;

  nsCOMPtr<nsISocketTransportService> socketService =
      do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
  if (NS_FAILED(rv)) return rv;

  Log("SetupWithUrlCallback", nullptr, "clearing IMAP_CONNECTION_IS_OPEN");
  ClearFlag(IMAP_CONNECTION_IS_OPEN);
  const char* connectionType = nullptr;

  if (m_socketType == nsMsgSocketType::SSL)
    connectionType = "ssl";
  else if (m_socketType == nsMsgSocketType::alwaysSTARTTLS)
    connectionType = "starttls";
  // This can go away once we think everyone is migrated
  // away from the trySTARTTLS socket type.
  else if (m_socketType == nsMsgSocketType::trySTARTTLS)
    connectionType = "starttls";

  int32_t port = -1;
  nsCOMPtr<nsIURI> uri = do_QueryInterface(m_runningUrl, &rv);
  if (NS_FAILED(rv)) return rv;
  uri->GetPort(&port);

  AutoTArray<nsCString, 1> connectionTypeArray;
  if (connectionType) connectionTypeArray.AppendElement(connectionType);
  // NOTE: Some errors won't show up until the first read attempt (SSL bad
  // certificate errors, for example).
  rv = socketService->CreateTransport(connectionTypeArray, m_hostName, port,
                                      aProxyInfo, nullptr,
                                      getter_AddRefs(m_transport));
  if (NS_FAILED(rv) && m_socketType == nsMsgSocketType::trySTARTTLS) {
    connectionType = nullptr;
    m_socketType = nsMsgSocketType::plain;
    rv = socketService->CreateTransport(connectionTypeArray, m_hostName, port,
                                        aProxyInfo, nullptr,
                                        getter_AddRefs(m_transport));
  }

  // remember so we can know whether we can issue a start tls or not...
  m_connectionType = connectionType;
  if (m_transport && m_mockChannel) {
    uint8_t qos;
    rv = GetQoSBits(&qos);
    if (NS_SUCCEEDED(rv)) m_transport->SetQoSBits(qos);

    // Ensure that the socket can get the notification callbacks
    SetSecurityCallbacksFromChannel(m_transport, m_mockChannel);

    // open buffered, blocking input stream
    rv = m_transport->OpenInputStream(nsITransport::OPEN_BLOCKING, 0, 0,
                                      getter_AddRefs(m_inputStream));
    if (NS_FAILED(rv)) return rv;

    // open buffered, blocking output stream
    rv = m_transport->OpenOutputStream(nsITransport::OPEN_BLOCKING, 0, 0,
                                       getter_AddRefs(m_outputStream));
    if (NS_FAILED(rv)) return rv;
    SetFlag(IMAP_CONNECTION_IS_OPEN);
  }

  return rv;
}

// when the connection is done processing the current state, free any per url
// state data...
void nsImapProtocol::ReleaseUrlState(bool rerunning) {
  // clear out the socket's reference to the notification callbacks for this
  // transaction
  {
    MutexAutoLock mon(mLock);
    if (m_transport) {
      m_transport->SetSecurityCallbacks(nullptr);
      m_transport->SetEventSink(nullptr, nullptr);
    }
  }

  if (m_mockChannel && !rerunning) {
    // Proxy the close of the channel to the ui thread.
    if (m_imapMailFolderSink)
      m_imapMailFolderSink->CloseMockChannel(m_mockChannel);
    else
      m_mockChannel->Close();

    {
      // grab a lock so m_mockChannel doesn't get cleared out
      // from under us.
      MutexAutoLock mon(mLock);
      if (m_mockChannel) {
        // Proxy the release of the channel to the main thread.  This is
        // something that the xpcom proxy system should do for us!
        NS_ReleaseOnMainThread("nsImapProtocol::m_mockChannel",
                               m_mockChannel.forget());
      }
    }
  }

  m_imapMessageSink = nullptr;

  // Proxy the release of the listener to the main thread.  This is something
  // that the xpcom proxy system should do for us!
  {
    // grab a lock so the m_channelListener doesn't get cleared.
    MutexAutoLock mon(mLock);
    if (m_channelListener) {
      NS_ReleaseOnMainThread("nsImapProtocol::m_channelListener",
                             m_channelListener.forget());
    }
  }
  m_channelInputStream = nullptr;
  m_channelOutputStream = nullptr;

  nsCOMPtr<nsIMsgMailNewsUrl> mailnewsurl;
  nsCOMPtr<nsIImapMailFolderSink> saveFolderSink;

  {
    MutexAutoLock mon(mLock);
    if (m_runningUrl) {
      mailnewsurl = do_QueryInterface(m_runningUrl);
      // It is unclear what 'saveFolderSink' is used for, most likely to hold
      // a reference for a little longer. See bug 1324893 and bug 391259.
      saveFolderSink = m_imapMailFolderSink;

      m_runningUrl =
          nullptr;  // force us to release our last reference on the url
      m_urlInProgress = false;
    }
  }
  // Need to null this out whether we have an m_runningUrl or not
  m_imapMailFolderSink = nullptr;

  // we want to make sure the imap protocol's last reference to the url gets
  // released back on the UI thread. This ensures that the objects the imap url
  // hangs on to properly get released back on the UI thread.
  if (mailnewsurl) {
    NS_ReleaseOnMainThread("nsImapProtocol::m_runningUrl",
                           mailnewsurl.forget());
  }
  saveFolderSink = nullptr;
}

class nsImapCancelProxy : public mozilla::Runnable {
 public:
  explicit nsImapCancelProxy(nsICancelable* aProxyRequest)
      : mozilla::Runnable("nsImapCancelProxy"), mRequest(aProxyRequest) {}
  NS_IMETHOD Run() {
    if (mRequest) mRequest->Cancel(NS_BINDING_ABORTED);
    return NS_OK;
  }

 private:
  nsCOMPtr<nsICancelable> mRequest;
};

bool nsImapProtocol::RunImapThreadMainLoop() {
  PR_CEnterMonitor(this);
  NS_ASSERTION(!m_imapThreadIsRunning,
               "Oh. oh. thread is already running. What's wrong here?");
  if (m_imapThreadIsRunning) {
    PR_CExitMonitor(this);
    return false;
  }

  m_imapThreadIsRunning = true;
  PR_CExitMonitor(this);

  // call the platform specific main loop ....
  ImapThreadMainLoop();

  if (m_proxyRequest) {
    // Cancel proxy on main thread.
    RefPtr<nsImapCancelProxy> cancelProxy =
        new nsImapCancelProxy(m_proxyRequest);
    NS_DispatchAndSpinEventLoopUntilComplete(
        "nsImapProtocol::RunImapThreadMainLoop"_ns,
        GetMainThreadSerialEventTarget(), cancelProxy.forget());
    m_proxyRequest = nullptr;
  }

  if (m_runningUrl) {
    NS_ReleaseOnMainThread("nsImapProtocol::m_runningUrl",
                           m_runningUrl.forget());
  }

  // close streams via UI thread if it's not already done
  if (m_imapProtocolSink) m_imapProtocolSink->CloseStreams();

  m_imapMailFolderSink = nullptr;
  m_imapMailFolderSinkSelected = nullptr;
  m_imapMessageSink = nullptr;

  return true;
}

//
// Must be called from UI thread only
//
NS_IMETHODIMP nsImapProtocol::CloseStreams() {
  // make sure that it is called by the UI thread
  MOZ_ASSERT(NS_IsMainThread(),
             "CloseStreams() should not be called from an off UI thread");

  {
    MutexAutoLock mon(mLock);
    if (m_transport) {
      // make sure the transport closes (even if someone is still indirectly
      // referencing it).
      m_transport->Close(NS_ERROR_ABORT);
      m_transport = nullptr;
    }
    m_inputStream = nullptr;
    m_outputStream = nullptr;
    m_channelListener = nullptr;
    if (m_mockChannel) {
      m_mockChannel->Close();
      m_mockChannel = nullptr;
    }
    m_channelInputStream = nullptr;
    m_channelOutputStream = nullptr;

    // Close scope because we must let go of the monitor before calling
    // RemoveConnection to unblock anyone who tries to get a monitor to the
    // protocol object while holding onto a monitor to the server.
  }
  nsCOMPtr<nsIMsgIncomingServer> me_server = do_QueryReferent(m_server);
  if (me_server) {
    nsresult result;
    nsCOMPtr<nsIImapIncomingServer> aImapServer(
        do_QueryInterface(me_server, &result));
    if (NS_SUCCEEDED(result)) aImapServer->RemoveConnection(this);
    me_server = nullptr;
  }
  m_server = nullptr;
  // take this opportunity of being on the UI thread to
  // persist chunk prefs if they've changed
  if (gChunkSizeDirty) {
    nsCOMPtr<nsIPrefBranch> prefBranch(
        do_GetService(NS_PREFSERVICE_CONTRACTID));
    if (prefBranch) {
      prefBranch->SetIntPref("mail.imap.chunk_size", gChunkSize);
      prefBranch->SetIntPref("mail.imap.min_chunk_size_threshold",
                             gChunkThreshold);
      gChunkSizeDirty = false;
    }
  }
  return NS_OK;
}

NS_IMETHODIMP nsImapProtocol::GetUrlWindow(nsIMsgMailNewsUrl* aUrl,
                                           nsIMsgWindow** aMsgWindow) {
  NS_ENSURE_ARG_POINTER(aUrl);
  NS_ENSURE_ARG_POINTER(aMsgWindow);
  return aUrl->GetMsgWindow(aMsgWindow);
}

NS_IMETHODIMP nsImapProtocol::SetupMainThreadProxies() {
  return SetupSinkProxy();
}

NS_IMETHODIMP nsImapProtocol::OnInputStreamReady(nsIAsyncInputStream* inStr) {
  // should we check if it's a close vs. data available?
  if (m_idle) {
    uint64_t bytesAvailable = 0;
    (void)inStr->Available(&bytesAvailable);
    // check if data available - might be a close
    if (bytesAvailable != 0) {
      ReentrantMonitorAutoEnter mon(m_urlReadyToRunMonitor);
      m_lastActiveTime = PR_Now();
      m_idleResponseReadyToHandle = true;
      mon.Notify();
    }
  }
  return NS_OK;
}

// this is to be called from the UI thread. It sets m_threadShouldDie,
// and then signals the imap thread, which, when it wakes up, should exit.
// The imap thread cleanup code will check m_safeToCloseConnection.
NS_IMETHODIMP
nsImapProtocol::TellThreadToDie(bool aIsSafeToClose) {
  MOZ_DIAGNOSTIC_ASSERT(
      NS_IsMainThread(),
      "TellThreadToDie(aIsSafeToClose) should only be called from UI thread");
  MutexAutoLock mon(mLock);

  nsCOMPtr<nsIMsgIncomingServer> me_server = do_QueryReferent(m_server);
  if (me_server) {
    nsresult rv;
    nsCOMPtr<nsIImapIncomingServer> aImapServer(
        do_QueryInterface(me_server, &rv));
    if (NS_SUCCEEDED(rv)) aImapServer->RemoveConnection(this);
    m_server = nullptr;
    me_server = nullptr;
  }
  {
    ReentrantMonitorAutoEnter deathMon(m_threadDeathMonitor);
    m_safeToCloseConnection = aIsSafeToClose;
    m_threadShouldDie = true;
  }
  ReentrantMonitorAutoEnter readyMon(m_urlReadyToRunMonitor);
  m_nextUrlReadyToRun = true;
  readyMon.Notify();
  return NS_OK;
}

/**
 * Dispatch socket thread to to determine if connection is alive.
 */
nsresult nsImapProtocol::IsTransportAlive(bool* alive) {
  nsresult rv;
  auto GetIsAlive = [transport = nsCOMPtr{m_transport}, &rv, alive]() mutable {
    rv = transport->IsAlive(alive);
  };
  nsCOMPtr<nsIEventTarget> socketThread(
      do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID));
  if (socketThread) {
    mozilla::SyncRunnable::DispatchToThread(
        socketThread,
        NS_NewRunnableFunction("nsImapProtocol::IsTransportAlive", GetIsAlive));
  } else {
    rv = NS_ERROR_NOT_AVAILABLE;
  }
  return rv;
}

/**
 * Dispatch socket thread to initiate STARTTLS handshakes.
 */
nsresult nsImapProtocol::TransportStartTLS() {
  nsresult rv = NS_ERROR_NOT_AVAILABLE;
  nsCOMPtr<nsITLSSocketControl> tlsSocketControl;
  if (m_transport &&
      NS_SUCCEEDED(
          m_transport->GetTlsSocketControl(getter_AddRefs(tlsSocketControl))) &&
      tlsSocketControl) {
    auto CallStartTLS = [sockCon = nsCOMPtr{tlsSocketControl}, &rv]() mutable {
      rv = sockCon->StartTLS();
    };
    nsCOMPtr<nsIEventTarget> socketThread(
        do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID));
    if (socketThread) {
      mozilla::SyncRunnable::DispatchToThread(
          socketThread, NS_NewRunnableFunction(
                            "nsImapProtocol::TransportStartTLS", CallStartTLS));
    } else {
      rv = NS_ERROR_NOT_AVAILABLE;
    }
  }
  return rv;
}

/**
 * Dispatch socket thread to obtain transport security information.
 */
void nsImapProtocol::GetTransportSecurityInfo(
    nsITransportSecurityInfo** aSecurityInfo) {
  *aSecurityInfo = nullptr;
  nsCOMPtr<nsIEventTarget> socketThread(
      do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID));
  nsCOMPtr<nsITLSSocketControl> tlsSocketControl;
  if (socketThread &&
      NS_SUCCEEDED(
          m_transport->GetTlsSocketControl(getter_AddRefs(tlsSocketControl))) &&
      tlsSocketControl) {
    if (socketThread) {
      nsCOMPtr<nsITransportSecurityInfo> secInfo;
      auto GetSecurityInfo = [&tlsSocketControl, &secInfo]() mutable {
        tlsSocketControl->GetSecurityInfo(getter_AddRefs(secInfo));
      };
      mozilla::SyncRunnable::DispatchToThread(
          socketThread,
          NS_NewRunnableFunction("nsImapProtocol::GetTransportSecurityInfo",
                                 GetSecurityInfo));
      NS_IF_ADDREF(*aSecurityInfo = secInfo);
    }
  }
}

void nsImapProtocol::TellThreadToDie() {
  nsresult rv = NS_OK;
  MOZ_DIAGNOSTIC_ASSERT(
      !NS_IsMainThread(),
      "TellThreadToDie() should not be called from UI thread");

  // prevent re-entering this method because it may lock the UI.
  if (m_inThreadShouldDie) return;
  m_inThreadShouldDie = true;

  // This routine is called only from the imap protocol thread.
  // The UI thread causes this to be called by calling TellThreadToDie.
  // In that case, m_safeToCloseConnection will be FALSE if it's dropping a
  // timed out connection, true when closing a cached connection.
  // We're using PR_CEnter/ExitMonitor because Monitors don't like having
  // us to hold one monitor and call code that gets a different monitor. And
  // some of the methods we call here use Monitors.
  PR_CEnterMonitor(this);

  m_urlInProgress = true;  // let's say it's busy so no one tries to use
                           // this about to die connection.
  bool urlWritingData = false;
  bool connectionIdle = !m_runningUrl;

  if (!connectionIdle)
    urlWritingData = m_imapAction == nsIImapUrl::nsImapAppendMsgFromFile ||
                     m_imapAction == nsIImapUrl::nsImapAppendDraftFromFile;

  bool closeNeeded = GetServerStateParser().GetIMAPstate() ==
                         nsImapServerResponseParser::kFolderSelected &&
                     m_safeToCloseConnection;
  nsCString command;
  // if a url is writing data, we can't even logout, so we're just
  // going to close the connection as if the user pressed stop.
  if (m_currentServerCommandTagNumber > 0 && !urlWritingData) {
    bool isAlive = false;
    if (m_transport) rv = IsTransportAlive(&isAlive);

    if (TestFlag(IMAP_CONNECTION_IS_OPEN) && m_idle && isAlive) EndIdle(false);

    if (NS_SUCCEEDED(rv) && isAlive && closeNeeded &&
        GetDeleteIsMoveToTrash() && TestFlag(IMAP_CONNECTION_IS_OPEN) &&
        m_outputStream)
      ImapClose(true, connectionIdle);

    if (NS_SUCCEEDED(rv) && isAlive && TestFlag(IMAP_CONNECTION_IS_OPEN) &&
        NS_SUCCEEDED(GetConnectionStatus()) && m_outputStream)
      Logout(true, connectionIdle);
  }
  PR_CExitMonitor(this);
  // close streams via UI thread
  if (m_imapProtocolSink) {
    m_imapProtocolSink->CloseStreams();
    m_imapProtocolSink = nullptr;
  }
  Log("TellThreadToDie", nullptr, "close socket connection");

  {
    ReentrantMonitorAutoEnter mon(m_threadDeathMonitor);
    m_threadShouldDie = true;
  }
  {
    ReentrantMonitorAutoEnter dataMon(m_dataAvailableMonitor);
    dataMon.Notify();
  }
  ReentrantMonitorAutoEnter urlReadyMon(m_urlReadyToRunMonitor);
  urlReadyMon.NotifyAll();
}

NS_IMETHODIMP
nsImapProtocol::GetLastActiveTimeStamp(PRTime* aTimeStamp) {
  if (aTimeStamp) *aTimeStamp = m_lastActiveTime;
  return NS_OK;
}

static void DoomCacheEntry(nsIMsgMailNewsUrl* url);
NS_IMETHODIMP
nsImapProtocol::PseudoInterruptMsgLoad(nsIMsgFolder* aImapFolder,
                                       nsIMsgWindow* aMsgWindow,
                                       bool* interrupted) {
  NS_ENSURE_ARG(interrupted);

  *interrupted = false;

  PR_CEnterMonitor(this);

  if (m_runningUrl && !TestFlag(IMAP_CLEAN_UP_URL_STATE)) {
    nsImapAction imapAction;
    m_runningUrl->GetImapAction(&imapAction);

    if (imapAction == nsIImapUrl::nsImapMsgFetch) {
      nsresult rv = NS_OK;
      nsCOMPtr<nsIImapUrl> runningImapURL;

      rv = GetRunningImapURL(getter_AddRefs(runningImapURL));
      if (NS_SUCCEEDED(rv) && runningImapURL) {
        nsCOMPtr<nsIMsgFolder> runningImapFolder;
        nsCOMPtr<nsIMsgWindow> msgWindow;
        nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl =
            do_QueryInterface(runningImapURL);
        mailnewsUrl->GetMsgWindow(getter_AddRefs(msgWindow));
        mailnewsUrl->GetFolder(getter_AddRefs(runningImapFolder));
        if (aImapFolder == runningImapFolder && msgWindow == aMsgWindow) {
          MOZ_LOG(IMAPCache, LogLevel::Debug,
                  ("%s: Set PseudoInterrupt", __func__));
          PseudoInterrupt(true);
          *interrupted = true;
        }
        // If we're pseudo-interrupted, doom any incomplete cache entry.
        // But if mock channel indicates fetch is complete so cache write is
        // done, then don't doom the cache entry.
        bool cacheWriteInProgress = true;
        if (m_mockChannel)
          m_mockChannel->GetWritingToCache(&cacheWriteInProgress);
        if (cacheWriteInProgress) {
          MOZ_LOG(IMAPCache, LogLevel::Debug,
                  ("%s: Call DoomCacheEntry()", __func__));
          DoomCacheEntry(mailnewsUrl);
        }
      }
    }
  }
  PR_CExitMonitor(this);
#ifdef DEBUG_bienvenu
  printf("interrupt msg load : %s\n", (*interrupted) ? "TRUE" : "FALSE");
#endif
  return NS_OK;
}

void nsImapProtocol::ImapThreadMainLoop() {
  MOZ_LOG(IMAP, LogLevel::Debug,
          ("ImapThreadMainLoop entering [this=%p]", this));

  PRIntervalTime sleepTime = kImapSleepTime;
  bool idlePending = false;
  while (!DeathSignalReceived()) {
    nsresult rv = NS_OK;
    bool urlReadyToRun;

    // wait for a URL or idle response to process...
    {
      ReentrantMonitorAutoEnter mon(m_urlReadyToRunMonitor);

      while (NS_SUCCEEDED(rv) && !DeathSignalReceived() &&
             !m_nextUrlReadyToRun && !m_idleResponseReadyToHandle &&
             !m_threadShouldDie) {
        rv = mon.Wait(sleepTime);
        if (idlePending) break;
      }

      urlReadyToRun = m_nextUrlReadyToRun;
      m_nextUrlReadyToRun = false;
    }
    // This will happen if the UI thread signals us to die
    if (m_threadShouldDie) {
      TellThreadToDie();
      break;
    }

    if (NS_FAILED(rv) && PR_PENDING_INTERRUPT_ERROR == PR_GetError()) {
      printf("error waiting for monitor\n");
      break;
    }

    // If an idle response has occurred, handle only it on this pass through
    // the loop.
    if (m_idleResponseReadyToHandle && !m_threadShouldDie) {
      m_idleResponseReadyToHandle = false;
      HandleIdleResponses();
      if (urlReadyToRun) {
        // A URL is also ready. Process it on next loop.
        m_nextUrlReadyToRun = true;
        urlReadyToRun = false;
      }
    }

    if (urlReadyToRun && m_runningUrl) {
      if (m_currentServerCommandTagNumber && m_transport) {
        bool isAlive;
        rv = IsTransportAlive(&isAlive);
        // if the transport is not alive, and we've ever sent a command with
        // this connection, kill it. otherwise, we've probably just not finished
        // setting it so don't kill it!
        if (NS_FAILED(rv) || !isAlive) {
          // This says we never started running the url, which is the case.
          m_runningUrl->SetRerunningUrl(false);
          RetryUrl();
          return;
        }
      }
      //
      // NOTE: Although we cleared m_nextUrlReadyToRun above, it may now be set
      //       again by LoadImapUrlInternal(), which runs on the main thread.
      //       Because of this, we must not clear m_nextUrlReadyToRun here.
      //
      if (ProcessCurrentURL()) {
        // Another URL has been setup to run. Process it on next loop.
        m_nextUrlReadyToRun = true;
        m_imapMailFolderSink = nullptr;
      } else {
        // No more URLs setup to run. Set idle pending if user has configured
        // idle and if a URL is not in progress and if the server has IDLE
        // capability. Just set idlePending since want to wait a short time
        // to see if more URLs occurs before actually entering idle.
        if (!idlePending && m_useIdle && !m_urlInProgress &&
            GetServerStateParser().GetCapabilityFlag() & kHasIdleCapability &&
            GetServerStateParser().GetIMAPstate() ==
                nsImapServerResponseParser::kFolderSelected) {
          // Set-up short wait time in milliseconds
          static const PRIntervalTime kIdleWait =
              PR_MillisecondsToInterval(2000);
          sleepTime = kIdleWait;
          idlePending = true;
          Log("ImapThreadMainLoop", nullptr, "idlePending set");
        } else {
          // if not idle used, don't need to remember folder sink
          m_imapMailFolderSink = nullptr;
        }
      }
    } else {
      // No URL to run detected on wake up.
      if (idlePending) {
        // Have seen no URLs for the short time (kIdleWait) so go into idle mode
        // and set the loop sleep time back to its original longer time.
        Idle();
        if (!m_idle) {
          // Server rejected IDLE. Treat like IDLE not enabled or available.
          m_imapMailFolderSink = nullptr;
        }
        idlePending = false;
        sleepTime = kImapSleepTime;
      }
    }
    if (!GetServerStateParser().Connected()) break;
#ifdef DEBUG_bienvenu
    else
      printf("ready to run but no url and not idle\n");
#endif
    // This can happen if the UI thread closes cached connections in the
    // OnStopRunningUrl notification.
    if (m_threadShouldDie) TellThreadToDie();
  }
  m_imapThreadIsRunning = false;

  MOZ_LOG(IMAP, LogLevel::Debug,
          ("ImapThreadMainLoop leaving [this=%p]", this));
}

// This handles the response to Idle() command and handles responses sent by
// the server after in idle mode. Returns true if a BAD or NO response is
// not seen which is needed when called from Idle().
bool nsImapProtocol::HandleIdleResponses() {
  bool rvIdleOk = true;
  bool untagged = false;
  NS_ASSERTION(PL_strstr(m_currentCommand.get(), " IDLE"), "not IDLE!");
  do {
    ParseIMAPandCheckForNewMail();
    rvIdleOk = rvIdleOk && !GetServerStateParser().CommandFailed();
    untagged = untagged || GetServerStateParser().UntaggedResponse();
  } while (m_inputStreamBuffer->NextLineAvailable() &&
           GetServerStateParser().Connected());

  // If still connected and rvIdleOk is true and an untagged response was
  // detected and have the sink pointer, OnNewIdleMessage will invoke a URL to
  // update the folder. Otherwise just setup so we get notified of idle response
  // data on the socket transport thread by OnInputStreamReady() above, which
  // will trigger the imap thread main loop to run and call this function again.
  if (GetServerStateParser().Connected() && rvIdleOk) {
    if (m_imapMailFolderSinkSelected && untagged) {
      Log("HandleIdleResponses", nullptr, "idle response");
      m_imapMailFolderSinkSelected->OnNewIdleMessages();
    } else {
      // Enable async wait mode. Occurs when Idle() called.
      nsCOMPtr<nsIAsyncInputStream> asyncInputStream =
          do_QueryInterface(m_inputStream);
      if (asyncInputStream) {
        asyncInputStream->AsyncWait(this, 0, 0, nullptr);
        Log("HandleIdleResponses", nullptr, "idle mode async waiting");
      }
    }
  }
  return rvIdleOk;
}

void nsImapProtocol::EstablishServerConnection() {
#define ESC_LENGTH(x) (sizeof(x) - 1)
#define ESC_OK "* OK"
#define ESC_OK_LEN ESC_LENGTH(ESC_OK)
#define ESC_PREAUTH "* PREAUTH"
#define ESC_PREAUTH_LEN ESC_LENGTH(ESC_PREAUTH)
#define ESC_CAPABILITY_STAR "* "
#define ESC_CAPABILITY_STAR_LEN ESC_LENGTH(ESC_CAPABILITY_STAR)
#define ESC_CAPABILITY_OK "* OK ["
#define ESC_CAPABILITY_OK_LEN ESC_LENGTH(ESC_CAPABILITY_OK)
#define ESC_CAPABILITY_GREETING (ESC_CAPABILITY_OK "CAPABILITY")
#define ESC_CAPABILITY_GREETING_LEN ESC_LENGTH(ESC_CAPABILITY_GREETING)

  char* serverResponse = CreateNewLineFromSocket();  // read in the greeting
  // record the fact that we've received a greeting for this connection so we
  // don't ever try to do it again..
  if (serverResponse) SetFlag(IMAP_RECEIVED_GREETING);

  if (!PL_strncasecmp(serverResponse, ESC_OK, ESC_OK_LEN)) {
    SetConnectionStatus(NS_OK);

    if (!PL_strncasecmp(serverResponse, ESC_CAPABILITY_GREETING,
                        ESC_CAPABILITY_GREETING_LEN)) {
      nsAutoCString tmpstr(serverResponse);
      int32_t endIndex = tmpstr.FindChar(']', ESC_CAPABILITY_GREETING_LEN);
      if (endIndex >= 0) {
        // Allocate the new buffer here. This buffer will be passed to
        // ParseIMAPServerResponse() where it will be used to fill the
        // fCurrentLine field and will be freed by the next call to
        // ResetLexAnalyzer().
        char* fakeServerResponse = (char*)PR_Malloc(PL_strlen(serverResponse));
        // Munge the greeting into something that would pass for an IMAP
        // server's response to a "CAPABILITY" command.
        strcpy(fakeServerResponse, ESC_CAPABILITY_STAR);
        strcat(fakeServerResponse, serverResponse + ESC_CAPABILITY_OK_LEN);
        fakeServerResponse[endIndex - ESC_CAPABILITY_OK_LEN +
                           ESC_CAPABILITY_STAR_LEN] = '\0';
        // Tell the response parser that we just issued a "CAPABILITY" and
        // got the following back.
        GetServerStateParser().ParseIMAPServerResponse("1 CAPABILITY", true,
                                                       fakeServerResponse);
      }
    }
  } else if (!PL_strncasecmp(serverResponse, ESC_PREAUTH, ESC_PREAUTH_LEN)) {
    // PREAUTH greeting received. We've been pre-authenticated by the server.
    // We can skip sending a password and transition right into the
    // kAuthenticated state; but we won't if the user has configured STARTTLS.
    // (STARTTLS can only occur with the server in non-authenticated state.)
    if (!(m_socketType == nsMsgSocketType::alwaysSTARTTLS ||
          m_socketType == nsMsgSocketType::trySTARTTLS)) {
      GetServerStateParser().PreauthSetAuthenticatedState();

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

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

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

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

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

// This can get called from the UI thread or an imap thread.
// It makes sure we don't get left with partial messages in
// the memory cache.
static void DoomCacheEntry(nsIMsgMailNewsUrl* url) {
  bool readingFromMemCache = false;
  nsCOMPtr<nsIImapUrl> imapUrl = do_QueryInterface(url);
  imapUrl->GetMsgLoadingFromCache(&readingFromMemCache);
  if (!readingFromMemCache) {
    nsCOMPtr<nsICacheEntry> cacheEntry;
    url->GetMemCacheEntry(getter_AddRefs(cacheEntry));
    if (cacheEntry) {
      MOZ_LOG(IMAPCache, LogLevel::Debug,
              ("%s: Call AsyncDoom(), url=%s", __func__,
               url->GetSpecOrDefault().get()));
      cacheEntry->AsyncDoom(nullptr);
    }
  }
}

/**
 * ProcessCurrentURL() runs the current URL (m_runningUrl).
 * Things to remember:
 * - IMAP protocol URLs don't correspond directly to IMAP commands. A single
 *   URL might cause multiple IMAP commands to be issued.
 * - This is all synchronous. But that's OK, because we're running in our
 *   own thread.
 *
 * @return true if another url was run, false otherwise.
 */
bool nsImapProtocol::ProcessCurrentURL() {
  nsresult rv = NS_OK;
  if (m_idle) EndIdle();

  if (m_retryUrlOnError) {
    // we clear this flag if we're re-running immediately, because that
    // means we never sent a start running url notification, and later we
    // don't send start running notification if we think we're rerunning
    // the url (see first call to SetUrlState below). This means we won't
    // send a start running notification, which means our stop running
    // notification will be ignored because we don't think we were running.
    m_runningUrl->SetRerunningUrl(false);
    return RetryUrl();
  }
  Log("ProcessCurrentURL", nullptr, "entering");
  (void)GetImapHostName();  // force m_hostName to get set.

  bool logonFailed = false;
  bool anotherUrlRun = false;
  bool rerunningUrl = false;
  bool isExternalUrl;
  bool validUrl = true;

  PseudoInterrupt(false);  // clear this if left over from previous url.

  m_runningUrl->GetRerunningUrl(&rerunningUrl);
  m_runningUrl->GetExternalLinkUrl(&isExternalUrl);
  m_runningUrl->GetValidUrl(&validUrl);
  m_runningUrl->GetImapAction(&m_imapAction);

  if (isExternalUrl) {
    if (m_imapAction == nsIImapUrl::nsImapSelectFolder) {
      // we need to send a start request so that the doc loader
      // will call HandleContent on the imap service so we
      // can abort this url, and run a new url in a new msg window
      // to run the folder load url and get off this crazy merry-go-round.
      if (m_channelListener) {
        m_channelListener->OnStartRequest(m_mockChannel);
      }
      return false;
    }
  }

  if (!m_imapMailFolderSink && m_imapProtocolSink) {
    // This occurs when running another URL in the main thread loop
    rv = m_imapProtocolSink->SetupMainThreadProxies();
    NS_ENSURE_SUCCESS(rv, false);
  }

  // Reinitialize the parser
  GetServerStateParser().InitializeState();
  GetServerStateParser().SetConnected(true);

  // acknowledge that we are running the url now..
  nsCOMPtr<nsIMsgMailNewsUrl> mailnewsurl =
      do_QueryInterface(m_runningUrl, &rv);
  nsAutoCString urlSpec;
  rv = mailnewsurl->GetSpec(urlSpec);
  NS_ENSURE_SUCCESS(rv, false);
  Log("ProcessCurrentURL", urlSpec.get(),
      (validUrl) ? " = currentUrl" : " is not valid");
  if (!validUrl) return false;

  if (NS_SUCCEEDED(rv) && mailnewsurl && m_imapMailFolderSink && !rerunningUrl)
    m_imapMailFolderSink->SetUrlState(this, mailnewsurl, true, false, NS_OK);

  // if we are set up as a channel, we should notify our channel listener that
  // we are starting... so pass in ourself as the channel and not the underlying
  // socket or file channel the protocol happens to be using
  if (m_channelListener)  // ### not sure we want to do this if rerunning url...
  {
    m_channelListener->OnStartRequest(m_mockChannel);
  }
  // If we haven't received the greeting yet, we need to make sure we strip
  // it out of the input before we start to do useful things...
  if (!TestFlag(IMAP_RECEIVED_GREETING)) EstablishServerConnection();

  // Step 1: If we have not moved into the authenticated state yet then do so
  // by attempting to logon.
  if (!DeathSignalReceived() && NS_SUCCEEDED(GetConnectionStatus()) &&
      (GetServerStateParser().GetIMAPstate() ==
       nsImapServerResponseParser::kNonAuthenticated)) {
    /* if we got here, the server's greeting should not have been PREAUTH */
    // If greeting did not contain a capability response and if user has not
    // configured STARTTLS, request capabilities. If STARTTLS configured,
    // capabilities will be requested after TLS handshakes are complete.
    if ((GetServerStateParser().GetCapabilityFlag() == kCapabilityUndefined) &&
        (m_socketType != nsMsgSocketType::alwaysSTARTTLS)) {
      Capability();
    }

    // If capability response has yet to occur and STARTTLS is not
    // configured then drop the connection since this should not happen. Also
    // drop the connection if capability response has occurred and
    // the imap version is unacceptable. Show alert only for wrong version.
    if (((GetServerStateParser().GetCapabilityFlag() == kCapabilityUndefined) &&
         (m_socketType != nsMsgSocketType::alwaysSTARTTLS)) ||
        (GetServerStateParser().GetCapabilityFlag() &&
         !(GetServerStateParser().GetCapabilityFlag() &
           (kIMAP4Capability | kIMAP4rev1Capability | kIMAP4other)))) {
      if (!DeathSignalReceived() && NS_SUCCEEDED(GetConnectionStatus()) &&
          GetServerStateParser().GetCapabilityFlag())
        AlertUserEventUsingName("imapServerNotImap4");

      SetConnectionStatus(NS_ERROR_FAILURE);  // stop netlib
    } else {
      if ((m_connectionType.EqualsLiteral("starttls") &&
           (m_socketType == nsMsgSocketType::trySTARTTLS &&
            (GetServerStateParser().GetCapabilityFlag() &
             kHasStartTLSCapability))) ||
          m_socketType == nsMsgSocketType::alwaysSTARTTLS) {
        StartTLS();  // Send imap STARTTLS command
        if (GetServerStateParser().LastCommandSuccessful()) {
          NS_ENSURE_TRUE(m_transport, false);
          MOZ_ASSERT(!NS_IsMainThread());
          rv = TransportStartTLS();  // Initiate STARTTLS handshakes
          if (NS_SUCCEEDED(rv)) {
            // Transition to secure state is now enabled but handshakes and
            // negotiation has not yet occurred. Make sure that
            // the stream input response buffer is drained to avoid false
            // responses to subsequent commands (capability, login etc),
            // i.e., due to possible MitM attack doing pre-TLS response
            // injection. We are discarding any possible malicious data
            // stored prior to TransportStartTLS().
            // Note: If any non-TLS related data arrives while transitioning
            // to secure state (after TransportStartTLS()), it will
            // cause the TLS negotiation to fail so any injected data is never
            // accessed since the transport connection will be dropped.
            char discardBuf[80];
            uint64_t numBytesInStream = 0;
            uint32_t numBytesRead;
            rv = m_inputStream->Available(&numBytesInStream);
            nsCOMPtr<nsIInputStream> kungFuGrip = m_inputStream;
            // Read and discard any data available in socket buffer.
            while (numBytesInStream > 0 && NS_SUCCEEDED(rv)) {
              rv = m_inputStream->Read(
                  discardBuf,
                  std::min(uint64_t(sizeof discardBuf), numBytesInStream),
                  &numBytesRead);
              numBytesInStream -= numBytesRead;
            }
            kungFuGrip = nullptr;

            // Discard any data lines previously read from socket buffer.
            m_inputStreamBuffer->ClearBuffer();

            // Force re-issue of "capability", because servers may
            // enable other auth features (e.g. remove LOGINDISABLED
            // and add AUTH=PLAIN). Sending imap data here first triggers
            // the TLS negotiation handshakes.
            Capability();

            // If user has set pref mail.server.serverX.socketType to 1
            // (trySTARTTLS, now deprecated in UI) and Capability()
            // succeeds, indicating TLS handshakes succeeded, set and
            // latch the socketType to 2 (alwaysSTARTTLS) for this server.
            if ((m_socketType == nsMsgSocketType::trySTARTTLS) &&
                GetServerStateParser().LastCommandSuccessful())
              m_imapServerSink->UpdateTrySTARTTLSPref(true);

            // Courier imap doesn't return STARTTLS capability if we've done
            // a STARTTLS! But we need to remember this capability so we'll
            // try to use STARTTLS next time.
            // Update: This may not be a problem since "next time" will be
            // on a new connection that is not yet in secure state. So the
            // capability greeting *will* contain STARTTLS. I observed and
            // tested this on Courier imap server. But keep this to be sure.
            eIMAPCapabilityFlags capabilityFlag =
                GetServerStateParser().GetCapabilityFlag();
            if (!(capabilityFlag & kHasStartTLSCapability)) {
              capabilityFlag |= kHasStartTLSCapability;
              GetServerStateParser().SetCapabilityFlag(capabilityFlag);
              CommitCapability();
            }
          }
          if (NS_FAILED(rv)) {
            nsAutoCString logLine("Enable of STARTTLS failed. Error 0x");
            logLine.AppendInt(static_cast<uint32_t>(rv), 16);
            Log("ProcessCurrentURL", nullptr, logLine.get());
            if (m_socketType == nsMsgSocketType::alwaysSTARTTLS) {
              SetConnectionStatus(rv);  // stop netlib
              if (m_transport) m_transport->Close(rv);
            } else if (m_socketType == nsMsgSocketType::trySTARTTLS)
              m_imapServerSink->UpdateTrySTARTTLSPref(false);
          }
        } else if (m_socketType == nsMsgSocketType::alwaysSTARTTLS) {
          SetConnectionStatus(NS_ERROR_FAILURE);  // stop netlib
          if (m_transport) m_transport->Close(rv);
        } else if (m_socketType == nsMsgSocketType::trySTARTTLS) {
          // STARTTLS failed, so downgrade socket type
          m_imapServerSink->UpdateTrySTARTTLSPref(false);
        }
      } else if (m_socketType == nsMsgSocketType::trySTARTTLS) {
        // we didn't know the server supported TLS when we created
        // the socket, so we're going to retry with a STARTTLS socket
        if (GetServerStateParser().GetCapabilityFlag() &
            kHasStartTLSCapability) {
          ClearFlag(IMAP_CONNECTION_IS_OPEN);
          TellThreadToDie();
          SetConnectionStatus(NS_ERROR_FAILURE);
          return RetryUrl();
        }
        // trySTARTTLS set, but server doesn't have TLS capability,
        // so downgrade socket type
        m_imapServerSink->UpdateTrySTARTTLSPref(false);
        m_socketType = nsMsgSocketType::plain;
      }
      if (!DeathSignalReceived() && (NS_SUCCEEDED(GetConnectionStatus()))) {
        logonFailed = !TryToLogon();
      }
      if (m_retryUrlOnError) return RetryUrl();
    }
  }  // if death signal not received

  // We assume one IMAP thread is used for exactly one server, only.
  if (m_transport && !m_securityInfo) {
    MOZ_ASSERT(!NS_IsMainThread());
    GetTransportSecurityInfo(getter_AddRefs(m_securityInfo));
  }

  if (!DeathSignalReceived() && (NS_SUCCEEDED(GetConnectionStatus()))) {
    // if the server supports a language extension then we should
    // attempt to issue the language extension.
    if (GetServerStateParser().GetCapabilityFlag() & kHasLanguageCapability)
      Language();

    if (m_runningUrl) {
      bool foundMailboxesAlready = false;
      m_hostSessionList->GetHaveWeEverDiscoveredFoldersForHost(
          GetImapServerKey(), foundMailboxesAlready);
      if (!foundMailboxesAlready) FindMailboxesIfNecessary();
    }

    nsImapState imapState = nsIImapUrl::ImapStatusNone;
    if (m_runningUrl) m_runningUrl->GetRequiredImapState(&imapState);

    if (imapState == nsIImapUrl::nsImapAuthenticatedState)
      ProcessAuthenticatedStateURL();
    else  // must be a url that requires us to be in the selected state
      ProcessSelectedStateURL();

    if (m_retryUrlOnError) return RetryUrl();

    // The URL has now been processed
    if ((!logonFailed && NS_FAILED(GetConnectionStatus())) ||
        DeathSignalReceived())
      HandleCurrentUrlError();

  } else if (!logonFailed)
    HandleCurrentUrlError();

  // if we are set up as a channel, we should notify our channel listener that
  // we are stopping... so pass in ourself as the channel and not the underlying
  // socket or file channel the protocol happens to be using
  if (m_channelListener) {
    NS_ASSERTION(m_mockChannel, "no request");
    if (m_mockChannel) {
      nsresult status;
      m_mockChannel->GetStatus(&status);
      if (!GetServerStateParser().LastCommandSuccessful() &&
          NS_SUCCEEDED(status))
        status = NS_MSG_ERROR_IMAP_COMMAND_FAILED;
      rv = m_channelListener->OnStopRequest(m_mockChannel, status);
    }
  }
  bool suspendUrl = false;
  m_runningUrl->GetMoreHeadersToDownload(&suspendUrl);
  if (mailnewsurl && m_imapMailFolderSink) {
    rv = GetConnectionStatus();
    // There are error conditions to check even if the connection is OK.
    if (NS_SUCCEEDED(rv)) {
      if (logonFailed) {
        rv = NS_ERROR_FAILURE;
      } else if (GetServerStateParser().CommandFailed()) {
        rv = NS_MSG_ERROR_IMAP_COMMAND_FAILED;
      }
    }
    if (NS_FAILED(rv)) {
      MOZ_LOG(
          IMAP, LogLevel::Debug,
          ("URL failed with code 0x%" PRIx32 " (%s)", static_cast<uint32_t>(rv),
           mailnewsurl->GetSpecOrDefault().get()));
      // If discovery URL fails, clear the in-progress flag.
      if (m_imapAction == nsIImapUrl::nsImapDiscoverAllBoxesUrl) {
        m_hostSessionList->SetDiscoveryForHostInProgress(GetImapServerKey(),
                                                         false);
      }
    }
    // Inform any nsIUrlListeners that the URL has finished. This will invoke
    // nsIUrlListener.onStopRunningUrl().
    m_imapMailFolderSink->SetUrlState(this, mailnewsurl, false, suspendUrl, rv);

    // Doom the cache entry if shutting down or thread is terminated.
    if (NS_FAILED(rv) && DeathSignalReceived() && m_mockChannel) {
      MOZ_LOG(IMAPCache, LogLevel::Debug,
              ("ProcessCurrentURL(): Call DoomCacheEntry()"));
      DoomCacheEntry(mailnewsurl);
    }
  } else {
    // That's seen at times in debug sessions.
    NS_WARNING("missing url or sink");
  }

  // disable timeouts before caching connection.
  if (m_transport)
    m_transport->SetTimeout(nsISocketTransport::TIMEOUT_READ_WRITE,
                            PR_UINT32_MAX);

  SetFlag(IMAP_CLEAN_UP_URL_STATE);

  nsCOMPtr<nsISupports> copyState;
  if (m_runningUrl) m_runningUrl->GetCopyState(getter_AddRefs(copyState));
  // this is so hokey...we MUST clear any local references to the url
  // BEFORE calling ReleaseUrlState
  mailnewsurl = nullptr;

  if (suspendUrl) m_imapServerSink->SuspendUrl(m_runningUrl);
  // save the imap folder sink since we need it to do the CopyNextStreamMessage
  RefPtr<ImapMailFolderSinkProxy> imapMailFolderSink = m_imapMailFolderSink;
  // release the url as we are done with it...
  ReleaseUrlState(false);
  ResetProgressInfo();

  ClearFlag(IMAP_CLEAN_UP_URL_STATE);

  if (imapMailFolderSink) {
    if (copyState) {
      rv = imapMailFolderSink->CopyNextStreamMessage(
          GetServerStateParser().LastCommandSuccessful() &&
              NS_SUCCEEDED(GetConnectionStatus()),
          copyState);
      if (NS_FAILED(rv))
        MOZ_LOG(IMAP, LogLevel::Info,
                ("CopyNextStreamMessage failed: %" PRIx32,
                 static_cast<uint32_t>(rv)));

      NS_ReleaseOnMainThread("nsImapProtocol, copyState", copyState.forget());
    }
    // we might need this to stick around for IDLE support
    m_imapMailFolderSink = imapMailFolderSink;
    imapMailFolderSink = nullptr;
  } else
    MOZ_LOG(IMAP, LogLevel::Info, ("null imapMailFolderSink"));

  // now try queued urls, now that we've released this connection.
  if (m_imapServerSink) {
    if (NS_SUCCEEDED(GetConnectionStatus()))
      rv = m_imapServerSink->LoadNextQueuedUrl(this, &anotherUrlRun);
    else  // if we don't do this, they'll just sit and spin until
          // we run some other url on this server.
    {
      Log("ProcessCurrentURL", nullptr, "aborting queued urls");
      rv = m_imapServerSink->AbortQueuedUrls();
    }
  }

  // if we didn't run another url, release the server sink to
  // cut circular refs.
  if (!anotherUrlRun) m_imapServerSink = nullptr;

  if (NS_FAILED(GetConnectionStatus()) || !GetServerStateParser().Connected() ||
      GetServerStateParser().SyntaxError()) {
    if (m_imapServerSink) m_imapServerSink->RemoveServerConnection(this);

    if (!DeathSignalReceived()) {
      TellThreadToDie();
    }
  } else {
    if (m_imapServerSink) {
      bool shuttingDown;
      m_imapServerSink->GetServerShuttingDown(&shuttingDown);
      if (shuttingDown) m_useIdle = false;
    }
  }
  return anotherUrlRun;
}

bool nsImapProtocol::RetryUrl() {
  nsCOMPtr<nsIImapUrl> kungFuGripImapUrl = m_runningUrl;
  nsCOMPtr<nsIImapMockChannel> saveMockChannel;

  // the mock channel might be null - that's OK.
  if (m_imapServerSink)
    (void)m_imapServerSink->PrepareToRetryUrl(kungFuGripImapUrl,
                                              getter_AddRefs(saveMockChannel));

  ReleaseUrlState(true);
  if (m_imapServerSink) {
    m_imapServerSink->RemoveServerConnection(this);
    m_imapServerSink->RetryUrl(kungFuGripImapUrl, saveMockChannel);
  }

  // Hack for Bug 1586494.
  // (this is a workaround to try and prevent a specific crash, and
  // does nothing clarify the threading mess!)
  // RetryUrl() is only ever called from the imap thread.
  // Mockchannel dtor insists upon being run on the main thread.
  // So make sure we don't accidentally cause the mockchannel to die right now.
  if (saveMockChannel) {
    NS_ReleaseOnMainThread("nsImapProtocol::RetryUrl",
                           saveMockChannel.forget());
  }

  return (m_imapServerSink != nullptr);  // we're running a url (the same url)
}

// ignoreBadAndNOResponses --> don't throw a error dialog if this command
// results in a NO or Bad response from the server..in other words the command
// is "exploratory" and we don't really care if it succeeds or fails.
void nsImapProtocol::ParseIMAPandCheckForNewMail(
    const char* commandString, bool aIgnoreBadAndNOResponses) {
  if (commandString)
    GetServerStateParser().ParseIMAPServerResponse(commandString,
                                                   aIgnoreBadAndNOResponses);
  else
    GetServerStateParser().ParseIMAPServerResponse(m_currentCommand.get(),
                                                   aIgnoreBadAndNOResponses);
  // **** fix me for new mail biff state *****
}

/////////////////////////////////////////////////////////////////////////////////////////////
// End of nsIStreamListenerSupport
//////////////////////////////////////////////////////////////////////////////////////////////

NS_IMETHODIMP
nsImapProtocol::GetRunningUrl(nsIURI** result) {
  if (result && m_runningUrl)
    return m_runningUrl->QueryInterface(NS_GET_IID(nsIURI), (void**)result);
  return NS_ERROR_NULL_POINTER;
}

NS_IMETHODIMP nsImapProtocol::GetRunningImapURL(nsIImapUrl** aImapUrl) {
  if (aImapUrl && m_runningUrl)
    return m_runningUrl->QueryInterface(NS_GET_IID(nsIImapUrl),
                                        (void**)aImapUrl);
  return NS_ERROR_NULL_POINTER;
}

/*
 * Writes the data contained in dataBuffer into the current output stream. It
 * also informs the transport layer that this data is now available for
 * transmission. Returns a positive number for success, 0 for failure (not all
 * the bytes were written to the stream, etc). We need to make another pass
 * through this file to install an error system (mscott)
 */

nsresult nsImapProtocol::SendData(const char* dataBuffer,
                                  bool aSuppressLogging) {
  nsresult rv = NS_ERROR_NULL_POINTER;

  if (!m_transport) {
    Log("SendData", nullptr, "clearing IMAP_CONNECTION_IS_OPEN");
    // the connection died unexpectedly! so clear the open connection flag
    ClearFlag(IMAP_CONNECTION_IS_OPEN);
    TellThreadToDie();
    SetConnectionStatus(NS_ERROR_FAILURE);
    return NS_ERROR_FAILURE;
  }

  if (dataBuffer && m_outputStream) {
    m_currentCommand = dataBuffer;
    if (!aSuppressLogging)
      Log("SendData", nullptr, dataBuffer);
    else
      Log("SendData", nullptr,
          "Logging suppressed for this command (it probably contained "
          "authentication information)");

    {
      // don't allow someone to close the stream/transport out from under us
      // this can happen when the ui thread calls TellThreadToDie.
      PR_CEnterMonitor(this);
      uint32_t n;
      if (m_outputStream)
        rv = m_outputStream->Write(dataBuffer, PL_strlen(dataBuffer), &n);
      PR_CExitMonitor(this);
    }
    if (NS_FAILED(rv)) {
      Log("SendData", nullptr, "clearing IMAP_CONNECTION_IS_OPEN");
      // the connection died unexpectedly! so clear the open connection flag
      ClearFlag(IMAP_CONNECTION_IS_OPEN);
      TellThreadToDie();
      SetConnectionStatus(rv);
      if (m_runningUrl && !m_retryUrlOnError) {
        bool alreadyRerunningUrl;
        m_runningUrl->GetRerunningUrl(&alreadyRerunningUrl);
        if (!alreadyRerunningUrl) {
          m_runningUrl->SetRerunningUrl(true);
          m_retryUrlOnError = true;
        }
      }
    }
  }

  return rv;
}

/////////////////////////////////////////////////////////////////////////////////////////////
// Begin protocol state machine functions...
//////////////////////////////////////////////////////////////////////////////////////////////

// ProcessProtocolState - we override this only so we'll link - it should never
// get called.

nsresult nsImapProtocol::ProcessProtocolState(nsIURI* url,
                                              nsIInputStream* inputStream,
                                              uint64_t sourceOffset,
                                              uint32_t length) {
  return NS_OK;
}

class UrlListenerNotifierEvent : public mozilla::Runnable {
 public:
  UrlListenerNotifierEvent(nsIMsgMailNewsUrl* aUrl, nsIImapProtocol* aProtocol)
      : mozilla::Runnable("UrlListenerNotifierEvent"),
        mUrl(aUrl),
        mProtocol(aProtocol) {}

  NS_IMETHOD Run() {
    if (mUrl) {
      nsCOMPtr<nsIMsgFolder> folder;
      mUrl->GetFolder(getter_AddRefs(folder));
      NS_ENSURE_TRUE(folder, NS_OK);
      nsCOMPtr<nsIImapMailFolderSink> folderSink(do_QueryInterface(folder));
      // This causes the url listener to get OnStart and Stop notifications.
      folderSink->SetUrlState(mProtocol, mUrl, true, false, NS_OK);
      folderSink->SetUrlState(mProtocol, mUrl, false, false, NS_OK);
    }
    return NS_OK;
  }

 private:
  nsCOMPtr<nsIMsgMailNewsUrl> mUrl;
  nsCOMPtr<nsIImapProtocol> mProtocol;
};

bool nsImapProtocol::TryToRunUrlLocally(nsIURI* aURL, nsISupports* aConsumer) {
  nsresult rv;
  nsCOMPtr<nsIImapUrl> imapUrl(do_QueryInterface(aURL, &rv));
  NS_ENSURE_SUCCESS(rv, false);
  nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(aURL);
  nsCString messageIdString;
  imapUrl->GetListOfMessageIds(messageIdString);
  bool useLocalCache = false;
  if (!messageIdString.IsEmpty() &&
      !HandlingMultipleMessages(messageIdString)) {
    nsImapAction action;
    imapUrl->GetImapAction(&action);
    nsCOMPtr<nsIMsgFolder> folder;
    mailnewsUrl->GetFolder(getter_AddRefs(folder));
    NS_ENSURE_TRUE(folder, false);

    folder->HasMsgOffline(strtoul(messageIdString.get(), nullptr, 10),
                          &useLocalCache);
    mailnewsUrl->SetMsgIsInLocalCache(useLocalCache);
    // We're downloading a single message for offline use, and it's
    // already offline. So we shouldn't do anything, but we do
    // need to notify the url listener.
    if (useLocalCache && action == nsIImapUrl::nsImapMsgDownloadForOffline) {
      nsCOMPtr<nsIRunnable> event =
          new UrlListenerNotifierEvent(mailnewsUrl, this);
      // Post this as an event because it can lead to re-entrant calls to
      // LoadNextQueuedUrl if the listener runs a new url.
      if (event) NS_DispatchToCurrentThread(event);
      return true;
    }
  }
  if (!useLocalCache) return false;

  nsCOMPtr<nsIImapMockChannel> mockChannel;
  imapUrl->GetMockChannel(getter_AddRefs(mockChannel));
  if (!mockChannel) return false;

  nsImapMockChannel* imapChannel =
      static_cast<nsImapMockChannel*>(mockChannel.get());
  if (!imapChannel) return false;

  nsCOMPtr<nsILoadGroup> loadGroup;
  imapChannel->GetLoadGroup(getter_AddRefs(loadGroup));
  if (!loadGroup)  // if we don't have one, the url will snag one from the msg
                   // window...
    mailnewsUrl->GetLoadGroup(getter_AddRefs(loadGroup));

  if (loadGroup)
    loadGroup->RemoveRequest((nsIRequest*)mockChannel,
                             nullptr /* context isupports */, NS_OK);

  if (imapChannel->ReadFromLocalCache()) {
    (void)imapChannel->NotifyStartEndReadFromCache(true);
    return true;
  }
  return false;
}

// LoadImapUrl takes a url, initializes all of our url specific data by calling
// SetupUrl. Finally, we signal the url to run monitor to let the imap main
// thread loop process the current url (it is waiting on this monitor). There
// is a contract that the imap thread has already been started before we
// attempt to load a url...
// LoadImapUrl() is called by nsImapIncomingServer to run a queued url on a free
// connection.
NS_IMETHODIMP nsImapProtocol::LoadImapUrl(nsIURI* aURL,
                                          nsISupports* aConsumer) {
  nsresult rv = NS_ERROR_FAILURE;
  if (aURL) {
#ifdef DEBUG_bienvenu
    printf("loading url %s\n", aURL->GetSpecOrDefault().get());
#endif
    // We might be able to fulfill the request locally (e.g. fetching a message
    // which is already stored offline).
    if (TryToRunUrlLocally(aURL, aConsumer)) return NS_OK;
    m_urlInProgress = true;
    m_imapMailFolderSink = nullptr;
    rv = SetupWithUrl(aURL, aConsumer);
    m_lastActiveTime = PR_Now();
  }
  return rv;
}

nsresult nsImapProtocol::LoadImapUrlInternal() {
  nsresult rv = NS_ERROR_FAILURE;

  if (m_transport && m_mockChannel) {
    m_transport->SetTimeout(nsISocketTransport::TIMEOUT_CONNECT,
                            gResponseTimeout + 60);
    int32_t readWriteTimeout = gResponseTimeout;
    if (m_runningUrl) {
      m_runningUrl->GetImapAction(&m_imapAction);
      // This is a silly hack, but the default of 100 seconds is typically way
      // too long for things like APPEND, which should come back immediately.
      // However, for large messages on some servers the final append response
      // time can be longer. So now it is one-fifth of the configured
      // `mailnews.tcptimeout' which defaults to 20 seconds.
      if (m_imapAction == nsIImapUrl::nsImapAppendMsgFromFile ||
          m_imapAction == nsIImapUrl::nsImapAppendDraftFromFile) {
        readWriteTimeout = gAppendTimeout;
      } else if (m_imapAction == nsIImapUrl::nsImapOnlineMove ||
                 m_imapAction == nsIImapUrl::nsImapOnlineCopy) {
        nsCString messageIdString;
        m_runningUrl->GetListOfMessageIds(messageIdString);
        uint32_t copyCount = CountMessagesInIdString(messageIdString.get());
        // If we're move/copying a large number of messages,
        // which should be rare, increase the timeout based on number
        // of messages. 40 messages per second should be sufficiently slow.
        if (copyCount > 2400)  // 40 * 60, 60 is default read write timeout
          readWriteTimeout =
              std::max(readWriteTimeout, (int32_t)copyCount / 40);
      }
    }
    m_transport->SetTimeout(nsISocketTransport::TIMEOUT_READ_WRITE,
                            readWriteTimeout);
    // Set the security info for the mock channel to be the security info for
    // our underlying transport.
    if (m_securityInfo) {
      m_mockChannel->SetSecurityInfo(m_securityInfo);
    }

    SetSecurityCallbacksFromChannel(m_transport, m_mockChannel);

    nsCOMPtr<nsITransportEventSink> sinkMC = do_QueryInterface(m_mockChannel);
    if (sinkMC) {
      nsCOMPtr<nsIThread> thread = do_GetMainThread();
      RefPtr<nsImapTransportEventSink> sink = new nsImapTransportEventSink;
      rv = net_NewTransportEventSinkProxy(getter_AddRefs(sink->m_proxy), sinkMC,
                                          thread);
      NS_ENSURE_SUCCESS(rv, rv);
      m_transport->SetEventSink(sink, nullptr);
    }

    // And if we have a cache2 entry that we are saving the message to, set the
    // security info on it too.
    nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_runningUrl);
    if (mailnewsUrl && m_securityInfo) {
      nsCOMPtr<nsICacheEntry> cacheEntry;
      mailnewsUrl->GetMemCacheEntry(getter_AddRefs(cacheEntry));
      if (cacheEntry) {
        cacheEntry->SetSecurityInfo(m_securityInfo);
      }
    }
  }

  rv = SetupSinkProxy();  // generate proxies for all of the event sinks in the
                          // url
  if (NS_FAILED(rv))      // URL can be invalid.
    return rv;

  if (m_transport && m_runningUrl) {
    nsImapAction imapAction;
    m_runningUrl->GetImapAction(&imapAction);
    // if we're shutting down, and not running the kinds of urls we run at
    // shutdown, then this should fail because running urls during
    // shutdown will very likely fail and potentially hang.
    nsCOMPtr<nsIMsgAccountManager> accountMgr =
        do_GetService("@mozilla.org/messenger/account-manager;1", &rv);
    NS_ENSURE_SUCCESS(rv, rv);
    bool shuttingDown = false;
    (void)accountMgr->GetShutdownInProgress(&shuttingDown);
    if (shuttingDown && imapAction != nsIImapUrl::nsImapExpungeFolder &&
        imapAction != nsIImapUrl::nsImapDeleteAllMsgs &&
        imapAction != nsIImapUrl::nsImapDeleteFolder)
      return NS_ERROR_FAILURE;

    // if we're running a select or delete all, do a noop first.
    // this should really be in the connection cache code when we know
    // we're pulling out a selected state connection, but maybe we
    // can get away with this.
    m_needNoop = (imapAction == nsIImapUrl::nsImapSelectFolder ||
                  imapAction == nsIImapUrl::nsImapDeleteAllMsgs);

    // We now have a url to run so signal the monitor for url ready to be
    // processed...
    ReentrantMonitorAutoEnter urlReadyMon(m_urlReadyToRunMonitor);
    m_nextUrlReadyToRun = true;
    urlReadyMon.Notify();

  }  // if we have an imap url and a transport
  else {
    NS_ASSERTION(false, "missing channel or running url");
  }

  return rv;
}

NS_IMETHODIMP nsImapProtocol::IsBusy(bool* aIsConnectionBusy,
                                     bool* isInboxConnection) {
  if (!aIsConnectionBusy || !isInboxConnection) return NS_ERROR_NULL_POINTER;
  nsresult rv = NS_OK;
  *aIsConnectionBusy = false;
  *isInboxConnection = false;
  if (!m_transport) {
    // this connection might not be fully set up yet.
    rv = NS_ERROR_FAILURE;
  } else {
    if (m_urlInProgress)  // do we have a url? That means we're working on it...
      *aIsConnectionBusy = true;

    if (GetServerStateParser().GetIMAPstate() ==
            nsImapServerResponseParser::kFolderSelected &&
        GetServerStateParser().GetSelectedMailboxName() &&
        PL_strcasecmp(GetServerStateParser().GetSelectedMailboxName(),
                      "Inbox") == 0)
      *isInboxConnection = true;
  }
  return rv;
}

#define IS_SUBSCRIPTION_RELATED_ACTION(action)        \
  (action == nsIImapUrl::nsImapSubscribe ||           \
   action == nsIImapUrl::nsImapUnsubscribe ||         \
   action == nsIImapUrl::nsImapDiscoverAllBoxesUrl || \
   action == nsIImapUrl::nsImapListFolder)

// canRunUrl means the connection is not busy, and is in the selected state
// for the desired folder (or authenticated).
// has to wait means it's in the right selected state, but busy.
NS_IMETHODIMP nsImapProtocol::CanHandleUrl(nsIImapUrl* aImapUrl,
                                           bool* aCanRunUrl, bool* hasToWait) {
  if (!aCanRunUrl || !hasToWait || !aImapUrl) return NS_ERROR_NULL_POINTER;
  nsresult rv = NS_OK;
  MutexAutoLock mon(mLock);

  *aCanRunUrl = false;  // assume guilty until proven otherwise...
  *hasToWait = false;

  if (DeathSignalReceived()) return NS_ERROR_FAILURE;

  bool isBusy = false;
  bool isInboxConnection = false;

  if (!m_transport) {
    // this connection might not be fully set up yet.
    return NS_ERROR_FAILURE;
  }
  IsBusy(&isBusy, &isInboxConnection);
  bool inSelectedState = GetServerStateParser().GetIMAPstate() ==
                         nsImapServerResponseParser::kFolderSelected;

  nsAutoCString curSelectedUrlFolderName;
  nsAutoCString pendingUrlFolderName;
  if (inSelectedState)
    curSelectedUrlFolderName = GetServerStateParser().GetSelectedMailboxName();

  if (isBusy) {
    nsImapState curUrlImapState;
    NS_ASSERTION(m_runningUrl, "isBusy, but no running url.");
    if (m_runningUrl) {
      m_runningUrl->GetRequiredImapState(&curUrlImapState);
      if (curUrlImapState == nsIImapUrl::nsImapSelectedState) {
        char* folderName = GetFolderPathString();
        if (!curSelectedUrlFolderName.Equals(folderName))
          pendingUrlFolderName.Assign(folderName);
        inSelectedState = true;
        PR_Free(folderName);
      }
    }
  }

  nsImapState imapState;
  nsImapAction actionForProposedUrl;
  aImapUrl->GetImapAction(&actionForProposedUrl);
  aImapUrl->GetRequiredImapState(&imapState);

  // OK, this is a bit of a hack - we're going to pretend that
  // these types of urls requires a selected state connection on
  // the folder in question. This isn't technically true,
  // but we would much rather use that connection for several reasons,
  // one is that some UW servers require us to use that connection
  // the other is that we don't want to leave a connection dangling in
  // the selected state for the deleted folder.
  // If we don't find a connection in that selected state,
  // we'll fall back to the first free connection.
  bool isSelectedStateUrl =
      imapState == nsIImapUrl::nsImapSelectedState ||
      actionForProposedUrl == nsIImapUrl::nsImapDeleteFolder ||
      actionForProposedUrl == nsIImapUrl::nsImapRenameFolder ||
      actionForProposedUrl == nsIImapUrl::nsImapMoveFolderHierarchy ||
      actionForProposedUrl == nsIImapUrl::nsImapAppendDraftFromFile ||
      actionForProposedUrl == nsIImapUrl::nsImapAppendMsgFromFile ||
      actionForProposedUrl == nsIImapUrl::nsImapFolderStatus;

  nsCOMPtr<nsIMsgMailNewsUrl> msgUrl = do_QueryInterface(aImapUrl);
  nsCOMPtr<nsIMsgIncomingServer> server;
  rv = msgUrl->GetServer(getter_AddRefs(server));
  if (NS_SUCCEEDED(rv)) {
    // compare host/user between url and connection.
    nsCString urlHostName;
    nsCString urlUserName;
    rv = server->GetHostName(urlHostName);
    NS_ENSURE_SUCCESS(rv, rv);
    rv = server->GetUsername(urlUserName);
    NS_ENSURE_SUCCESS(rv, rv);

    if ((GetImapHostName().IsEmpty() ||
         urlHostName.Equals(GetImapHostName(),
                            nsCaseInsensitiveCStringComparator)) &&
        (GetImapUserName().IsEmpty() ||
         urlUserName.Equals(GetImapUserName(),
                            nsCaseInsensitiveCStringComparator))) {
      if (isSelectedStateUrl) {
        if (inSelectedState) {
          // *** jt - in selected state can only run url with
          // matching foldername
          char* folderNameForProposedUrl = nullptr;
          rv = aImapUrl->CreateServerSourceFolderPathString(
              &folderNameForProposedUrl);
          if (NS_SUCCEEDED(rv) && folderNameForProposedUrl) {
            bool isInbox =
                PL_strcasecmp("Inbox", folderNameForProposedUrl) == 0;
            if (!curSelectedUrlFolderName.IsEmpty() ||
                !pendingUrlFolderName.IsEmpty()) {
              bool matched = isInbox
                                 ? PL_strcasecmp(curSelectedUrlFolderName.get(),
                                                 folderNameForProposedUrl) == 0
                                 : PL_strcmp(curSelectedUrlFolderName.get(),
                                             folderNameForProposedUrl) == 0;
              if (!matched && !pendingUrlFolderName.IsEmpty()) {
                matched = isInbox ? PL_strcasecmp(pendingUrlFolderName.get(),
                                                  folderNameForProposedUrl) == 0
                                  : PL_strcmp(pendingUrlFolderName.get(),
                                              folderNameForProposedUrl) == 0;
              }
              if (matched) {
                if (isBusy)
                  *hasToWait = true;
                else
                  *aCanRunUrl = true;
              }
            }
          }
          MOZ_LOG(IMAP, LogLevel::Debug,
                  ("proposed url = %s folder for connection %s has To Wait = "
                   "%s can run = %s",
                   folderNameForProposedUrl, curSelectedUrlFolderName.get(),
                   (*hasToWait) ? "true" : "false",
                   (*aCanRunUrl) ? "true" : "false"));
          PR_FREEIF(folderNameForProposedUrl);
        }
      } else  // *** jt - an authenticated state url can be run in either
              // authenticated or selected state
      {
        nsImapAction actionForRunningUrl;

        // If proposed url is subscription related, and we are currently running
        // a subscription url, then we want to queue the proposed url after the
        // current url. Otherwise, we can run this url if we're not busy. If we
        // never find a running subscription-related url, the caller will just
        // use whatever free connection it can find, which is what we want.
        if (IS_SUBSCRIPTION_RELATED_ACTION(actionForProposedUrl)) {
          if (isBusy && m_runningUrl) {
            m_runningUrl->GetImapAction(&actionForRunningUrl);
            if (IS_SUBSCRIPTION_RELATED_ACTION(actionForRunningUrl)) {
              *aCanRunUrl = false;
              *hasToWait = true;
            }
          }
        } else {
          if (!isBusy) *aCanRunUrl = true;
        }
      }
    }
  }
  return rv;
}

// Command tag handling stuff.
// Zero tag number indicates never used so set it to an initial random number
// between 1 and 100. Otherwise just increment the uint32_t value unless it
// rolls to zero then set it to 1. Then convert the tag number to a string for
// use in IMAP commands.
void nsImapProtocol::IncrementCommandTagNumber() {
  if (m_currentServerCommandTagNumber == 0) {
    srand((unsigned)m_lastCheckTime);
    m_currentServerCommandTagNumber = 1 + (rand() % 100);
  } else if (++m_currentServerCommandTagNumber == 0) {
    m_currentServerCommandTagNumber = 1;
  }
  sprintf(m_currentServerCommandTag, "%u", m_currentServerCommandTagNumber);
}

const char* nsImapProtocol::GetServerCommandTag() {
  return m_currentServerCommandTag;
}

/**
 *  ProcessSelectedStateURL() is a helper for ProcessCurrentURL(). It handles
 *  running URLs which require the connection to be in the selected state.
 *  It will issue SELECT commands if needed to make sure the correct mailbox
 *  is selected.
 */
void nsImapProtocol::ProcessSelectedStateURL() {
  nsCString mailboxName;
  bool bMessageIdsAreUids = true;
  bool moreHeadersToDownload;
  imapMessageFlagsType msgFlags = 0;
  nsCString urlHost;

  // this can't fail, can it?
  nsresult res;
  res = m_runningUrl->GetImapAction(&m_imapAction);
  m_runningUrl->MessageIdsAreUids(&bMessageIdsAreUids);
  m_runningUrl->GetMsgFlags(&msgFlags);
  m_runningUrl->GetMoreHeadersToDownload(&moreHeadersToDownload);

  res = CreateServerSourceFolderPathString(getter_Copies(mailboxName));
  if (NS_FAILED(res))
    Log("ProcessSelectedStateURL", nullptr,
        "error getting source folder path string");

  if (NS_SUCCEEDED(res) && !DeathSignalReceived()) {
    bool selectIssued = false;
    if (GetServerStateParser().GetIMAPstate() ==
        nsImapServerResponseParser::kFolderSelected) {
      if (GetServerStateParser().GetSelectedMailboxName() &&
          PL_strcmp(GetServerStateParser().GetSelectedMailboxName(),
                    mailboxName.get())) {  // we are selected in another folder
        if (m_closeNeededBeforeSelect) ImapClose();
        if (GetServerStateParser().LastCommandSuccessful()) {
          selectIssued = true;
          SelectMailbox(mailboxName.get());
        }
      } else if (!GetServerStateParser()
                      .GetSelectedMailboxName()) {  // why are we in the
                                                    // selected state with no
                                                    // box name?
        SelectMailbox(mailboxName.get());
        selectIssued = true;
      } else if (moreHeadersToDownload &&
                 m_imapMailFolderSink)  // we need to fetch older headers
      {
        nsTArray<nsMsgKey> msgIdList;
        bool more;
        m_imapMailFolderSink->GetMsgHdrsToDownload(
            &more, &m_progressExpectedNumber, msgIdList);
        if (msgIdList.Length() > 0) {
          FolderHeaderDump(msgIdList.Elements(), msgIdList.Length());
          m_runningUrl->SetMoreHeadersToDownload(more);
          // We're going to be re-running this url.
          if (more) m_runningUrl->SetRerunningUrl(true);
        }
        HeaderFetchCompleted();
      } else {
        // get new message counts, if any, from server
        if (m_needNoop) {
          // For some IMAP servers, to detect new email we must send imap
          // SELECT even if already SELECTed on the same mailbox.
          if (m_forceSelect) {
            SelectMailbox(mailboxName.get());
            selectIssued = true;
          }

          m_noopCount++;
          if ((gPromoteNoopToCheckCount > 0 &&
               (m_noopCount % gPromoteNoopToCheckCount) == 0) ||
              CheckNeeded())
            Check();
          else
            Noop();  // I think this is needed when we're using a cached
                     // connection
          m_needNoop = false;
        }
      }
    } else {
      // go to selected state
      SelectMailbox(mailboxName.get());
      selectIssued = GetServerStateParser().LastCommandSuccessful();
    }

    if (selectIssued) RefreshACLForFolderIfNecessary(mailboxName.get());

    bool uidValidityOk = true;
    if (GetServerStateParser().LastCommandSuccessful() && selectIssued &&
        (m_imapAction != nsIImapUrl::nsImapSelectFolder) &&
        (m_imapAction != nsIImapUrl::nsImapLiteSelectFolder)) {
      // error on the side of caution, if the fe event fails to set
      // uidStruct->returnValidity, then assume that UIDVALIDITY did not roll.
      // This is a common case event for attachments that are fetched within a
      // browser context.
      if (!DeathSignalReceived())
        uidValidityOk = m_uidValidity == kUidUnknown ||
                        m_uidValidity == GetServerStateParser().FolderUID();
    }

    if (!uidValidityOk)
      Log("ProcessSelectedStateURL", nullptr, "uid validity not ok");
    if (GetServerStateParser().LastCommandSuccessful() &&
        !DeathSignalReceived() &&
        (uidValidityOk || m_imapAction == nsIImapUrl::nsImapDeleteAllMsgs)) {
      if (GetServerStateParser().CurrentFolderReadOnly()) {
        Log("ProcessSelectedStateURL", nullptr, "current folder read only");
        if (m_imapAction == nsIImapUrl::nsImapAddMsgFlags ||
            m_imapAction == nsIImapUrl::nsImapSubtractMsgFlags) {
          bool canChangeFlag = false;
          if (GetServerStateParser().ServerHasACLCapability() &&
              m_imapMailFolderSink) {
            uint32_t aclFlags = 0;

            if (NS_SUCCEEDED(m_imapMailFolderSink->GetAclFlags(&aclFlags)) &&
                aclFlags != 0)  // make sure we have some acl flags
              canChangeFlag = ((msgFlags & kImapMsgSeenFlag) &&
                               (aclFlags & IMAP_ACL_STORE_SEEN_FLAG));
          } else
            canChangeFlag = (GetServerStateParser().SettablePermanentFlags() &
                             msgFlags) == msgFlags;
          if (!canChangeFlag) return;
        }
        if (m_imapAction == nsIImapUrl::nsImapExpungeFolder ||
            m_imapAction == nsIImapUrl::nsImapDeleteMsg ||
            m_imapAction == nsIImapUrl::nsImapDeleteAllMsgs)
          return;
      }
      switch (m_imapAction) {
        case nsIImapUrl::nsImapLiteSelectFolder:
          if (GetServerStateParser().LastCommandSuccessful() &&
              m_imapMailFolderSink && !moreHeadersToDownload) {
            m_imapMailFolderSink->SetUidValidity(
                GetServerStateParser().FolderUID());
            ProcessMailboxUpdate(false);  // handle uidvalidity change
          }
          break;
        case nsIImapUrl::nsImapSaveMessageToDisk:
        case nsIImapUrl::nsImapMsgFetch:
        case nsIImapUrl::nsImapMsgFetchPeek:
        case nsIImapUrl::nsImapMsgDownloadForOffline:
        case nsIImapUrl::nsImapMsgPreview: {
          nsCString messageIdString;
          m_runningUrl->GetListOfMessageIds(messageIdString);
          // we don't want to send the flags back in a group
          if (HandlingMultipleMessages(messageIdString) ||
              m_imapAction == nsIImapUrl::nsImapMsgDownloadForOffline ||
              m_imapAction == nsIImapUrl::nsImapMsgPreview) {
            // multiple messages, fetch them all
            SetProgressString(IMAP_MESSAGES_STRING_INDEX);

            m_progressCurrentNumber[m_stringIndex] = 0;
            m_progressExpectedNumber =
                CountMessagesInIdString(messageIdString.get());

            FetchMessage(messageIdString,
                         (m_imapAction == nsIImapUrl::nsImapMsgPreview)
                             ? kBodyStart
                             : kEveryThingRFC822Peek);
            if (m_imapAction == nsIImapUrl::nsImapMsgPreview)
              HeaderFetchCompleted();
            SetProgressString(IMAP_EMPTY_STRING_INDEX);
          } else {
            // A single message ID
            nsIMAPeFetchFields whatToFetch = kEveryThingRFC822;
            if (m_imapAction == nsIImapUrl::nsImapMsgFetchPeek)
              whatToFetch = kEveryThingRFC822Peek;

            // Note: Should no longer fetch a specific imap section (part).
            // First, let's see if we're requesting a specific MIME part.
            char* imappart = nullptr;
            m_runningUrl->GetImapPartToFetch(&imappart);
            MOZ_ASSERT(!imappart, "no longer fetching imap section/imappart");
            // downloading a single message: try to do it by bodystructure,
            // and/or do it by chunks
            // Note: No longer doing bodystructure.
            uint32_t messageSize = GetMessageSize(messageIdString);

            // The "wontFit" and cache parameter calculations (customLimit,
            // realLimit) are only for debug information logging below.
            if (MOZ_LOG_TEST(IMAPCache, LogLevel::Debug)) {
              nsCOMPtr<nsIMsgMailNewsUrl> mailnewsurl =
                  do_QueryInterface(m_runningUrl);
              if (mailnewsurl) {
                bool wontFit = net::CacheObserver::EntryIsTooBig(
                    messageSize, gUseDiskCache2);
                int64_t customLimit;
                int64_t realLimit;
                if (gUseDiskCache2) {
                  customLimit = net::CacheObserver::MaxDiskEntrySize();
                  realLimit = net::CacheObserver::DiskCacheCapacity();
                } else {
                  customLimit = net::CacheObserver::MaxMemoryEntrySize();
                  realLimit = net::CacheObserver::MemoryCacheCapacity();
                }
                if (!(customLimit & (int64_t)0x80000000))
                  customLimit <<= 10;  // multiply by 1024 to get num bytes
                else
                  customLimit = (int32_t)customLimit;  // make it negative
                realLimit <<= (10 - 3);  // 1/8th capacity, num bytes.
                if (customLimit > -1 && customLimit < realLimit)
                  realLimit = customLimit;
                MOZ_LOG(IMAPCache, LogLevel::Debug,
                        ("%s: customLimit=%" PRId64 ", realLimit=%" PRId64,
                         __func__, customLimit, realLimit));
                MOZ_LOG(IMAPCache, LogLevel::Debug,
                        ("%s: URL=%s, messageSize=%d, cache too small=%d(bool)",
                         __func__, mailnewsurl->GetSpecOrDefault().get(),
                         messageSize, wontFit));
              }
            }
            // Note again: No longer doing bodystructure.
            // Fetch the whole thing, and try to do it in chunks.
            MOZ_LOG(
                IMAPCache, LogLevel::Debug,
                ("%s: Fetch entire message with FetchTryChunking", __func__));
            FetchTryChunking(messageIdString, whatToFetch, bMessageIdsAreUids,
                             NULL, messageSize, true);
            // If fetch was not a peek, ensure that the message displays as
            // read (not bold) in case the server fails to mark the message
            // as SEEN.
            if (GetServerStateParser().LastCommandSuccessful() &&
                m_imapAction != nsIImapUrl::nsImapMsgFetchPeek) {
              uint32_t uid = strtoul(messageIdString.get(), nullptr, 10);
              int32_t index;
              bool foundIt;
              imapMessageFlagsType flags =
                  m_flagState->GetMessageFlagsFromUID(uid, &foundIt, &index);
              if (foundIt) {
                flags |= kImapMsgSeenFlag;
                m_flagState->SetMessageFlags(index, flags);
              }
            }
          }
        } break;
        case nsIImapUrl::nsImapExpungeFolder:
          Expunge();
          // note fall through to next cases.
          [[fallthrough]];
        case nsIImapUrl::nsImapSelectFolder:
        case nsIImapUrl::nsImapSelectNoopFolder:
          if (!moreHeadersToDownload) ProcessMailboxUpdate(true);
          break;
        case nsIImapUrl::nsImapMsgHeader: {
          nsCString messageIds;
          m_runningUrl->GetListOfMessageIds(messageIds);

          FetchMessage(messageIds, kHeadersRFC822andUid);
          // if we explicitly ask for headers, as opposed to getting them as a
          // result of selecting the folder, or biff, send the
          // headerFetchCompleted notification to flush out the header cache.
          HeaderFetchCompleted();
        } break;
        case nsIImapUrl::nsImapSearch: {
          nsAutoCString searchCriteriaString;
          m_runningUrl->CreateSearchCriteriaString(
              getter_Copies(searchCriteriaString));
          Search(searchCriteriaString.get(), bMessageIdsAreUids);
          // drop the results on the floor for now
        } break;
        case nsIImapUrl::nsImapUserDefinedMsgCommand: {
          nsCString messageIdString;
          nsCString command;

          m_runningUrl->GetCommand(command);
          m_runningUrl->GetListOfMessageIds(messageIdString);
          IssueUserDefinedMsgCommand(command.get(), messageIdString.get());
        } break;
        case nsIImapUrl::nsImapUserDefinedFetchAttribute: {
          nsCString messageIdString;
          nsCString attribute;

          m_runningUrl->GetCustomAttributeToFetch(attribute);
          m_runningUrl->GetListOfMessageIds(messageIdString);
          FetchMsgAttribute(messageIdString, attribute);
        } break;
        case nsIImapUrl::nsImapMsgStoreCustomKeywords: {
          // If the server doesn't support user defined flags, don't try to
          // define/set new ones. But if this is an attempt by TB to set or
          // reset flags "Junk" or "NonJunk", change "Junk" or "NonJunk" to
          // "$Junk" or "$NotJunk" respectively and store the modified flag
          // name if the server doesn't support storing user defined flags
          // and the server does allow storing the almost-standard flag names
          // "$Junk" and "$NotJunk". Yahoo imap server is an example of this.
          uint16_t userFlags = 0;
          GetSupportedUserFlags(&userFlags);
          bool userDefinedSettable = userFlags & kImapMsgSupportUserFlag;
          bool stdJunkOk = GetServerStateParser().IsStdJunkNotJunkUseOk();

          nsCString messageIdString;
          nsCString addFlags;
          nsCString subtractFlags;

          m_runningUrl->GetListOfMessageIds(messageIdString);
          m_runningUrl->GetCustomAddFlags(addFlags);
          m_runningUrl->GetCustomSubtractFlags(subtractFlags);
          if (!addFlags.IsEmpty()) {
            if (!userDefinedSettable) {
              if (stdJunkOk) {
                if (addFlags.EqualsIgnoreCase("junk"))
                  addFlags = "$Junk";
                else if (addFlags.EqualsIgnoreCase("nonjunk"))
                  addFlags = "$NotJunk";
                else
                  break;
              } else
                break;
            }
            nsAutoCString storeString("+FLAGS (");
            storeString.Append(addFlags);
            storeString.Append(')');
            Store(messageIdString, storeString.get(), true);
          }
          if (!subtractFlags.IsEmpty()) {
            if (!userDefinedSettable) {
              if (stdJunkOk) {
                if (subtractFlags.EqualsIgnoreCase("junk"))
                  subtractFlags = "$Junk";
                else if (subtractFlags.EqualsIgnoreCase("nonjunk"))
                  subtractFlags = "$NotJunk";
                else
                  break;
              } else
                break;
            }
            nsAutoCString storeString("-FLAGS (");
            storeString.Append(subtractFlags);
            storeString.Append(')');
            Store(messageIdString, storeString.get(), true);
          }
        } break;
        case nsIImapUrl::nsImapDeleteMsg: {
          nsCString messageIdString;
          m_runningUrl->GetListOfMessageIds(messageIdString);

          ProgressEventFunctionUsingName(
              HandlingMultipleMessages(messageIdString)
                  ? "imapDeletingMessages"
                  : "imapDeletingMessage");

          Store(messageIdString, "+FLAGS (\\Deleted)", bMessageIdsAreUids);

          if (GetServerStateParser().LastCommandSuccessful()) {
            nsCString canonicalName;
            const char* selectedMailboxName =
                GetServerStateParser().GetSelectedMailboxName();
            if (selectedMailboxName) {
              m_runningUrl->AllocateCanonicalPath(
                  selectedMailboxName, kOnlineHierarchySeparatorUnknown,
                  getter_Copies(canonicalName));
            }

            if (m_imapMessageSink)
              m_imapMessageSink->NotifyMessageDeleted(
                  canonicalName.get(), false, messageIdString.get());
            // notice we don't wait for this to finish...
          } else
            HandleMemoryFailure();
        } break;
        case nsIImapUrl::nsImapDeleteFolderAndMsgs:
          DeleteFolderAndMsgs(mailboxName.get());
          break;
        case nsIImapUrl::nsImapDeleteAllMsgs: {
          uint32_t numberOfMessages = GetServerStateParser().NumberOfMessages();
          if (numberOfMessages) {
            Store("1:*"_ns, "+FLAGS.SILENT (\\Deleted)",
                  false);  // use sequence #'s

            if (GetServerStateParser().LastCommandSuccessful())
              Expunge();  // expunge messages with deleted flag
            if (GetServerStateParser().LastCommandSuccessful()) {
              nsCString canonicalName;
              const char* selectedMailboxName =
                  GetServerStateParser().GetSelectedMailboxName();
              if (selectedMailboxName) {
                m_runningUrl->AllocateCanonicalPath(
                    selectedMailboxName, kOnlineHierarchySeparatorUnknown,
                    getter_Copies(canonicalName));
              }

              if (m_imapMessageSink)
                m_imapMessageSink->NotifyMessageDeleted(canonicalName.get(),
                                                        true, nullptr);
            }
          }
          bool deleteSelf = false;
          DeleteSubFolders(mailboxName.get(), deleteSelf);  // don't delete self
        } break;
        case nsIImapUrl::nsImapAppendDraftFromFile: {
          OnAppendMsgFromFile();
        } break;
        case nsIImapUrl::nsImapAddMsgFlags: {
          nsCString messageIdString;
          m_runningUrl->GetListOfMessageIds(messageIdString);

          ProcessStoreFlags(messageIdString, bMessageIdsAreUids, msgFlags,
                            true);
        } break;
        case nsIImapUrl::nsImapSubtractMsgFlags: {
          nsCString messageIdString;
          m_runningUrl->GetListOfMessageIds(messageIdString);

          ProcessStoreFlags(messageIdString, bMessageIdsAreUids, msgFlags,
                            false);
        } break;
        case nsIImapUrl::nsImapSetMsgFlags: {
          nsCString messageIdString;
          m_runningUrl->GetListOfMessageIds(messageIdString);

          ProcessStoreFlags(messageIdString, bMessageIdsAreUids, msgFlags,
                            true);
          ProcessStoreFlags(messageIdString, bMessageIdsAreUids, ~msgFlags,
                            false);
        } break;
        case nsIImapUrl::nsImapBiff:
          PeriodicBiff();
          break;
        case nsIImapUrl::nsImapOnlineCopy:
        case nsIImapUrl::nsImapOnlineMove: {
          nsCString messageIdString;
          m_runningUrl->GetListOfMessageIds(messageIdString);
          char* destinationMailbox =
              OnCreateServerDestinationFolderPathString();

          if (destinationMailbox) {
            if (m_imapAction == nsIImapUrl::nsImapOnlineMove) {
              if (HandlingMultipleMessages(messageIdString))
                ProgressEventFunctionUsingNameWithString("imapMovingMessages",
                                                         destinationMailbox);
              else
                ProgressEventFunctionUsingNameWithString("imapMovingMessage",
                                                         destinationMailbox);
            } else {
              if (HandlingMultipleMessages(messageIdString))
                ProgressEventFunctionUsingNameWithString("imapCopyingMessages",
                                                         destinationMailbox);
              else
                ProgressEventFunctionUsingNameWithString("imapCopyingMessage",
                                                         destinationMailbox);
            }
            Copy(messageIdString.get(), destinationMailbox, bMessageIdsAreUids);
            PR_FREEIF(destinationMailbox);
            ImapOnlineCopyState copyState;
            if (DeathSignalReceived())
              copyState = ImapOnlineCopyStateType::kInterruptedState;
            else
              copyState = GetServerStateParser().LastCommandSuccessful()
                              ? (ImapOnlineCopyState)
                                    ImapOnlineCopyStateType::kSuccessfulCopy
                              : (ImapOnlineCopyState)
                                    ImapOnlineCopyStateType::kFailedCopy;
            if (m_imapMailFolderSink)
              m_imapMailFolderSink->OnlineCopyCompleted(this, copyState);
            // Don't mark message 'Deleted' for AOL servers or standard imap
            // servers that support MOVE since we already issued an 'xaol-move'
            // or 'move' command.
            if (GetServerStateParser().LastCommandSuccessful() &&
                (m_imapAction == nsIImapUrl::nsImapOnlineMove) &&
                !(GetServerStateParser().ServerIsAOLServer() ||
                  GetServerStateParser().GetCapabilityFlag() &
                      kHasMoveCapability)) {
              // Simulate MOVE for servers that don't support MOVE: do
              // COPY-DELETE-EXPUNGE.
              Store(messageIdString, "+FLAGS (\\Deleted \\Seen)",
                    bMessageIdsAreUids);
              bool storeSuccessful =
                  GetServerStateParser().LastCommandSuccessful();
              if (storeSuccessful) {
                if (gExpungeAfterDelete) {
                  // This will expunge all emails marked as deleted in mailbox,
                  // not just the ones marked as deleted above.
                  Expunge();
                } else {
                  // Check if UIDPLUS capable so we can just expunge emails we
                  // just copied and marked as deleted. This prevents expunging
                  // emails that other clients may have marked as deleted in the
                  // mailbox and don't want them to disappear. Only do
                  // UidExpunge() when user selected delete method is "Move it
                  // to this folder" or "Remove it immediately", not when the
                  // delete method is "Just mark it as deleted".
                  if (!GetShowDeletedMessages() &&
                      (GetServerStateParser().GetCapabilityFlag() &
                       kUidplusCapability)) {
                    UidExpunge(messageIdString);
                  }
                }
              }
              if (m_imapMailFolderSink) {
                copyState = storeSuccessful
                                ? (ImapOnlineCopyState)
                                      ImapOnlineCopyStateType::kSuccessfulDelete
                                : (ImapOnlineCopyState)
                                      ImapOnlineCopyStateType::kFailedDelete;
                m_imapMailFolderSink->OnlineCopyCompleted(this, copyState);
              }
            }
          } else
            HandleMemoryFailure();
        } break;
        case nsIImapUrl::nsImapOnlineToOfflineCopy:
        case nsIImapUrl::nsImapOnlineToOfflineMove: {
          nsCString messageIdString;
          nsresult rv = m_runningUrl->GetListOfMessageIds(messageIdString);
          if (NS_SUCCEEDED(rv)) {
            SetProgressString(IMAP_MESSAGES_STRING_INDEX);
            m_progressCurrentNumber[m_stringIndex] = 0;
            m_progressExpectedNumber =
                CountMessagesInIdString(messageIdString.get());

            FetchMessage(messageIdString, kEveryThingRFC822Peek);

            SetProgressString(IMAP_EMPTY_STRING_INDEX);
            if (m_imapMailFolderSink) {
              ImapOnlineCopyState copyStatus;
              copyStatus = GetServerStateParser().LastCommandSuccessful()
                               ? ImapOnlineCopyStateType::kSuccessfulCopy
                               : ImapOnlineCopyStateType::kFailedCopy;

              m_imapMailFolderSink->OnlineCopyCompleted(this, copyStatus);
              if (GetServerStateParser().LastCommandSuccessful() &&
                  (m_imapAction == nsIImapUrl::nsImapOnlineToOfflineMove)) {
                Store(messageIdString, "+FLAGS (\\Deleted \\Seen)",
                      bMessageIdsAreUids);
                if (GetServerStateParser().LastCommandSuccessful()) {
                  copyStatus = ImapOnlineCopyStateType::kSuccessfulDelete;
                  if (gExpungeAfterDelete) Expunge();
                } else
                  copyStatus = ImapOnlineCopyStateType::kFailedDelete;

                m_imapMailFolderSink->OnlineCopyCompleted(this, copyStatus);
              }
            }
          } else
            HandleMemoryFailure();
        } break;
        default:
          if (GetServerStateParser().LastCommandSuccessful() && !uidValidityOk)
            ProcessMailboxUpdate(false);  // handle uidvalidity change
          break;
      }
    }
  } else if (!DeathSignalReceived())
    HandleMemoryFailure();
}

nsresult nsImapProtocol::BeginMessageDownLoad(
    uint32_t total_message_size,  // for user, headers and body
    const char* content_type) {
  nsresult rv = NS_OK;
  char* sizeString = PR_smprintf("OPEN Size: %ld", total_message_size);
  Log("STREAM", sizeString, "Begin Message Download Stream");
  PR_Free(sizeString);
  // start counting how many bytes we see in this message after all
  // transformations
  m_bytesToChannel = 0;

  if (content_type) {
    m_fromHeaderSeen = false;
    if (GetServerStateParser().GetDownloadingHeaders()) {
      // if we get multiple calls to BeginMessageDownload w/o intervening
      // calls to NormalEndMessageDownload or Abort, then we're just
      // going to fake a NormalMessageEndDownload. This will most likely
      // cause an empty header to get written to the db, and the user
      // will have to delete the empty header themselves, which
      // should remove the message from the server as well.
      if (m_curHdrInfo) NormalMessageEndDownload();
      if (!m_curHdrInfo) m_curHdrInfo = m_hdrDownloadCache->StartNewHdr();
      if (m_curHdrInfo) m_curHdrInfo->SetMsgSize(total_message_size);
      return NS_OK;
    }
    // if we have a mock channel, that means we have a channel listener who
    // wants the message. So set up a pipe. We'll write the message into one end
    // of the pipe and they will read it out of the other end.
    if (m_channelListener) {
      // create a pipe to pump the message into...the output will go to whoever
      // is consuming the message display
      // we create an "infinite" pipe in case we get extremely long lines from
      // the imap server, and the consumer is waiting for a whole line
      nsCOMPtr<nsIPipe> pipe = do_CreateInstance("@mozilla.org/pipe;1");
      rv = pipe->Init(false, false, 4096, PR_UINT32_MAX);
      NS_ENSURE_SUCCESS(rv, rv);

      // These always succeed because the pipe is initialized above.
      MOZ_ALWAYS_SUCCEEDS(
          pipe->GetInputStream(getter_AddRefs(m_channelInputStream)));
      MOZ_ALWAYS_SUCCEEDS(
          pipe->GetOutputStream(getter_AddRefs(m_channelOutputStream)));
    }
    // else, if we are saving the message to disk!
    else if (m_imapMessageSink /* && m_imapAction == nsIImapUrl::nsImapSaveMessageToDisk */)
    {
      // we get here when download the inbox for offline use
      nsCOMPtr<nsIFile> file;
      bool addDummyEnvelope = true;
      nsCOMPtr<nsIMsgMessageUrl> msgurl = do_QueryInterface(m_runningUrl);
      msgurl->GetMessageFile(getter_AddRefs(file));
      msgurl->GetAddDummyEnvelope(&addDummyEnvelope);
      if (file)
        rv = m_imapMessageSink->SetupMsgWriteStream(file, addDummyEnvelope);
    }
    if (m_imapMailFolderSink && m_runningUrl) {
      nsCOMPtr<nsISupports> copyState;
      if (m_runningUrl) {
        m_runningUrl->GetCopyState(getter_AddRefs(copyState));
        if (copyState)  // only need this notification during copy
        {
          nsCOMPtr<nsIMsgMailNewsUrl> mailurl = do_QueryInterface(m_runningUrl);
          m_imapMailFolderSink->StartMessage(mailurl);
        }
      }
    }

  } else
    HandleMemoryFailure();
  return rv;
}

void nsImapProtocol::GetShouldDownloadAllHeaders(bool* aResult) {
  if (m_imapMailFolderSink)
    m_imapMailFolderSink->GetShouldDownloadAllHeaders(aResult);
}

void nsImapProtocol::GetArbitraryHeadersToDownload(nsCString& aResult) {
  if (m_imapServerSink) m_imapServerSink->GetArbitraryHeaders(aResult);
}

void nsImapProtocol::AdjustChunkSize() {
  int32_t deltaInSeconds;

  m_endTime = PR_Now();
  PRTime2Seconds(m_endTime - m_startTime, &deltaInSeconds);
  m_trackingTime = false;
  if (deltaInSeconds < 0) return;  // bogus for some reason

  if (deltaInSeconds <= m_tooFastTime && m_curFetchSize >= m_chunkSize) {
    m_chunkSize += m_chunkAddSize;
    m_chunkThreshold = m_chunkSize + (m_chunkSize / 2);
    // we used to have a max for the chunk size - I don't think that's needed.
  } else if (deltaInSeconds <= m_idealTime)
    return;
  else {
    if (m_chunkSize > m_chunkStartSize)
      m_chunkSize = m_chunkStartSize;
    else if (m_chunkSize > (m_chunkAddSize * 2))
      m_chunkSize -= m_chunkAddSize;
    m_chunkThreshold = m_chunkSize + (m_chunkSize / 2);
  }
  // remember these new values globally so new connections
  // can take advantage of them.
  if (gChunkSize != m_chunkSize) {
    // will cause chunk size pref to be written in CloseStream.
    gChunkSizeDirty = true;
    gChunkSize = m_chunkSize;
    gChunkThreshold = m_chunkThreshold;
  }
}

// authenticated state commands

// escape any backslashes or quotes.  Backslashes are used a lot with our NT
// server
void nsImapProtocol::CreateEscapedMailboxName(const char* rawName,
                                              nsCString& escapedName) {
  escapedName.Assign(rawName);

  for (int32_t strIndex = 0; *rawName; strIndex++) {
    char currentChar = *rawName++;
    if ((currentChar == '\\') || (currentChar == '\"'))
      escapedName.Insert('\\', strIndex++);
  }
}
void nsImapProtocol::SelectMailbox(const char* mailboxName) {
  ProgressEventFunctionUsingNameWithString("imapStatusSelectingMailbox",
                                           mailboxName);
  IncrementCommandTagNumber();

  m_closeNeededBeforeSelect = false;  // initial value
  GetServerStateParser().ResetFlagInfo();
  nsCString escapedName;
  CreateEscapedMailboxName(mailboxName, escapedName);
  nsCString commandBuffer(GetServerCommandTag());
  commandBuffer.AppendLiteral(" select \"");
  commandBuffer.Append(escapedName.get());
  commandBuffer.Append('"');
  if (UseCondStore()) commandBuffer.AppendLiteral(" (CONDSTORE)");
  commandBuffer.Append(CRLF);

  nsresult res;
  res = SendData(commandBuffer.get());
  if (NS_FAILED(res)) return;
  ParseIMAPandCheckForNewMail();

  // Save the folder sink obtained in SetupSinkProxy() for whatever URL just
  // caused this SELECT. Needed so idle and noop responses are using the correct
  // folder when detecting changed flags or new messages.
  m_imapMailFolderSinkSelected = m_imapMailFolderSink;
  MOZ_ASSERT(m_imapMailFolderSinkSelected);
  Log("SelectMailbox", nullptr, "got m_imapMailFolderSinkSelected");

  int32_t numOfMessagesInFlagState = 0;
  nsImapAction imapAction;
  m_flagState->GetNumberOfMessages(&numOfMessagesInFlagState);
  res = m_runningUrl->GetImapAction(&imapAction);
  // if we've selected a mailbox, and we're not going to do an update because of
  // the url type, but don't have the flags, go get them!
  if (GetServerStateParser().LastCommandSuccessful() && NS_SUCCEEDED(res) &&
      imapAction != nsIImapUrl::nsImapSelectFolder &&
      imapAction != nsIImapUrl::nsImapExpungeFolder &&
      imapAction != nsIImapUrl::nsImapLiteSelectFolder &&
      imapAction != nsIImapUrl::nsImapDeleteAllMsgs &&
      ((GetServerStateParser().NumberOfMessages() !=
        numOfMessagesInFlagState) &&
       (numOfMessagesInFlagState == 0))) {
    ProcessMailboxUpdate(false);
  }
}

void nsImapProtocol::FetchMsgAttribute(const nsCString& messageIds,
                                       const nsCString& attribute) {
  IncrementCommandTagNumber();

  nsAutoCString commandString(GetServerCommandTag());
  commandString.AppendLiteral(" UID fetch ");
  commandString.Append(messageIds);
  commandString.AppendLiteral(" (");
  commandString.Append(attribute);
  commandString.AppendLiteral(")" CRLF);
  nsresult rv = SendData(commandString.get());

  if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail(commandString.get());
  GetServerStateParser().SetFetchingFlags(false);
  // Always clear this flag after every fetch.
  m_fetchingWholeMessage = false;
}

// this routine is used to fetch a message or messages, or headers for a
// message...

void nsImapProtocol::FallbackToFetchWholeMsg(const nsCString& messageId,
                                             uint32_t messageSize) {
  if (m_imapMessageSink && m_runningUrl) {
    bool shouldStoreMsgOffline;
    m_runningUrl->GetStoreOfflineOnFallback(&shouldStoreMsgOffline);
    m_runningUrl->SetStoreResultsOffline(shouldStoreMsgOffline);
  }
  FetchTryChunking(messageId,
                   m_imapAction == nsIImapUrl::nsImapMsgFetchPeek
                       ? kEveryThingRFC822Peek
                       : kEveryThingRFC822,
                   true, nullptr, messageSize, true);
}

void nsImapProtocol::FetchMessage(const nsCString& messageIds,
                                  nsIMAPeFetchFields whatToFetch,
                                  const char* fetchModifier, uint32_t startByte,
                                  uint32_t numBytes, char* part) {
  IncrementCommandTagNumber();

  nsCString commandString;
  commandString = "%s UID fetch";

  switch (whatToFetch) {
    case kEveryThingRFC822:
      m_flagChangeCount++;
      m_fetchingWholeMessage = true;
      if (m_trackingTime) AdjustChunkSize();  // we started another segment
      m_startTime = PR_Now();                 // save start of download time
      m_trackingTime = true;
      MOZ_LOG(IMAP, LogLevel::Debug,
              ("FetchMessage everything: curFetchSize %u numBytes %u",
               m_curFetchSize, numBytes));
      if (numBytes > 0) m_curFetchSize = numBytes;

      if (GetServerStateParser().ServerHasIMAP4Rev1Capability()) {
        if (GetServerStateParser().GetCapabilityFlag() & kHasXSenderCapability)
          commandString.AppendLiteral(" %s (XSENDER UID RFC822.SIZE BODY[]");
        else
          commandString.AppendLiteral(" %s (UID RFC822.SIZE BODY[]");
      } else {
        if (GetServerStateParser().GetCapabilityFlag() & kHasXSenderCapability)
          commandString.AppendLiteral(" %s (XSENDER UID RFC822.SIZE RFC822");
        else
          commandString.AppendLiteral(" %s (UID RFC822.SIZE RFC822");
      }
      if (numBytes > 0) {
        // if we are retrieving chunks
        char* byterangeString = PR_smprintf("<%ld.%ld>", startByte, numBytes);
        if (byterangeString) {
          commandString.Append(byterangeString);
          PR_Free(byterangeString);
        }
      }
      commandString.Append(')');

      break;

    case kEveryThingRFC822Peek: {
      MOZ_LOG(IMAP, LogLevel::Debug,
              ("FetchMessage peek: curFetchSize %u numBytes %u", m_curFetchSize,
               numBytes));
      if (numBytes > 0) m_curFetchSize = numBytes;
      const char* formatString = "";
      eIMAPCapabilityFlags server_capabilityFlags =
          GetServerStateParser().GetCapabilityFlag();

      m_fetchingWholeMessage = true;
      if (server_capabilityFlags & kIMAP4rev1Capability) {
        // use body[].peek since rfc822.peek is not in IMAP4rev1
        if (server_capabilityFlags & kHasXSenderCapability)
          formatString = " %s (XSENDER UID RFC822.SIZE BODY.PEEK[]";
        else
          formatString = " %s (UID RFC822.SIZE BODY.PEEK[]";
      } else {
        if (server_capabilityFlags & kHasXSenderCapability)
          formatString = " %s (XSENDER UID RFC822.SIZE RFC822.peek";
        else
          formatString = " %s (UID RFC822.SIZE RFC822.peek";
      }

      commandString.Append(formatString);
      if (numBytes > 0) {
        // if we are retrieving chunks
        char* byterangeString = PR_smprintf("<%ld.%ld>", startByte, numBytes);
        if (byterangeString) {
          commandString.Append(byterangeString);
          PR_Free(byterangeString);
        }
      }
      commandString.Append(')');
    } break;
    case kHeadersRFC822andUid:
      if (GetServerStateParser().ServerHasIMAP4Rev1Capability()) {
        eIMAPCapabilityFlags server_capabilityFlags =
            GetServerStateParser().GetCapabilityFlag();
        bool aolImapServer =
            ((server_capabilityFlags & kAOLImapCapability) != 0);
        bool downloadAllHeaders = false;
        // checks if we're filtering on "any header" or running a spam filter
        // requiring all headers
        GetShouldDownloadAllHeaders(&downloadAllHeaders);

        if (!downloadAllHeaders)  // if it's ok -- no filters on any header,
                                  // etc.
        {
          char* headersToDL = nullptr;
          char* what = nullptr;
          const char* dbHeaders =
              (gUseEnvelopeCmd) ? IMAP_DB_HEADERS : IMAP_ENV_AND_DB_HEADERS;
          nsCString arbitraryHeaders;
          GetArbitraryHeadersToDownload(arbitraryHeaders);
          for (uint32_t i = 0; i < mCustomDBHeaders.Length(); i++) {
            if (!FindInReadable(mCustomDBHeaders[i], arbitraryHeaders,
                                nsCaseInsensitiveCStringComparator)) {
              if (!arbitraryHeaders.IsEmpty()) arbitraryHeaders.Append(' ');
              arbitraryHeaders.Append(mCustomDBHeaders[i]);
            }
          }
          for (uint32_t i = 0; i < mCustomHeaders.Length(); i++) {
            if (!FindInReadable(mCustomHeaders[i], arbitraryHeaders,
                                nsCaseInsensitiveCStringComparator)) {
              if (!arbitraryHeaders.IsEmpty()) arbitraryHeaders.Append(' ');
              arbitraryHeaders.Append(mCustomHeaders[i]);
            }
          }
          if (arbitraryHeaders.IsEmpty())
            headersToDL = strdup(dbHeaders);
          else
            headersToDL =
                PR_smprintf("%s %s", dbHeaders, arbitraryHeaders.get());

          if (gUseEnvelopeCmd)
            what = PR_smprintf(" ENVELOPE BODY.PEEK[HEADER.FIELDS (%s)])",
                               headersToDL);
          else
            what = PR_smprintf(" BODY.PEEK[HEADER.FIELDS (%s)])", headersToDL);
          free(headersToDL);
          if (what) {
            commandString.AppendLiteral(" %s (UID ");
            if (m_isGmailServer)
              commandString.AppendLiteral("X-GM-MSGID X-GM-THRID X-GM-LABELS ");
            if (aolImapServer)
              commandString.AppendLiteral(" XAOL.SIZE");
            else
              commandString.AppendLiteral("RFC822.SIZE");
            commandString.AppendLiteral(" FLAGS");
            commandString.Append(what);
            PR_Free(what);
          } else {
            commandString.AppendLiteral(
                " %s (UID RFC822.SIZE BODY.PEEK[HEADER] FLAGS)");
          }
        } else
          commandString.AppendLiteral(
              " %s (UID RFC822.SIZE BODY.PEEK[HEADER] FLAGS)");
      } else
        commandString.AppendLiteral(
            " %s (UID RFC822.SIZE RFC822.HEADER FLAGS)");
      break;
    case kUid:
      commandString.AppendLiteral(" %s (UID)");
      break;
    case kFlags:
      GetServerStateParser().SetFetchingFlags(true);
      commandString.AppendLiteral(" %s (FLAGS)");
      break;
    case kRFC822Size:
      commandString.AppendLiteral(" %s (RFC822.SIZE)");
      break;
    case kBodyStart: {
      int32_t numBytesToFetch;
      m_runningUrl->GetNumBytesToFetch(&numBytesToFetch);

      commandString.AppendLiteral(
          " %s (UID BODY.PEEK[HEADER.FIELDS (Content-Type "
          "Content-Transfer-Encoding)] BODY.PEEK[TEXT]<0.");
      commandString.AppendInt(numBytesToFetch);
      commandString.AppendLiteral(">)");
    } break;
    case kRFC822HeadersOnly:
      if (GetServerStateParser().ServerHasIMAP4Rev1Capability()) {
        if (part) {
          commandString.AppendLiteral(" %s (BODY[");
          char* what = PR_smprintf("%s.HEADER])", part);
          if (what) {
            commandString.Append(what);
            PR_Free(what);
          } else
            HandleMemoryFailure();
        } else {
          // headers for the top-level message
          commandString.AppendLiteral(" %s (BODY[HEADER])");
        }
      } else
        commandString.AppendLiteral(" %s (RFC822.HEADER)");
      break;
    case kMIMEPart:
      commandString.AppendLiteral(" %s (BODY.PEEK[%s]");
      if (numBytes > 0) {
        // if we are retrieving chunks
        char* byterangeString = PR_smprintf("<%ld.%ld>", startByte, numBytes);
        if (byterangeString) {
          commandString.Append(byterangeString);
          PR_Free(byterangeString);
        }
      }
      commandString.Append(')');
      break;
    case kMIMEHeader:
      commandString.AppendLiteral(" %s (BODY[%s.MIME])");
      break;
  }

  if (fetchModifier) commandString.Append(fetchModifier);

  commandString.Append(CRLF);

  // since messageIds can be infinitely long, use a dynamic buffer rather than
  // the fixed one
  const char* commandTag = GetServerCommandTag();
  int protocolStringSize = commandString.Length() + messageIds.Length() +
                           PL_strlen(commandTag) + 1 +
                           (part ? PL_strlen(part) : 0);
  char* protocolString = (char*)PR_CALLOC(protocolStringSize);

  if (protocolString) {
    char* cCommandStr = ToNewCString(commandString);
    if ((whatToFetch == kMIMEPart) || (whatToFetch == kMIMEHeader)) {
      PR_snprintf(protocolString,      // string to create
                  protocolStringSize,  // max size
                  cCommandStr,         // format string
                  commandTag,          // command tag
                  messageIds.get(), part);
    } else {
      PR_snprintf(protocolString,      // string to create
                  protocolStringSize,  // max size
                  cCommandStr,         // format string
                  commandTag,          // command tag
                  messageIds.get());
    }

    nsresult rv = SendData(protocolString);

    free(cCommandStr);
    if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail(protocolString);
    PR_Free(protocolString);
    GetServerStateParser().SetFetchingFlags(false);
    // Always clear this flag after every fetch.
    m_fetchingWholeMessage = false;
    if (GetServerStateParser().LastCommandSuccessful() && CheckNeeded())
      Check();
  } else
    HandleMemoryFailure();
}

void nsImapProtocol::FetchTryChunking(const nsCString& messageIds,
                                      nsIMAPeFetchFields whatToFetch,
                                      bool idIsUid, char* part,
                                      uint32_t downloadSize, bool tryChunking) {
  GetServerStateParser().SetTotalDownloadSize(downloadSize);
  MOZ_LOG(IMAP, LogLevel::Debug,
          ("FetchTryChunking: curFetchSize %u", downloadSize));
  MOZ_ASSERT(!part, "fetching a part should no longer occur");
  m_curFetchSize = downloadSize;  // we'll change this if chunking.
  if (m_fetchByChunks && tryChunking &&
      GetServerStateParser().ServerHasIMAP4Rev1Capability() &&
      (downloadSize > (uint32_t)m_chunkThreshold)) {
    uint32_t startByte = 0;
    m_curFetchSize = m_chunkSize;
    GetServerStateParser().ClearLastFetchChunkReceived();
    while (!DeathSignalReceived() && !GetPseudoInterrupted() &&
           !GetServerStateParser().GetLastFetchChunkReceived() &&
           GetServerStateParser().ContinueParse()) {
      GetServerStateParser().ClearNumBytesFetched();
      // This chunk is a fetch of m_chunkSize bytes. But m_chunkSize can be
      // changed inside FetchMessage(). Save the original value of m_chunkSize
      // to set the correct offset (startByte) for the next chunk.
      int32_t bytesFetched = m_chunkSize;
      FetchMessage(messageIds, whatToFetch, nullptr, startByte, bytesFetched,
                   part);
      if (!GetServerStateParser().GetNumBytesFetched()) {
        // Fetch returned zero bytes chunk from server. This occurs if the
        // message was expunged during the fetch.
        MOZ_LOG(IMAP, LogLevel::Info,
                ("FetchTryChunking: Zero bytes chunk fetched; message probably "
                 "expunged"));
        break;
      }
      startByte += bytesFetched;
    }

    // Only abort the stream if this is a normal message download
    // Otherwise, let the body shell abort the stream.
    if ((whatToFetch == kEveryThingRFC822) &&
        ((startByte > 0 && (startByte < downloadSize) &&
          (DeathSignalReceived() || GetPseudoInterrupted())) ||
         !GetServerStateParser().ContinueParse())) {
      AbortMessageDownLoad();
      PseudoInterrupt(false);
    }
  } else {
    // small message, or (we're not chunking and not doing bodystructure),
    // or the server is not rev1.
    // Just fetch the whole thing.
    FetchMessage(messageIds, whatToFetch, nullptr, 0, 0, part);
  }
}

void nsImapProtocol::PostLineDownLoadEvent(const char* line,
                                           uint32_t uidOfMessage) {
  if (!GetServerStateParser().GetDownloadingHeaders()) {
    uint32_t byteCount = PL_strlen(line);
    bool echoLineToMessageSink = false;
    // if we have a channel listener, then just spool the message
    // directly to the listener
    if (m_channelListener) {
      uint32_t count = 0;
      if (m_channelOutputStream) {
        nsresult rv = m_channelOutputStream->Write(line, byteCount, &count);
        NS_ASSERTION(count == byteCount,
                     "IMAP channel pipe couldn't buffer entire write");
        if (NS_SUCCEEDED(rv)) {
          m_channelListener->OnDataAvailable(m_mockChannel,
                                             m_channelInputStream, 0, count);
        }
        // else some sort of explosion?
      }
    }
    if (m_runningUrl)
      m_runningUrl->GetStoreResultsOffline(&echoLineToMessageSink);

    m_bytesToChannel += byteCount;
    if (m_imapMessageSink && line && echoLineToMessageSink &&
        !GetPseudoInterrupted())
      m_imapMessageSink->ParseAdoptedMsgLine(line, uidOfMessage, m_runningUrl);
  }
  // ***** We need to handle the pseudo interrupt here *****
}

// Handle a line seen by the parser.
// * The argument |lineCopy| must be nullptr or should contain the same string
//   as |line|.  |lineCopy| will be modified.
// * A line may be passed by parts, e.g., "part1 part2\r\n" may be passed as
//     HandleMessageDownLoadLine("part 1 ", 1);
//     HandleMessageDownLoadLine("part 2\r\n", 0);
//   However, it is assumed that a CRLF or a CRCRLF is never split (i.e., this
//   is ensured *before* invoking this method).
void nsImapProtocol::HandleMessageDownLoadLine(const char* line,
                                               bool isPartialLine,
                                               char* lineCopy) {
  NS_ENSURE_TRUE_VOID(line);
  NS_ASSERTION(lineCopy == nullptr || !PL_strcmp(line, lineCopy),
               "line and lineCopy must contain the same string");
  const char* messageLine = line;
  uint32_t lineLength = strlen(messageLine);
  const char* cEndOfLine = messageLine + lineLength;
  char* localMessageLine = nullptr;

  // If we obtain a partial line (due to fetching by chunks), we do not
  // add/modify the end-of-line terminator.
  if (!isPartialLine) {
    // Change this line to native line termination, duplicate if necessary.
    // Do not assume that the line really ends in CRLF
    // to start with, even though it is supposed to be RFC822

    // normalize line endings to CRLF unless we are saving the message to disk
    bool canonicalLineEnding = true;
    nsCOMPtr<nsIMsgMessageUrl> msgUrl = do_QueryInterface(m_runningUrl);

    if (m_imapAction == nsIImapUrl::nsImapSaveMessageToDisk && msgUrl)
      msgUrl->GetCanonicalLineEnding(&canonicalLineEnding);

    NS_ASSERTION(MSG_LINEBREAK_LEN == 1 || (MSG_LINEBREAK_LEN == 2 &&
                                            !PL_strcmp(CRLF, MSG_LINEBREAK)),
                 "violated assumptions on MSG_LINEBREAK");
    if (MSG_LINEBREAK_LEN == 1 && !canonicalLineEnding) {
      bool lineEndsWithCRorLF =
          lineLength >= 1 && (cEndOfLine[-1] == '\r' || cEndOfLine[-1] == '\n');
      char* endOfLine;
      if (lineCopy && lineEndsWithCRorLF)  // true for most lines
      {
        endOfLine = lineCopy + lineLength;
        messageLine = lineCopy;
      } else {
        // leave enough room for one more char, MSG_LINEBREAK[0]
        localMessageLine = (char*)PR_MALLOC(lineLength + 2);
        if (!localMessageLine)  // memory failure
          return;
        PL_strcpy(localMessageLine, line);
        endOfLine = localMessageLine + lineLength;
        messageLine = localMessageLine;
      }

      if (lineLength >= 2 && endOfLine[-2] == '\r' && endOfLine[-1] == '\n') {
        if (lineLength >= 3 && endOfLine[-3] == '\r')  // CRCRLF
        {
          endOfLine--;
          lineLength--;
        }
        /* CRLF -> CR or LF */
        endOfLine[-2] = MSG_LINEBREAK[0];
        endOfLine[-1] = '\0';
        lineLength--;
      } else if (lineLength >= 1 &&
                 ((endOfLine[-1] == '\r') || (endOfLine[-1] == '\n'))) {
        /* CR -> LF or LF -> CR */
        endOfLine[-1] = MSG_LINEBREAK[0];
      } else  // no eol characters at all
      {
        endOfLine[0] = MSG_LINEBREAK[0];  // CR or LF
        endOfLine[1] = '\0';
        lineLength++;
      }
    } else  // enforce canonical CRLF linebreaks
    {
      if (lineLength == 0 || (lineLength == 1 && cEndOfLine[-1] == '\n')) {
        messageLine = CRLF;
        lineLength = 2;
      } else if (cEndOfLine[-1] != '\n' || cEndOfLine[-2] != '\r' ||
                 (lineLength >= 3 && cEndOfLine[-3] == '\r')) {
        // The line does not end in CRLF (or it ends in CRCRLF).
        // Copy line and leave enough room for two more chars (CR and LF).
        localMessageLine = (char*)PR_MALLOC(lineLength + 3);
        if (!localMessageLine)  // memory failure
          return;
        PL_strcpy(localMessageLine, line);
        char* endOfLine = localMessageLine + lineLength;
        messageLine = localMessageLine;

        if (lineLength >= 3 && endOfLine[-1] == '\n' && endOfLine[-2] == '\r') {
          // CRCRLF -> CRLF
          endOfLine[-2] = '\n';
          endOfLine[-1] = '\0';
          lineLength--;
        } else if ((endOfLine[-1] == '\r') || (endOfLine[-1] == '\n')) {
          // LF -> CRLF or CR -> CRLF
          endOfLine[-1] = '\r';
          endOfLine[0] = '\n';
          endOfLine[1] = '\0';
          lineLength++;
        } else  // no eol characters at all
        {
          endOfLine[0] = '\r';
          endOfLine[1] = '\n';
          endOfLine[2] = '\0';
          lineLength += 2;
        }
      }
    }
  }
  NS_ASSERTION(lineLength == PL_strlen(messageLine), "lineLength not accurate");

  // check if sender obtained via XSENDER server extension matches "From:" field
  const char* xSenderInfo = GetServerStateParser().GetXSenderInfo();
  if (xSenderInfo && *xSenderInfo && !m_fromHeaderSeen) {
    if (!PL_strncmp("From: ", messageLine, 6)) {
      m_fromHeaderSeen = true;
      if (PL_strstr(messageLine, xSenderInfo) != NULL)
        // Adding a X-Mozilla-Status line here is not very elegant but it
        // works.  Another X-Mozilla-Status line is added to the message when
        // downloading to a local folder; this new line will also contain the
        // 'authed' flag we are adding here.  (If the message is again
        // uploaded to the server, this flag is lost.)
        // 0x0200 == nsMsgMessageFlags::SenderAuthed
        HandleMessageDownLoadLine("X-Mozilla-Status: 0200\r\n", false);
      GetServerStateParser().FreeXSenderInfo();
    }
  }

  if (GetServerStateParser().GetDownloadingHeaders()) {
    if (!m_curHdrInfo)
      BeginMessageDownLoad(GetServerStateParser().SizeOfMostRecentMessage(),
                           MESSAGE_RFC822);
    if (m_curHdrInfo) {
      if (NS_FAILED(m_curHdrInfo->CacheLine(
              messageLine, GetServerStateParser().CurrentResponseUID())))
        NS_ERROR("CacheLine for a header failed");
    }
    PR_Free(localMessageLine);
    return;
  }
  // if this line is for a different message, or the incoming line is too big
  if (((m_downloadLineCache->CurrentUID() !=
        GetServerStateParser().CurrentResponseUID()) &&
       !m_downloadLineCache->CacheEmpty()) ||
      (m_downloadLineCache->SpaceAvailable() < lineLength + 1))
    FlushDownloadCache();

  // so now the cache is flushed, but this string might still be too big
  if (m_downloadLineCache->SpaceAvailable() < lineLength + 1)
    PostLineDownLoadEvent(messageLine,
                          GetServerStateParser().CurrentResponseUID());
  else {
    NS_ASSERTION(
        (PL_strlen(messageLine) + 1) <= m_downloadLineCache->SpaceAvailable(),
        "Oops... line length greater than space available");
    if (NS_FAILED(m_downloadLineCache->CacheLine(
            messageLine, GetServerStateParser().CurrentResponseUID())))
      NS_ERROR("CacheLine for message body failed");
  }
  PR_Free(localMessageLine);
}

void nsImapProtocol::FlushDownloadCache() {
  if (!m_downloadLineCache->CacheEmpty()) {
    msg_line_info* downloadLine = m_downloadLineCache->GetCurrentLineInfo();
    PostLineDownLoadEvent(downloadLine->adoptedMessageLine,
                          downloadLine->uidOfMessage);
    m_downloadLineCache->ResetCache();
  }
}

void nsImapProtocol::NormalMessageEndDownload() {
  Log("STREAM", "CLOSE", "Normal Message End Download Stream");

  if (m_trackingTime) AdjustChunkSize();
  if (m_imapMailFolderSink && m_curHdrInfo &&
      GetServerStateParser().GetDownloadingHeaders()) {
    m_curHdrInfo->SetMsgSize(GetServerStateParser().SizeOfMostRecentMessage());
    m_curHdrInfo->SetMsgUid(GetServerStateParser().CurrentResponseUID());
    m_hdrDownloadCache->FinishCurrentHdr();
    int32_t numHdrsCached;
    m_hdrDownloadCache->GetNumHeaders(&numHdrsCached);
    if (numHdrsCached == kNumHdrsToXfer) {
      m_imapMailFolderSink->ParseMsgHdrs(this, m_hdrDownloadCache);
      m_hdrDownloadCache->ResetAll();
    }
  }
  FlushDownloadCache();

  if (!GetServerStateParser().GetDownloadingHeaders()) {
    int32_t updatedMessageSize = -1;
    if (m_fetchingWholeMessage) {
      updatedMessageSize = m_bytesToChannel;
      if (m_bytesToChannel !=
          GetServerStateParser().SizeOfMostRecentMessage()) {
        MOZ_LOG(IMAP, LogLevel::Debug,
                ("STREAM:CLOSE Server's RFC822.SIZE %u, actual size %u",
                 GetServerStateParser().SizeOfMostRecentMessage(),
                 m_bytesToChannel));
      }
    }
    // need to know if we're downloading for display or not. We'll use action ==
    // nsImapMsgFetch for now
    nsImapAction imapAction =
        nsIImapUrl::nsImapSelectFolder;  // just set it to some legal value
    if (m_runningUrl) m_runningUrl->GetImapAction(&imapAction);

    if (m_imapMessageSink) {
      if (m_mockChannel) {
        // Have a mock channel, tell channel that write to cache is done.
        m_mockChannel->SetWritingToCache(false);
        MOZ_LOG(IMAP, LogLevel::Debug, ("%s: End cache write", __func__));
      }
      m_imapMessageSink->NormalEndMsgWriteStream(
          m_downloadLineCache->CurrentUID(),
          imapAction == nsIImapUrl::nsImapMsgFetch, m_runningUrl,
          updatedMessageSize);
    }

    if (m_runningUrl && m_imapMailFolderSink) {
      nsCOMPtr<nsISupports> copyState;
      m_runningUrl->GetCopyState(getter_AddRefs(copyState));
      if (copyState)  // only need this notification during copy
      {
        nsCOMPtr<nsIMsgMailNewsUrl> mailUrl(do_QueryInterface(m_runningUrl));
        m_imapMailFolderSink->EndMessage(mailUrl,
                                         m_downloadLineCache->CurrentUID());
      }
    }
  }
  m_curHdrInfo = nullptr;
}

void nsImapProtocol::AbortMessageDownLoad() {
  Log("STREAM", "CLOSE", "Abort Message  Download Stream");

  if (m_trackingTime) AdjustChunkSize();
  FlushDownloadCache();
  if (GetServerStateParser().GetDownloadingHeaders()) {
    if (m_imapMailFolderSink)
      m_imapMailFolderSink->AbortHeaderParseStream(this);
  } else if (m_imapMessageSink)
    m_imapMessageSink->AbortMsgWriteStream();

  m_curHdrInfo = nullptr;
}

void nsImapProtocol::ProcessMailboxUpdate(bool handlePossibleUndo) {
  if (DeathSignalReceived()) return;

  // Update quota information
  char* boxName;
  GetSelectedMailboxName(&boxName);
  GetQuotaDataIfSupported(boxName);
  PR_Free(boxName);

  // fetch the flags and uids of all existing messages or new ones
  if (!DeathSignalReceived() && GetServerStateParser().NumberOfMessages()) {
    if (handlePossibleUndo) {
      // undo any delete flags we may have asked to
      nsCString undoIdsStr;
      nsAutoCString undoIds;

      GetCurrentUrl()->GetListOfMessageIds(undoIdsStr);
      undoIds.Assign(undoIdsStr);
      if (!undoIds.IsEmpty()) {
        char firstChar = (char)undoIds.CharAt(0);
        undoIds.Cut(0, 1);  // remove first character
        // if this string started with a '-', then this is an undo of a delete
        // if its a '+' its a redo
        if (firstChar == '-')
          Store(undoIds, "-FLAGS (\\Deleted)",
                true);  // most servers will fail silently on a failure, deal
                        // with it?
        else if (firstChar == '+')
          Store(undoIds, "+FLAGS (\\Deleted)",
                true);  // most servers will fail silently on a failure, deal
                        // with it?
        else
          NS_ASSERTION(false, "bogus undo Id's");
      }
    }

    // make the parser record these flags
    nsCString fetchStr;
    int32_t added = 0, deleted = 0;

    m_flagState->GetNumberOfMessages(&added);
    deleted = m_flagState->NumberOfDeletedMessages();
    bool flagStateEmpty = !added;
    bool useCS = UseCondStore();

    // Figure out if we need to do a full sync (UID Fetch Flags 1:*),
    // a partial sync using CHANGEDSINCE, or a sync from the previous
    // highwater mark.

    // If the folder doesn't know about the highest uid, or the flag state
    // is empty, and we're not using CondStore, we definitely need a full sync.
    //
    // Print to log items affecting needFullFolderSync:
    MOZ_LOG(IMAP_CS, LogLevel::Debug,
            ("Do full sync?: mFolderHighestUID=%" PRIu32 ", added=%" PRId32
             ", useCS=%s",
             mFolderHighestUID, added, useCS ? "true" : "false"));
    bool needFullFolderSync = !mFolderHighestUID || (flagStateEmpty && !useCS);
    bool needFolderSync = false;

    if (!needFullFolderSync) {
      // Figure out if we need to do a non-highwater mark sync.
      // Set needFolderSync true when at least 1 of these 3 cases is true:
      // 1. Have no uids in flag array or all flag elements are marked deleted
      // AND not using CONDSTORE.
      // 2. Have no uids in flag array or all flag elements are marked deleted
      // AND using "just mark as deleted" and EXISTS response count differs from
      // stored message count for folder.
      // 3. Using CONDSTORE and highest MODSEQ response is not equal to stored
      // mod seq for folder.

      // Print to log items affecting needFolderSync:
      // clang-format off
      MOZ_LOG(IMAP_CS, LogLevel::Debug,
              ("1. Do a sync?: added=%" PRId32 ", deleted=%" PRId32 ", useCS=%s",
               added, deleted, useCS ? "true" : "false"));
      MOZ_LOG(IMAP_CS, LogLevel::Debug,
              ("2. Do a sync?: ShowDeletedMsgs=%s, exists=%" PRId32
               ", mFolderTotalMsgCount=%" PRId32,
               GetShowDeletedMessages() ? "true" : "false",
               GetServerStateParser().NumberOfMessages(), mFolderTotalMsgCount));
      // clang-format on
      MOZ_LOG(IMAP_CS, LogLevel::Debug,
              ("3. Do a sync?: fHighestModSeq=%" PRIu64
               ", mFolderLastModSeq=%" PRIu64,
               GetServerStateParser().fHighestModSeq, mFolderLastModSeq));

      needFolderSync =
          ((flagStateEmpty || added == deleted) &&
           (!useCS || (GetShowDeletedMessages() &&
                       GetServerStateParser().NumberOfMessages() !=
                           mFolderTotalMsgCount))) ||
          (useCS && GetServerStateParser().fHighestModSeq != mFolderLastModSeq);
    }
    MOZ_LOG(IMAP_CS, LogLevel::Debug,
            ("needFullFolderSync=%s, needFolderSync=%s",
             needFullFolderSync ? "true" : "false",
             needFolderSync ? "true" : "false"));

    if (needFullFolderSync || needFolderSync) {
      nsCString idsToFetch("1:*");
      char fetchModifier[40] = "";
      if (!needFullFolderSync && !GetShowDeletedMessages() && useCS) {
        m_flagState->StartCapture();
        MOZ_LOG(IMAP_CS, LogLevel::Debug,
                ("Doing UID fetch 1:* (CHANGEDSINCE %" PRIu64 ")",
                 mFolderLastModSeq));
        PR_snprintf(fetchModifier, sizeof(fetchModifier),
                    " (CHANGEDSINCE %llu)", mFolderLastModSeq);
      } else
        m_flagState->SetPartialUIDFetch(false);

      FetchMessage(idsToFetch, kFlags, fetchModifier);
      // lets see if we should expunge during a full sync of flags.
      if (GetServerStateParser().LastCommandSuccessful()) {
        // if we did a CHANGEDSINCE fetch, do a sanity check on the msg counts
        // to see if some other client may have done an expunge.
        if (m_flagState->GetPartialUIDFetch()) {
          uint32_t numExists = GetServerStateParser().NumberOfMessages();
          uint32_t numPrevExists = mFolderTotalMsgCount;
          MOZ_LOG(IMAP_CS, LogLevel::Debug,
                  ("Sanity, deleted=%" PRId32 ", numPrevExists=%" PRIu32
                   ", numExists=%" PRIu32,
                   m_flagState->NumberOfDeletedMessages(), numPrevExists,
                   numExists));
          // Determine the number of new UIDs just fetched that are greater than
          // the saved highest UID for the folder. numToCheck will contain the
          // number of UIDs just fetched and, of course, not all are new.
          uint32_t numNewUIDs = 0;
          uint32_t numToCheck = m_flagState->GetNumAdded();
          bool flagChangeDetected = false;
          bool expungeHappened = false;
          MOZ_LOG(IMAP_CS, LogLevel::Debug,
                  ("numToCheck=%" PRIu32, numToCheck));
          if (numToCheck && mFolderHighestUID) {
            uint32_t uid;
            int32_t topIndex;
            m_flagState->GetNumberOfMessages(&topIndex);
            MOZ_LOG(
                IMAP_CS, LogLevel::Debug,
                ("Partial fetching. Number of UIDs stored=%" PRId32, topIndex));
            do {
              topIndex--;
              // Check for potential infinite loop here. This has happened but
              // don't know why. If topIndex is negative at this point, set
              // expungeHappened true to recover by doing a full flag fetch.
              if (topIndex < 0) {
                expungeHappened = true;
                MOZ_LOG(IMAP_CS, LogLevel::Error,
                        ("Zero or negative number of UIDs stored, do full flag "
                         "fetch"));
                break;
              }
              m_flagState->GetUidOfMessage(topIndex, &uid);
              if (uid && uid != nsMsgKey_None) {
                if (uid > mFolderHighestUID) {
                  numNewUIDs++;
                  MOZ_LOG(IMAP_CS, LogLevel::Debug,
                          ("numNewUIDs=%" PRIu32 ", Added new UID=%" PRIu32,
                           numNewUIDs, uid));
                  numToCheck--;
                } else {
                  // Just a flag change on an existing UID. No more new UIDs
                  // will be found. This does not detect an expunged message.
                  flagChangeDetected = true;
                  MOZ_LOG(IMAP_CS, LogLevel::Debug,
                          ("Not new uid=%" PRIu32, uid));
                  break;
                }
              } else {
                MOZ_LOG(IMAP_CS, LogLevel::Debug,
                        ("UID is 0 or a gap, uid=0x%" PRIx32, uid));
                break;
              }
            } while (numToCheck);
          }

          // Another client expunged at least one message if the number of new
          // UIDs is not equal to the observed change in the number of messages
          // existing in the folder.
          expungeHappened =
              expungeHappened || numNewUIDs != (numExists - numPrevExists);
          if (expungeHappened) {
            // Sanity check failed - need full fetch to remove expunged msgs.
            MOZ_LOG(IMAP_CS, LogLevel::Debug,
                    ("Other client expunged msgs, do full fetch to remove "
                     "expunged msgs"));
            m_flagState->Reset();
            m_flagState->SetPartialUIDFetch(false);
            FetchMessage("1:*"_ns, kFlags);
          } else if (numNewUIDs == 0) {
            // Nothing has been expunged and no new UIDs, so if just a flag
            // change on existing message(s), avoid unneeded fetch of flags for
            // messages with UIDs at and above uid (see var uid above) when
            // "highwater mark" fetch occurs below.
            if (mFolderHighestUID && flagChangeDetected) {
              MOZ_LOG(IMAP_CS, LogLevel::Debug,
                      ("Avoid unneeded fetches after just flag changes"));
              GetServerStateParser().ResetHighestRecordedUID();
            }
          }
        }
        int32_t numDeleted = m_flagState->NumberOfDeletedMessages();
        // Don't do expunge when we are lite selecting folder (because we
        // could be doing undo) or if gExpungeOption is kAutoExpungeNever.
        // Expunge if we're always expunging, or the number of deleted messages
        // is over the threshold, and we're either always respecting the
        // threshold, or we're expunging based on the delete model, and the
        // delete model is not "just mark it as deleted" (imap delete model).
        if (m_imapAction != nsIImapUrl::nsImapLiteSelectFolder &&
            gExpungeOption != kAutoExpungeNever &&
            (gExpungeOption == kAutoExpungeAlways ||
             (numDeleted >= gExpungeThreshold &&
              (gExpungeOption == kAutoExpungeOnThreshold ||
               (gExpungeOption == kAutoExpungeDeleteModel &&
                !GetShowDeletedMessages())))))
          Expunge();
      }
    } else {
      // Obtain the highest (highwater mark) UID seen since the last UIDVALIDITY
      // response occurred (associated with the most recent SELECT for the
      // folder).
      uint32_t highestRecordedUID = GetServerStateParser().HighestRecordedUID();
      // if we're using CONDSTORE, and the parser hasn't seen any UIDs, use
      // the highest UID previously seen and saved for the folder instead.
      if (useCS && !highestRecordedUID) highestRecordedUID = mFolderHighestUID;
      // clang-format off
      MOZ_LOG(IMAP_CS, LogLevel::Debug,
              ("Check for new messages above UID=%" PRIu32, highestRecordedUID));
      // clang-format on
      AppendUid(fetchStr, highestRecordedUID + 1);
      fetchStr.AppendLiteral(":*");
      FetchMessage(fetchStr, kFlags);  // only new messages please
    }
  } else if (GetServerStateParser().LastCommandSuccessful()) {
    GetServerStateParser().ResetFlagInfo();
    // the flag state is empty, but not partial.
    m_flagState->SetPartialUIDFetch(false);
  }

  if (GetServerStateParser().LastCommandSuccessful()) {
    nsImapAction imapAction;
    nsresult res = m_runningUrl->GetImapAction(&imapAction);
    if (NS_SUCCEEDED(res) && imapAction == nsIImapUrl::nsImapLiteSelectFolder)
      return;
  }

  nsTArray<nsMsgKey> msgIdList;

  if (GetServerStateParser().LastCommandSuccessful()) {
    ReentrantMonitorAutoEnter mon(m_waitForBodyIdsMonitor);
    RefPtr<nsImapMailboxSpec> new_spec =
        GetServerStateParser().CreateCurrentMailboxSpec();
    nsImapAction imapAction;
    nsresult res = m_runningUrl->GetImapAction(&imapAction);
    if (NS_SUCCEEDED(res) && imapAction == nsIImapUrl::nsImapExpungeFolder)
      new_spec->mBoxFlags |= kJustExpunged;

    if (m_imapMailFolderSink) {
      bool more;
      m_imapMailFolderSink->UpdateImapMailboxInfo(this, new_spec);
      m_imapMailFolderSink->GetMsgHdrsToDownload(
          &more, &m_progressExpectedNumber, msgIdList);
      // Assert that either it's empty string OR it must be header string.
      MOZ_ASSERT((m_stringIndex == IMAP_EMPTY_STRING_INDEX) ||
                 (m_stringIndex == IMAP_HEADERS_STRING_INDEX));
      m_progressCurrentNumber[m_stringIndex] = 0;
      m_runningUrl->SetMoreHeadersToDownload(more);
      // We're going to be re-running this url if there are more headers.
      if (more) m_runningUrl->SetRerunningUrl(true);
    }
  }

  if (GetServerStateParser().LastCommandSuccessful()) {
    if (msgIdList.Length() > 0) {
      FolderHeaderDump(msgIdList.Elements(), msgIdList.Length());
    }
    HeaderFetchCompleted();
    // this might be bogus, how are we going to do pane notification and stuff
    // when we fetch bodies without headers!
  }

  // wait for a list of bodies to fetch.
  if (GetServerStateParser().LastCommandSuccessful()) {
    nsTArray<nsMsgKey> msgIds;
    WaitForPotentialListOfBodysToFetch(msgIds);
    if (msgIds.Length() > 0 && GetServerStateParser().LastCommandSuccessful()) {
      // Tell the url that it should store the msg fetch results offline,
      // while we're dumping the messages, and then restore the setting.
      bool wasStoringOffline;
      m_runningUrl->GetStoreResultsOffline(&wasStoringOffline);
      m_runningUrl->SetStoreResultsOffline(true);
      // Assert that either it's empty string OR it must be message string.
      MOZ_ASSERT((m_stringIndex == IMAP_EMPTY_STRING_INDEX) ||
                 (m_stringIndex == IMAP_MESSAGES_STRING_INDEX));
      m_progressCurrentNumber[m_stringIndex] = 0;
      m_progressExpectedNumber = msgIds.Length();
      FolderMsgDump(msgIds.Elements(), msgIds.Length(), kEveryThingRFC822Peek);
      m_runningUrl->SetStoreResultsOffline(wasStoringOffline);
    }
  }
  if (!GetServerStateParser().LastCommandSuccessful())
    GetServerStateParser().ResetFlagInfo();
}

void nsImapProtocol::FolderHeaderDump(uint32_t* msgUids, uint32_t msgCount) {
  FolderMsgDump(msgUids, msgCount, kHeadersRFC822andUid);
}

void nsImapProtocol::FolderMsgDump(uint32_t* msgUids, uint32_t msgCount,
                                   nsIMAPeFetchFields fields) {
  // lets worry about this progress stuff later.
  switch (fields) {
    case kHeadersRFC822andUid:
      SetProgressString(IMAP_HEADERS_STRING_INDEX);
      break;
    case kFlags:
      SetProgressString(IMAP_FLAGS_STRING_INDEX);
      break;
    default:
      SetProgressString(IMAP_MESSAGES_STRING_INDEX);
      break;
  }

  FolderMsgDumpLoop(msgUids, msgCount, fields);

  SetProgressString(IMAP_EMPTY_STRING_INDEX);
}

void nsImapProtocol::WaitForPotentialListOfBodysToFetch(
    nsTArray<nsMsgKey>& msgIdList) {
  PRIntervalTime sleepTime = kImapSleepTime;

  ReentrantMonitorAutoEnter fetchListMon(m_fetchBodyListMonitor);
  while (!m_fetchBodyListIsNew && !DeathSignalReceived())
    fetchListMon.Wait(sleepTime);
  m_fetchBodyListIsNew = false;

  msgIdList = m_fetchBodyIdList.Clone();
}

// libmsg uses this to notify a running imap url about message bodies it should
// download. why not just have libmsg explicitly download the message bodies?
NS_IMETHODIMP nsImapProtocol::NotifyBodysToDownload(
    const nsTArray<nsMsgKey>& keys) {
  ReentrantMonitorAutoEnter fetchListMon(m_fetchBodyListMonitor);
  m_fetchBodyIdList = keys.Clone();
  m_fetchBodyListIsNew = true;
  fetchListMon.Notify();
  return NS_OK;
}

NS_IMETHODIMP nsImapProtocol::GetFlagsForUID(uint32_t uid, bool* foundIt,
                                             imapMessageFlagsType* resultFlags,
                                             char** customFlags) {
  int32_t i;

  imapMessageFlagsType flags =
      m_flagState->GetMessageFlagsFromUID(uid, foundIt, &i);
  if (*foundIt) {
    *resultFlags = flags;
    if ((flags & kImapMsgCustomKeywordFlag) && customFlags)
      m_flagState->GetCustomFlags(uid, customFlags);
  }
  return NS_OK;
}

NS_IMETHODIMP nsImapProtocol::GetFlagAndUidState(
    nsIImapFlagAndUidState** aFlagState) {
  NS_ENSURE_ARG_POINTER(aFlagState);
  NS_IF_ADDREF(*aFlagState = m_flagState);
  return NS_OK;
}

NS_IMETHODIMP nsImapProtocol::GetSupportedUserFlags(uint16_t* supportedFlags) {
  if (!supportedFlags) return NS_ERROR_NULL_POINTER;

  *supportedFlags = m_flagState->GetSupportedUserFlags();
  return NS_OK;
}
void nsImapProtocol::FolderMsgDumpLoop(uint32_t* msgUids, uint32_t msgCount,
                                       nsIMAPeFetchFields fields) {
  int32_t msgCountLeft = msgCount;
  uint32_t msgsDownloaded = 0;
  do {
    nsCString idString;
    uint32_t msgsToDownload = msgCountLeft;
    AllocateImapUidString(msgUids + msgsDownloaded, msgsToDownload, m_flagState,
                          idString);  // 20 * 200
    FetchMessage(idString, fields);
    msgsDownloaded += msgsToDownload;
    msgCountLeft -= msgsToDownload;
  } while (msgCountLeft > 0 && !DeathSignalReceived());
}

void nsImapProtocol::HeaderFetchCompleted() {
  if (m_imapMailFolderSink)
    m_imapMailFolderSink->ParseMsgHdrs(this, m_hdrDownloadCache);
  m_hdrDownloadCache->ReleaseAll();

  if (m_imapMailFolderSink) m_imapMailFolderSink->HeaderFetchCompleted(this);
}

// Use the noop to tell the server we are still here, and therefore we are
// willing to receive status updates. The recent or exists response from the
// server could tell us that there is more mail waiting for us, but we need to
// check the flags of the mail and the high water mark to make sure that we do
// not tell the user that there is new mail when perhaps they have already read
// it in another machine.

void nsImapProtocol::PeriodicBiff() {
  nsMsgBiffState startingState = m_currentBiffState;

  if (GetServerStateParser().GetIMAPstate() ==
      nsImapServerResponseParser::kFolderSelected) {
    Noop();  // check the latest number of messages
    int32_t numMessages = 0;
    m_flagState->GetNumberOfMessages(&numMessages);
    if (GetServerStateParser().NumberOfMessages() != numMessages) {
      uint32_t id = GetServerStateParser().HighestRecordedUID() + 1;
      nsCString fetchStr;  // only update flags
      uint32_t added = 0, deleted = 0;

      deleted = m_flagState->NumberOfDeletedMessages();
      added = numMessages;
      if (!added || (added == deleted))  // empty keys, get them all
        id = 1;

      // sprintf(fetchStr, "%ld:%ld", id, id +
      // GetServerStateParser().NumberOfMessages() -
      // fFlagState->GetNumberOfMessages());
      AppendUid(fetchStr, id);
      fetchStr.AppendLiteral(":*");
      FetchMessage(fetchStr, kFlags);
      if (((uint32_t)m_flagState->GetHighestNonDeletedUID() >= id) &&
          m_flagState->IsLastMessageUnseen())
        m_currentBiffState = nsIMsgFolder::nsMsgBiffState_NewMail;
      else
        m_currentBiffState = nsIMsgFolder::nsMsgBiffState_NoMail;
    } else
      m_currentBiffState = nsIMsgFolder::nsMsgBiffState_NoMail;
  } else
    m_currentBiffState = nsIMsgFolder::nsMsgBiffState_Unknown;

  if (startingState != m_currentBiffState)
    SendSetBiffIndicatorEvent(m_currentBiffState);
}

void nsImapProtocol::SendSetBiffIndicatorEvent(nsMsgBiffState newState) {
  if (m_imapMailFolderSink)
    m_imapMailFolderSink->SetBiffStateAndUpdate(newState);
}

/* static */ void nsImapProtocol::LogImapUrl(const char* logMsg,
                                             nsIImapUrl* imapUrl) {
  if (MOZ_LOG_TEST(IMAP, LogLevel::Info)) {
    nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(imapUrl);
    if (mailnewsUrl) {
      nsAutoCString urlSpec, unescapedUrlSpec;
      nsresult rv = mailnewsUrl->GetSpec(urlSpec);
      if (NS_FAILED(rv)) return;
      MsgUnescapeString(urlSpec, 0, unescapedUrlSpec);
      MOZ_LOG(IMAP, LogLevel::Info, ("%s:%s", logMsg, unescapedUrlSpec.get()));
    }
  }
}

// log info including current state...
void nsImapProtocol::Log(const char* logSubName, const char* extraInfo,
                         const char* logData) {
  if (MOZ_LOG_TEST(IMAP, LogLevel::Info)) {
    static const char nonAuthStateName[] = "NA";
    static const char authStateName[] = "A";
    static const char selectedStateName[] = "S";
    const nsCString& hostName =
        GetImapHostName();  // initialize to empty string

    int32_t logDataLen = PL_strlen(logData);  // PL_strlen checks for null
    nsCString logDataLines;
    const char* logDataToLog;
    int32_t lastLineEnd;

    // nspr line length is 512, and we allow some space for the log preamble.
    const int kLogDataChunkSize = 400;

    // break up buffers > 400 bytes on line boundaries.
    if (logDataLen > kLogDataChunkSize) {
      logDataLines.Assign(logData);
      lastLineEnd = logDataLines.RFindChar('\n', kLogDataChunkSize);
      // null terminate the last line
      if (lastLineEnd == kNotFound) lastLineEnd = kLogDataChunkSize - 1;

      logDataLines.Insert('\0', lastLineEnd + 1);
      logDataToLog = logDataLines.get();
    } else {
      logDataToLog = logData;
      lastLineEnd = logDataLen;
    }
    switch (GetServerStateParser().GetIMAPstate()) {
      case nsImapServerResponseParser::kFolderSelected:
        if (extraInfo)
          MOZ_LOG(IMAP, LogLevel::Info,
                  ("%p:%s:%s-%s:%s:%s: %.400s", this, hostName.get(),
                   selectedStateName,
                   GetServerStateParser().GetSelectedMailboxName(), logSubName,
                   extraInfo, logDataToLog));
        else
          MOZ_LOG(IMAP, LogLevel::Info,
                  ("%p:%s:%s-%s:%s: %.400s", this, hostName.get(),
                   selectedStateName,
                   GetServerStateParser().GetSelectedMailboxName(), logSubName,
                   logDataToLog));
        break;
      case nsImapServerResponseParser::kNonAuthenticated:
      case nsImapServerResponseParser::kAuthenticated: {
        const char* stateName = (GetServerStateParser().GetIMAPstate() ==
                                 nsImapServerResponseParser::kNonAuthenticated)
                                    ? nonAuthStateName
                                    : authStateName;
        if (extraInfo)
          MOZ_LOG(IMAP, LogLevel::Info,
                  ("%p:%s:%s:%s:%s: %.400s", this, hostName.get(), stateName,
                   logSubName, extraInfo, logDataToLog));
        else
          MOZ_LOG(IMAP, LogLevel::Info,
                  ("%p:%s:%s:%s: %.400s", this, hostName.get(), stateName,
                   logSubName, logDataToLog));
      }
    }

    // dump the rest of the string in < 400 byte chunks
    while (logDataLen > kLogDataChunkSize) {
      logDataLines.Cut(
          0,
          lastLineEnd + 2);  // + 2 to account for the LF and the '\0' we added
      logDataLen = logDataLines.Length();
      lastLineEnd = (logDataLen > kLogDataChunkSize)
                        ? logDataLines.RFindChar('\n', kLogDataChunkSize)
                        : kNotFound;
      // null terminate the last line
      if (lastLineEnd == kNotFound) lastLineEnd = kLogDataChunkSize - 1;
      logDataLines.Insert('\0', lastLineEnd + 1);
      logDataToLog = logDataLines.get();
      MOZ_LOG(IMAP, LogLevel::Info, ("%.400s", logDataToLog));
    }
  }
}

// In 4.5, this posted an event back to libmsg and blocked until it got a
// response. We may still have to do this.It would be nice if we could preflight
// this value, but we may not always know when we'll need it.
uint32_t nsImapProtocol::GetMessageSize(const nsACString& messageId) {
  uint32_t size = 0;
  if (m_imapMessageSink)
    m_imapMessageSink->GetMessageSizeFromDB(PromiseFlatCString(messageId).get(),
                                            &size);
  if (DeathSignalReceived()) size = 0;
  return size;
}

// message id string utility functions
/* static */ bool nsImapProtocol::HandlingMultipleMessages(
    const nsCString& messageIdString) {
  return (MsgFindCharInSet(messageIdString, ",:") != kNotFound);
}

uint32_t nsImapProtocol::CountMessagesInIdString(const char* idString) {
  uint32_t numberOfMessages = 0;
  char* uidString = PL_strdup(idString);

  if (uidString) {
    // This is in the form <id>,<id>, or <id1>:<id2>
    char curChar = *uidString;
    bool isRange = false;
    int32_t curToken;
    int32_t saveStartToken = 0;

    for (char* curCharPtr = uidString; curChar && *curCharPtr;) {
      char* currentKeyToken = curCharPtr;
      curChar = *curCharPtr;
      while (curChar != ':' && curChar != ',' && curChar != '\0')
        curChar = *curCharPtr++;
      *(curCharPtr - 1) = '\0';
      curToken = atol(currentKeyToken);
      if (isRange) {
        while (saveStartToken < curToken) {
          numberOfMessages++;
          saveStartToken++;
        }
      }

      numberOfMessages++;
      isRange = (curChar == ':');
      if (isRange) saveStartToken = curToken + 1;
    }
    PR_Free(uidString);
  }
  return numberOfMessages;
}

// It would be really nice not to have to use this method nearly as much as we
// did in 4.5 - we need to think about this some. Some of it may just go away in
// the new world order
bool nsImapProtocol::DeathSignalReceived() {
  // ignore mock channel status if we've been pseudo interrupted
  // ### need to make sure we clear pseudo interrupted status appropriately.
  if (!GetPseudoInterrupted() && m_mockChannel) {
    nsresult returnValue;
    m_mockChannel->GetStatus(&returnValue);
    if (NS_FAILED(returnValue)) return false;
  }

  // Check the other way of cancelling.
  ReentrantMonitorAutoEnter threadDeathMon(m_threadDeathMonitor);
  return m_threadShouldDie;
}

NS_IMETHODIMP nsImapProtocol::ResetToAuthenticatedState() {
  GetServerStateParser().PreauthSetAuthenticatedState();
  return NS_OK;
}

NS_IMETHODIMP nsImapProtocol::GetSelectedMailboxName(char** folderName) {
  if (!folderName) return NS_ERROR_NULL_POINTER;
  if (GetServerStateParser().GetSelectedMailboxName())
    *folderName = PL_strdup((GetServerStateParser().GetSelectedMailboxName()));
  return NS_OK;
}

bool nsImapProtocol::GetPseudoInterrupted() {
  ReentrantMonitorAutoEnter pseudoInterruptMon(m_pseudoInterruptMonitor);
  return m_pseudoInterrupted;
}

NS_IMETHODIMP
nsImapProtocol::PseudoInterrupt(bool interrupt) {
  ReentrantMonitorAutoEnter pseudoInterruptMon(m_pseudoInterruptMonitor);
  m_pseudoInterrupted = interrupt;
  if (interrupt) Log("CONTROL", NULL, "PSEUDO-Interrupted");
  return NS_OK;
}

void nsImapProtocol::SetActive(bool active) {
  ReentrantMonitorAutoEnter dataMemberMon(m_dataMemberMonitor);
  m_active = active;
}

bool nsImapProtocol::GetActive() {
  ReentrantMonitorAutoEnter dataMemberMon(m_dataMemberMonitor);
  return m_active;
}

bool nsImapProtocol::GetShowAttachmentsInline() {
  bool showAttachmentsInline = true;
  if (m_imapServerSink)
    m_imapServerSink->GetShowAttachmentsInline(&showAttachmentsInline);
  return showAttachmentsInline;
}

// Adds a set of rights for a given user on a given mailbox on the current host.
// if userName is NULL, it means "me," or MYRIGHTS.
void nsImapProtocol::AddFolderRightsForUser(const char* mailboxName,
                                            const char* userName,
                                            const char* rights) {
  if (!userName) userName = "";
  if (m_imapServerSink)
    m_imapServerSink->AddFolderRights(nsDependentCString(mailboxName),
                                      nsDependentCString(userName),
                                      nsDependentCString(rights));
}

void nsImapProtocol::SetCopyResponseUid(const char* msgIdString) {
  if (m_imapMailFolderSink)
    m_imapMailFolderSink->SetCopyResponseUid(msgIdString, m_runningUrl);
}

void nsImapProtocol::CommitNamespacesForHostEvent() {
  if (m_imapServerSink) m_imapServerSink->CommitNamespaces();
}

// notifies libmsg that we have new capability data for the current host
void nsImapProtocol::CommitCapability() {
  if (m_imapServerSink) {
    m_imapServerSink->SetCapability(GetServerStateParser().GetCapabilityFlag());
  }
}

// rights is a single string of rights, as specified by RFC2086, the IMAP ACL
// extension. Clears all rights for a given folder, for all users.
void nsImapProtocol::ClearAllFolderRights() {
  if (m_imapMailFolderSink) m_imapMailFolderSink->ClearFolderRights();
}

// Reads a line from the socket.
// Upon failure, the thread will be flagged for shutdown, and
// m_connectionStatus will be set to a failing code.
// Remember that some socket errors are deferred until the first read
// attempt, so this function could be the first place we hear about
// connection issues (e.g. bad certificates for SSL).
char* nsImapProtocol::CreateNewLineFromSocket() {
  bool needMoreData = false;
  char* newLine = nullptr;
  uint32_t numBytesInLine = 0;
  nsresult rv = NS_OK;
  // we hold a ref to the input stream in case we get cancelled from the
  // ui thread, which releases our ref to the input stream, and can
  // cause the pipe to get deleted before the monitor the read is
  // blocked on gets notified. When that happens, the imap thread
  // will stay blocked.
  nsCOMPtr<nsIInputStream> kungFuGrip = m_inputStream;

  if (m_mockChannel) {
    nsImapMockChannel* imapChannel =
        static_cast<nsImapMockChannel*>(m_mockChannel.get());

    mozilla::MonitorAutoLock lock(imapChannel->mSuspendedMonitor);

    bool suspended = imapChannel->mSuspended;
    if (suspended)
      MOZ_LOG(IMAP, LogLevel::Debug,
              ("Waiting until [imapChannel=%p] is resumed.", imapChannel));
    while (imapChannel->mSuspended) {
      lock.Wait();
    }
    if (suspended)
      MOZ_LOG(
          IMAP, LogLevel::Debug,
          ("Done waiting, [imapChannel=%p] has been resumed.", imapChannel));
  }

  do {
    newLine = m_inputStreamBuffer->ReadNextLine(m_inputStream, numBytesInLine,
                                                needMoreData, &rv);
    MOZ_LOG(IMAP, LogLevel::Verbose,
            ("ReadNextLine [rv=0x%" PRIx32 " stream=%p nb=%u needmore=%u]",
             static_cast<uint32_t>(rv), m_inputStream.get(), numBytesInLine,
             needMoreData));

  } while (!newLine && NS_SUCCEEDED(rv) &&
           !DeathSignalReceived());  // until we get the next line and haven't
                                     // been interrupted

  kungFuGrip = nullptr;

  if (NS_FAILED(rv)) {
    switch (rv) {
      case NS_ERROR_UNKNOWN_HOST:
      case NS_ERROR_UNKNOWN_PROXY_HOST:
        AlertUserEventUsingName("imapUnknownHostError");
        break;
      case NS_ERROR_CONNECTION_REFUSED:
      case NS_ERROR_PROXY_CONNECTION_REFUSED:
        AlertUserEventUsingName("imapConnectionRefusedError");
        break;
      case NS_ERROR_NET_TIMEOUT:
      case NS_ERROR_NET_RESET:
      case NS_BASE_STREAM_CLOSED:
      case NS_ERROR_NET_INTERRUPT:
        // we should retry on RESET, especially for SSL...
        if ((TestFlag(IMAP_RECEIVED_GREETING) || rv == NS_ERROR_NET_RESET) &&
            m_runningUrl && !m_retryUrlOnError) {
          bool rerunningUrl;
          nsImapAction imapAction;
          m_runningUrl->GetRerunningUrl(&rerunningUrl);
          m_runningUrl->GetImapAction(&imapAction);
          // don't rerun if we already were rerunning. And don't rerun
          // online move/copies that timeout.
          if (!rerunningUrl && (rv != NS_ERROR_NET_TIMEOUT ||
                                (imapAction != nsIImapUrl::nsImapOnlineCopy &&
                                 imapAction != nsIImapUrl::nsImapOnlineMove))) {
            m_runningUrl->SetRerunningUrl(true);
            m_retryUrlOnError = true;
            break;
          }
        }
        if (rv == NS_ERROR_NET_TIMEOUT)
          AlertUserEventUsingName("imapNetTimeoutError");
        else
          AlertUserEventUsingName(TestFlag(IMAP_RECEIVED_GREETING)
                                      ? "imapServerDisconnected"
                                      : "imapServerDroppedConnection");
        break;
      default:
        // This is probably a TLS error. Usually TLS errors won't show up until
        // we do ReadNextLine() above. Since we're in the IMAP thread we can't
        // call NSSErrorsService::GetErrorClass() to determine if the error
        // should result in an non-fatal override dialog (usually certificate
        // issues) or if it's a fatal protocol error that the user must be
        // alerted to. Instead, we use some publicly-accessible macros and a
        // function to determine this.
        if (NS_ERROR_GET_MODULE(rv) == NS_ERROR_MODULE_SECURITY &&
            NS_ERROR_GET_SEVERITY(rv) == NS_ERROR_SEVERITY_ERROR) {
          // It's an error of class 21 (SSL/TLS/Security), e.g., overridable
          // SSL_ERROR_BAD_CERT_DOMAIN from security/nss/lib/ssl/sslerr.h
          // rv = 0x80000000 + 0x00450000 + 0x00150000 + 0x00002ff4 = 0x805A2ff4
          int32_t sec_error = -1 * NS_ERROR_GET_CODE(rv);  // = 0xFFFFD00C
          if (!mozilla::psm::ErrorIsOverridable(sec_error)) {
            AlertUserEventUsingName("imapTlsError");
          }

          // Stash the socket transport securityInfo on the URL so it will be
          // available in nsIUrlListener OnStopRunningUrl() callbacks to trigger
          // the override dialog or a security related error message.
          // Currently this is only used to trigger the override dialog.
          if (m_runningUrl) {
            MOZ_ASSERT(!NS_IsMainThread());
            nsCOMPtr<nsIMsgMailNewsUrl> mailNewsUrl =
                do_QueryInterface(m_runningUrl);
            if (mailNewsUrl) {
              nsCOMPtr<nsITransportSecurityInfo> securityInfo;
              GetTransportSecurityInfo(getter_AddRefs(securityInfo));
              if (securityInfo) {
                nsAutoCString logMsg("Security error - error code=");
                nsAutoString errorCodeString;
                securityInfo->GetErrorCodeString(errorCodeString);
                logMsg.Append(NS_ConvertUTF16toUTF8(errorCodeString));
                Log("CreateNewLineFromSocket", nullptr, logMsg.get());

                mailNewsUrl->SetFailedSecInfo(securityInfo);
              }
            }
          }
        }
        break;
    }

    nsAutoCString logMsg("clearing IMAP_CONNECTION_IS_OPEN - rv = ");
    logMsg.AppendInt(static_cast<uint32_t>(rv), 16);
    Log("CreateNewLineFromSocket", nullptr, logMsg.get());
    ClearFlag(IMAP_CONNECTION_IS_OPEN);
    TellThreadToDie();
  }
  Log("CreateNewLineFromSocket", nullptr, newLine);
  SetConnectionStatus(newLine && numBytesInLine
                          ? NS_OK
                          : rv);  // set > 0 if string is not null or empty
  return newLine;
}

nsresult nsImapProtocol::GetConnectionStatus() { return m_connectionStatus; }

void nsImapProtocol::SetConnectionStatus(nsresult status) {
  // Log failure at Debug level, otherwise use Verbose to avoid huge logs
  MOZ_LOG(
      IMAP, NS_SUCCEEDED(status) ? LogLevel::Verbose : LogLevel::Debug,
      ("SetConnectionStatus(0x%" PRIx32 ")", static_cast<uint32_t>(status)));
  m_connectionStatus = status;
}

void nsImapProtocol::NotifyMessageFlags(imapMessageFlagsType flags,
                                        const nsACString& keywords,
                                        nsMsgKey key, uint64_t highestModSeq) {
  if (m_imapMessageSink) {
    // if we're selecting the folder, don't need to report the flags; we've
    // already fetched them.
    if (m_imapAction != nsIImapUrl::nsImapSelectFolder)
      m_imapMessageSink->NotifyMessageFlags(flags, keywords, key,
                                            highestModSeq);
  }
}

void nsImapProtocol::NotifySearchHit(const char* hitLine) {
  nsresult rv;
  nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl =
      do_QueryInterface(m_runningUrl, &rv);
  if (m_imapMailFolderSink)
    m_imapMailFolderSink->NotifySearchHit(mailnewsUrl, hitLine);
}

void nsImapProtocol::SetMailboxDiscoveryStatus(EMailboxDiscoverStatus status) {
  ReentrantMonitorAutoEnter mon(m_dataMemberMonitor);
  m_discoveryStatus = status;
}

EMailboxDiscoverStatus nsImapProtocol::GetMailboxDiscoveryStatus() {
  ReentrantMonitorAutoEnter mon(m_dataMemberMonitor);
  return m_discoveryStatus;
}

bool nsImapProtocol::GetSubscribingNow() {
  // ***** code me *****
  return false;  // ***** for now
}

void nsImapProtocol::DiscoverMailboxSpec(nsImapMailboxSpec* adoptedBoxSpec) {
  nsImapNamespace* ns = nullptr;

  NS_ASSERTION(m_hostSessionList, "fatal null host session list");
  if (!m_hostSessionList) return;

  m_hostSessionList->GetDefaultNamespaceOfTypeForHost(GetImapServerKey(),
                                                      kPersonalNamespace, ns);
  const char* nsPrefix = ns ? ns->GetPrefix() : 0;

  if (m_specialXListMailboxes.Count() > 0) {
    nsCString strHashKey(adoptedBoxSpec->mAllocatedPathName);
    int32_t hashValue = m_specialXListMailboxes.Get(strHashKey);
    adoptedBoxSpec->mBoxFlags |= hashValue;
  }

  switch (m_hierarchyNameState) {
    case kXListing:
      if (adoptedBoxSpec->mBoxFlags &
          (kImapXListTrash | kImapAllMail | kImapInbox | kImapSent | kImapSpam |
           kImapDrafts)) {
        nsCString mailboxName(adoptedBoxSpec->mAllocatedPathName);
        m_specialXListMailboxes.InsertOrUpdate(mailboxName,
                                               adoptedBoxSpec->mBoxFlags);
        // Remember hierarchy delimiter in case this is the first time we've
        // connected to the server and we need it to be correct for the
        // two-level XLIST we send (INBOX is guaranteed to be in the first
        // response).
        if (adoptedBoxSpec->mBoxFlags & kImapInbox)
          m_runningUrl->SetOnlineSubDirSeparator(
              adoptedBoxSpec->mHierarchySeparator);
      }
      break;
    case kListingForFolderFlags: {
      // store mailbox flags from LIST for use by LSUB
      nsCString mailboxName(adoptedBoxSpec->mAllocatedPathName);
      m_standardListMailboxes.InsertOrUpdate(mailboxName,
                                             adoptedBoxSpec->mBoxFlags);
    } break;
    case kListingForCreate:
    case kNoOperationInProgress:
    case kDiscoverTrashFolderInProgress:
    case kListingForInfoAndDiscovery: {
      // standard mailbox specs are stored in m_standardListMailboxes
      // because LSUB does necessarily return all mailbox flags.
      // count should be > 0 only when we are looking at response of LSUB
      if (m_standardListMailboxes.Count() > 0) {
        int32_t hashValue = 0;
        nsCString strHashKey(adoptedBoxSpec->mAllocatedPathName);
        if (m_standardListMailboxes.Get(strHashKey, &hashValue))
          adoptedBoxSpec->mBoxFlags |= hashValue;
        else
          // if mailbox is not in hash list, then it is subscribed but does not
          // exist, so we make sure it can't be selected
          adoptedBoxSpec->mBoxFlags |= kNoselect;
      }
      if (ns &&
          nsPrefix)  // if no personal namespace, there can be no Trash folder
      {
        bool onlineTrashFolderExists = false;
        if (m_hostSessionList) {
          if (adoptedBoxSpec->mBoxFlags & (kImapTrash | kImapXListTrash)) {
            m_hostSessionList->SetOnlineTrashFolderExistsForHost(
                GetImapServerKey(), true);
            onlineTrashFolderExists = true;
          } else {
            m_hostSessionList->GetOnlineTrashFolderExistsForHost(
                GetImapServerKey(), onlineTrashFolderExists);
          }
        }

        // Don't set the Trash flag if not using the Trash model
        if (GetDeleteIsMoveToTrash() && !onlineTrashFolderExists &&
            FindInReadable(m_trashFolderPath,
                           adoptedBoxSpec->mAllocatedPathName,
                           nsCaseInsensitiveCStringComparator)) {
          bool trashExists = false;
          if (StringBeginsWith(m_trashFolderPath, "INBOX/"_ns,
                               nsCaseInsensitiveCStringComparator)) {
            nsAutoCString pathName(adoptedBoxSpec->mAllocatedPathName.get() +
                                   6);
            trashExists =
                StringBeginsWith(
                    adoptedBoxSpec->mAllocatedPathName, m_trashFolderPath,
                    nsCaseInsensitiveCStringComparator) && /* "INBOX/" */
                pathName.Equals(Substring(m_trashFolderPath, 6),
                                nsCaseInsensitiveCStringComparator);
          } else
            trashExists = adoptedBoxSpec->mAllocatedPathName.Equals(
                m_trashFolderPath, nsCaseInsensitiveCStringComparator);

          if (m_hostSessionList)
            m_hostSessionList->SetOnlineTrashFolderExistsForHost(
                GetImapServerKey(), trashExists);

          if (trashExists) adoptedBoxSpec->mBoxFlags |= kImapTrash;
        }
      }

      // Discover the folder (shuttle over to libmsg, yay)
      // Do this only if the folder name is not empty (i.e. the root)
      if (!adoptedBoxSpec->mAllocatedPathName.IsEmpty()) {
        if (m_hierarchyNameState == kListingForCreate)
          adoptedBoxSpec->mBoxFlags |= kNewlyCreatedFolder;

        if (m_imapServerSink) {
          bool newFolder;

          m_imapServerSink->PossibleImapMailbox(
              adoptedBoxSpec->mAllocatedPathName,
              adoptedBoxSpec->mHierarchySeparator, adoptedBoxSpec->mBoxFlags,
              &newFolder);
          // if it's a new folder to the server sink, setting discovery status
          // to eContinueNew will cause us to get the ACL for the new folder.
          if (newFolder) SetMailboxDiscoveryStatus(eContinueNew);

          bool useSubscription = false;

          if (m_hostSessionList)
            m_hostSessionList->GetHostIsUsingSubscription(GetImapServerKey(),
                                                          useSubscription);

          if ((GetMailboxDiscoveryStatus() != eContinue) &&
              (GetMailboxDiscoveryStatus() != eContinueNew) &&
              (GetMailboxDiscoveryStatus() != eListMyChildren)) {
            SetConnectionStatus(NS_ERROR_FAILURE);
          } else if (!adoptedBoxSpec->mAllocatedPathName.IsEmpty() &&
                     (GetMailboxDiscoveryStatus() == eListMyChildren) &&
                     (!useSubscription || GetSubscribingNow())) {
            NS_ASSERTION(false, "we should never get here anymore");
            SetMailboxDiscoveryStatus(eContinue);
          } else if (GetMailboxDiscoveryStatus() == eContinueNew) {
            if (m_hierarchyNameState == kListingForInfoAndDiscovery &&
                !adoptedBoxSpec->mAllocatedPathName.IsEmpty() &&
                !(adoptedBoxSpec->mBoxFlags & kNameSpace)) {
              // remember the info here also
              nsIMAPMailboxInfo* mb =
                  new nsIMAPMailboxInfo(adoptedBoxSpec->mAllocatedPathName,
                                        adoptedBoxSpec->mHierarchySeparator);
              m_listedMailboxList.AppendElement(mb);
            }
            SetMailboxDiscoveryStatus(eContinue);
          }
        }
      }
    } break;
    case kDeleteSubFoldersInProgress: {
      NS_ASSERTION(m_deletableChildren, "Oops .. null m_deletableChildren");
      m_deletableChildren->AppendElement(adoptedBoxSpec->mAllocatedPathName);
    } break;
    case kListingForInfoOnly: {
      // UpdateProgressWindowForUpgrade(adoptedBoxSpec->allocatedPathName);
      ProgressEventFunctionUsingNameWithString(
          "imapDiscoveringMailbox", adoptedBoxSpec->mAllocatedPathName.get());
      nsIMAPMailboxInfo* mb =
          new nsIMAPMailboxInfo(adoptedBoxSpec->mAllocatedPathName,
                                adoptedBoxSpec->mHierarchySeparator);
      m_listedMailboxList.AppendElement(mb);
    } break;
    case kDiscoveringNamespacesOnly: {
    } break;
    default:
      NS_ASSERTION(false, "we aren't supposed to be here");
      break;
  }
}

void nsImapProtocol::AlertUserEventUsingName(const char* aMessageName) {
  if (m_imapServerSink) {
    bool suppressErrorMsg = false;

    nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_runningUrl);
    if (mailnewsUrl) mailnewsUrl->GetSuppressErrorMsgs(&suppressErrorMsg);

    if (!suppressErrorMsg)
      m_imapServerSink->FEAlertWithName(aMessageName, mailnewsUrl);
  }
}

void nsImapProtocol::AlertUserEvent(const char* message) {
  if (m_imapServerSink) {
    bool suppressErrorMsg = false;

    nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_runningUrl);
    if (mailnewsUrl) mailnewsUrl->GetSuppressErrorMsgs(&suppressErrorMsg);

    if (!suppressErrorMsg)
      m_imapServerSink->FEAlert(NS_ConvertASCIItoUTF16(message), mailnewsUrl);
  }
}

void nsImapProtocol::AlertUserEventFromServer(const char* aServerEvent,
                                              bool aForIdle) {
  if (aServerEvent) {
    // If called due to BAD/NO imap IDLE response, the server sink and running
    // url are typically null when IDLE command is sent. So use the stored
    // latest values for these so that the error alert notification occurs.
    if (aForIdle && !m_imapServerSink && !m_runningUrl &&
        m_imapServerSinkLatest) {
      nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl =
          do_QueryInterface(m_runningUrlLatest);
      m_imapServerSinkLatest->FEAlertFromServer(
          nsDependentCString(aServerEvent), mailnewsUrl);
    } else if (m_imapServerSink) {
      nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_runningUrl);
      m_imapServerSink->FEAlertFromServer(nsDependentCString(aServerEvent),
                                          mailnewsUrl);
    }
  }
}

void nsImapProtocol::ResetProgressInfo() {
  m_lastProgressTime = 0;
  m_lastPercent = -1;
  m_lastProgressStringName.Truncate();
}

void nsImapProtocol::SetProgressString(uint32_t aStringIndex) {
  m_stringIndex = aStringIndex;
  MOZ_ASSERT(m_stringIndex <= IMAP_EMPTY_STRING_INDEX);
  switch (m_stringIndex) {
    case IMAP_HEADERS_STRING_INDEX:
      m_progressStringName = "imapReceivingMessageHeaders3";
      break;
    case IMAP_MESSAGES_STRING_INDEX:
      m_progressStringName = "imapFolderReceivingMessageOf3";
      break;
    case IMAP_FLAGS_STRING_INDEX:
      m_progressStringName = "imapReceivingMessageFlags3";
      break;
    case IMAP_EMPTY_STRING_INDEX:
    default:
      break;
  }
}

void nsImapProtocol::ShowProgress() {
  if (m_imapServerSink && (m_stringIndex != IMAP_EMPTY_STRING_INDEX)) {
    nsString progressString;
    const char* mailboxName = GetServerStateParser().GetSelectedMailboxName();
    nsString unicodeMailboxName;
    nsresult rv = CopyFolderNameToUTF16(nsDependentCString(mailboxName),
                                        unicodeMailboxName);
    NS_ENSURE_SUCCESS_VOID(rv);

    int32_t progressCurrentNumber = ++m_progressCurrentNumber[m_stringIndex];

    PercentProgressUpdateEvent(m_progressStringName, unicodeMailboxName,
                               progressCurrentNumber, m_progressExpectedNumber);
  }
}

void nsImapProtocol::ProgressEventFunctionUsingName(const char* aMsgName) {
  if (m_imapMailFolderSink && !m_lastProgressStringName.Equals(aMsgName)) {
    m_imapMailFolderSink->ProgressStatusString(this, aMsgName, nullptr);
    m_lastProgressStringName.Assign(aMsgName);
    // who's going to free this? Does ProgressStatusString complete
    // synchronously?
  }
}

void nsImapProtocol::ProgressEventFunctionUsingNameWithString(
    const char* aMsgName, const char* aExtraInfo) {
  if (m_imapMailFolderSink) {
    nsString unicodeStr;
    nsresult rv =
        CopyFolderNameToUTF16(nsDependentCString(aExtraInfo), unicodeStr);
    if (NS_SUCCEEDED(rv))
      m_imapMailFolderSink->ProgressStatusString(this, aMsgName,
                                                 unicodeStr.get());
  }
}

void nsImapProtocol::PercentProgressUpdateEvent(nsACString const& fmtStringName,
                                                nsAString const& mailbox,
                                                int64_t currentProgress,
                                                int64_t maxProgress) {
  int64_t nowMS = 0;
  int32_t percent = (100 * currentProgress) / maxProgress;
  if (percent == m_lastPercent)
    return;  // hasn't changed, right? So just return. Do we need to clear this
             // anywhere?

  if (percent < 100)  // always need to do 100%
  {
    nowMS = PR_IntervalToMilliseconds(PR_IntervalNow());
    if (nowMS - m_lastProgressTime < 750) return;
  }

  m_lastPercent = percent;
  m_lastProgressTime = nowMS;

  // set our max progress on the running URL
  if (m_runningUrl) {
    nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl(do_QueryInterface(m_runningUrl));
    mailnewsUrl->SetMaxProgress(maxProgress);
  }

  if (m_imapMailFolderSink) {
    m_imapMailFolderSink->PercentProgress(this, fmtStringName, mailbox,
                                          currentProgress, maxProgress);
  }
}

// imap commands issued by the parser
void nsImapProtocol::Store(const nsCString& messageList,
                           const char* messageData, bool idsAreUid) {
  // turn messageList back into key array and then back into a message id list,
  // but use the flag state to handle ranges correctly.
  nsCString messageIdList;
  nsTArray<nsMsgKey> msgKeys;
  if (idsAreUid) ParseUidString(messageList.get(), msgKeys);

  int32_t msgCountLeft = msgKeys.Length();
  uint32_t msgsHandled = 0;
  do {
    nsCString idString;

    uint32_t msgsToHandle = msgCountLeft;
    if (idsAreUid)
      AllocateImapUidString(msgKeys.Elements() + msgsHandled, msgsToHandle,
                            m_flagState, idString);  // 20 * 200
    else
      idString.Assign(messageList);

    msgsHandled += msgsToHandle;
    msgCountLeft -= msgsToHandle;

    IncrementCommandTagNumber();
    const char* formatString;
    if (idsAreUid)
      formatString = "%s uid store %s %s\015\012";
    else
      formatString = "%s store %s %s\015\012";

    // we might need to close this mailbox after this
    m_closeNeededBeforeSelect =
        GetDeleteIsMoveToTrash() && (PL_strcasestr(messageData, "\\Deleted"));

    const char* commandTag = GetServerCommandTag();
    int protocolStringSize = PL_strlen(formatString) + messageList.Length() +
                             PL_strlen(messageData) + PL_strlen(commandTag) + 1;
    char* protocolString = (char*)PR_CALLOC(protocolStringSize);

    if (protocolString) {
      PR_snprintf(protocolString,      // string to create
                  protocolStringSize,  // max size
                  formatString,        // format string
                  commandTag,          // command tag
                  idString.get(), messageData);

      nsresult rv = SendData(protocolString);
      if (NS_SUCCEEDED(rv)) {
        m_flagChangeCount++;
        ParseIMAPandCheckForNewMail(protocolString);
        if (GetServerStateParser().LastCommandSuccessful() && CheckNeeded())
          Check();
      }
      PR_Free(protocolString);
    } else
      HandleMemoryFailure();
  } while (msgCountLeft > 0 && !DeathSignalReceived());
}

void nsImapProtocol::IssueUserDefinedMsgCommand(const char* command,
                                                const char* messageList) {
  IncrementCommandTagNumber();

  const char* formatString;
  formatString = "%s uid %s %s\015\012";

  const char* commandTag = GetServerCommandTag();
  int protocolStringSize = PL_strlen(formatString) + PL_strlen(messageList) +
                           PL_strlen(command) + PL_strlen(commandTag) + 1;
  char* protocolString = (char*)PR_CALLOC(protocolStringSize);

  if (protocolString) {
    PR_snprintf(protocolString,      // string to create
                protocolStringSize,  // max size
                formatString,        // format string
                commandTag,          // command tag
                command, messageList);

    nsresult rv = SendData(protocolString);
    if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail(protocolString);
    PR_Free(protocolString);
  } else
    HandleMemoryFailure();
}

void nsImapProtocol::UidExpunge(const nsCString& messageSet) {
  IncrementCommandTagNumber();
  nsCString command(GetServerCommandTag());
  command.AppendLiteral(" uid expunge ");
  command.Append(messageSet);
  command.Append(CRLF);
  nsresult rv = SendData(command.get());
  if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail();
}

void nsImapProtocol::Expunge() {
  uint32_t aclFlags = 0;
  if (GetServerStateParser().ServerHasACLCapability() && m_imapMailFolderSink)
    m_imapMailFolderSink->GetAclFlags(&aclFlags);

  if (aclFlags && !(aclFlags & IMAP_ACL_EXPUNGE_FLAG)) return;
  ProgressEventFunctionUsingName("imapStatusExpungingMailbox");

  if (gCheckDeletedBeforeExpunge) {
    GetServerStateParser().ResetSearchResultSequence();
    Search("SEARCH DELETED", false, false);
    if (GetServerStateParser().LastCommandSuccessful()) {
      nsImapSearchResultIterator* search =
          GetServerStateParser().CreateSearchResultIterator();
      nsMsgKey key = search->GetNextMessageNumber();
      delete search;
      if (key == 0) return;  // no deleted messages to expunge (bug 235004)
    }
  }

  IncrementCommandTagNumber();
  nsAutoCString command(GetServerCommandTag());
  command.AppendLiteral(" expunge" CRLF);

  nsresult rv = SendData(command.get());
  if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail();
}

void nsImapProtocol::HandleMemoryFailure() {
  PR_CEnterMonitor(this);
  // **** jefft fix me!!!!!! ******
  // m_imapThreadIsRunning = false;
  // SetConnectionStatus(-1);
  PR_CExitMonitor(this);
}

void nsImapProtocol::HandleCurrentUrlError() {
  // This is to handle a move/copy failing, especially because the user
  // cancelled the password prompt.
  (void)m_runningUrl->GetImapAction(&m_imapAction);
  if (m_imapAction == nsIImapUrl::nsImapOfflineToOnlineMove ||
      m_imapAction == nsIImapUrl::nsImapAppendMsgFromFile ||
      m_imapAction == nsIImapUrl::nsImapAppendDraftFromFile) {
    if (m_imapMailFolderSink)
      m_imapMailFolderSink->OnlineCopyCompleted(
          this, ImapOnlineCopyStateType::kFailedCopy);
  }
}

void nsImapProtocol::StartTLS() {
  IncrementCommandTagNumber();
  nsCString tag(GetServerCommandTag());
  nsCString command(tag);

  command.AppendLiteral(" STARTTLS" CRLF);
  nsresult rv = SendData(command.get());
  bool ok = false;
  if (NS_SUCCEEDED(rv)) {
    nsCString expectOkResponse = tag + " OK "_ns;
    char* serverResponse = nullptr;
    do {
      // This reads and discards lines not starting with "<tag> OK " or
      // "<tag> BAD " and exits when when either are found. Otherwise, this
      // exits on timeout when all lines in the buffer are read causing
      // serverResponse to be set null. Usually just "<tag> OK " is present.
      serverResponse = CreateNewLineFromSocket();
      ok = serverResponse &&
           !PL_strncasecmp(serverResponse, expectOkResponse.get(),
                           expectOkResponse.Length());
      if (!ok && serverResponse) {
        // Check for possible BAD response, e.g., server not STARTTLS capable.
        nsCString expectBadResponse = tag + " BAD "_ns;
        if (!PL_strncasecmp(serverResponse, expectBadResponse.get(),
                            expectBadResponse.Length())) {
          PR_Free(serverResponse);
          break;
        }
      }
      PR_Free(serverResponse);
    } while (serverResponse && !ok);
  }
  // ok == false implies a "<tag> BAD " response or time out on socket read.
  // It could also be due to failure on SendData() above.
  GetServerStateParser().SetCommandFailed(!ok);
}

void nsImapProtocol::Capability() {
  ProgressEventFunctionUsingName("imapStatusCheckCompat");
  IncrementCommandTagNumber();
  nsCString command(GetServerCommandTag());

  command.AppendLiteral(" capability" CRLF);

  nsresult rv = SendData(command.get());
  if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail();
}

void nsImapProtocol::ID() {
  if (!gAppName[0]) return;
  IncrementCommandTagNumber();
  nsCString command(GetServerCommandTag());
  command.AppendLiteral(" ID (\"name\" \"");
  command.Append(gAppName);
  command.AppendLiteral("\" \"version\" \"");
  command.Append(gAppVersion);
  command.AppendLiteral("\")" CRLF);

  nsresult rv = SendData(command.get());
  if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail();
}

void nsImapProtocol::EnableUTF8Accept() {
  IncrementCommandTagNumber();
  nsCString command(GetServerCommandTag());
  command.AppendLiteral(" ENABLE UTF8=ACCEPT" CRLF);

  nsresult rv = SendData(command.get());
  if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail();
}

void nsImapProtocol::EnableCondStore() {
  IncrementCommandTagNumber();
  nsCString command(GetServerCommandTag());

  command.AppendLiteral(" ENABLE CONDSTORE" CRLF);

  nsresult rv = SendData(command.get());
  if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail();
}

void nsImapProtocol::StartCompressDeflate() {
  // only issue a compression request if we haven't already
  if (!TestFlag(IMAP_ISSUED_COMPRESS_REQUEST)) {
    SetFlag(IMAP_ISSUED_COMPRESS_REQUEST);
    IncrementCommandTagNumber();
    nsCString command(GetServerCommandTag());

    command.AppendLiteral(" COMPRESS DEFLATE" CRLF);

    nsresult rv = SendData(command.get());
    if (NS_SUCCEEDED(rv)) {
      ParseIMAPandCheckForNewMail();
      if (GetServerStateParser().LastCommandSuccessful()) {
        rv = BeginCompressing();
        if (NS_FAILED(rv)) {
          Log("CompressDeflate", nullptr, "failed to enable compression");
          // we can't use this connection without compression any more, so die
          ClearFlag(IMAP_CONNECTION_IS_OPEN);
          TellThreadToDie();
          SetConnectionStatus(rv);
          return;
        }
      }
    }
  }
}

nsresult nsImapProtocol::BeginCompressing() {
  // wrap the streams in compression layers that compress or decompress
  // all traffic.
  RefPtr<nsMsgCompressIStream> new_in = new nsMsgCompressIStream();
  if (!new_in) return NS_ERROR_OUT_OF_MEMORY;

  nsresult rv = new_in->InitInputStream(m_inputStream);
  NS_ENSURE_SUCCESS(rv, rv);

  m_inputStream = new_in;

  RefPtr<nsMsgCompressOStream> new_out = new nsMsgCompressOStream();
  if (!new_out) return NS_ERROR_OUT_OF_MEMORY;

  rv = new_out->InitOutputStream(m_outputStream);
  NS_ENSURE_SUCCESS(rv, rv);

  m_outputStream = new_out;
  return rv;
}

void nsImapProtocol::Language() {
  // only issue the language request if we haven't done so already...
  if (!TestFlag(IMAP_ISSUED_LANGUAGE_REQUEST)) {
    SetFlag(IMAP_ISSUED_LANGUAGE_REQUEST);
    ProgressEventFunctionUsingName("imapStatusCheckCompat");
    IncrementCommandTagNumber();
    nsCString command(GetServerCommandTag());

    // extract the desired language attribute from prefs
    nsresult rv = NS_OK;

    // we need to parse out the first language out of this comma separated
    // list.... i.e if we have en,ja we only want to send en to the server.
    if (mAcceptLanguages.get()) {
      nsAutoCString extractedLanguage;
      LossyCopyUTF16toASCII(mAcceptLanguages, extractedLanguage);
      int32_t pos = extractedLanguage.FindChar(',');
      if (pos > 0)  // we have a comma separated list of languages...
        extractedLanguage.SetLength(pos);  // truncate everything after the
                                           // first comma (including the comma)

      if (extractedLanguage.IsEmpty()) return;

      command.AppendLiteral(" LANGUAGE ");
      command.Append(extractedLanguage);
      command.Append(CRLF);

      rv = SendData(command.get());
      if (NS_SUCCEEDED(rv))
        ParseIMAPandCheckForNewMail(nullptr, true /* ignore bad or no result from the server for this command */);
    }
  }
}

void nsImapProtocol::EscapeUserNamePasswordString(const char* strToEscape,
                                                  nsCString* resultStr) {
  if (strToEscape) {
    uint32_t i = 0;
    uint32_t escapeStrlen = strlen(strToEscape);
    for (i = 0; i < escapeStrlen; i++) {
      if (strToEscape[i] == '\\' || strToEscape[i] == '\"') {
        resultStr->Append('\\');
      }
      resultStr->Append(strToEscape[i]);
    }
  }
}

void nsImapProtocol::InitPrefAuthMethods(int32_t authMethodPrefValue,
                                         nsIMsgIncomingServer* aServer) {
  // for m_prefAuthMethods, using the same flags as server capabilities.
  switch (authMethodPrefValue) {
    case nsMsgAuthMethod::none:
      m_prefAuthMethods = kHasAuthNoneCapability;
      break;
    case nsMsgAuthMethod::old:
      m_prefAuthMethods = kHasAuthOldLoginCapability;
      break;
    case nsMsgAuthMethod::passwordCleartext:
      m_prefAuthMethods = kHasAuthOldLoginCapability | kHasAuthLoginCapability |
                          kHasAuthPlainCapability;
      break;
    case nsMsgAuthMethod::passwordEncrypted:
      m_prefAuthMethods = kHasCRAMCapability;
      break;
    case nsMsgAuthMethod::NTLM:
      m_prefAuthMethods = kHasAuthNTLMCapability | kHasAuthMSNCapability;
      break;
    case nsMsgAuthMethod::GSSAPI:
      m_prefAuthMethods = kHasAuthGssApiCapability;
      break;
    case nsMsgAuthMethod::External:
      m_prefAuthMethods = kHasAuthExternalCapability;
      break;
    case nsMsgAuthMethod::secure:
      m_prefAuthMethods = kHasCRAMCapability | kHasAuthGssApiCapability |
                          kHasAuthNTLMCapability | kHasAuthMSNCapability;
      break;
    case nsMsgAuthMethod::OAuth2:
      m_prefAuthMethods = kHasXOAuth2Capability;
      break;
    default:
      NS_ASSERTION(false, "IMAP: authMethod pref invalid");
      MOZ_LOG(IMAP, LogLevel::Error,
              ("IMAP: bad pref authMethod = %d", authMethodPrefValue));
      // fall to any
      [[fallthrough]];
    case nsMsgAuthMethod::anything:
      m_prefAuthMethods = kHasAuthOldLoginCapability | kHasAuthLoginCapability |
                          kHasAuthPlainCapability | kHasCRAMCapability |
                          kHasAuthGssApiCapability | kHasAuthNTLMCapability |
                          kHasAuthMSNCapability | kHasAuthExternalCapability |
                          kHasXOAuth2Capability;
      break;
  }

  if (m_prefAuthMethods & kHasXOAuth2Capability) {
    mOAuth2Support = new mozilla::mailnews::OAuth2ThreadHelper(aServer);
    if (!mOAuth2Support || !mOAuth2Support->SupportsOAuth2()) {
      // Disable OAuth2 support if we don't have the prefs installed.
      m_prefAuthMethods &= ~kHasXOAuth2Capability;
      mOAuth2Support = nullptr;
      MOZ_LOG(IMAP, LogLevel::Warning,
              ("IMAP: no OAuth2 support for this server."));
    }
  }
}

/**
 * Changes m_currentAuthMethod to pick the best remaining one
 * which is allowed by server and prefs and not marked failed.
 * The order of preference and trying of auth methods is encoded here.
 */
nsresult nsImapProtocol::ChooseAuthMethod() {
  eIMAPCapabilityFlags serverCaps = GetServerStateParser().GetCapabilityFlag();
  eIMAPCapabilityFlags availCaps =
      serverCaps & m_prefAuthMethods & ~m_failedAuthMethods;

  MOZ_LOG(IMAP, LogLevel::Debug,
          ("IMAP auth: server caps 0x%" PRIx64 ", pref 0x%" PRIx64
           ", failed 0x%" PRIx64 ", avail caps 0x%" PRIx64,
           serverCaps, m_prefAuthMethods, m_failedAuthMethods, availCaps));
  // clang-format off
  MOZ_LOG(IMAP, LogLevel::Debug,
          ("(GSSAPI = 0x%" PRIx64 ", CRAM = 0x%" PRIx64 ", NTLM = 0x%" PRIx64
           ", MSN = 0x%" PRIx64 ", PLAIN = 0x%" PRIx64 ", LOGIN = 0x%" PRIx64
           ", old-style IMAP login = 0x%" PRIx64
           ", auth external IMAP login = 0x%" PRIx64 ", OAUTH2 = 0x%" PRIx64 ")",
           kHasAuthGssApiCapability, kHasCRAMCapability, kHasAuthNTLMCapability,
           kHasAuthMSNCapability, kHasAuthPlainCapability, kHasAuthLoginCapability,
           kHasAuthOldLoginCapability, kHasAuthExternalCapability,
           kHasXOAuth2Capability));
  // clang-format on

  if (kHasAuthExternalCapability & availCaps)
    m_currentAuthMethod = kHasAuthExternalCapability;
  else if (kHasAuthGssApiCapability & availCaps)
    m_currentAuthMethod = kHasAuthGssApiCapability;
  else if (kHasCRAMCapability & availCaps)
    m_currentAuthMethod = kHasCRAMCapability;
  else if (kHasAuthNTLMCapability & availCaps)
    m_currentAuthMethod = kHasAuthNTLMCapability;
  else if (kHasAuthMSNCapability & availCaps)
    m_currentAuthMethod = kHasAuthMSNCapability;
  else if (kHasXOAuth2Capability & availCaps)
    m_currentAuthMethod = kHasXOAuth2Capability;
  else if (kHasAuthPlainCapability & availCaps)
    m_currentAuthMethod = kHasAuthPlainCapability;
  else if (kHasAuthLoginCapability & availCaps)
    m_currentAuthMethod = kHasAuthLoginCapability;
  else if (kHasAuthOldLoginCapability & availCaps)
    m_currentAuthMethod = kHasAuthOldLoginCapability;
  else {
    MOZ_LOG(IMAP, LogLevel::Debug, ("No remaining auth method"));
    m_currentAuthMethod = kCapabilityUndefined;
    return NS_ERROR_FAILURE;
  }
  MOZ_LOG(IMAP, LogLevel::Debug,
          ("Trying auth method 0x%" PRIx64, m_currentAuthMethod));
  return NS_OK;
}

void nsImapProtocol::MarkAuthMethodAsFailed(
    eIMAPCapabilityFlags failedAuthMethod) {
  MOZ_LOG(IMAP, LogLevel::Debug,
          ("Marking auth method 0x%" PRIx64 " failed", failedAuthMethod));
  m_failedAuthMethods |= failedAuthMethod;
}

/**
 * Start over, trying all auth methods again
 */
void nsImapProtocol::ResetAuthMethods() {
  MOZ_LOG(IMAP, LogLevel::Debug, ("resetting (failed) auth methods"));
  m_currentAuthMethod = kCapabilityUndefined;
  m_failedAuthMethods = 0;
}

nsresult nsImapProtocol::SendDataParseIMAPandCheckForNewMail(
    const char* aData, const char* aCommand) {
  nsresult rv;
  bool isResend = false;
  while (true) {
    // Send authentication string (true: suppress logging the string).
    rv = SendData(aData, true);
    if (NS_FAILED(rv)) break;
    ParseIMAPandCheckForNewMail(aCommand);
    if (!GetServerStateParser().WaitingForMoreClientInput()) break;

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

  return rv;
}

nsresult nsImapProtocol::ClientID() {
  IncrementCommandTagNumber();
  nsCString command(GetServerCommandTag());
  command += " CLIENTID UUID ";
  command += m_clientId;
  command += CRLF;
  nsresult rv = SendDataParseIMAPandCheckForNewMail(command.get(), nullptr);
  NS_ENSURE_SUCCESS(rv, rv);
  if (!GetServerStateParser().LastCommandSuccessful()) {
    return NS_ERROR_FAILURE;
  }
  return NS_OK;
}

nsresult nsImapProtocol::AuthLogin(const char* userName,
                                   const nsString& aPassword,
                                   eIMAPCapabilityFlag flag) {
  ProgressEventFunctionUsingName("imapStatusSendingAuthLogin");
  IncrementCommandTagNumber();

  char* currentCommand = nullptr;
  nsresult rv;
  NS_ConvertUTF16toUTF8 password(aPassword);
  MOZ_LOG(IMAP, LogLevel::Debug,
          ("IMAP: trying auth method 0x%" PRIx64, m_currentAuthMethod));

  if (flag & kHasAuthExternalCapability) {
    char* base64UserName = PL_Base64Encode(userName, strlen(userName), nullptr);
    nsAutoCString command(GetServerCommandTag());
    command.AppendLiteral(" authenticate EXTERNAL ");
    command.Append(base64UserName);
    command.Append(CRLF);
    PR_Free(base64UserName);
    rv = SendData(command.get());
    ParseIMAPandCheckForNewMail();
    nsImapServerResponseParser& parser = GetServerStateParser();
    if (parser.LastCommandSuccessful()) return NS_OK;
    parser.SetCapabilityFlag(parser.GetCapabilityFlag() &
                             ~kHasAuthExternalCapability);
  } else if (flag & kHasCRAMCapability) {
    NS_ENSURE_TRUE(m_imapServerSink, NS_ERROR_NULL_POINTER);
    MOZ_LOG(IMAP, LogLevel::Debug, ("MD5 auth"));
    // inform the server that we want to begin a CRAM authentication
    // procedure...
    nsAutoCString command(GetServerCommandTag());
    command.AppendLiteral(" authenticate CRAM-MD5" CRLF);
    rv = SendData(command.get());
    NS_ENSURE_SUCCESS(rv, rv);
    ParseIMAPandCheckForNewMail();
    if (GetServerStateParser().LastCommandSuccessful()) {
      char* digest = nullptr;
      char* cramDigest = GetServerStateParser().fAuthChallenge;
      char* decodedChallenge =
          PL_Base64Decode(cramDigest, strlen(cramDigest), nullptr);
      rv = m_imapServerSink->CramMD5Hash(decodedChallenge, password.get(),
                                         &digest);
      PR_Free(decodedChallenge);
      NS_ENSURE_SUCCESS(rv, rv);
      NS_ENSURE_TRUE(digest, NS_ERROR_NULL_POINTER);
      // The encoded digest is the hexadecimal representation of
      // DIGEST_LENGTH characters, so it will be twice that length.
      nsAutoCStringN<2 * DIGEST_LENGTH> encodedDigest;

      for (uint32_t j = 0; j < DIGEST_LENGTH; j++) {
        char hexVal[3];
        PR_snprintf(hexVal, 3, "%.2x", 0x0ff & (unsigned short)(digest[j]));
        encodedDigest.Append(hexVal);
      }

      PR_snprintf(m_dataOutputBuf, OUTPUT_BUFFER_SIZE, "%.255s %s", userName,
                  encodedDigest.get());
      char* base64Str =
          PL_Base64Encode(m_dataOutputBuf, strlen(m_dataOutputBuf), nullptr);
      PR_snprintf(m_dataOutputBuf, OUTPUT_BUFFER_SIZE, "%s" CRLF, base64Str);
      PR_Free(base64Str);
      PR_Free(digest);
      rv = SendData(m_dataOutputBuf);
      NS_ENSURE_SUCCESS(rv, rv);
      ParseIMAPandCheckForNewMail(command.get());
    }
  }  // if CRAM response was received
  else if (flag & kHasAuthGssApiCapability) {
    MOZ_LOG(IMAP, LogLevel::Debug, ("MD5 auth"));

    // Only try GSSAPI once - if it fails, its going to be because we don't
    // have valid credentials
    // MarkAuthMethodAsFailed(kHasAuthGssApiCapability);

    // We do step1 first, so we don't try GSSAPI against a server which
    // we can't get credentials for.
    nsAutoCString response;

    nsAutoCString service("imap@");
    service.Append(m_hostName);
    rv = DoGSSAPIStep1(service, userName, response);
    NS_ENSURE_SUCCESS(rv, rv);

    nsAutoCString command(GetServerCommandTag());
    command.AppendLiteral(" authenticate GSSAPI" CRLF);
    rv = SendData(command.get());
    NS_ENSURE_SUCCESS(rv, rv);

    ParseIMAPandCheckForNewMail("AUTH GSSAPI");
    if (GetServerStateParser().LastCommandSuccessful()) {
      response += CRLF;
      rv = SendData(response.get());
      NS_ENSURE_SUCCESS(rv, rv);
      ParseIMAPandCheckForNewMail(command.get());
      nsresult gssrv = NS_OK;

      while (GetServerStateParser().LastCommandSuccessful() &&
             NS_SUCCEEDED(gssrv) && gssrv != NS_SUCCESS_AUTH_FINISHED) {
        nsCString challengeStr(GetServerStateParser().fAuthChallenge);
        gssrv = DoGSSAPIStep2(challengeStr, response);
        if (NS_SUCCEEDED(gssrv)) {
          response += CRLF;
          rv = SendData(response.get());
        } else
          rv = SendData("*" CRLF);

        NS_ENSURE_SUCCESS(rv, rv);
        ParseIMAPandCheckForNewMail(command.get());
      }
      // TODO: whether it worked or not is shown by LastCommandSuccessful(), not
      // gssrv, right?
    }
  } else if (flag & (kHasAuthNTLMCapability | kHasAuthMSNCapability)) {
    MOZ_LOG(IMAP, LogLevel::Debug, ("NTLM auth"));
    nsAutoCString command(GetServerCommandTag());
    command.Append((flag & kHasAuthNTLMCapability) ? " authenticate NTLM" CRLF
                                                   : " authenticate MSN" CRLF);
    rv = SendData(command.get());
    ParseIMAPandCheckForNewMail(
        "AUTH NTLM");  // this just waits for ntlm step 1
    if (GetServerStateParser().LastCommandSuccessful()) {
      nsAutoCString cmd;
      rv = DoNtlmStep1(nsDependentCString(userName), aPassword, cmd);
      NS_ENSURE_SUCCESS(rv, rv);
      cmd += CRLF;
      rv = SendData(cmd.get());
      NS_ENSURE_SUCCESS(rv, rv);
      ParseIMAPandCheckForNewMail(command.get());
      if (GetServerStateParser().LastCommandSuccessful()) {
        nsCString challengeStr(GetServerStateParser().fAuthChallenge);
        nsCString response;
        rv = DoNtlmStep2(challengeStr, response);
        NS_ENSURE_SUCCESS(rv, rv);
        response += CRLF;
        rv = SendData(response.get());
        ParseIMAPandCheckForNewMail(command.get());
      }
    }
  } else if (flag & kHasAuthPlainCapability) {
    MOZ_LOG(IMAP, LogLevel::Debug, ("PLAIN auth"));
    PR_snprintf(m_dataOutputBuf, OUTPUT_BUFFER_SIZE,
                "%s authenticate PLAIN" CRLF, GetServerCommandTag());
    rv = SendData(m_dataOutputBuf);
    NS_ENSURE_SUCCESS(rv, rv);
    currentCommand = PL_strdup(
        m_dataOutputBuf); /* StrAllocCopy(currentCommand, GetOutputBuffer()); */
    ParseIMAPandCheckForNewMail();
    if (GetServerStateParser().LastCommandSuccessful()) {
      // RFC 4616
      char plain_string[513];
      memset(plain_string, 0, 513);
      PR_snprintf(&plain_string[1], 256, "%.255s", userName);
      uint32_t len = std::min<uint32_t>(PL_strlen(userName), 255u) +
                     2;  // We include two <NUL> characters.
      PR_snprintf(&plain_string[len], 256, "%.255s", password.get());
      len += std::min<uint32_t>(password.Length(), 255u);
      char* base64Str = PL_Base64Encode(plain_string, len, nullptr);
      PR_snprintf(m_dataOutputBuf, OUTPUT_BUFFER_SIZE, "%s" CRLF, base64Str);
      PR_Free(base64Str);

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

    if (GetServerStateParser().LastCommandSuccessful()) {
      char* base64Str = PL_Base64Encode(
          userName, std::min<uint32_t>(PL_strlen(userName), 255u), nullptr);
      PR_snprintf(m_dataOutputBuf, OUTPUT_BUFFER_SIZE, "%s" CRLF, base64Str);
      PR_Free(base64Str);
      rv = SendData(m_dataOutputBuf, true /* suppress logging */);
      if (NS_SUCCEEDED(rv)) {
        ParseIMAPandCheckForNewMail(currentCommand);
        if (GetServerStateParser().LastCommandSuccessful()) {
          base64Str = PL_Base64Encode(
              password.get(), std::min<uint32_t>(password.Length(), 255u),
              nullptr);
          PR_snprintf(m_dataOutputBuf, OUTPUT_BUFFER_SIZE, "%s" CRLF,
                      base64Str);
          PR_Free(base64Str);
          rv = SendData(m_dataOutputBuf, true /* suppress logging */);
          if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail(currentCommand);
        }  // if last command successful
      }    // if last command successful
    }      // if last command successful
  }        // if has auth login capability
  else if (flag & kHasAuthOldLoginCapability) {
    MOZ_LOG(IMAP, LogLevel::Debug, ("old-style auth"));
    ProgressEventFunctionUsingName("imapStatusSendingLogin");
    IncrementCommandTagNumber();
    nsCString command(GetServerCommandTag());
    nsAutoCString escapedUserName;
    command.AppendLiteral(" login \"");
    EscapeUserNamePasswordString(userName, &escapedUserName);
    command.Append(escapedUserName);
    command.AppendLiteral("\" \"");

    // if the password contains a \, login will fail
    // turn foo\bar into foo\\bar
    nsAutoCString correctedPassword;
    // We're assuming old style login doesn't want UTF-8
    EscapeUserNamePasswordString(NS_LossyConvertUTF16toASCII(aPassword).get(),
                                 &correctedPassword);
    command.Append(correctedPassword);
    command.AppendLiteral("\"" CRLF);
    rv = SendData(command.get(), true /* suppress logging */);
    NS_ENSURE_SUCCESS(rv, rv);
    ParseIMAPandCheckForNewMail();
  } else if (flag & kHasXOAuth2Capability) {
    MOZ_LOG(IMAP, LogLevel::Debug, ("XOAUTH2 auth"));

    // Get the XOAuth2 base64 string.
    NS_ASSERTION(mOAuth2Support,
                 "What are we doing here without OAuth2 helper?");
    if (!mOAuth2Support) return NS_ERROR_UNEXPECTED;
    nsAutoCString base64Str;
    mOAuth2Support->GetXOAuth2String(base64Str);
    mOAuth2Support = nullptr;  // Its purpose has been served.
    if (base64Str.IsEmpty()) {
      MOZ_LOG(IMAP, LogLevel::Debug, ("OAuth2 failed"));
      return NS_ERROR_FAILURE;
    }

    // Send the data on the network.
    nsAutoCString command(GetServerCommandTag());
    command += " AUTHENTICATE XOAUTH2 ";
    command += base64Str;
    command += CRLF;

    rv = SendDataParseIMAPandCheckForNewMail(command.get(), nullptr);
  } else if (flag & kHasAuthNoneCapability) {
    // TODO What to do? "login <username>" like POP?
    return NS_ERROR_NOT_IMPLEMENTED;
  } else {
    MOZ_LOG(IMAP, LogLevel::Error, ("flags param has no auth scheme selected"));
    return NS_ERROR_ILLEGAL_VALUE;
  }

  PR_Free(currentCommand);
  NS_ENSURE_SUCCESS(rv, rv);
  return GetServerStateParser().LastCommandSuccessful() ? NS_OK
                                                        : NS_ERROR_FAILURE;
}

void nsImapProtocol::OnLSubFolders() {
  // **** use to find out whether Drafts, Sent, & Templates folder
  // exists or not even the user didn't subscribe to it
  char* mailboxName = OnCreateServerSourceFolderPathString();
  if (mailboxName) {
    ProgressEventFunctionUsingName("imapStatusLookingForMailbox");
    IncrementCommandTagNumber();
    PR_snprintf(m_dataOutputBuf, OUTPUT_BUFFER_SIZE, "%s list \"\" \"%s\"" CRLF,
                GetServerCommandTag(), mailboxName);
    nsresult rv = SendData(m_dataOutputBuf);
    if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail();
    PR_Free(mailboxName);
  } else {
    HandleMemoryFailure();
  }
}

void nsImapProtocol::OnAppendMsgFromFile() {
  nsCOMPtr<nsIFile> file;
  nsresult rv = NS_OK;
  rv = m_runningUrl->GetMsgFile(getter_AddRefs(file));
  if (NS_SUCCEEDED(rv) && file) {
    char* mailboxName = OnCreateServerSourceFolderPathString();
    if (mailboxName) {
      imapMessageFlagsType flagsToSet = 0;
      uint32_t msgFlags = 0;
      PRTime date = 0;
      nsCString keywords;
      if (m_imapMessageSink)
        m_imapMessageSink->GetCurMoveCopyMessageInfo(m_runningUrl, &date,
                                                     keywords, &msgFlags);

      if (msgFlags & nsMsgMessageFlags::Read) flagsToSet |= kImapMsgSeenFlag;
      if (msgFlags & nsMsgMessageFlags::MDNReportSent)
        flagsToSet |= kImapMsgMDNSentFlag;
      // convert msg flag label (0xE000000) to imap flag label (0x0E00)
      if (msgFlags & nsMsgMessageFlags::Labels)
        flagsToSet |= (msgFlags & nsMsgMessageFlags::Labels) >> 16;
      if (msgFlags & nsMsgMessageFlags::Marked)
        flagsToSet |= kImapMsgFlaggedFlag;
      if (msgFlags & nsMsgMessageFlags::Replied)
        flagsToSet |= kImapMsgAnsweredFlag;
      if (msgFlags & nsMsgMessageFlags::Forwarded)
        flagsToSet |= kImapMsgForwardedFlag;

      // If the message copied was a draft, flag it as such
      nsImapAction imapAction;
      rv = m_runningUrl->GetImapAction(&imapAction);
      if (NS_SUCCEEDED(rv) &&
          (imapAction == nsIImapUrl::nsImapAppendDraftFromFile))
        flagsToSet |= kImapMsgDraftFlag;
      UploadMessageFromFile(file, mailboxName, date, flagsToSet, keywords);
      PR_Free(mailboxName);
    } else {
      HandleMemoryFailure();
    }
  }
}

void nsImapProtocol::UploadMessageFromFile(nsIFile* file,
                                           const char* mailboxName, PRTime date,
                                           imapMessageFlagsType flags,
                                           nsCString& keywords) {
  if (!file || !mailboxName) return;
  IncrementCommandTagNumber();

  int64_t fileSize = 0;
  int64_t totalSize;
  uint32_t readCount;
  char* dataBuffer = nullptr;
  nsCString command(GetServerCommandTag());
  nsCString escapedName;
  CreateEscapedMailboxName(mailboxName, escapedName);
  nsresult rv;
  bool urlOk = false;
  nsCString flagString;

  nsCOMPtr<nsIInputStream> fileInputStream;

  if (!escapedName.IsEmpty()) {
    command.AppendLiteral(" append \"");
    command.Append(escapedName);
    command.Append('"');
    if (flags || keywords.Length()) {
      command.AppendLiteral(" (");

      if (flags) {
        SetupMessageFlagsString(flagString, flags,
                                GetServerStateParser().SupportsUserFlags());
        command.Append(flagString);
      }
      if (keywords.Length()) {
        if (flags) command.Append(' ');
        command.Append(keywords);
      }
      command.Append(')');
    }

    // date should never be 0, but just in case...
    if (date) {
      /* Use PR_FormatTimeUSEnglish() to format the date in US English format,
        then figure out what our local GMT offset is, and append it (since
        PR_FormatTimeUSEnglish() can't do that.) Generate four digit years as
        per RFC 1123 (superseding RFC 822.)
        */
      char szDateTime[64];
      char dateStr[100];
      PRExplodedTime exploded;
      PR_ExplodeTime(date, PR_LocalTimeParameters, &exploded);
      PR_FormatTimeUSEnglish(szDateTime, sizeof(szDateTime),
                             "%d-%b-%Y %H:%M:%S", &exploded);
      PRExplodedTime now;
      PR_ExplodeTime(PR_Now(), PR_LocalTimeParameters, &now);
      int gmtoffset =
          (now.tm_params.tp_gmt_offset + now.tm_params.tp_dst_offset) / 60;
      PR_snprintf(dateStr, sizeof(dateStr), " \"%s %c%02d%02d\"", szDateTime,
                  (gmtoffset >= 0 ? '+' : '-'),
                  ((gmtoffset >= 0 ? gmtoffset : -gmtoffset) / 60),
                  ((gmtoffset >= 0 ? gmtoffset : -gmtoffset) % 60));

      command.Append(dateStr);
    }
    if (m_allowUTF8Accept)
      command.AppendLiteral(" UTF8 (~{");
    else
      command.AppendLiteral(" {");

    dataBuffer = (char*)PR_CALLOC(COPY_BUFFER_SIZE + 1);
    if (!dataBuffer) goto done;
    rv = file->GetFileSize(&fileSize);
    NS_ASSERTION(fileSize, "got empty file in UploadMessageFromFile");
    if (NS_FAILED(rv) || !fileSize) goto done;
    rv = NS_NewLocalFileInputStream(getter_AddRefs(fileInputStream), file);
    if (NS_FAILED(rv) || !fileInputStream) goto done;
    command.AppendInt((int32_t)fileSize);

    // Set useLiteralPlus to true if server has capability LITERAL+ and
    // LITERAL+ usage is enabled in the config editor,
    // i.e., "mail.imap.use_literal_plus" = true.
    bool useLiteralPlus =
        (GetServerStateParser().GetCapabilityFlag() & kLiteralPlusCapability) &&
        gUseLiteralPlus;
    if (useLiteralPlus)
      command.AppendLiteral("+}" CRLF);
    else
      command.AppendLiteral("}" CRLF);

    rv = SendData(command.get());
    if (NS_FAILED(rv)) goto done;

    if (!useLiteralPlus) {
      ParseIMAPandCheckForNewMail();
      if (!GetServerStateParser().LastCommandSuccessful()) goto done;
    }

    totalSize = fileSize;
    readCount = 0;
    while (NS_SUCCEEDED(rv) && totalSize > 0) {
      if (DeathSignalReceived()) goto done;
      rv = fileInputStream->Read(dataBuffer, COPY_BUFFER_SIZE, &readCount);
      if (NS_SUCCEEDED(rv) && !readCount) rv = NS_ERROR_FAILURE;

      if (NS_SUCCEEDED(rv)) {
        NS_ASSERTION(readCount <= (uint32_t)totalSize,
                     "got more bytes than there should be");
        dataBuffer[readCount] = 0;
        rv = SendData(dataBuffer);
        totalSize -= readCount;
        PercentProgressUpdateEvent(""_ns, u""_ns, fileSize - totalSize,
                                   fileSize);
        if (!totalSize) {
          // The full message has been queued for sending, but the actual send
          // is just now starting and can still potentially fail. From this
          // point the progress cannot be determined, so just set the progress
          // to "indeterminate" so that the user does not see an incorrect 100%
          // complete on the progress bar while waiting for the retry dialog to
          // appear if the send should fail.
          m_lastProgressTime = 0;  // Force progress bar update
          m_lastPercent = -1;      // Force progress bar update
          PercentProgressUpdateEvent(""_ns, u""_ns, 0, -1);  // Indeterminate
        }
      }
    }  // end while appending chunks

    if (NS_SUCCEEDED(rv)) {  // complete the append
      if (m_allowUTF8Accept)
        rv = SendData(")" CRLF);
      else
        rv = SendData(CRLF);
      if (NS_FAILED(rv)) goto done;

      ParseIMAPandCheckForNewMail(command.get());
      if (!GetServerStateParser().LastCommandSuccessful()) goto done;

      // If reached, the append completed without error. No more goto's!
      // May still find problems in imap responses below so urlOk may still
      // become false.
      urlOk = true;

      nsImapAction imapAction;
      m_runningUrl->GetImapAction(&imapAction);

      if (imapAction == nsIImapUrl::nsImapAppendDraftFromFile ||
          imapAction == nsIImapUrl::nsImapAppendMsgFromFile) {
        if (GetServerStateParser().GetCapabilityFlag() & kUidplusCapability) {
          nsMsgKey newKey = GetServerStateParser().CurrentResponseUID();
          if (m_imapMailFolderSink)
            m_imapMailFolderSink->SetAppendMsgUid(newKey, m_runningUrl);

          // Courier imap server seems to have problems with recently
          // appended messages. Noop seems to clear its confusion.
          if (FolderIsSelected(mailboxName)) Noop();

          nsCString oldMsgId;
          rv = m_runningUrl->GetListOfMessageIds(oldMsgId);
          if (NS_SUCCEEDED(rv) && !oldMsgId.IsEmpty()) {
            bool idsAreUids = true;
            m_runningUrl->MessageIdsAreUids(&idsAreUids);
            Store(oldMsgId, "+FLAGS (\\Deleted)", idsAreUids);
            UidExpunge(oldMsgId);
          }
          // Only checks the last imap command in sequence above.
          if (!GetServerStateParser().LastCommandSuccessful()) urlOk = false;
        }
        // for non UIDPLUS servers this code used to check for
        // imapAction==nsIImapUrl::nsImapAppendMsgFromFile, which meant we'd get
        // into this code whenever sending a message, as well as when copying
        // messages to an imap folder from local folders or an other imap
        // server. This made sending a message slow when there was a large sent
        // folder. I don't believe this code worked anyway.
        // *** code me to search for the newly appended message
        else if (m_imapMailFolderSink &&
                 imapAction == nsIImapUrl::nsImapAppendDraftFromFile) {
          // go to selected state
          nsCString messageId;
          rv = m_imapMailFolderSink->GetMessageId(m_runningUrl, messageId);
          if (NS_SUCCEEDED(rv) && !messageId.IsEmpty()) {
            // if the appended to folder isn't selected in the connection,
            // select it.
            if (!FolderIsSelected(mailboxName))
              SelectMailbox(mailboxName);
            else
              Noop();  // See if this makes SEARCH work on the newly appended
                       // msg.

            if (GetServerStateParser().LastCommandSuccessful()) {
              command = "SEARCH UNDELETED HEADER Message-ID ";
              command.Append(messageId);

              // Clean up result sequence before issuing the cmd.
              GetServerStateParser().ResetSearchResultSequence();

              Search(command.get(), true, false);
              if (GetServerStateParser().LastCommandSuccessful()) {
                nsMsgKey newkey = nsMsgKey_None;
                nsImapSearchResultIterator* searchResult =
                    GetServerStateParser().CreateSearchResultIterator();
                newkey = searchResult->GetNextMessageNumber();
                delete searchResult;
                if (newkey != nsMsgKey_None)
                  m_imapMailFolderSink->SetAppendMsgUid(newkey, m_runningUrl);
              } else
                urlOk = false;
            } else
              urlOk = false;
          }
        }
      }
    }
  }
done:
  // If imap command fails or network goes down, make sure URL sees the failure.
  if (!urlOk) GetServerStateParser().SetCommandFailed(true);

  PR_Free(dataBuffer);
  if (fileInputStream) fileInputStream->Close();
}

// caller must free using PR_Free
char* nsImapProtocol::OnCreateServerSourceFolderPathString() {
  char* sourceMailbox = nullptr;
  char hierarchyDelimiter = 0;
  char onlineDelimiter = 0;
  m_runningUrl->GetOnlineSubDirSeparator(&hierarchyDelimiter);
  if (m_imapMailFolderSink)
    m_imapMailFolderSink->GetOnlineDelimiter(&onlineDelimiter);

  if (onlineDelimiter != kOnlineHierarchySeparatorUnknown &&
      onlineDelimiter != hierarchyDelimiter)
    m_runningUrl->SetOnlineSubDirSeparator(onlineDelimiter);

  m_runningUrl->CreateServerSourceFolderPathString(&sourceMailbox);

  return sourceMailbox;
}

// caller must free using PR_Free, safe to call from ui thread
char* nsImapProtocol::GetFolderPathString() {
  char* sourceMailbox = nullptr;
  char onlineSubDirDelimiter = 0;
  char hierarchyDelimiter = 0;
  nsCOMPtr<nsIMsgFolder> msgFolder;

  m_runningUrl->GetOnlineSubDirSeparator(&onlineSubDirDelimiter);
  nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_runningUrl);
  mailnewsUrl->GetFolder(getter_AddRefs(msgFolder));
  if (msgFolder) {
    nsCOMPtr<nsIMsgImapMailFolder> imapFolder = do_QueryInterface(msgFolder);
    if (imapFolder) {
      imapFolder->GetHierarchyDelimiter(&hierarchyDelimiter);
      if (hierarchyDelimiter != kOnlineHierarchySeparatorUnknown &&
          onlineSubDirDelimiter != hierarchyDelimiter)
        m_runningUrl->SetOnlineSubDirSeparator(hierarchyDelimiter);
    }
  }
  m_runningUrl->CreateServerSourceFolderPathString(&sourceMailbox);

  return sourceMailbox;
}

nsresult nsImapProtocol::CreateServerSourceFolderPathString(char** result) {
  NS_ENSURE_ARG(result);
  *result = OnCreateServerSourceFolderPathString();
  return (*result) ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
}

// caller must free using PR_Free
char* nsImapProtocol::OnCreateServerDestinationFolderPathString() {
  char* destinationMailbox = nullptr;
  char hierarchyDelimiter = 0;
  char onlineDelimiter = 0;
  m_runningUrl->GetOnlineSubDirSeparator(&hierarchyDelimiter);
  if (m_imapMailFolderSink)
    m_imapMailFolderSink->GetOnlineDelimiter(&onlineDelimiter);
  if (onlineDelimiter != kOnlineHierarchySeparatorUnknown &&
      onlineDelimiter != hierarchyDelimiter)
    m_runningUrl->SetOnlineSubDirSeparator(onlineDelimiter);

  m_runningUrl->CreateServerDestinationFolderPathString(&destinationMailbox);

  return destinationMailbox;
}

void nsImapProtocol::OnCreateFolder(const char* aSourceMailbox) {
  bool created = CreateMailboxRespectingSubscriptions(aSourceMailbox);
  if (created) {
    m_hierarchyNameState = kListingForCreate;
    nsCString mailboxWODelim(aSourceMailbox);
    RemoveHierarchyDelimiter(mailboxWODelim);
    List(mailboxWODelim.get(), false);
    m_hierarchyNameState = kNoOperationInProgress;
  } else
    FolderNotCreated(aSourceMailbox);
}

void nsImapProtocol::OnEnsureExistsFolder(const char* aSourceMailbox) {
  // We need to handle the following edge case where the destination server
  // wasn't authenticated when the folder name was encoded in
  // `EnsureFolderExists()'. In this case we always get MUTF-7. Here we are
  // authenticated and can rely on `m_allowUTF8Accept'. If the folder appears
  // to be MUTF-7 and we need UTF-8, we re-encode it. If it's not ASCII, it
  // must be already correct in UTF-8. And if it was ASCII to start with, it
  // doesn't matter that we MUTF-7 decode and UTF-8 re-encode.

  // `aSourceMailbox' is a path with hierarchy delimiters possibly. To determine
  // if the edge case is in effect, we only want to check the leaf node for
  // ASCII and this is only necessary when `m_allowUTF8Accept' is true.

  // `fullPath' is modified below if leaf re-encoding is necessary and it must
  // be defined here at top level so it stays in scope.
  nsAutoCString fullPath(aSourceMailbox);

  if (m_allowUTF8Accept) {
    char onlineDirSeparator = kOnlineHierarchySeparatorUnknown;
    m_runningUrl->GetOnlineSubDirSeparator(&onlineDirSeparator);

    int32_t leafStart = fullPath.RFindChar(onlineDirSeparator);
    nsAutoCString leafName;
    if (leafStart == kNotFound) {
      // This is a root level mailbox
      leafName = fullPath;
      fullPath.SetLength(0);
    } else {
      leafName = Substring(fullPath, leafStart + 1);
      fullPath.SetLength(leafStart + 1);
    }

    if (NS_IsAscii(leafName.get())) {
      MOZ_LOG(IMAP, LogLevel::Debug,
              ("re-encode leaf of mailbox %s to UTF-8", aSourceMailbox));
      nsAutoString utf16LeafName;
      CopyMUTF7toUTF16(leafName, utf16LeafName);

      // Convert UTF-16 to UTF-8 to create the folder.
      nsAutoCString utf8LeafName;
      CopyUTF16toUTF8(utf16LeafName, utf8LeafName);
      fullPath.Append(utf8LeafName);
      aSourceMailbox = fullPath.get();
      MOZ_LOG(IMAP, LogLevel::Debug,
              ("re-encoded leaf of mailbox %s to UTF-8", aSourceMailbox));
    }
  }
  List(aSourceMailbox, false);  // how to tell if that succeeded?
  // If List() produces OK tagged response and an untagged "* LIST" response
  // then the folder exists on the server. For simplicity, just look for
  // a general untagged response.
  bool folderExists = false;
  if (GetServerStateParser().LastCommandSuccessful())
    folderExists = GetServerStateParser().UntaggedResponse();

  // try converting aSourceMailbox to canonical format
  nsImapNamespace* nsForMailbox = nullptr;
  m_hostSessionList->GetNamespaceForMailboxForHost(
      GetImapServerKey(), aSourceMailbox, nsForMailbox);
  // NS_ASSERTION (nsForMailbox, "Oops .. null nsForMailbox");

  nsCString name;

  if (nsForMailbox)
    m_runningUrl->AllocateCanonicalPath(
        aSourceMailbox, nsForMailbox->GetDelimiter(), getter_Copies(name));
  else
    m_runningUrl->AllocateCanonicalPath(
        aSourceMailbox, kOnlineHierarchySeparatorUnknown, getter_Copies(name));

  // Also check that the folder has been verified to exist in server sink.
  bool verifiedExists = false;
  if (folderExists && m_imapServerSink)
    m_imapServerSink->FolderVerifiedOnline(name, &verifiedExists);

  // If folder exists on server and is verified and known to exists in server
  // sink, just subscribe the folder. Otherwise, create a new folder and
  // then subscribe and do list again to make sure it's created.
  if (folderExists && verifiedExists) {
    Subscribe(aSourceMailbox);
  } else {
    bool created = CreateMailboxRespectingSubscriptions(aSourceMailbox);
    if (created) {
      List(aSourceMailbox, false);
      // Check that we see an untagged response indicating folder now exists.
      folderExists = GetServerStateParser().UntaggedResponse();
    }
  }
  if (!GetServerStateParser().LastCommandSuccessful() || !folderExists)
    FolderNotCreated(aSourceMailbox);
}

void nsImapProtocol::OnSubscribe(const char* sourceMailbox) {
  Subscribe(sourceMailbox);
}

void nsImapProtocol::OnUnsubscribe(const char* sourceMailbox) {
  // When we try to auto-unsubscribe from \Noselect folders,
  // some servers report errors if we were already unsubscribed
  // from them.
  bool lastReportingErrors = GetServerStateParser().GetReportingErrors();
  GetServerStateParser().SetReportingErrors(false);
  Unsubscribe(sourceMailbox);
  GetServerStateParser().SetReportingErrors(lastReportingErrors);
}

void nsImapProtocol::RefreshACLForFolderIfNecessary(const char* mailboxName) {
  if (GetServerStateParser().ServerHasACLCapability()) {
    if (!m_folderNeedsACLRefreshed && m_imapMailFolderSink)
      m_imapMailFolderSink->GetFolderNeedsACLListed(&m_folderNeedsACLRefreshed);
    if (m_folderNeedsACLRefreshed) {
      RefreshACLForFolder(mailboxName);
      m_folderNeedsACLRefreshed = false;
    }
  }
}

void nsImapProtocol::RefreshACLForFolder(const char* mailboxName) {
  nsImapNamespace* ns = nullptr;
  m_hostSessionList->GetNamespaceForMailboxForHost(GetImapServerKey(),
                                                   mailboxName, ns);
  if (ns) {
    switch (ns->GetType()) {
      case kPersonalNamespace:
        // It's a personal folder, most likely.
        // I find it hard to imagine a server that supports ACL that doesn't
        // support NAMESPACE, so most likely we KNOW that this is a personal,
        // rather than the default, namespace.

        // First, clear what we have.
        ClearAllFolderRights();
        // Now, get the new one.
        GetMyRightsForFolder(mailboxName);
        if (m_imapMailFolderSink) {
          uint32_t aclFlags = 0;
          if (NS_SUCCEEDED(m_imapMailFolderSink->GetAclFlags(&aclFlags)) &&
              aclFlags & IMAP_ACL_ADMINISTER_FLAG)
            GetACLForFolder(mailboxName);
        }

        // We're all done, refresh the icon/flags for this folder
        RefreshFolderACLView(mailboxName, ns);
        break;
      default:
        // We know it's a public folder or other user's folder.
        // We only want our own rights

        // First, clear what we have
        ClearAllFolderRights();
        // Now, get the new one.
        GetMyRightsForFolder(mailboxName);
        // We're all done, refresh the icon/flags for this folder
        RefreshFolderACLView(mailboxName, ns);
        break;
    }
  } else {
    // no namespace, not even default... can this happen?
    NS_ASSERTION(false, "couldn't get namespace");
  }
}

void nsImapProtocol::RefreshFolderACLView(const char* mailboxName,
                                          nsImapNamespace* nsForMailbox) {
  nsCString canonicalMailboxName;

  if (nsForMailbox)
    m_runningUrl->AllocateCanonicalPath(mailboxName,
                                        nsForMailbox->GetDelimiter(),
                                        getter_Copies(canonicalMailboxName));
  else
    m_runningUrl->AllocateCanonicalPath(mailboxName,
                                        kOnlineHierarchySeparatorUnknown,
                                        getter_Copies(canonicalMailboxName));

  if (m_imapServerSink)
    m_imapServerSink->RefreshFolderRights(canonicalMailboxName);
}

void nsImapProtocol::GetACLForFolder(const char* mailboxName) {
  IncrementCommandTagNumber();

  nsCString command(GetServerCommandTag());
  nsCString escapedName;
  CreateEscapedMailboxName(mailboxName, escapedName);
  command.AppendLiteral(" getacl \"");
  command.Append(escapedName);
  command.AppendLiteral("\"" CRLF);

  nsresult rv = SendData(command.get());
  if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail();
}

void nsImapProtocol::OnRefreshAllACLs() {
  m_hierarchyNameState = kListingForInfoOnly;
  nsIMAPMailboxInfo* mb = NULL;

  // This will fill in the list
  List("*", true);

  int32_t total = m_listedMailboxList.Length(), count = 0;
  GetServerStateParser().SetReportingErrors(false);
  for (int32_t i = 0; i < total; i++) {
    mb = m_listedMailboxList.ElementAt(i);
    if (mb)  // paranoia
    {
      char* onlineName = nullptr;
      m_runningUrl->AllocateServerPath(
          PromiseFlatCString(mb->GetMailboxName()).get(), mb->GetDelimiter(),
          &onlineName);
      if (onlineName) {
        RefreshACLForFolder(onlineName);
        free(onlineName);
      }
      PercentProgressUpdateEvent(""_ns, u""_ns, count, total);
      delete mb;
      count++;
    }
  }
  m_listedMailboxList.Clear();

  PercentProgressUpdateEvent(""_ns, u""_ns, 100, 100);
  GetServerStateParser().SetReportingErrors(true);
  m_hierarchyNameState = kNoOperationInProgress;
}

// any state commands
void nsImapProtocol::Logout(bool shuttingDown /* = false */,
                            bool waitForResponse /* = true */) {
  if (!shuttingDown) ProgressEventFunctionUsingName("imapStatusLoggingOut");

  /******************************************************************
   * due to the undo functionality we cannot issue ImapClose when logout; there
   * is no way to do an undo if the message has been permanently expunge
   * jt - 07/12/1999

      bool closeNeeded = GetServerStateParser().GetIMAPstate() ==
          nsImapServerResponseParser::kFolderSelected;

      if (closeNeeded && GetDeleteIsMoveToTrash())
          ImapClose();
  ********************/

  IncrementCommandTagNumber();

  nsCString command(GetServerCommandTag());

  command.AppendLiteral(" logout" CRLF);

  nsresult rv = SendData(command.get());
  if (m_transport && shuttingDown)
    m_transport->SetTimeout(nsISocketTransport::TIMEOUT_READ_WRITE, 5);
  // the socket may be dead before we read the response, so drop it.
  if (NS_SUCCEEDED(rv) && waitForResponse) ParseIMAPandCheckForNewMail();
}

void nsImapProtocol::Noop() {
  // ProgressUpdateEvent("noop...");
  IncrementCommandTagNumber();
  nsCString command(GetServerCommandTag());

  command.AppendLiteral(" noop" CRLF);

  nsresult rv = SendData(command.get());
  if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail();
}

void nsImapProtocol::XServerInfo() {
  ProgressEventFunctionUsingName("imapGettingServerInfo");
  IncrementCommandTagNumber();
  nsCString command(GetServerCommandTag());

  command.AppendLiteral(
      " XSERVERINFO MANAGEACCOUNTURL MANAGELISTSURL MANAGEFILTERSURL" CRLF);

  nsresult rv = SendData(command.get());
  if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail();
}

void nsImapProtocol::Netscape() {
  ProgressEventFunctionUsingName("imapGettingServerInfo");
  IncrementCommandTagNumber();

  nsCString command(GetServerCommandTag());

  command.AppendLiteral(" netscape" CRLF);

  nsresult rv = SendData(command.get());
  if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail();
}

void nsImapProtocol::XMailboxInfo(const char* mailboxName) {
  ProgressEventFunctionUsingName("imapGettingMailboxInfo");
  IncrementCommandTagNumber();
  nsCString command(GetServerCommandTag());

  command.AppendLiteral(" XMAILBOXINFO \"");
  command.Append(mailboxName);
  command.AppendLiteral("\" MANAGEURL POSTURL" CRLF);

  nsresult rv = SendData(command.get());
  if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail();
}

void nsImapProtocol::Namespace() {
  IncrementCommandTagNumber();

  nsCString command(GetServerCommandTag());
  command.AppendLiteral(" namespace" CRLF);

  nsresult rv = SendData(command.get());
  if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail();
}

void nsImapProtocol::MailboxData() {
  IncrementCommandTagNumber();

  nsCString command(GetServerCommandTag());
  command.AppendLiteral(" mailboxdata" CRLF);

  nsresult rv = SendData(command.get());
  if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail();
}

void nsImapProtocol::GetMyRightsForFolder(const char* mailboxName) {
  IncrementCommandTagNumber();

  nsCString command(GetServerCommandTag());
  nsCString escapedName;
  CreateEscapedMailboxName(mailboxName, escapedName);

  if (MailboxIsNoSelectMailbox(escapedName.get()))
    return;  // Don't issue myrights on Noselect folder

  command.AppendLiteral(" myrights \"");
  command.Append(escapedName);
  command.AppendLiteral("\"" CRLF);

  nsresult rv = SendData(command.get());
  if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail();
}

bool nsImapProtocol::FolderIsSelected(const char* mailboxName) {
  return (GetServerStateParser().GetIMAPstate() ==
              nsImapServerResponseParser::kFolderSelected &&
          GetServerStateParser().GetSelectedMailboxName() &&
          PL_strcmp(GetServerStateParser().GetSelectedMailboxName(),
                    mailboxName) == 0);
}

void nsImapProtocol::OnStatusForFolder(const char* mailboxName) {
  bool untaggedResponse;
  // RFC 3501 says:
  // "the STATUS command SHOULD NOT be used on the currently selected mailbox",
  // so use NOOP instead if mailboxName is the selected folder on this
  // connection.
  if (FolderIsSelected(mailboxName)) {
    Noop();
    // Did untagged responses occur during the NOOP response? If so, this
    // indicates new mail or other changes in the mailbox. Handle this like an
    // IDLE response which will cause a folder update.
    if ((untaggedResponse = GetServerStateParser().UntaggedResponse()) &&
        m_imapMailFolderSinkSelected) {
      Log("OnStatusForFolder", nullptr,
          "mailbox change on selected folder during noop");
      m_imapMailFolderSinkSelected->OnNewIdleMessages();
    }
    mailboxName = nullptr;  // for new_spec below. Obtain SELECTed mailbox data.
  } else {
    // Imap connection is not in selected state or imap connection is selected
    // on a mailbox other than than the mailbox folderstatus URL is requesting
    // status for.
    untaggedResponse = true;  // STATUS always produces an untagged response
    IncrementCommandTagNumber();

    nsAutoCString command(GetServerCommandTag());
    nsCString escapedName;
    CreateEscapedMailboxName(mailboxName, escapedName);

    command.AppendLiteral(" STATUS \"");
    command.Append(escapedName);
    command.AppendLiteral("\" (UIDNEXT MESSAGES UNSEEN RECENT)" CRLF);

    int32_t prevNumMessages = GetServerStateParser().NumberOfMessages();
    nsresult rv = SendData(command.get());
    if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail();

    // Respond to possible untagged responses EXISTS and RECENT for the SELECTed
    // folder. Handle as though this were an IDLE response. Can't check for any
    // untagged as for Noop() above since STATUS always produces an untagged
    // response for the target mailbox and possibly also for the SELECTed box.
    // Of cource, this won't occur if imap connection is not in selected state.
    if (GetServerStateParser().GetIMAPstate() ==
            nsImapServerResponseParser::kFolderSelected &&
        m_imapMailFolderSinkSelected &&
        (GetServerStateParser().NumberOfRecentMessages() ||
         prevNumMessages != GetServerStateParser().NumberOfMessages())) {
      Log("OnStatusForFolder", nullptr,
          "new mail on selected folder during status");
      m_imapMailFolderSinkSelected->OnNewIdleMessages();
    }
    MOZ_ASSERT(m_imapMailFolderSink != m_imapMailFolderSinkSelected);
  }

  // Do this to ensure autosync detects changes in server counts and thus
  // triggers a full body fetch for when NOOP or STATUS is sent above.
  // But if NOOP didn't produce an untagged response, no need to do this.
  // Note: For SELECTed noop() above, "folder sink" and "folder sink selected"
  // both reference the same folder but are not always equal. So OK to use
  // m_imapMailFolderSink below since it is correct for NOOP and STATUS cases.
  if (untaggedResponse && GetServerStateParser().LastCommandSuccessful()) {
    RefPtr<nsImapMailboxSpec> new_spec =
        GetServerStateParser().CreateCurrentMailboxSpec(mailboxName);
    if (new_spec && m_imapMailFolderSink)
      m_imapMailFolderSink->UpdateImapMailboxStatus(this, new_spec);
  }
}

void nsImapProtocol::OnListFolder(const char* aSourceMailbox, bool aBool) {
  List(aSourceMailbox, aBool);
}

// Returns true if the mailbox is a NoSelect mailbox.
// If we don't know about it, returns false.
bool nsImapProtocol::MailboxIsNoSelectMailbox(const char* mailboxName) {
  bool rv = false;

  nsImapNamespace* nsForMailbox = nullptr;
  m_hostSessionList->GetNamespaceForMailboxForHost(GetImapServerKey(),
                                                   mailboxName, nsForMailbox);
  // NS_ASSERTION (nsForMailbox, "Oops .. null nsForMailbox");

  nsCString name;

  if (nsForMailbox)
    m_runningUrl->AllocateCanonicalPath(
        mailboxName, nsForMailbox->GetDelimiter(), getter_Copies(name));
  else
    m_runningUrl->AllocateCanonicalPath(
        mailboxName, kOnlineHierarchySeparatorUnknown, getter_Copies(name));

  if (name.IsEmpty()) return false;

  NS_ASSERTION(m_imapServerSink,
               "unexpected, no imap server sink, see bug #194335");
  if (m_imapServerSink) m_imapServerSink->FolderIsNoSelect(name, &rv);
  return rv;
}

nsresult nsImapProtocol::SetFolderAdminUrl(const char* mailboxName) {
  nsresult rv =
      NS_ERROR_NULL_POINTER;  // if m_imapServerSink is null, rv will be this.

  nsImapNamespace* nsForMailbox = nullptr;
  m_hostSessionList->GetNamespaceForMailboxForHost(GetImapServerKey(),
                                                   mailboxName, nsForMailbox);

  nsCString name;

  if (nsForMailbox)
    m_runningUrl->AllocateCanonicalPath(
        mailboxName, nsForMailbox->GetDelimiter(), getter_Copies(name));
  else
    m_runningUrl->AllocateCanonicalPath(
        mailboxName, kOnlineHierarchySeparatorUnknown, getter_Copies(name));

  if (m_imapServerSink)
    rv = m_imapServerSink->SetFolderAdminURL(
        name, nsDependentCString(GetServerStateParser().GetManageFolderUrl()));
  return rv;
}
// returns true is the delete succeeded (regardless of subscription changes)
bool nsImapProtocol::DeleteMailboxRespectingSubscriptions(
    const char* mailboxName) {
  bool rv = true;
  if (!MailboxIsNoSelectMailbox(mailboxName)) {
    // Only try to delete it if it really exists
    DeleteMailbox(mailboxName);
    rv = GetServerStateParser().LastCommandSuccessful();
  }

  // We can unsubscribe even if the mailbox doesn't exist.
  if (rv && m_autoUnsubscribe)  // auto-unsubscribe is on
  {
    bool reportingErrors = GetServerStateParser().GetReportingErrors();
    GetServerStateParser().SetReportingErrors(false);
    Unsubscribe(mailboxName);
    GetServerStateParser().SetReportingErrors(reportingErrors);
  }
  return (rv);
}

// returns true is the rename succeeded (regardless of subscription changes)
// reallyRename tells us if we should really do the rename (true) or if we
// should just move subscriptions (false)
bool nsImapProtocol::RenameMailboxRespectingSubscriptions(
    const char* existingName, const char* newName, bool reallyRename) {
  bool rv = true;
  if (reallyRename && !MailboxIsNoSelectMailbox(existingName)) {
    RenameMailbox(existingName, newName);
    rv = GetServerStateParser().LastCommandSuccessful();
  }

  if (rv) {
    if (m_autoSubscribe)  // if auto-subscribe is on
    {
      bool reportingErrors = GetServerStateParser().GetReportingErrors();
      GetServerStateParser().SetReportingErrors(false);
      Subscribe(newName);
      GetServerStateParser().SetReportingErrors(reportingErrors);
    }
    if (m_autoUnsubscribe)  // if auto-unsubscribe is on
    {
      bool reportingErrors = GetServerStateParser().GetReportingErrors();
      GetServerStateParser().SetReportingErrors(false);
      Unsubscribe(existingName);
      GetServerStateParser().SetReportingErrors(reportingErrors);
    }
  }
  return (rv);
}

bool nsImapProtocol::RenameHierarchyByHand(const char* oldParentMailboxName,
                                           const char* newParentMailboxName) {
  bool renameSucceeded = true;
  char onlineDirSeparator = kOnlineHierarchySeparatorUnknown;
  m_deletableChildren = new nsTArray<nsCString>();

  bool nonHierarchicalRename =
      ((GetServerStateParser().GetCapabilityFlag() & kNoHierarchyRename) ||
       MailboxIsNoSelectMailbox(oldParentMailboxName));

  m_hierarchyNameState = kDeleteSubFoldersInProgress;
  nsImapNamespace* ns = nullptr;
  m_hostSessionList->GetNamespaceForMailboxForHost(GetImapServerKey(),
                                                   oldParentMailboxName,
                                                   ns);  // for delimiter
  if (!ns) {
    if (!PL_strcasecmp(oldParentMailboxName, "INBOX"))
      m_hostSessionList->GetDefaultNamespaceOfTypeForHost(
          GetImapServerKey(), kPersonalNamespace, ns);
  }
  if (ns) {
    nsCString pattern(oldParentMailboxName);
    pattern += ns->GetDelimiter();
    pattern += "*";
    bool isUsingSubscription = false;
    m_hostSessionList->GetHostIsUsingSubscription(GetImapServerKey(),
                                                  isUsingSubscription);

    if (isUsingSubscription)
      Lsub(pattern.get(), false);
    else
      List(pattern.get(), false);
  }
  m_hierarchyNameState = kNoOperationInProgress;

  if (GetServerStateParser().LastCommandSuccessful())
    renameSucceeded =  // rename this, and move subscriptions
        RenameMailboxRespectingSubscriptions(oldParentMailboxName,
                                             newParentMailboxName, true);

  size_t numberToDelete = m_deletableChildren->Length();

  for (size_t childIndex = 0; (childIndex < numberToDelete) && renameSucceeded;
       childIndex++) {
    nsCString name = m_deletableChildren->ElementAt(childIndex);
    char* serverName = nullptr;
    m_runningUrl->AllocateServerPath(name.get(), onlineDirSeparator,
                                     &serverName);
    if (!serverName) {
      renameSucceeded = false;
      break;
    }
    char* currentName = serverName;

    // calculate the new name and do the rename
    nsCString newChildName(newParentMailboxName);
    newChildName += (currentName + PL_strlen(oldParentMailboxName));
    // Pass in 'nonHierarchicalRename' to determine if we should really
    // rename, or just move subscriptions.
    renameSucceeded = RenameMailboxRespectingSubscriptions(
        currentName, newChildName.get(), nonHierarchicalRename);
    PR_FREEIF(currentName);
  }

  delete m_deletableChildren;
  m_deletableChildren = nullptr;

  return renameSucceeded;
}

bool nsImapProtocol::DeleteSubFolders(const char* selectedMailbox,
                                      bool& aDeleteSelf) {
  bool deleteSucceeded = true;
  m_deletableChildren = new nsTArray<nsCString>;

  bool folderDeleted = false;

  m_hierarchyNameState = kDeleteSubFoldersInProgress;
  nsCString pattern(selectedMailbox);
  char onlineDirSeparator = kOnlineHierarchySeparatorUnknown;
  m_runningUrl->GetOnlineSubDirSeparator(&onlineDirSeparator);
  pattern.Append(onlineDirSeparator);
  pattern.Append('*');

  if (!pattern.IsEmpty()) {
    List(pattern.get(), false);
  }
  m_hierarchyNameState = kNoOperationInProgress;

  // this should be a short list so perform a sequential search for the
  // longest name mailbox.  Deleting the longest first will hopefully
  // prevent the server from having problems about deleting parents
  // ** jt - why? I don't understand this.
  size_t numberToDelete = m_deletableChildren->Length();
  size_t outerIndex, innerIndex;

  // intelligently decide if myself(either plain format or following the
  // dir-separator) is in the sub-folder list
  bool folderInSubfolderList = false;  // For Performance
  char* selectedMailboxDir = nullptr;
  {
    int32_t length = strlen(selectedMailbox);
    selectedMailboxDir = (char*)PR_MALLOC(length + 2);
    if (selectedMailboxDir)  // only do the intelligent test if there is
                             // enough memory
    {
      strcpy(selectedMailboxDir, selectedMailbox);
      selectedMailboxDir[length] = onlineDirSeparator;
      selectedMailboxDir[length + 1] = '\0';
      size_t i;
      for (i = 0; i < numberToDelete && !folderInSubfolderList; i++) {
        const char* currentName = m_deletableChildren->ElementAt(i).get();
        if (!strcmp(currentName, selectedMailbox) ||
            !strcmp(currentName, selectedMailboxDir))
          folderInSubfolderList = true;
      }
    }
  }

  deleteSucceeded = GetServerStateParser().LastCommandSuccessful();
  for (outerIndex = 0; (outerIndex < numberToDelete) && deleteSucceeded;
       outerIndex++) {
    char* longestName = nullptr;
    size_t longestIndex = 0;  // fix bogus warning by initializing
    for (innerIndex = 0; innerIndex < m_deletableChildren->Length();
         innerIndex++) {
      const char* currentName =
          m_deletableChildren->ElementAt(innerIndex).get();
      if (!longestName || strlen(longestName) < strlen(currentName)) {
        longestName = (char*)currentName;
        longestIndex = innerIndex;
      }
    }
    if (longestName) {
      char* serverName = nullptr;
      m_runningUrl->AllocateServerPath(longestName, onlineDirSeparator,
                                       &serverName);
      m_deletableChildren->RemoveElementAt(longestIndex);
      longestName = serverName;
    }

    // some imap servers include the selectedMailbox in the list of
    // subfolders of the selectedMailbox.  Check for this so we don't
    // delete the selectedMailbox (usually the trash and doing an
    // empty trash)
    // The Cyrus imap server ignores the "INBOX.Trash" constraining
    // string passed to the list command.  Be defensive and make sure
    // we only delete children of the trash
    if (longestName && strcmp(selectedMailbox, longestName) &&
        !strncmp(selectedMailbox, longestName, strlen(selectedMailbox))) {
      if (selectedMailboxDir &&
          !strcmp(selectedMailboxDir, longestName))  // just myself
      {
        if (aDeleteSelf) {
          bool deleted = DeleteMailboxRespectingSubscriptions(longestName);
          if (deleted) FolderDeleted(longestName);
          folderDeleted = deleted;
          deleteSucceeded = deleted;
        }
      } else {
        if (m_imapServerSink)
          m_imapServerSink->ResetServerConnection(
              nsDependentCString(longestName));
        bool deleted = false;
        if (folderInSubfolderList)  // for performance
        {
          nsTArray<nsCString>* pDeletableChildren = m_deletableChildren;
          m_deletableChildren = nullptr;
          bool folderDeleted = true;
          deleted = DeleteSubFolders(longestName, folderDeleted);
          // longestName may have subfolder list including itself
          if (!folderDeleted) {
            if (deleted)
              deleted = DeleteMailboxRespectingSubscriptions(longestName);
            if (deleted) FolderDeleted(longestName);
          }
          m_deletableChildren = pDeletableChildren;
        } else {
          deleted = DeleteMailboxRespectingSubscriptions(longestName);
          if (deleted) FolderDeleted(longestName);
        }
        deleteSucceeded = deleted;
      }
    }
    PR_FREEIF(longestName);
  }

  aDeleteSelf = folderDeleted;  // feedback if myself is deleted
  PR_Free(selectedMailboxDir);

  delete m_deletableChildren;
  m_deletableChildren = nullptr;

  return deleteSucceeded;
}

void nsImapProtocol::FolderDeleted(const char* mailboxName) {
  char onlineDelimiter = kOnlineHierarchySeparatorUnknown;
  nsCString orphanedMailboxName;

  if (mailboxName) {
    m_runningUrl->AllocateCanonicalPath(mailboxName, onlineDelimiter,
                                        getter_Copies(orphanedMailboxName));
    if (m_imapServerSink)
      m_imapServerSink->OnlineFolderDelete(orphanedMailboxName);
  }
}

void nsImapProtocol::FolderNotCreated(const char* folderName) {
  if (folderName && m_imapServerSink)
    m_imapServerSink->OnlineFolderCreateFailed(nsDependentCString(folderName));
}

void nsImapProtocol::FolderRenamed(const char* oldName, const char* newName) {
  char onlineDelimiter = kOnlineHierarchySeparatorUnknown;

  if ((m_hierarchyNameState == kNoOperationInProgress) ||
      (m_hierarchyNameState == kListingForInfoAndDiscovery))

  {
    nsCString canonicalOldName, canonicalNewName;
    m_runningUrl->AllocateCanonicalPath(oldName, onlineDelimiter,
                                        getter_Copies(canonicalOldName));
    m_runningUrl->AllocateCanonicalPath(newName, onlineDelimiter,
                                        getter_Copies(canonicalNewName));
    AutoProxyReleaseMsgWindow msgWindow;
    GetMsgWindow(getter_AddRefs(msgWindow));
    m_imapServerSink->OnlineFolderRename(msgWindow, canonicalOldName,
                                         canonicalNewName);
  }
}

void nsImapProtocol::OnDeleteFolder(const char* sourceMailbox) {
  // intelligently delete the folder
  bool folderDeleted = true;
  bool deleted = DeleteSubFolders(sourceMailbox, folderDeleted);
  if (!folderDeleted) {
    if (deleted) deleted = DeleteMailboxRespectingSubscriptions(sourceMailbox);
    if (deleted) FolderDeleted(sourceMailbox);
  }
}

void nsImapProtocol::RemoveMsgsAndExpunge() {
  uint32_t numberOfMessages = GetServerStateParser().NumberOfMessages();
  if (numberOfMessages) {
    // Remove all msgs and expunge the folder (ie, compact it).
    Store("1:*"_ns, "+FLAGS.SILENT (\\Deleted)",
          false);  // use sequence #'s
    if (GetServerStateParser().LastCommandSuccessful()) Expunge();
  }
}

void nsImapProtocol::DeleteFolderAndMsgs(const char* sourceMailbox) {
  RemoveMsgsAndExpunge();
  if (GetServerStateParser().LastCommandSuccessful()) {
    // All msgs are deleted successfully - let's remove the folder itself.
    bool reportingErrors = GetServerStateParser().GetReportingErrors();
    GetServerStateParser().SetReportingErrors(false);
    OnDeleteFolder(sourceMailbox);
    GetServerStateParser().SetReportingErrors(reportingErrors);
  }
}

void nsImapProtocol::OnRenameFolder(const char* sourceMailbox) {
  char* destinationMailbox = OnCreateServerDestinationFolderPathString();

  if (destinationMailbox) {
    bool renamed = RenameHierarchyByHand(sourceMailbox, destinationMailbox);
    if (renamed) FolderRenamed(sourceMailbox, destinationMailbox);

    // Cause a LIST and re-discovery when slash and/or ^ are escaped. Also
    // needed when folder renamed to non-ASCII UTF-8 when UTF8=ACCEPT in
    // effect.
    m_hierarchyNameState = kListingForCreate;
    nsCString mailboxWODelim(destinationMailbox);
    RemoveHierarchyDelimiter(mailboxWODelim);
    List(mailboxWODelim.get(), false);
    m_hierarchyNameState = kNoOperationInProgress;

    PR_Free(destinationMailbox);
  } else
    HandleMemoryFailure();
}

void nsImapProtocol::OnMoveFolderHierarchy(const char* sourceMailbox) {
  char* destinationMailbox = OnCreateServerDestinationFolderPathString();

  if (destinationMailbox) {
    nsCString newBoxName;
    newBoxName.Adopt(destinationMailbox);

    char onlineDirSeparator = kOnlineHierarchySeparatorUnknown;
    m_runningUrl->GetOnlineSubDirSeparator(&onlineDirSeparator);

    nsCString oldBoxName(sourceMailbox);
    int32_t leafStart = oldBoxName.RFindChar(onlineDirSeparator);
    nsCString leafName;

    if (-1 == leafStart)
      leafName = oldBoxName;  // this is a root level box
    else
      leafName = Substring(oldBoxName, leafStart + 1);

    if (!newBoxName.IsEmpty()) newBoxName.Append(onlineDirSeparator);
    newBoxName.Append(leafName);
    bool renamed = RenameHierarchyByHand(sourceMailbox, newBoxName.get());
    if (renamed) FolderRenamed(sourceMailbox, newBoxName.get());
  } else
    HandleMemoryFailure();
}

// This is called to do mailbox discovery if discovery not already complete
// for the "host" (i.e., server or account). Discovery still only occurs if
// the imap action is appropriate and if discovery is not in progress due to
// a running "discoverallboxes" URL.
void nsImapProtocol::FindMailboxesIfNecessary() {
  // biff should not discover mailboxes
  nsImapAction imapAction;
  (void)m_runningUrl->GetImapAction(&imapAction);
  if ((imapAction != nsIImapUrl::nsImapBiff) &&
      (imapAction != nsIImapUrl::nsImapVerifylogon) &&
      (imapAction != nsIImapUrl::nsImapDiscoverAllBoxesUrl) &&
      (imapAction != nsIImapUrl::nsImapUpgradeToSubscription) &&
      !GetSubscribingNow()) {
    // If discovery in progress, don't kick-off another discovery.
    bool discoveryInProgress = false;
    m_hostSessionList->GetDiscoveryForHostInProgress(GetImapServerKey(),
                                                     discoveryInProgress);
    if (!discoveryInProgress) {
      m_hostSessionList->SetDiscoveryForHostInProgress(GetImapServerKey(),
                                                       true);
      DiscoverMailboxList();
    }
  }
}

void nsImapProtocol::DiscoverAllAndSubscribedBoxes() {
  // used for subscribe pane
  // iterate through all namespaces
  uint32_t count = 0;
  m_hostSessionList->GetNumberOfNamespacesForHost(GetImapServerKey(), count);

  for (uint32_t i = 0; i < count; i++) {
    nsImapNamespace* ns = nullptr;
    m_hostSessionList->GetNamespaceNumberForHost(GetImapServerKey(), i, ns);
    if (!ns) {
      continue;
    }
    if ((gHideOtherUsersFromList && (ns->GetType() != kOtherUsersNamespace)) ||
        !gHideOtherUsersFromList) {
      const char* prefix = ns->GetPrefix();
      if (prefix) {
        nsAutoCString inboxNameWithDelim("INBOX");
        inboxNameWithDelim.Append(ns->GetDelimiter());

        // Only do it for non-empty namespace prefixes.
        if (!gHideUnusedNamespaces && *prefix &&
            PL_strcasecmp(prefix, inboxNameWithDelim.get())) {
          // Explicitly discover each Namespace, just so they're
          // there in the subscribe UI
          RefPtr<nsImapMailboxSpec> boxSpec = new nsImapMailboxSpec;
          boxSpec->mFolderSelected = false;
          boxSpec->mHostName.Assign(GetImapHostName());
          boxSpec->mConnection = this;
          boxSpec->mFlagState = nullptr;
          boxSpec->mDiscoveredFromLsub = true;
          boxSpec->mOnlineVerified = true;
          boxSpec->mBoxFlags = kNoselect;
          boxSpec->mHierarchySeparator = ns->GetDelimiter();

          m_runningUrl->AllocateCanonicalPath(
              ns->GetPrefix(), ns->GetDelimiter(),
              getter_Copies(boxSpec->mAllocatedPathName));
          boxSpec->mNamespaceForFolder = ns;
          boxSpec->mBoxFlags |= kNameSpace;

          switch (ns->GetType()) {
            case kPersonalNamespace:
              boxSpec->mBoxFlags |= kPersonalMailbox;
              break;
            case kPublicNamespace:
              boxSpec->mBoxFlags |= kPublicMailbox;
              break;
            case kOtherUsersNamespace:
              boxSpec->mBoxFlags |= kOtherUsersMailbox;
              break;
            default:  // (kUnknownNamespace)
              break;
          }

          DiscoverMailboxSpec(boxSpec);
        }

        nsAutoCString allPattern(prefix);
        allPattern += '*';

        if (!m_imapServerSink) return;

        m_imapServerSink->SetServerDoingLsub(true);
        Lsub(allPattern.get(), true);  // LSUB all the subscribed

        m_imapServerSink->SetServerDoingLsub(false);
        List(allPattern.get(), true);  // LIST all folders
      }
    }
  }
}

// DiscoverMailboxList() is used to actually do the discovery of folders
// for a host.  This is used both when we initially start up (and re-sync)
// and also when the user manually requests a re-sync, by collapsing and
// expanding a host in the folder pane.  This is not used for the subscribe
// pane.
// DiscoverMailboxList() also gets the ACLs for each newly discovered folder
void nsImapProtocol::DiscoverMailboxList() {
  bool usingSubscription = false;

  m_hostSessionList->GetHostIsUsingSubscription(GetImapServerKey(),
                                                usingSubscription);
  // Pretend that the Trash folder doesn't exist, so we will rediscover it if we
  // need to.
  m_hostSessionList->SetOnlineTrashFolderExistsForHost(GetImapServerKey(),
                                                       false);

  // should we check a pref here, to be able to turn off XList?
  bool hasXLIST =
      GetServerStateParser().GetCapabilityFlag() & kHasXListCapability;
  if (hasXLIST && usingSubscription) {
    m_hierarchyNameState = kXListing;
    nsAutoCString pattern("%");
    List("%", true, true);
    // We list the first and second levels since special folders are unlikely
    // to be more than 2 levels deep.
    char separator = 0;
    m_runningUrl->GetOnlineSubDirSeparator(&separator);
    pattern.Append(separator);
    pattern += '%';
    List(pattern.get(), true, true);
  }

  SetMailboxDiscoveryStatus(eContinue);
  if (GetServerStateParser().ServerHasACLCapability())
    m_hierarchyNameState = kListingForInfoAndDiscovery;
  else
    m_hierarchyNameState = kNoOperationInProgress;

  // iterate through all namespaces and LSUB them.
  uint32_t count = 0;
  m_hostSessionList->GetNumberOfNamespacesForHost(GetImapServerKey(), count);
  for (uint32_t i = 0; i < count; i++) {
    nsImapNamespace* ns = nullptr;
    m_hostSessionList->GetNamespaceNumberForHost(GetImapServerKey(), i, ns);
    if (ns) {
      const char* prefix = ns->GetPrefix();
      if (prefix) {
        nsAutoCString inboxNameWithDelim("INBOX");
        inboxNameWithDelim.Append(ns->GetDelimiter());

        // static bool gHideUnusedNamespaces = true;
        // mscott -> WARNING!!! i where are we going to get this
        // global variable for unused name spaces from???
        // dmb - we should get this from a per-host preference,
        // I'd say. But for now, just make it true.
        // Only do it for non-empty namespace prefixes, and for non-INBOX prefix
        if (!gHideUnusedNamespaces && *prefix &&
            PL_strcasecmp(prefix, inboxNameWithDelim.get())) {
          // Explicitly discover each Namespace, so that we can
          // create subfolders of them,
          RefPtr<nsImapMailboxSpec> boxSpec = new nsImapMailboxSpec;
          boxSpec->mFolderSelected = false;
          boxSpec->mHostName = GetImapHostName();
          boxSpec->mConnection = this;
          boxSpec->mFlagState = nullptr;
          boxSpec->mDiscoveredFromLsub = true;
          boxSpec->mOnlineVerified = true;
          boxSpec->mBoxFlags = kNoselect;
          boxSpec->mHierarchySeparator = ns->GetDelimiter();
          // Until |AllocateCanonicalPath()| gets updated:
          m_runningUrl->AllocateCanonicalPath(
              ns->GetPrefix(), ns->GetDelimiter(),
              getter_Copies(boxSpec->mAllocatedPathName));
          boxSpec->mNamespaceForFolder = ns;
          boxSpec->mBoxFlags |= kNameSpace;

          switch (ns->GetType()) {
            case kPersonalNamespace:
              boxSpec->mBoxFlags |= kPersonalMailbox;
              break;
            case kPublicNamespace:
              boxSpec->mBoxFlags |= kPublicMailbox;
              break;
            case kOtherUsersNamespace:
              boxSpec->mBoxFlags |= kOtherUsersMailbox;
              break;
            default:  // (kUnknownNamespace)
              break;
          }

          DiscoverMailboxSpec(boxSpec);
        }

        // now do the folders within this namespace
        nsCString pattern;
        nsCString pattern2;
        if (usingSubscription) {
          pattern.Append(prefix);
          pattern.Append('*');
        } else {
          pattern.Append(prefix);
          pattern.Append('%');  // mscott just need one percent right?
          // pattern = PR_smprintf("%s%%", prefix);
          char delimiter = ns->GetDelimiter();
          if (delimiter) {
            // delimiter might be NIL, in which case there's no hierarchy anyway
            pattern2 = prefix;
            pattern2 += "%";
            pattern2 += delimiter;
            pattern2 += "%";
            // pattern2 = PR_smprintf("%s%%%c%%", prefix, delimiter);
          }
        }
        // Note: It is important to make sure we are respecting the
        // server_sub_directory preference when calling List and Lsub (2nd arg =
        // true), otherwise we end up with performance issues or even crashes
        // when connecting to servers that expose the users entire home
        // directory (like UW-IMAP).
        if (usingSubscription) {  // && !GetSubscribingNow())  should never get
                                  // here from subscribe pane
          if (GetServerStateParser().GetCapabilityFlag() &
              kHasListExtendedCapability)
            Lsub(pattern.get(), true);  // do LIST (SUBSCRIBED)
          else {
            // store mailbox flags from LIST
            EMailboxHierarchyNameState currentState = m_hierarchyNameState;
            m_hierarchyNameState = kListingForFolderFlags;
            List(pattern.get(), true);
            m_hierarchyNameState = currentState;
            // then do LSUB using stored flags
            Lsub(pattern.get(), true);
            m_standardListMailboxes.Clear();
          }
        } else {
          List(pattern.get(), true, hasXLIST);
          List(pattern2.get(), true, hasXLIST);
        }
      }
    }
  }

  // explicitly LIST the INBOX if (a) we're not using subscription, or (b) we
  // are using subscription and the user wants us to always show the INBOX.
  bool listInboxForHost = false;
  m_hostSessionList->GetShouldAlwaysListInboxForHost(GetImapServerKey(),
                                                     listInboxForHost);
  if (!usingSubscription || listInboxForHost) List("INBOX", true);

  m_hierarchyNameState = kNoOperationInProgress;

  MailboxDiscoveryFinished();

  // Get the ACLs for newly discovered folders
  if (GetServerStateParser().ServerHasACLCapability()) {
    int32_t total = m_listedMailboxList.Length(), cnt = 0;
    // Let's not turn this off here, since we don't turn it on after
    // GetServerStateParser().SetReportingErrors(false);
    if (total) {
      ProgressEventFunctionUsingName("imapGettingACLForFolder");
      nsIMAPMailboxInfo* mb = nullptr;
      do {
        if (m_listedMailboxList.Length() == 0) break;

        mb = m_listedMailboxList[0];  // get top element
        m_listedMailboxList.RemoveElementAt(
            0);  // XP_ListRemoveTopObject(fListedMailboxList);
        if (mb) {
          if (FolderNeedsACLInitialized(
                  PromiseFlatCString(mb->GetMailboxName()).get())) {
            char* onlineName = nullptr;
            m_runningUrl->AllocateServerPath(
                PromiseFlatCString(mb->GetMailboxName()).get(),
                mb->GetDelimiter(), &onlineName);
            if (onlineName) {
              RefreshACLForFolder(onlineName);
              PR_Free(onlineName);
            }
          }
          PercentProgressUpdateEvent(""_ns, u""_ns, cnt, total);
          delete mb;  // this is the last time we're using the list, so delete
                      // the entries here
          cnt++;
        }
      } while (mb && !DeathSignalReceived());
    }
  }
}

bool nsImapProtocol::FolderNeedsACLInitialized(const char* folderName) {
  bool rv = false;
  m_imapServerSink->FolderNeedsACLInitialized(nsDependentCString(folderName),
                                              &rv);
  return rv;
}

void nsImapProtocol::MailboxDiscoveryFinished() {
  if (!DeathSignalReceived() && !GetSubscribingNow() &&
      ((m_hierarchyNameState == kNoOperationInProgress) ||
       (m_hierarchyNameState == kListingForInfoAndDiscovery))) {
    nsImapNamespace* ns = nullptr;
    m_hostSessionList->GetDefaultNamespaceOfTypeForHost(GetImapServerKey(),
                                                        kPersonalNamespace, ns);
    const char* personalDir = ns ? ns->GetPrefix() : 0;

    bool trashFolderExists = false;
    bool usingSubscription = false;
    m_hostSessionList->GetOnlineTrashFolderExistsForHost(GetImapServerKey(),
                                                         trashFolderExists);
    m_hostSessionList->GetHostIsUsingSubscription(GetImapServerKey(),
                                                  usingSubscription);
    if (!trashFolderExists && GetDeleteIsMoveToTrash() && usingSubscription) {
      // maybe we're not subscribed to the Trash folder
      if (personalDir) {
        m_hierarchyNameState = kDiscoverTrashFolderInProgress;
        List(m_trashFolderPath.get(), true);
        m_hierarchyNameState = kNoOperationInProgress;
      }
    }

    // There is no Trash folder (either LIST'd or LSUB'd), and we're using the
    // Delete-is-move-to-Trash model, and there is a personal namespace
    if (!trashFolderExists && GetDeleteIsMoveToTrash() && ns) {
      nsCString onlineTrashName;
      m_runningUrl->AllocateServerPath(m_trashFolderPath.get(),
                                       ns->GetDelimiter(),
                                       getter_Copies(onlineTrashName));

      GetServerStateParser().SetReportingErrors(false);
      bool created =
          CreateMailboxRespectingSubscriptions(onlineTrashName.get());
      GetServerStateParser().SetReportingErrors(true);

      // force discovery of new trash folder.
      if (created) {
        m_hierarchyNameState = kDiscoverTrashFolderInProgress;
        List(onlineTrashName.get(), false);
        m_hierarchyNameState = kNoOperationInProgress;
      } else
        m_hostSessionList->SetOnlineTrashFolderExistsForHost(GetImapServerKey(),
                                                             true);
    }  // if trash folder doesn't exist
    m_hostSessionList->SetHaveWeEverDiscoveredFoldersForHost(GetImapServerKey(),
                                                             true);
    // notify front end that folder discovery is complete....
    if (m_imapServerSink) m_imapServerSink->DiscoveryDone();

    // Clear the discovery in progress flag.
    m_hostSessionList->SetDiscoveryForHostInProgress(GetImapServerKey(), false);
  }
}

// returns the mailboxName with the IMAP delimiter removed from the tail end
void nsImapProtocol::RemoveHierarchyDelimiter(nsCString& mailboxName) {
  char onlineDelimiter[2] = {0, 0};
  if (m_imapMailFolderSink)
    m_imapMailFolderSink->GetOnlineDelimiter(&onlineDelimiter[0]);
  // take the hierarchy delimiter off the end, if any.
  if (onlineDelimiter[0]) mailboxName.Trim(onlineDelimiter, false, true);
}

// returns true is the create succeeded (regardless of subscription changes)
bool nsImapProtocol::CreateMailboxRespectingSubscriptions(
    const char* mailboxName) {
  CreateMailbox(mailboxName);
  bool rv = GetServerStateParser().LastCommandSuccessful();
  if (rv && m_autoSubscribe)  // auto-subscribe is on
  {
    // create succeeded - let's subscribe to it
    bool reportingErrors = GetServerStateParser().GetReportingErrors();
    GetServerStateParser().SetReportingErrors(false);
    nsCString mailboxWODelim(mailboxName);
    RemoveHierarchyDelimiter(mailboxWODelim);
    OnSubscribe(mailboxWODelim.get());
    GetServerStateParser().SetReportingErrors(reportingErrors);
  }
  return rv;
}

void nsImapProtocol::CreateMailbox(const char* mailboxName) {
  ProgressEventFunctionUsingName("imapStatusCreatingMailbox");

  IncrementCommandTagNumber();

  nsCString escapedName;
  CreateEscapedMailboxName(mailboxName, escapedName);
  nsCString command(GetServerCommandTag());
  command += " create \"";
  command += escapedName;
  command += "\"" CRLF;

  nsresult rv = SendData(command.get());
  if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail();
  // If that failed, let's list the parent folder to see if
  // it allows inferiors, so we won't try to create sub-folders
  // of the parent folder again in the current session.
  if (GetServerStateParser().CommandFailed()) {
    // Figure out parent folder name.
    nsCString parentName(mailboxName);
    char hierarchyDelimiter;
    m_runningUrl->GetOnlineSubDirSeparator(&hierarchyDelimiter);
    int32_t leafPos = parentName.RFindChar(hierarchyDelimiter);
    if (leafPos > 0) {
      parentName.SetLength(leafPos);
      List(parentName.get(), false);
      // We still want the caller to know the create failed, so restore that.
      GetServerStateParser().SetCommandFailed(true);
    }
  }
}

void nsImapProtocol::DeleteMailbox(const char* mailboxName) {
  // check if this connection currently has the folder to be deleted selected.
  // If so, we should close it because at least some UW servers don't like you
  // deleting a folder you have open.
  if (FolderIsSelected(mailboxName)) ImapClose();

  ProgressEventFunctionUsingNameWithString("imapStatusDeletingMailbox",
                                           mailboxName);

  IncrementCommandTagNumber();

  nsCString escapedName;
  CreateEscapedMailboxName(mailboxName, escapedName);
  nsCString command(GetServerCommandTag());
  command += " delete \"";
  command += escapedName;
  command += "\"" CRLF;

  nsresult rv = SendData(command.get());
  if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail();
}

void nsImapProtocol::RenameMailbox(const char* existingName,
                                   const char* newName) {
  // just like DeleteMailbox; Some UW servers don't like it.
  if (FolderIsSelected(existingName)) ImapClose();

  ProgressEventFunctionUsingNameWithString("imapStatusRenamingMailbox",
                                           existingName);

  IncrementCommandTagNumber();

  nsCString escapedExistingName;
  nsCString escapedNewName;
  CreateEscapedMailboxName(existingName, escapedExistingName);
  CreateEscapedMailboxName(newName, escapedNewName);
  nsCString command(GetServerCommandTag());
  command += " rename \"";
  command += escapedExistingName;
  command += "\" \"";
  command += escapedNewName;
  command += "\"" CRLF;

  nsresult rv = SendData(command.get());
  if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail();
}

bool nsImapProtocol::GetListSubscribedIsBrokenOnServer() {
  // This is a workaround for an issue with LIST(SUBSCRIBED) crashing older
  // versions of Zimbra
  if (FindInReadable("\"NAME\" \"Zimbra\""_ns,
                     GetServerStateParser().GetServerID(),
                     nsCaseInsensitiveCStringComparator)) {
    nsCString serverID(GetServerStateParser().GetServerID());
    int start = serverID.LowerCaseFindASCII("\"version\" \"") + 11;
    int length = serverID.LowerCaseFindASCII("\" ", start);
    const nsDependentCSubstring serverVersionSubstring =
        Substring(serverID, start, length);
    nsCString serverVersionStr(serverVersionSubstring);
    Version serverVersion(serverVersionStr.get());
    Version sevenTwoThree("7.2.3_");
    Version eightZeroZero("8.0.0_");
    Version eightZeroThree("8.0.3_");
    if ((serverVersion < sevenTwoThree) ||
        ((serverVersion >= eightZeroZero) && (serverVersion < eightZeroThree)))
      return true;
  }
  return false;
}

void nsImapProtocol::Lsub(const char* mailboxPattern,
                          bool addDirectoryIfNecessary) {
  ProgressEventFunctionUsingName("imapStatusLookingForMailbox");

  IncrementCommandTagNumber();

  char* boxnameWithOnlineDirectory = nullptr;
  if (addDirectoryIfNecessary)
    m_runningUrl->AddOnlineDirectoryIfNecessary(mailboxPattern,
                                                &boxnameWithOnlineDirectory);

  nsCString escapedPattern;
  CreateEscapedMailboxName(
      boxnameWithOnlineDirectory ? boxnameWithOnlineDirectory : mailboxPattern,
      escapedPattern);

  nsCString command(GetServerCommandTag());
  eIMAPCapabilityFlags flag = GetServerStateParser().GetCapabilityFlag();
  bool useListSubscribed = (flag & kHasListExtendedCapability) &&
                           !GetListSubscribedIsBrokenOnServer();
  if (useListSubscribed)
    command += " list (subscribed)";
  else
    command += " lsub";
  command += " \"\" \"";
  command += escapedPattern;
  if (useListSubscribed && (flag & kHasSpecialUseCapability))
    command += "\" return (special-use)" CRLF;
  else
    command += "\"" CRLF;

  PR_Free(boxnameWithOnlineDirectory);

  nsresult rv = SendData(command.get());
  if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail(command.get(), true);
}

void nsImapProtocol::List(const char* mailboxPattern,
                          bool addDirectoryIfNecessary, bool useXLIST) {
  ProgressEventFunctionUsingName("imapStatusLookingForMailbox");

  IncrementCommandTagNumber();

  char* boxnameWithOnlineDirectory = nullptr;
  if (addDirectoryIfNecessary)
    m_runningUrl->AddOnlineDirectoryIfNecessary(mailboxPattern,
                                                &boxnameWithOnlineDirectory);

  nsCString escapedPattern;
  CreateEscapedMailboxName(
      boxnameWithOnlineDirectory ? boxnameWithOnlineDirectory : mailboxPattern,
      escapedPattern);

  nsCString command(GetServerCommandTag());
  command += useXLIST ? " xlist \"\" \"" : " list \"\" \"";
  command += escapedPattern;
  command += "\"" CRLF;

  PR_Free(boxnameWithOnlineDirectory);

  nsresult rv = SendData(command.get());
  if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail(command.get(), true);
}

void nsImapProtocol::Subscribe(const char* mailboxName) {
  ProgressEventFunctionUsingNameWithString("imapStatusSubscribeToMailbox",
                                           mailboxName);

  IncrementCommandTagNumber();

  nsCString escapedName;
  CreateEscapedMailboxName(mailboxName, escapedName);

  nsCString command(GetServerCommandTag());
  command += " subscribe \"";
  command += escapedName;
  command += "\"" CRLF;

  nsresult rv = SendData(command.get());
  if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail();
}

void nsImapProtocol::Unsubscribe(const char* mailboxName) {
  ProgressEventFunctionUsingNameWithString("imapStatusUnsubscribeMailbox",
                                           mailboxName);
  IncrementCommandTagNumber();

  nsCString escapedName;
  CreateEscapedMailboxName(mailboxName, escapedName);

  nsCString command(GetServerCommandTag());
  command += " unsubscribe \"";
  command += escapedName;
  command += "\"" CRLF;

  nsresult rv = SendData(command.get());
  if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail();
}

void nsImapProtocol::Idle() {
  IncrementCommandTagNumber();

  if (m_urlInProgress) return;
  nsAutoCString command(GetServerCommandTag());
  command += " IDLE" CRLF;
  nsresult rv = SendData(command.get());
  if (NS_SUCCEEDED(rv)) {
    // Typically, we'll just get back only a continuation char on IDLE response,
    // "+ idling". However, it is possible untagged responses will occur before
    // and/or after the '+' which we treat the same as later untagged responses
    // signaled by the socket thread. If untagged responses occur on IDLE,
    // HandleIdleResponses() will trigger a select URL which will exit idle mode
    // and update the selected folder. Finally, if IDLE responds with tagged BAD
    // or NO, HandleIdleResponses() will return false.
    m_idle = HandleIdleResponses();
  }
}

// until we can fix the hang on shutdown waiting for server
// responses, we need to not wait for the server response
// on shutdown.
void nsImapProtocol::EndIdle(bool waitForResponse /* = true */) {
  // clear the async wait - otherwise, we have trouble doing a blocking read
  // below.
  nsCOMPtr<nsIAsyncInputStream> asyncInputStream =
      do_QueryInterface(m_inputStream);
  if (asyncInputStream) asyncInputStream->AsyncWait(nullptr, 0, 0, nullptr);
  nsresult rv = SendData("DONE" CRLF);
  // set a short timeout if we don't want to wait for a response
  if (m_transport && !waitForResponse)
    m_transport->SetTimeout(nsISocketTransport::TIMEOUT_READ_WRITE, 5);
  if (NS_SUCCEEDED(rv)) {
    m_idle = false;
    ParseIMAPandCheckForNewMail();
    // If waiting for response (i.e., not shutting down), check for IDLE
    // untagged response(s) occurring after DONE is sent, which can occur and is
    // mentioned in the IDLE rfc as a possibility. This is similar to the checks
    // done in OnStatusForFolder().
    if (waitForResponse && m_imapMailFolderSinkSelected &&
        GetServerStateParser().UntaggedResponse()) {
      Log("EndIdle", nullptr, "idle response after idle DONE");
      m_imapMailFolderSinkSelected->OnNewIdleMessages();
    }
  }
  // Set m_imapMailFolderSink null only if shutting down or if DONE succeeds.
  // We need to keep m_imapMailFolderSink if DONE fails or times out when not
  // shutting down so the URL that is attempting to run on this connection can
  // retry or signal a failed status when SetUrlState is called in
  // ProcessCurrentUrl to invoke nsIUrlListener.onStopRunningUrl.
  if (!waitForResponse || GetServerStateParser().LastCommandSuccessful())
    m_imapMailFolderSink = nullptr;
}

void nsImapProtocol::Search(const char* searchCriteria, bool useUID,
                            bool notifyHit /* true */) {
  m_notifySearchHit = notifyHit;
  ProgressEventFunctionUsingName("imapStatusSearchMailbox");
  IncrementCommandTagNumber();

  nsCString protocolString(GetServerCommandTag());
  // the searchCriteria string contains the 'search ....' string
  if (useUID) protocolString.AppendLiteral(" uid");
  protocolString.Append(' ');
  protocolString.Append(searchCriteria);
  // the search criteria can contain string literals, which means we
  // need to break up the protocol string by CRLF's, and after sending CRLF,
  // wait for the server to respond OK before sending more data
  nsresult rv;
  int32_t crlfIndex;
  while ((crlfIndex = protocolString.Find(CRLF)) != kNotFound &&
         !DeathSignalReceived()) {
    nsAutoCString tempProtocolString;
    tempProtocolString = StringHead(protocolString, crlfIndex + 2);
    rv = SendData(tempProtocolString.get());
    if (NS_FAILED(rv)) return;
    ParseIMAPandCheckForNewMail();
    protocolString.Cut(0, crlfIndex + 2);
  }
  protocolString.Append(CRLF);

  rv = SendData(protocolString.get());
  if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail();
}

void nsImapProtocol::Copy(const char* messageList,
                          const char* destinationMailbox, bool idsAreUid) {
  IncrementCommandTagNumber();

  nsCString escapedDestination;
  CreateEscapedMailboxName(destinationMailbox, escapedDestination);

  // turn messageList back into key array and then back into a message id list,
  // but use the flag state to handle ranges correctly.
  nsCString messageIdList;
  nsTArray<nsMsgKey> msgKeys;
  if (idsAreUid) ParseUidString(messageList, msgKeys);

  int32_t msgCountLeft = msgKeys.Length();
  uint32_t msgsHandled = 0;

  do {
    nsCString idString;

    uint32_t msgsToHandle = msgCountLeft;
    if (idsAreUid)
      AllocateImapUidString(msgKeys.Elements() + msgsHandled, msgsToHandle,
                            m_flagState, idString);
    else
      idString.Assign(messageList);

    msgsHandled += msgsToHandle;
    msgCountLeft -= msgsToHandle;

    IncrementCommandTagNumber();
    nsAutoCString protocolString(GetServerCommandTag());
    if (idsAreUid) protocolString.AppendLiteral(" uid");
    // If it's a MOVE operation on aol servers then use 'xaol-move' cmd.
    if ((m_imapAction == nsIImapUrl::nsImapOnlineMove) &&
        GetServerStateParser().ServerIsAOLServer())
      protocolString.AppendLiteral(" xaol-move ");
    else if ((m_imapAction == nsIImapUrl::nsImapOnlineMove) &&
             GetServerStateParser().GetCapabilityFlag() & kHasMoveCapability)
      protocolString.AppendLiteral(" move ");
    else
      protocolString.AppendLiteral(" copy ");

    protocolString.Append(idString);
    protocolString.AppendLiteral(" \"");
    protocolString.Append(escapedDestination);
    protocolString.AppendLiteral("\"" CRLF);

    nsresult rv = SendData(protocolString.get());
    if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail(protocolString.get());
  } while (msgCountLeft > 0 && !DeathSignalReceived());
}

void nsImapProtocol::NthLevelChildList(const char* onlineMailboxPrefix,
                                       int32_t depth) {
  NS_ASSERTION(depth >= 0, "Oops ... depth must be equal or greater than 0");
  if (depth < 0) return;

  nsCString truncatedPrefix(onlineMailboxPrefix);
  char16_t slash = '/';
  if (truncatedPrefix.Last() == slash)
    truncatedPrefix.SetLength(truncatedPrefix.Length() - 1);

  nsAutoCString pattern(truncatedPrefix);
  nsAutoCString suffix;
  int count = 0;
  char separator = 0;
  m_runningUrl->GetOnlineSubDirSeparator(&separator);
  suffix.Assign(separator);
  suffix += '%';

  while (count < depth) {
    pattern += suffix;
    count++;
    List(pattern.get(), false);
  }
}

/**
 * ProcessAuthenticatedStateURL() is a helper for ProcessCurrentURL() which
 * handles running URLs which require the connection to be in the
 * Authenticated state.
 */
void nsImapProtocol::ProcessAuthenticatedStateURL() {
  nsImapAction imapAction;
  char* sourceMailbox = nullptr;
  m_runningUrl->GetImapAction(&imapAction);

  // switch off of the imap url action and take an appropriate action
  switch (imapAction) {
    case nsIImapUrl::nsImapLsubFolders:
      OnLSubFolders();
      break;
    case nsIImapUrl::nsImapAppendMsgFromFile:
      OnAppendMsgFromFile();
      break;
    case nsIImapUrl::nsImapDiscoverAllBoxesUrl:
      NS_ASSERTION(!GetSubscribingNow(),
                   "Oops ... should not get here from subscribe UI");
      DiscoverMailboxList();
      break;
    case nsIImapUrl::nsImapDiscoverAllAndSubscribedBoxesUrl:
      DiscoverAllAndSubscribedBoxes();
      break;
    case nsIImapUrl::nsImapCreateFolder:
      sourceMailbox = OnCreateServerSourceFolderPathString();
      OnCreateFolder(sourceMailbox);
      break;
    case nsIImapUrl::nsImapEnsureExistsFolder:
      sourceMailbox = OnCreateServerSourceFolderPathString();
      OnEnsureExistsFolder(sourceMailbox);
      break;
    case nsIImapUrl::nsImapDiscoverChildrenUrl: {
      char* canonicalParent = nullptr;
      m_runningUrl->CreateServerSourceFolderPathString(&canonicalParent);
      if (canonicalParent) {
        NthLevelChildList(canonicalParent, 2);
        PR_Free(canonicalParent);
      }
      break;
    }
    case nsIImapUrl::nsImapSubscribe:
      sourceMailbox = OnCreateServerSourceFolderPathString();
      OnSubscribe(sourceMailbox);  // used to be called subscribe

      if (GetServerStateParser().LastCommandSuccessful()) {
        bool shouldList;
        // if url is an external click url, then we should list the folder
        // after subscribing to it, so we can select it.
        m_runningUrl->GetExternalLinkUrl(&shouldList);
        if (shouldList) OnListFolder(sourceMailbox, true);
      }
      break;
    case nsIImapUrl::nsImapUnsubscribe:
      sourceMailbox = OnCreateServerSourceFolderPathString();
      OnUnsubscribe(sourceMailbox);
      break;
    case nsIImapUrl::nsImapRefreshACL:
      sourceMailbox = OnCreateServerSourceFolderPathString();
      RefreshACLForFolder(sourceMailbox);
      break;
    case nsIImapUrl::nsImapRefreshAllACLs:
      OnRefreshAllACLs();
      break;
    case nsIImapUrl::nsImapListFolder:
      sourceMailbox = OnCreateServerSourceFolderPathString();
      OnListFolder(sourceMailbox, false);
      break;
    case nsIImapUrl::nsImapFolderStatus:
      sourceMailbox = OnCreateServerSourceFolderPathString();
      OnStatusForFolder(sourceMailbox);
      break;
    case nsIImapUrl::nsImapRefreshFolderUrls:
      sourceMailbox = OnCreateServerSourceFolderPathString();
      XMailboxInfo(sourceMailbox);
      if (GetServerStateParser().LastCommandSuccessful())
        SetFolderAdminUrl(sourceMailbox);
      break;
    case nsIImapUrl::nsImapDeleteFolder:
      sourceMailbox = OnCreateServerSourceFolderPathString();
      OnDeleteFolder(sourceMailbox);
      break;
    case nsIImapUrl::nsImapRenameFolder:
      sourceMailbox = OnCreateServerSourceFolderPathString();
      OnRenameFolder(sourceMailbox);
      break;
    case nsIImapUrl::nsImapMoveFolderHierarchy:
      sourceMailbox = OnCreateServerSourceFolderPathString();
      OnMoveFolderHierarchy(sourceMailbox);
      break;
    case nsIImapUrl::nsImapVerifylogon:
      break;
    default:
      break;
  }
  PR_Free(sourceMailbox);
}

void nsImapProtocol::ProcessAfterAuthenticated() {
  // if we're a netscape server, and we haven't got the admin url, get it
  bool hasAdminUrl = true;

  // If a capability response didn't occur during authentication, request
  // the capabilities again to ensure the full capability set is known.
  if (!m_capabilityResponseOccurred) Capability();

  if (NS_SUCCEEDED(m_hostSessionList->GetHostHasAdminURL(GetImapServerKey(),
                                                         hasAdminUrl)) &&
      !hasAdminUrl) {
    if (GetServerStateParser().ServerHasServerInfo()) {
      XServerInfo();
      if (GetServerStateParser().LastCommandSuccessful() && m_imapServerSink) {
        m_imapServerSink->SetMailServerUrls(
            GetServerStateParser().GetMailAccountUrl(),
            GetServerStateParser().GetManageListsUrl(),
            GetServerStateParser().GetManageFiltersUrl());
        // we've tried to ask for it, so don't try again this session.
        m_hostSessionList->SetHostHasAdminURL(GetImapServerKey(), true);
      }
    } else if (GetServerStateParser().ServerIsNetscape3xServer()) {
      Netscape();
      if (GetServerStateParser().LastCommandSuccessful() && m_imapServerSink)
        m_imapServerSink->SetMailServerUrls(
            GetServerStateParser().GetMailAccountUrl(), EmptyCString(),
            EmptyCString());
    }
  }

  if (GetServerStateParser().ServerHasNamespaceCapability()) {
    bool nameSpacesOverridable = false;
    bool haveNameSpacesForHost = false;
    m_hostSessionList->GetNamespacesOverridableForHost(GetImapServerKey(),
                                                       nameSpacesOverridable);
    m_hostSessionList->GetGotNamespacesForHost(GetImapServerKey(),
                                               haveNameSpacesForHost);

    // mscott: VERIFY THIS CLAUSE!!!!!!!
    if (nameSpacesOverridable && !haveNameSpacesForHost) Namespace();
  }

  // If the server supports compression, turn it on now.
  // Choosing this spot (after login has finished) because
  // many proxies (e.g. perdition, nginx) talk IMAP to the
  // client until login is finished, then hand off to the
  // backend.  If we enable compression early the proxy
  // will be confused.
  if (UseCompressDeflate()) StartCompressDeflate();

  if ((GetServerStateParser().GetCapabilityFlag() & kHasEnableCapability) &&
      UseCondStore())
    EnableCondStore();

  if ((GetServerStateParser().GetCapabilityFlag() & kHasIDCapability) &&
      m_sendID) {
    ID();
    if (m_imapServerSink && !GetServerStateParser().GetServerID().IsEmpty())
      m_imapServerSink->SetServerID(GetServerStateParser().GetServerID());
  }

  bool utf8AcceptAllowed = m_allowUTF8Accept;
  m_allowUTF8Accept = false;
  if (utf8AcceptAllowed &&
      ((GetServerStateParser().GetCapabilityFlag() &
        (kHasEnableCapability | kHasUTF8AcceptCapability)) ==
       (kHasEnableCapability | kHasUTF8AcceptCapability))) {
    if (m_imapServerSink) {
      EnableUTF8Accept();
      m_allowUTF8Accept = GetServerStateParser().fUtf8AcceptEnabled;
      // m_allowUTF8Accept affects imap append handling. See
      // UploadMessageFromFile().
      m_imapServerSink->SetServerUtf8AcceptEnabled(m_allowUTF8Accept);
      GetServerStateParser().fUtf8AcceptEnabled = false;
    } else {
      NS_WARNING("UTF8=ACCEPT not enabled due to null m_imapServerSink");
    }
  }
}

void nsImapProtocol::SetupMessageFlagsString(nsCString& flagString,
                                             imapMessageFlagsType flags,
                                             uint16_t userFlags) {
  if (flags & kImapMsgSeenFlag) flagString.AppendLiteral("\\Seen ");
  if (flags & kImapMsgAnsweredFlag) flagString.AppendLiteral("\\Answered ");
  if (flags & kImapMsgFlaggedFlag) flagString.AppendLiteral("\\Flagged ");
  if (flags & kImapMsgDeletedFlag) flagString.AppendLiteral("\\Deleted ");
  if (flags & kImapMsgDraftFlag) flagString.AppendLiteral("\\Draft ");
  if (flags & kImapMsgRecentFlag) flagString.AppendLiteral("\\Recent ");
  if ((flags & kImapMsgForwardedFlag) &&
      (userFlags & kImapMsgSupportForwardedFlag))
    flagString.AppendLiteral("$Forwarded ");  // Not always available
  if ((flags & kImapMsgMDNSentFlag) && (userFlags & kImapMsgSupportMDNSentFlag))
    flagString.AppendLiteral("$MDNSent ");  // Not always available

  // eat the last space
  if (!flagString.IsEmpty()) flagString.SetLength(flagString.Length() - 1);
}

void nsImapProtocol::ProcessStoreFlags(const nsCString& messageIdsString,
                                       bool idsAreUids,
                                       imapMessageFlagsType flags,
                                       bool addFlags) {
  nsCString flagString;

  uint16_t userFlags = GetServerStateParser().SupportsUserFlags();
  uint16_t settableFlags = GetServerStateParser().SettablePermanentFlags();

  if (!addFlags && (flags & userFlags) && !(flags & settableFlags)) {
    if (m_runningUrl)
      m_runningUrl->SetExtraStatus(nsIImapUrl::ImapStatusFlagsNotSettable);
    return;  // if cannot set any of the flags bail out
  }

  if (addFlags)
    flagString = "+Flags (";
  else
    flagString = "-Flags (";

  if (flags & kImapMsgSeenFlag && kImapMsgSeenFlag & settableFlags)
    flagString.AppendLiteral("\\Seen ");
  if (flags & kImapMsgAnsweredFlag && kImapMsgAnsweredFlag & settableFlags)
    flagString.AppendLiteral("\\Answered ");
  if (flags & kImapMsgFlaggedFlag && kImapMsgFlaggedFlag & settableFlags)
    flagString.AppendLiteral("\\Flagged ");
  if (flags & kImapMsgDeletedFlag && kImapMsgDeletedFlag & settableFlags)
    flagString.AppendLiteral("\\Deleted ");
  if (flags & kImapMsgDraftFlag && kImapMsgDraftFlag & settableFlags)
    flagString.AppendLiteral("\\Draft ");
  if (flags & kImapMsgForwardedFlag && kImapMsgSupportForwardedFlag & userFlags)
    flagString.AppendLiteral("$Forwarded ");  // if supported
  if (flags & kImapMsgMDNSentFlag && kImapMsgSupportMDNSentFlag & userFlags)
    flagString.AppendLiteral("$MDNSent ");  // if supported

  if (flagString.Length() > 8)  // if more than "+Flags ("
  {
    // replace the final space with ')'
    flagString.SetCharAt(')', flagString.Length() - 1);

    Store(messageIdsString, flagString.get(), idsAreUids);
    if (m_runningUrl && idsAreUids) {
      nsCString messageIdString;
      m_runningUrl->GetListOfMessageIds(messageIdString);
      nsTArray<nsMsgKey> msgKeys;
      ParseUidString(messageIdString.get(), msgKeys);

      int32_t msgCount = msgKeys.Length();
      for (int32_t i = 0; i < msgCount; i++) {
        bool found;
        imapMessageFlagsType resultFlags;
        // check if the flags were added/removed, and if the uid really exists.
        nsresult rv = GetFlagsForUID(msgKeys[i], &found, &resultFlags, nullptr);
        if (NS_FAILED(rv) || !found ||
            (addFlags && ((flags & resultFlags) != flags)) ||
            (!addFlags && ((flags & resultFlags) != 0))) {
          m_runningUrl->SetExtraStatus(nsIImapUrl::ImapStatusFlagChangeFailed);
          break;
        }
      }
    }
  }
}

/**
 * This will cause all messages marked deleted to be expunged with no untagged
 * response so it can cause unexpected data loss if used improperly.
 */
void nsImapProtocol::ImapClose(bool shuttingDown /* = false */,
                               bool waitForResponse /* = true */) {
  IncrementCommandTagNumber();

  nsCString command(GetServerCommandTag());
  command.AppendLiteral(" close" CRLF);

  if (!shuttingDown) ProgressEventFunctionUsingName("imapStatusCloseMailbox");

  GetServerStateParser().ResetFlagInfo();

  nsresult rv = SendData(command.get());
  if (m_transport && shuttingDown)
    m_transport->SetTimeout(nsISocketTransport::TIMEOUT_READ_WRITE, 5);

  if (NS_SUCCEEDED(rv) && waitForResponse) ParseIMAPandCheckForNewMail();
}

void nsImapProtocol::XAOL_Option(const char* option) {
  IncrementCommandTagNumber();

  nsCString command(GetServerCommandTag());
  command.AppendLiteral(" XAOL-OPTION ");
  command.Append(option);
  command.Append(CRLF);

  nsresult rv = SendData(command.get());
  if (NS_SUCCEEDED(rv)) ParseIMAPandCheckForNewMail();
}

void nsImapProtocol::Check() {
  // ProgressUpdateEvent("Checking mailbox...");

  IncrementCommandTagNumber();

  nsCString command(GetServerCommandTag());
  command.AppendLiteral(" check" CRLF);

  nsresult rv = SendData(command.get());
  if (NS_SUCCEEDED(rv)) {
    m_flagChangeCount = 0;
    m_lastCheckTime = PR_Now();
    ParseIMAPandCheckForNewMail();
  }
}

nsresult nsImapProtocol::GetMsgWindow(nsIMsgWindow** aMsgWindow) {
  nsresult rv;
  *aMsgWindow = nullptr;
  nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl =
      do_QueryInterface(m_runningUrl, &rv);
  NS_ENSURE_SUCCESS(rv, rv);
  if (!m_imapProtocolSink) return NS_ERROR_FAILURE;
  return m_imapProtocolSink->GetUrlWindow(mailnewsUrl, aMsgWindow);
}

/**
 * Get password from RAM, disk (password manager) or user (dialog)
 * @return NS_MSG_PASSWORD_PROMPT_CANCELLED
 *    (which is NS_SUCCEEDED!) when user cancelled
 *    NS_FAILED(rv) for other errors
 */
nsresult nsImapProtocol::GetPassword(nsString& password,
                                     bool newPasswordRequested) {
  // we are in the imap thread so *NEVER* try to extract the password with UI
  NS_ENSURE_TRUE(m_imapServerSink, NS_ERROR_NULL_POINTER);
  NS_ENSURE_TRUE(m_server, NS_ERROR_NULL_POINTER);
  nsresult rv;

  password = nsString();
  // Get the password already stored in mem
  rv = m_imapServerSink->GetServerPassword(password);
  if (NS_FAILED(rv) || password.IsEmpty()) {
    // First see if there's an associated window. We don't want to produce a
    // password prompt if there is no window, e.g., during biff.
    AutoProxyReleaseMsgWindow msgWindow;
    GetMsgWindow(getter_AddRefs(msgWindow));
    if (msgWindow) {
      m_passwordStatus = NS_OK;
      m_passwordObtained = false;

      // Get the password from pw manager (harddisk) or user (dialog)
      rv = m_imapServerSink->AsyncGetPassword(this, newPasswordRequested,
                                              password);

      if (NS_SUCCEEDED(rv)) {
        while (password.IsEmpty()) {
          bool shuttingDown = false;
          (void)m_imapServerSink->GetServerShuttingDown(&shuttingDown);
          if (shuttingDown) {
            // Note: If we fix bug 1783573 this check could be ditched.
            rv = NS_ERROR_FAILURE;
            break;
          }

          ReentrantMonitorAutoEnter mon(m_passwordReadyMonitor);
          if (!m_passwordObtained && !NS_FAILED(m_passwordStatus) &&
              m_passwordStatus != NS_MSG_PASSWORD_PROMPT_CANCELLED &&
              !DeathSignalReceived()) {
            mon.Wait(PR_MillisecondsToInterval(1000));
          }

          if (NS_FAILED(m_passwordStatus) ||
              m_passwordStatus == NS_MSG_PASSWORD_PROMPT_CANCELLED) {
            rv = m_passwordStatus;
            break;
          }

          if (DeathSignalReceived()) {
            rv = NS_ERROR_FAILURE;
            break;
          }

          if (m_passwordObtained) {
            rv = m_passwordStatus;
            password = m_password;
            break;
          }
        }  // end while
      }
    } else {
      // If no msgWindow (i.e., unattended operation like biff, filtering or
      // autosync) try to get the password directly from login mgr. If it's not
      // there, will return NS_ERROR_NOT_AVAILABLE and the connection will fail
      // with only the IMAP log message: `password prompt failed or user
      // canceled it'. No password prompt occurs.
      rv = m_imapServerSink->SyncGetPassword(password);
    }
  }
  if (!password.IsEmpty()) m_lastPasswordSent = password;
  return rv;
}

NS_IMETHODIMP nsImapProtocol::OnPromptStartAsync(
    nsIMsgAsyncPromptCallback* aCallback) {
  bool result = false;
  OnPromptStart(&result);
  return aCallback->OnAuthResult(result);
}

// This is called from the UI thread.
NS_IMETHODIMP
nsImapProtocol::OnPromptStart(bool* aResult) {
  nsresult rv;
  nsCOMPtr<nsIImapIncomingServer> imapServer = do_QueryReferent(m_server, &rv);
  NS_ENSURE_SUCCESS(rv, rv);
  nsCOMPtr<nsIMsgWindow> msgWindow;

  *aResult = false;
  GetMsgWindow(getter_AddRefs(msgWindow));
  nsString password = m_lastPasswordSent;
  rv = imapServer->PromptPassword(msgWindow, password);

  ReentrantMonitorAutoEnter passwordMon(m_passwordReadyMonitor);

  m_password = password;
  m_passwordStatus = rv;
  if (!m_password.IsEmpty()) *aResult = true;

  // Notify the imap thread that we have a password.
  m_passwordObtained = true;
  passwordMon.Notify();
  return rv;
}

NS_IMETHODIMP
nsImapProtocol::OnPromptAuthAvailable() {
  nsresult rv;
  nsCOMPtr<nsIMsgIncomingServer> imapServer = do_QueryReferent(m_server, &rv);
  NS_ENSURE_SUCCESS(rv, rv);

  nsresult status = imapServer->GetPassword(m_password);

  ReentrantMonitorAutoEnter passwordMon(m_passwordReadyMonitor);

  m_passwordStatus = status;
  // Notify the imap thread that we have a password.
  m_passwordObtained = true;
  passwordMon.Notify();
  return m_passwordStatus;
}

NS_IMETHODIMP
nsImapProtocol::OnPromptCanceled() {
  // A prompt was cancelled, so notify the imap thread.
  ReentrantMonitorAutoEnter passwordMon(m_passwordReadyMonitor);
  m_passwordStatus = NS_MSG_PASSWORD_PROMPT_CANCELLED;
  passwordMon.Notify();
  return NS_OK;
}

// Called when capability response is parsed.
void nsImapProtocol::SetCapabilityResponseOccurred() {
  m_capabilityResponseOccurred = true;
}

bool nsImapProtocol::TryToLogon() {
  MOZ_LOG(IMAP, LogLevel::Debug, ("Try to log in"));
  NS_ENSURE_TRUE(m_imapServerSink, false);
  bool loginSucceeded = false;
  bool skipLoop = false;
  nsAutoString password;
  nsAutoCString userName;

  // If remains false when authentication is complete it means that a
  // capability response didn't occur within the authentication response so
  // capabilities will be requested explicitly.
  m_capabilityResponseOccurred = false;

  nsresult rv = ChooseAuthMethod();
  if (NS_FAILED(rv))  // all methods failed
  {
    // are there any matching login schemes at all?
    if (!(GetServerStateParser().GetCapabilityFlag() & m_prefAuthMethods)) {
      // Pref doesn't match server. Now, find an appropriate error msg.

      // pref has plaintext pw & server claims to support encrypted pw
      if (m_prefAuthMethods ==
              (kHasAuthOldLoginCapability | kHasAuthLoginCapability |
               kHasAuthPlainCapability) &&
          GetServerStateParser().GetCapabilityFlag() & kHasCRAMCapability)
        // tell user to change to encrypted pw
        AlertUserEventUsingName("imapAuthChangePlainToEncrypt");
      // pref has encrypted pw & server claims to support plaintext pw
      else if (m_prefAuthMethods == kHasCRAMCapability &&
               GetServerStateParser().GetCapabilityFlag() &
                   (kHasAuthOldLoginCapability | kHasAuthLoginCapability |
                    kHasAuthPlainCapability)) {
        // have SSL
        if (m_socketType == nsMsgSocketType::SSL ||
            m_socketType == nsMsgSocketType::alwaysSTARTTLS)
          // tell user to change to plaintext pw
          AlertUserEventUsingName("imapAuthChangeEncryptToPlainSSL");
        else
          // tell user to change to plaintext pw, with big warning
          AlertUserEventUsingName("imapAuthChangeEncryptToPlainNoSSL");
      } else
        // just "change auth method"
        AlertUserEventUsingName("imapAuthMechNotSupported");

      skipLoop = true;
    } else {
      // try to reset failed methods and try them again
      ResetAuthMethods();
      rv = ChooseAuthMethod();
      if (NS_FAILED(rv))  // all methods failed
      {
        MOZ_LOG(IMAP, LogLevel::Error,
                ("huch? there are auth methods, and we reset failed ones, but "
                 "ChooseAuthMethod still fails."));
        return false;
      }
    }
  }

  // Check the uri host for localhost indicators to see if we
  // should bypass the SSL check for clientid.
  // Unfortunately we cannot call IsOriginPotentiallyTrustworthy
  // here because it can only be called from the main thread.
  bool isLocalhostConnection = false;
  if (m_mockChannel) {
    nsCOMPtr<nsIURI> uri;
    m_mockChannel->GetURI(getter_AddRefs(uri));
    if (uri) {
      nsCString uriHost;
      uri->GetHost(uriHost);
      if (uriHost.Equals("127.0.0.1") || uriHost.Equals("::1") ||
          uriHost.Equals("localhost")) {
        isLocalhostConnection = true;
      }
    }
  }

  // Whether our connection can be considered 'secure' and whether
  // we should allow the CLIENTID to be sent over this channel.
  bool isSecureConnection =
      (m_connectionType.EqualsLiteral("starttls") ||
       m_connectionType.EqualsLiteral("ssl") || isLocalhostConnection);

  // Before running the ClientID command we check for clientid
  // support by checking the server capability flags for the
  // flag kHasClientIDCapability.
  // We check that the m_clientId string is not empty, and
  // we ensure the connection can be considered secure.
  if ((GetServerStateParser().GetCapabilityFlag() & kHasClientIDCapability) &&
      !m_clientId.IsEmpty() && isSecureConnection) {
    rv = ClientID();
    if (NS_FAILED(rv)) {
      MOZ_LOG(IMAP, LogLevel::Error,
              ("TryToLogon: Could not issue CLIENTID command"));
      skipLoop = true;
    }
  }

  // Get username, either the stored one or from user
  rv = m_imapServerSink->GetLoginUsername(userName);
  if (NS_FAILED(rv) || userName.IsEmpty()) {
    // The user hit "Cancel" on the dialog box
    skipLoop = true;
  }

  // clang-format off
  /*
   * Login can fail for various reasons:
   * 1. Server claims to support GSSAPI, but it really doesn't.
   *    Or the client doesn't support GSSAPI, or is not logged in yet.
   *    (GSSAPI is a mechanism without password in apps).
   * 2. Server claims to support CRAM-MD5, but it's broken and will fail despite correct password.
   * 2.1. Some servers say they support CRAM but are so badly broken that trying it causes
   *    all subsequent login attempts to fail during this connection (bug 231303).
   *    So we use CRAM/NTLM/MSN only if enabled in prefs.
   *    Update: if it affects only some ISPs, we can maybe use the ISP DB
   *    and disable CRAM specifically for these.
   * 3. Prefs are set to require auth methods which the server doesn't support
   *     (per CAPS or we tried and they failed).
   * 4. User provided wrong password.
   * 5. We tried too often and the server shut us down, so even a correct attempt
   *    will now (currently) fail.
   * The above problems may overlap, e.g. 3. with 1. and 2., and we can't differentiate
   * between 2. and 4., which is really unfortunate.
   */
  // clang-format on

  bool newPasswordRequested = false;
  // remember the msgWindow before we start trying to logon, because if the
  // server drops the connection on errors, TellThreadToDie will null out the
  // protocolsink and we won't be able to get the msgWindow.
  AutoProxyReleaseMsgWindow msgWindow;
  GetMsgWindow(getter_AddRefs(msgWindow));

  // This loops over 1) auth methods (only one per loop) and 2) password tries
  // (with UI)
  while (!loginSucceeded && !skipLoop && !DeathSignalReceived()) {
    // Get password
    if (m_currentAuthMethod !=
            kHasAuthGssApiCapability &&  // GSSAPI uses no pw in apps
        m_currentAuthMethod != kHasAuthExternalCapability &&
        m_currentAuthMethod != kHasXOAuth2Capability &&
        m_currentAuthMethod != kHasAuthNoneCapability) {
      rv = GetPassword(password, newPasswordRequested);
      newPasswordRequested = false;
      if (rv == NS_MSG_PASSWORD_PROMPT_CANCELLED || NS_FAILED(rv)) {
        MOZ_LOG(IMAP, LogLevel::Error,
                ("IMAP: password prompt failed or user canceled it"));
        break;
      }
      MOZ_LOG(IMAP, LogLevel::Debug, ("got new password"));
    }

    bool lastReportingErrors = GetServerStateParser().GetReportingErrors();
    GetServerStateParser().SetReportingErrors(
        false);  // turn off errors - we'll put up our own.

    rv = AuthLogin(userName.get(), password, m_currentAuthMethod);

    GetServerStateParser().SetReportingErrors(
        lastReportingErrors);  // restore error reports
    loginSucceeded = NS_SUCCEEDED(rv);

    if (!loginSucceeded) {
      MOZ_LOG(IMAP, LogLevel::Debug, ("authlogin failed"));
      MarkAuthMethodAsFailed(m_currentAuthMethod);
      rv = ChooseAuthMethod();  // change m_currentAuthMethod to try other one
                                // next round

      if (NS_FAILED(rv))  // all methods failed
      {
        if (m_prefAuthMethods == kHasAuthGssApiCapability) {
          // GSSAPI failed, and it's the only available method,
          // and it's password-less, so nothing left to do.
          AlertUserEventUsingName("imapAuthGssapiFailed");
          break;
        }

        if (m_prefAuthMethods & kHasXOAuth2Capability) {
          // OAuth2 failed. Entering password does not help.
          AlertUserEventUsingName("imapOAuth2Error");
          break;
        }

        // The reason that we failed might be a wrong password, so
        // ask user what to do
        MOZ_LOG(IMAP, LogLevel::Warning,
                ("IMAP: ask user what to do (after login failed): new "
                 "passwort, retry, cancel"));
        if (!m_imapServerSink) break;
        // if there's no msg window, don't forget the password
        if (!msgWindow) break;
        int32_t buttonPressed = 1;
        rv = m_imapServerSink->PromptLoginFailed(msgWindow, &buttonPressed);
        if (NS_FAILED(rv)) break;
        if (buttonPressed == 2)  // 'New password' button
        {
          MOZ_LOG(IMAP, LogLevel::Warning, ("new password button pressed."));
          // Forget the current password
          password.Truncate();
          m_hostSessionList->SetPasswordForHost(GetImapServerKey(),
                                                EmptyString());
          m_imapServerSink->ForgetPassword();
          m_password.Truncate();
          MOZ_LOG(IMAP, LogLevel::Warning, ("password reset (nulled)"));
          newPasswordRequested = true;
          // Will call GetPassword() in beginning of next loop

          // Try all possible auth methods again with the new password.
          ResetAuthMethods();
        } else if (buttonPressed == 0)  // Retry button
        {
          MOZ_LOG(IMAP, LogLevel::Warning, ("retry button pressed"));
          // Try all possible auth methods again
          ResetAuthMethods();
        } else if (buttonPressed == 1)  // Cancel button
        {
          MOZ_LOG(IMAP, LogLevel::Warning, ("cancel button pressed"));
          break;  // Abort quickly
        }

        // TODO what is this for? When does it get set to != unknown again?
        m_currentBiffState = nsIMsgFolder::nsMsgBiffState_Unknown;
        SendSetBiffIndicatorEvent(m_currentBiffState);
      }  // all methods failed
    }    // login failed
  }      // while

  if (loginSucceeded) {
    MOZ_LOG(IMAP, LogLevel::Debug, ("login succeeded"));
    bool passwordAlreadyVerified;
    m_hostSessionList->SetPasswordForHost(GetImapServerKey(), password);
    rv = m_hostSessionList->GetPasswordVerifiedOnline(GetImapServerKey(),
                                                      passwordAlreadyVerified);
    if (NS_SUCCEEDED(rv) && !passwordAlreadyVerified) {
      // First successful login for this server/host during this session.
      m_hostSessionList->SetPasswordVerifiedOnline(GetImapServerKey());
    }

    bool imapPasswordIsNew = !passwordAlreadyVerified;
    if (imapPasswordIsNew) {
      if (m_currentBiffState == nsIMsgFolder::nsMsgBiffState_Unknown) {
        m_currentBiffState = nsIMsgFolder::nsMsgBiffState_NoMail;
        SendSetBiffIndicatorEvent(m_currentBiffState);
      }
      m_imapServerSink->SetUserAuthenticated(true);
    }

    nsImapAction imapAction;
    m_runningUrl->GetImapAction(&imapAction);
    // We don't want to do any more processing if we're just
    // verifying the ability to logon because it can leave us in
    // a half-constructed state.
    if (imapAction != nsIImapUrl::nsImapVerifylogon)
      ProcessAfterAuthenticated();
  } else  // login failed
  {
    MOZ_LOG(IMAP, LogLevel::Error, ("login failed entirely"));
    m_currentBiffState = nsIMsgFolder::nsMsgBiffState_Unknown;
    SendSetBiffIndicatorEvent(m_currentBiffState);
    HandleCurrentUrlError();
    SetConnectionStatus(NS_ERROR_FAILURE);  // stop netlib
  }

  return loginSucceeded;
}

void nsImapProtocol::UpdateFolderQuotaData(nsImapQuotaAction aAction,
                                           nsCString& aQuotaRoot,
                                           uint64_t aUsed, uint64_t aMax) {
  NS_ASSERTION(m_imapMailFolderSink, "m_imapMailFolderSink is null!");

  m_imapMailFolderSink->SetFolderQuotaData(aAction, aQuotaRoot, aUsed, aMax);
}

void nsImapProtocol::GetQuotaDataIfSupported(const char* aBoxName) {
  // If server doesn't have quota support, don't do anything
  if (!(GetServerStateParser().GetCapabilityFlag() & kQuotaCapability)) return;

  nsCString escapedName;
  CreateEscapedMailboxName(aBoxName, escapedName);

  IncrementCommandTagNumber();

  nsAutoCString quotacommand(GetServerCommandTag());
  quotacommand.AppendLiteral(" getquotaroot \"");
  quotacommand.Append(escapedName);
  quotacommand.AppendLiteral("\"" CRLF);

  NS_ASSERTION(m_imapMailFolderSink, "m_imapMailFolderSink is null!");
  if (m_imapMailFolderSink)
    m_imapMailFolderSink->SetFolderQuotaCommandIssued(true);

  nsresult quotarv = SendData(quotacommand.get());
  if (NS_SUCCEEDED(quotarv))
    ParseIMAPandCheckForNewMail(nullptr, true);  // don't display errors.
}

bool nsImapProtocol::GetDeleteIsMoveToTrash() {
  bool rv = false;
  NS_ASSERTION(m_hostSessionList, "fatal... null host session list");
  if (m_hostSessionList)
    m_hostSessionList->GetDeleteIsMoveToTrashForHost(GetImapServerKey(), rv);
  return rv;
}

bool nsImapProtocol::GetShowDeletedMessages() {
  bool rv = false;
  if (m_hostSessionList)
    m_hostSessionList->GetShowDeletedMessagesForHost(GetImapServerKey(), rv);
  return rv;
}

bool nsImapProtocol::CheckNeeded() {
  if (m_flagChangeCount >= kFlagChangesBeforeCheck) return true;

  int32_t deltaInSeconds;

  PRTime2Seconds(PR_Now() - m_lastCheckTime, &deltaInSeconds);

  return (deltaInSeconds >= kMaxSecondsBeforeCheck);
}

bool nsImapProtocol::UseCondStore() {
  // Check that the server is capable of cond store, and the user
  // hasn't disabled the use of constore for this server.
  return m_useCondStore &&
         GetServerStateParser().GetCapabilityFlag() & kHasCondStoreCapability &&
         GetServerStateParser().fUseModSeq;
}

bool nsImapProtocol::UseCompressDeflate() {
  // Check that the server is capable of compression, and the user
  // hasn't disabled the use of compression for this server.
  return m_useCompressDeflate && GetServerStateParser().GetCapabilityFlag() &
                                     kHasCompressDeflateCapability;
}

//////////////////////////////////////////////////////////////////////////////////////////////
// The following is the implementation of nsImapMockChannel and an intermediary
// imap steam listener. The stream listener is used to make a clean binding
// between the imap mock channel and the memory cache channel (if we are reading
// from the cache)
// Used by both offline storage "cache" and by the system cache called "cache2".
//////////////////////////////////////////////////////////////////////////////////////////////

// WARNING: the cache stream listener is intended to be accessed from the UI
// thread! it will NOT create another proxy for the stream listener that gets
// passed in...
class nsImapCacheStreamListener : public nsIStreamListener {
 public:
  NS_DECL_ISUPPORTS
  NS_DECL_NSIREQUESTOBSERVER
  NS_DECL_NSISTREAMLISTENER

  nsImapCacheStreamListener();

  nsresult Init(nsIStreamListener* aStreamListener,
                nsIImapMockChannel* aMockChannelToUse, bool cache2 = false);

 protected:
  virtual ~nsImapCacheStreamListener();
  nsCOMPtr<nsIImapMockChannel> mChannelToUse;
  nsCOMPtr<nsIStreamListener> mListener;
  bool mCache2;    // Initialized for cache2 usage
  bool mStarting;  // Used with cache2. Indicates 1st data segment is read.

 private:
  static bool mGoodCache2;
  static const uint32_t kPeekBufSize;
  static nsresult Peeker(nsIInputStream* aInStr, void* aClosure,
                         const char* aBuffer, uint32_t aOffset, uint32_t aCount,
                         uint32_t* aCountWritten);
};

NS_IMPL_ADDREF(nsImapCacheStreamListener)
NS_IMPL_RELEASE(nsImapCacheStreamListener)

NS_INTERFACE_MAP_BEGIN(nsImapCacheStreamListener)
  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIStreamListener)
  NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
  NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
NS_INTERFACE_MAP_END

nsImapCacheStreamListener::nsImapCacheStreamListener() {}
bool nsImapCacheStreamListener::mGoodCache2 = false;
const uint32_t nsImapCacheStreamListener::kPeekBufSize = 101;

nsImapCacheStreamListener::~nsImapCacheStreamListener() { mStarting = true; }

nsresult nsImapCacheStreamListener::Init(nsIStreamListener* aStreamListener,
                                         nsIImapMockChannel* aMockChannelToUse,
                                         bool aCache2 /*false*/) {
  NS_ENSURE_ARG(aStreamListener);
  NS_ENSURE_ARG(aMockChannelToUse);

  mChannelToUse = aMockChannelToUse;
  mListener = aStreamListener;
  mCache2 = aCache2;
  mStarting = true;

  return NS_OK;
}

NS_IMETHODIMP
nsImapCacheStreamListener::OnStartRequest(nsIRequest* request) {
  if (!mChannelToUse) {
    NS_ERROR("OnStartRequest called after OnStopRequest");
    return NS_ERROR_NULL_POINTER;
  }
  if (!mCache2 || !mStarting) {
    return mListener->OnStartRequest(mChannelToUse);
  }
  return NS_OK;
}

NS_IMETHODIMP
nsImapCacheStreamListener::OnStopRequest(nsIRequest* request,
                                         nsresult aStatus) {
  if (!mListener) {
    NS_ERROR("OnStopRequest called twice");
    return NS_ERROR_NULL_POINTER;
  }

  nsresult rv = NS_OK;
  if (!mCache2 || !mStarting) {
    rv = mListener->OnStopRequest(mChannelToUse, aStatus);

    mListener = nullptr;
    mChannelToUse->Close();
    mChannelToUse = nullptr;
  }
  return rv;
}

/*
 * Called when cache2 is in effect on first available data segment returned
 * to check that cache entry looks like it it a valid email header. With
 * cache2 memory cache this could be done synchronously. But with disk cache
 * it can only be done asynchronously like this.
 * Note: If NS_OK returned, the peeked at bytes are consumed here and not passed
 * on to the listener so a special return value is used.
 */
nsresult nsImapCacheStreamListener::Peeker(nsIInputStream* aInStr,
                                           void* aClosure, const char* aBuffer,
                                           uint32_t aOffset, uint32_t aCount,
                                           uint32_t* aCountWritten) {
  char peekBuf[kPeekBufSize];
  aCount = aCount > sizeof peekBuf ? sizeof peekBuf : aCount;
  memcpy(peekBuf, aBuffer, aCount);
  peekBuf[aCount] = 0;  // Null terminate the starting header data.
  int32_t findPos = MsgFindCharInSet(nsDependentCString(peekBuf), ":\n\r", 0);
  // Check that the first line is a header line, i.e., with a ':' in it
  // Or that it begins with "From " because some IMAP servers allow that,
  // even though it's technically invalid.
  mGoodCache2 = ((findPos != -1 && peekBuf[findPos] == ':') ||
                 !(strncmp(peekBuf, "From ", 5)));
  return NS_BASE_STREAM_WOULD_BLOCK;  // So stream buffer not "consumed"
}

NS_IMETHODIMP
nsImapCacheStreamListener::OnDataAvailable(nsIRequest* request,
                                           nsIInputStream* aInStream,
                                           uint64_t aSourceOffset,
                                           uint32_t aCount) {
  if (mCache2 && mStarting) {
    // Peeker() does check of leading bytes and sets mGoodCache2.
    uint32_t numRead;
    aInStream->ReadSegments(Peeker, nullptr, kPeekBufSize - 1, &numRead);
    MOZ_LOG(IMAPCache, LogLevel::Debug,
            ("%s: mGoodCache2=%d(bool)", __func__, mGoodCache2));

    if (mGoodCache2) {
      // Do deferred setup of loadGroup and OnStartRequest and then forward
      // the verified first segment to the actual listener.
      mStarting = false;
      mListener->OnStartRequest(mChannelToUse);
    } else {
      MOZ_LOG(IMAPCache, LogLevel::Error,
              ("%s: cache entry bad so just read imap here", __func__));
      mChannelToUse->ReadFromImapConnection();
      return NS_ERROR_FAILURE;  // no more starts, one more stop occurs
    }
  }
  // Forward the segment to the actual listener.
  return mListener->OnDataAvailable(mChannelToUse, aInStream, aSourceOffset,
                                    aCount);
}

NS_IMPL_ISUPPORTS_INHERITED(nsImapMockChannel, nsHashPropertyBag,
                            nsIImapMockChannel, nsIMailChannel, nsIChannel,
                            nsIRequest, nsICacheEntryOpenCallback,
                            nsITransportEventSink, nsISupportsWeakReference)

nsImapMockChannel::nsImapMockChannel()
    : mSuspendedMonitor("nsImapMockChannel"), mSuspended(false) {
  m_cancelStatus = NS_OK;
  mLoadFlags = 0;
  mChannelClosed = false;
  mReadingFromCache = false;
  mContentLength = mozilla::dom::InternalResponse::UNKNOWN_BODY_SIZE;
  mContentDisposition = nsIChannel::DISPOSITION_INLINE;
  mWritingToCache = false;
}

nsImapMockChannel::~nsImapMockChannel() {
  // if we're offline, we may not get to close the channel correctly.
  // we need to do this to send the url state change notification in
  // the case of mem and disk cache reads.
  MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread(),
                        "should only access mock channel on ui thread");
  if (!mChannelClosed) Close();
}

nsresult nsImapMockChannel::NotifyStartEndReadFromCache(bool start) {
  nsresult rv = NS_OK;
  mReadingFromCache = start;
  nsCOMPtr<nsIImapUrl> imapUrl = do_QueryInterface(m_url, &rv);
  nsCOMPtr<nsIImapProtocol> imapProtocol = do_QueryReferent(mProtocol);
  if (imapUrl) {
    nsCOMPtr<nsIImapMailFolderSink> folderSink;
    rv = imapUrl->GetImapMailFolderSink(getter_AddRefs(folderSink));
    if (folderSink) {
      nsCOMPtr<nsIMsgMailNewsUrl> mailUrl = do_QueryInterface(m_url);
      rv = folderSink->SetUrlState(nullptr /* we don't know the protocol */,
                                   mailUrl, start, false, m_cancelStatus);

      // Required for killing ImapProtocol thread
      if (NS_FAILED(m_cancelStatus) && imapProtocol)
        imapProtocol->TellThreadToDie(false);
    }
  }
  return rv;
}

NS_IMETHODIMP nsImapMockChannel::Close() {
  if (mReadingFromCache)
    NotifyStartEndReadFromCache(false);
  else {
    nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_url);
    if (mailnewsUrl) {
      nsCOMPtr<nsICacheEntry> cacheEntry;
      mailnewsUrl->GetMemCacheEntry(getter_AddRefs(cacheEntry));
      if (cacheEntry) cacheEntry->MarkValid();
      // remove the channel from the load group
      nsCOMPtr<nsILoadGroup> loadGroup;
      GetLoadGroup(getter_AddRefs(loadGroup));
      // if the mock channel wasn't initialized with a load group then
      // use our load group (they may differ)
      if (!loadGroup) mailnewsUrl->GetLoadGroup(getter_AddRefs(loadGroup));
      if (loadGroup)
        loadGroup->RemoveRequest((nsIRequest*)this, nullptr, NS_OK);
    }
  }

  m_channelListener = nullptr;
  mChannelClosed = true;
  return NS_OK;
}

NS_IMETHODIMP nsImapMockChannel::GetProgressEventSink(
    nsIProgressEventSink** aProgressEventSink) {
  NS_IF_ADDREF(*aProgressEventSink = mProgressEventSink);
  return NS_OK;
}

NS_IMETHODIMP nsImapMockChannel::SetProgressEventSink(
    nsIProgressEventSink* aProgressEventSink) {
  mProgressEventSink = aProgressEventSink;
  return NS_OK;
}

NS_IMETHODIMP nsImapMockChannel::GetChannelListener(
    nsIStreamListener** aChannelListener) {
  NS_IF_ADDREF(*aChannelListener = m_channelListener);
  return NS_OK;
}

// now implement our mock implementation of the channel interface...we forward
// all calls to the real channel if we have one...otherwise we return something
// bogus...

NS_IMETHODIMP nsImapMockChannel::SetLoadGroup(nsILoadGroup* aLoadGroup) {
  m_loadGroup = aLoadGroup;
  return NS_OK;
}

NS_IMETHODIMP nsImapMockChannel::GetLoadGroup(nsILoadGroup** aLoadGroup) {
  NS_IF_ADDREF(*aLoadGroup = m_loadGroup);
  return NS_OK;
}

NS_IMETHODIMP nsImapMockChannel::GetTRRMode(nsIRequest::TRRMode* aTRRMode) {
  return GetTRRModeImpl(aTRRMode);
}

NS_IMETHODIMP nsImapMockChannel::SetTRRMode(nsIRequest::TRRMode aTRRMode) {
  return SetTRRModeImpl(aTRRMode);
}

NS_IMETHODIMP nsImapMockChannel::GetLoadInfo(nsILoadInfo** aLoadInfo) {
  NS_IF_ADDREF(*aLoadInfo = m_loadInfo);
  return NS_OK;
}

NS_IMETHODIMP nsImapMockChannel::SetLoadInfo(nsILoadInfo* aLoadInfo) {
  m_loadInfo = aLoadInfo;
  return NS_OK;
}

NS_IMETHODIMP nsImapMockChannel::GetOriginalURI(nsIURI** aURI) {
  // IMap does not seem to have the notion of an original URI :-(
  //  *aURI = m_originalUrl ? m_originalUrl : m_url;
  NS_IF_ADDREF(*aURI = m_url);
  return NS_OK;
}

NS_IMETHODIMP nsImapMockChannel::SetOriginalURI(nsIURI* aURI) {
  // IMap does not seem to have the notion of an original URI :-(
  //    MOZ_ASSERT_UNREACHABLE("nsImapMockChannel::SetOriginalURI");
  //    return NS_ERROR_NOT_IMPLEMENTED;
  return NS_OK;  // ignore
}

NS_IMETHODIMP nsImapMockChannel::GetURI(nsIURI** aURI) {
  NS_IF_ADDREF(*aURI = m_url);
  return NS_OK;
}

NS_IMETHODIMP nsImapMockChannel::SetURI(nsIURI* aURI) {
  m_url = aURI;
#ifdef DEBUG_bienvenu
  if (!aURI) printf("Clearing URI\n");
#endif
  if (m_url) {
    // if we don't have a progress event sink yet, get it from the url for
    // now...
    nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_url);
    if (mailnewsUrl && !mProgressEventSink) {
      nsCOMPtr<nsIMsgStatusFeedback> statusFeedback;
      mailnewsUrl->GetStatusFeedback(getter_AddRefs(statusFeedback));
      mProgressEventSink = do_QueryInterface(statusFeedback);
    }
    // If this is a fetch URL and we can, get the message size from the message
    // header and set it to be the content length.
    // Note that for an attachment URL, this will set the content length to be
    // equal to the size of the entire message.
    nsCOMPtr<nsIImapUrl> imapUrl(do_QueryInterface(m_url));
    nsImapAction imapAction;
    imapUrl->GetImapAction(&imapAction);
    if (imapAction == nsIImapUrl::nsImapMsgFetch) {
      nsCOMPtr<nsIMsgMessageUrl> msgUrl(do_QueryInterface(m_url));
      if (msgUrl) {
        nsCOMPtr<nsIMsgDBHdr> msgHdr;
        // A failure to get a message header isn't an error
        msgUrl->GetMessageHeader(getter_AddRefs(msgHdr));
        if (msgHdr) {
          uint32_t messageSize;
          if (NS_SUCCEEDED(msgHdr->GetMessageSize(&messageSize)))
            SetContentLength(messageSize);
        }
      }
    }
  }
  return NS_OK;
}

NS_IMETHODIMP nsImapMockChannel::Open(nsIInputStream** _retval) {
  nsCOMPtr<nsIStreamListener> listener;
  nsresult rv =
      nsContentSecurityManager::doContentSecurityCheck(this, listener);
  NS_ENSURE_SUCCESS(rv, rv);
  if (m_url) {
    nsCOMPtr<nsIImapUrl> imapUrl(do_QueryInterface(m_url, &rv));
    NS_ENSURE_SUCCESS(rv, rv);
    nsImapAction imapAction;
    imapUrl->GetImapAction(&imapAction);
    // If we're shutting down, and not running the kinds of urls we run at
    // shutdown, then this should fail because running urls during
    // shutdown will very likely fail and potentially hang.
    nsCOMPtr<nsIMsgAccountManager> accountMgr =
        do_GetService("@mozilla.org/messenger/account-manager;1", &rv);
    NS_ENSURE_SUCCESS(rv, rv);
    bool shuttingDown = false;
    (void)accountMgr->GetShutdownInProgress(&shuttingDown);
    if (shuttingDown && imapAction != nsIImapUrl::nsImapExpungeFolder &&
        imapAction != nsIImapUrl::nsImapDeleteAllMsgs &&
        imapAction != nsIImapUrl::nsImapDeleteFolder)
      return NS_ERROR_FAILURE;
  }
  return NS_ImplementChannelOpen(this, _retval);
}

NS_IMETHODIMP
nsImapMockChannel::OnCacheEntryAvailable(nsICacheEntry* entry, bool aNew,
                                         nsresult status) {
  if (MOZ_LOG_TEST(IMAPCache, LogLevel::Debug)) {
    MOZ_LOG(IMAPCache, LogLevel::Debug,
            ("%s: Create/write new cache entry=%s", __func__,
             aNew ? "true" : "false"));
    if (NS_SUCCEEDED(status)) {
      nsAutoCString key;
      entry->GetKey(key);
      MOZ_LOG(IMAPCache, LogLevel::Debug,
              ("%s: Cache entry key = |%s|", __func__, key.get()));
    }
  }

  // make sure we didn't close the channel before the async call back came in...
  // hmmm....if we had write access and we canceled this mock channel then I
  // wonder if we should be invalidating the cache entry before kicking out...
  if (mChannelClosed) {
    if (NS_SUCCEEDED(status)) {
      entry->AsyncDoom(nullptr);
    }
    return NS_OK;
  }

  if (!m_url) {
    // Something has gone terribly wrong.
    NS_WARNING("m_url is null in OnCacheEntryAvailable");
    return Cancel(NS_ERROR_UNEXPECTED);
  }

  do {
    // For "normal" read/write access we always see status == NS_OK here. aNew
    // indicates whether the cache entry is new and needs to be written, or not
    // new and can be read. If AsyncOpenURI() was called with access read-only,
    // status == NS_ERROR_CACHE_KEY_NOT_FOUND can be received here and we just
    // read the data directly from imap.
    if (NS_FAILED(status)) {
      MOZ_LOG(IMAPCache, LogLevel::Debug,
              ("%s: status parameter bad, preference "
               "browser.cache.memory.enable not true?",
               __func__));
      break;
    }

    nsresult rv = NS_OK;
    nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_url, &rv);
    mailnewsUrl->SetMemCacheEntry(entry);

    if (aNew) {
      // Writing cache so insert a "stream listener Tee" into the stream from
      // the imap fetch to direct the message into the cache and to our current
      // channel listener. But first get the size of the message to be fetched.
      // If message too big to fit in cache, the message just goes to the
      // stream listener. If unable to get the size, messageSize remains 0 so
      // assume it fits in cache, right or wrong.
      uint32_t messageSize = 0;
      nsCOMPtr<nsIMsgMessageUrl> msgUrl(do_QueryInterface(m_url));
      if (msgUrl) {
        nsCOMPtr<nsIMsgDBHdr> msgHdr;
        msgUrl->GetMessageHeader(getter_AddRefs(msgHdr));
        if (msgHdr) {
          msgHdr->GetMessageSize(&messageSize);
          MOZ_LOG(IMAPCache, LogLevel::Debug,
                  ("%s: messageSize=%d", __func__, messageSize));
        } else
          MOZ_LOG(IMAPCache, LogLevel::Debug,
                  ("%s: Can't get msgHdr", __func__));
      }
      // Check if message fits in a cache entry. If too big, or if unable to
      // create or initialize the tee or open the stream to the entry, will
      // fall thought and only do ReadFromImapConnection() called below and the
      // message will not be cached.
      bool tooBig =
          net::CacheObserver::EntryIsTooBig(messageSize, gUseDiskCache2);
      if (!tooBig) {
        // Message fits in cache. Create the tee.
        nsCOMPtr<nsIStreamListenerTee> tee =
            do_CreateInstance(NS_STREAMLISTENERTEE_CONTRACTID, &rv);
        if (NS_SUCCEEDED(rv)) {
          nsCOMPtr<nsIOutputStream> out;
          rv = entry->OpenOutputStream(0, -1, getter_AddRefs(out));
          if (NS_SUCCEEDED(rv)) {
            rv = tee->Init(m_channelListener, out, nullptr);
            m_channelListener = tee;
          } else
            NS_WARNING(
                "IMAP Protocol failed to open output stream to Necko cache");
        }
      }
      if (tooBig || NS_FAILED(rv)) {
        // Need this so next OpenCacheEntry() triggers OnCacheEntryAvailable()
        // since nothing was actually written to cache. Without this there is no
        // response to next OpenCacheEntry call.
        entry->AsyncDoom(nullptr);
        MOZ_LOG(IMAPCache, LogLevel::Debug,
                ("%s: Not writing to cache, msg too big or other errors",
                 __func__));
      } else {
        mWritingToCache = true;
        MOZ_LOG(IMAPCache, LogLevel::Debug,
                ("%s: Begin cache WRITE", __func__));
      }
    } else {
      // We are reading cache (!aNew)
      mWritingToCache = false;
      if (MOZ_LOG_TEST(IMAPCache, LogLevel::Debug)) {
        int64_t size = 0;
        rv = entry->GetDataSize(&size);
        if (rv == NS_ERROR_IN_PROGRESS)
          MOZ_LOG(IMAPCache, LogLevel::Debug,
                  ("%s: Concurrent cache READ, no size available", __func__));
        MOZ_LOG(IMAPCache, LogLevel::Debug,
                ("%s: Begin cache READ, size=%" PRIi64, __func__, size));
      }
      rv = ReadFromCache2(entry);
      if (NS_SUCCEEDED(rv)) {
        NotifyStartEndReadFromCache(true);
        entry->MarkValid();
        return NS_OK;  // Return here since reading from the cache succeeded.
      }
      entry->AsyncDoom(nullptr);  // Doom entry if we failed to read from cache.
      mailnewsUrl->SetMemCacheEntry(
          nullptr);  // We aren't going to be reading from the cache.
    }
  } while (false);

  // If reading from the cache failed or if we are writing into the cache, or if
  // or message is too big for cache or other errors occur, do
  // ReadFromImapConnection to fetch message from imap server.
  if (!aNew)
    MOZ_LOG(IMAPCache, LogLevel::Debug,
            ("%s: Cache READ failed so read from imap", __func__));
  return ReadFromImapConnection();
}

NS_IMETHODIMP
nsImapMockChannel::OnCacheEntryCheck(nsICacheEntry* entry, uint32_t* aResult) {
  *aResult = nsICacheEntryOpenCallback::ENTRY_WANTED;

  // Check concurrent read: We can't read concurrently since we don't know
  // that the entry will ever be written successfully. It may be aborted
  // due to a size limitation. If reading concurrently, the following function
  // will return NS_ERROR_IN_PROGRESS. Then we tell the cache to wait until
  // the write is finished.
  int64_t size = 0;
  nsresult rv = entry->GetDataSize(&size);
  if (rv == NS_ERROR_IN_PROGRESS) {
    *aResult = nsICacheEntryOpenCallback::RECHECK_AFTER_WRITE_FINISHED;
    MOZ_LOG(IMAPCache, LogLevel::Debug,
            ("OnCacheEntryCheck(): Attempted cache write while reading, will "
             "try again"));
  }
  return NS_OK;
}

nsresult nsImapMockChannel::OpenCacheEntry() {
  nsresult rv;
  if (!gCache2Storage) {
    // Only need to do this once since cache2 is used by all accounts and
    // folders. Get the cache storage object from the imap service.
    nsCOMPtr<nsIImapService> imapService =
        do_GetService("@mozilla.org/messenger/imapservice;1", &rv);
    NS_ENSURE_SUCCESS(rv, rv);

    // Obtain the cache storage object used by all channels in this session.
    // This will return disk cache (default) or memory cache as determined by
    // the boolean pref "mail.imap.use_disk_cache2"
    rv = imapService->GetCacheStorage(getter_AddRefs(gCache2Storage));
    NS_ENSURE_SUCCESS(rv, rv);
    MOZ_LOG(IMAPCache, LogLevel::Debug,
            ("%s: Obtained storage obj for |%s| cache2", __func__,
             gUseDiskCache2 ? "disk" : "mem"));
  }

  int32_t uidValidity = -1;
  uint32_t cacheAccess = nsICacheStorage::OPEN_NORMALLY;

  nsCOMPtr<nsIImapUrl> imapUrl = do_QueryInterface(m_url, &rv);
  NS_ENSURE_SUCCESS(rv, rv);

  nsCOMPtr<nsIImapMailFolderSink> folderSink;
  rv = imapUrl->GetImapMailFolderSink(getter_AddRefs(folderSink));
  if (folderSink) folderSink->GetUidValidity(&uidValidity);

  // If we're storing the message in the offline store, don't
  // write/save to cache2 cache. (Not sure if this even happens!)
  bool storeResultsOffline;
  imapUrl->GetStoreResultsOffline(&storeResultsOffline);
  if (storeResultsOffline) cacheAccess = nsICacheStorage::OPEN_READONLY;

  // clang-format off
  MOZ_LOG(IMAPCache, LogLevel::Debug,
          ("%s: For URL = |%s|", __func__, m_url->GetSpecOrDefault().get()));
  // clang-format on

  // Use the uid validity as part of the cache key, so that if the uid validity
  // changes, we won't re-use the wrong cache entries.
  nsAutoCString extension;
  extension.AppendInt(uidValidity, 16);

  // Open a cache entry where the key is the potentially modified URL.
  nsAutoCString path;
  m_url->GetPathQueryRef(path);

  // First we need to "normalise" the URL by extracting ?part= and &filename.
  // The path should only contain: ?part=x.y&filename=file.ext
  // These are seen in the wild:
  // /;section=2?part=1.2&filename=A01.JPG
  // ?section=2?part=1.2&filename=A01.JPG&type=image/jpeg&filename=A01.JPG
  // ?part=1.2&type=image/jpeg&filename=IMG_C0030.jpg
  // ?header=quotebody&part=1.2&filename=lijbmghmkilicioj.png
  nsCString partQuery = MsgExtractQueryPart(path, "?part=");
  if (partQuery.IsEmpty()) {
    partQuery = MsgExtractQueryPart(path, "&part=");
    if (!partQuery.IsEmpty()) {
      // ? indicates a part query, so set the first character to that.
      partQuery.SetCharAt('?', 0);
    }
  }
  nsCString filenameQuery = MsgExtractQueryPart(path, "&filename=");
  MOZ_LOG(IMAPCache, LogLevel::Debug,
          ("%s: part = |%s|, filename = |%s|", __func__, partQuery.get(),
           filenameQuery.get()));

  // Truncate path at either /; or ?
  MsgRemoveQueryPart(path);

  nsCOMPtr<nsIURI> newUri;
  rv = NS_MutateURI(m_url).SetPathQueryRef(path).Finalize(newUri);
  NS_ENSURE_SUCCESS(rv, rv);
  if (partQuery.IsEmpty()) {
    // Not accessing a part but the whole message.
    MOZ_LOG(IMAPCache, LogLevel::Debug,
            ("%s: Call AsyncOpenURI on entire message", __func__));
  } else {
    // Access just a part. Set up part extraction and read in the part from the
    // whole cached message. Note: Parts are now never individually written to
    // or read from cache.
    SetupPartExtractorListener(imapUrl, m_channelListener);
    MOZ_LOG(IMAPCache, LogLevel::Debug,
            ("%s: Call AsyncOpenURI to read part from entire message cache",
             __func__));
  }
  return gCache2Storage->AsyncOpenURI(newUri, extension, cacheAccess, this);
}

// Pumps content of cache2 entry to channel listener. If a part was
// requested in the original URL seen in OpenCacheEntry(), it will be extracted
// from the whole message by the channel listener. So to obtain a single part
// always requires reading the complete message from cache.
nsresult nsImapMockChannel::ReadFromCache2(nsICacheEntry* entry) {
  NS_ENSURE_ARG(entry);

  bool useCacheEntry = true;
  nsresult rv;
  nsAutoCString entryKey;

  entry->GetKey(entryKey);

  // Compare cache entry size with message size. Init to an invalid value.
  int64_t entrySize = -1;

  // We don't expect concurrent read here, so this call should always work.
  rv = entry->GetDataSize(&entrySize);

  nsCOMPtr<nsIMsgMessageUrl> msgUrl(do_QueryInterface(m_url));
  if (msgUrl && NS_SUCCEEDED(rv)) {
    nsCOMPtr<nsIMsgDBHdr> msgHdr;
    // A failure to get a message header isn't an automatic error
    msgUrl->GetMessageHeader(getter_AddRefs(msgHdr));
    if (msgHdr) {
      uint32_t messageSize;
      if (NS_SUCCEEDED(rv = msgHdr->GetMessageSize(&messageSize)) &&
          messageSize != entrySize) {
        // clang-format off
        MOZ_LOG(IMAP, LogLevel::Warning,
                ("%s: Size mismatch for %s: message %" PRIu32
                 ", cache %" PRIi64,
                 __func__, entryKey.get(), messageSize, entrySize));
        MOZ_LOG(IMAPCache, LogLevel::Debug,
                ("%s: Size mismatch for %s: message %" PRIu32
                 ", cache %" PRIi64,
                 __func__, entryKey.get(), messageSize, entrySize));
        // clang-format on
        useCacheEntry = false;
      }
    }
  }
  // Cache entry is invalid if GetDataSize() or GetMessageSize failed or if
  // otherwise unable to obtain the cache entry size. (Not sure if it's possible
  // to have a 0 length cache entry but negative is definitely invalid.)
  if (NS_FAILED(rv) || entrySize < 1) useCacheEntry = false;

  nsCOMPtr<nsIInputStream> ins;
  if (useCacheEntry) {
    if (NS_SUCCEEDED(rv = entry->OpenInputStream(0, getter_AddRefs(ins)))) {
      uint64_t bytesAvailable = 0;
      rv = ins->Available(&bytesAvailable);
      // Note: bytesAvailable will usually be zero (at least for disk cache
      // since only async access occurs) so don't check it.
      if (NS_FAILED(rv)) {
        MOZ_LOG(IMAPCache, LogLevel::Debug,
                ("%s: Input stream for disk cache not useable", __func__));
        useCacheEntry = false;
      }
    }
  }

  if (NS_SUCCEEDED(rv) && useCacheEntry) {
    nsCOMPtr<nsIInputStreamPump> pump;
    if (NS_SUCCEEDED(
            rv = NS_NewInputStreamPump(getter_AddRefs(pump), ins.forget()))) {
      // Create and use a cache listener object.
      RefPtr<nsImapCacheStreamListener> cacheListener =
          new nsImapCacheStreamListener();

      cacheListener->Init(m_channelListener, this, true);
      rv = pump->AsyncRead(cacheListener);
      if (NS_SUCCEEDED(rv)) {
        nsCOMPtr<nsIImapUrl> imapUrl = do_QueryInterface(m_url);
        imapUrl->SetMsgLoadingFromCache(true);
        // Set the cache entry's security info status as our security
        // info status...
        nsCOMPtr<nsITransportSecurityInfo> securityInfo;
        entry->GetSecurityInfo(getter_AddRefs(securityInfo));
        SetSecurityInfo(securityInfo);
        MOZ_LOG(IMAPCache, LogLevel::Debug,
                ("%s: Cache entry accepted and being read", __func__));
      }  // if AsyncRead succeeded.
    }    // new pump
  }      // if useCacheEntry

  if (!useCacheEntry || NS_FAILED(rv)) {
    // Cache entry appears to be unusable. Return an error so will still attempt
    // to read the data via just an imap fetch (the "old fashioned" way).
    if (NS_SUCCEEDED(rv)) rv = NS_ERROR_FAILURE;
    MOZ_LOG(IMAPCache, LogLevel::Debug,
            ("%s: Cache entry rejected, returning error %" PRIx32, __func__,
             static_cast<uint32_t>(rv)));
  }
  return rv;
}

class nsReadFromImapConnectionFailure : public mozilla::Runnable {
 public:
  explicit nsReadFromImapConnectionFailure(nsImapMockChannel* aChannel)
      : mozilla::Runnable("nsReadFromImapConnectionFailure"),
        mImapMockChannel(aChannel) {}

  NS_IMETHOD Run() {
    if (mImapMockChannel) {
      mImapMockChannel->RunOnStopRequestFailure();
    }
    return NS_OK;
  }

 private:
  RefPtr<nsImapMockChannel> mImapMockChannel;
};

nsresult nsImapMockChannel::RunOnStopRequestFailure() {
  if (m_channelListener) {
    m_channelListener->OnStopRequest(this, NS_MSG_ERROR_MSG_NOT_OFFLINE);
  }
  return NS_OK;
}

// This is called when the message requested by the url isn't yet in offline
// store or not yet in cache. It is also called if the storage is corrupt. This
// creates an imap connection to process the url. This is usually called from
// the mock channel or possibly from nsImapCacheStreamListener::OnDataAvailable.
NS_IMETHODIMP nsImapMockChannel::ReadFromImapConnection() {
  nsresult rv = NS_OK;
  nsCOMPtr<nsIImapUrl> imapUrl = do_QueryInterface(m_url);
  nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_url);

  bool localOnly = false;
  imapUrl->GetLocalFetchOnly(&localOnly);
  if (localOnly) {
    // This will cause an OnStartRunningUrl, and the subsequent close
    // will then cause an OnStopRunningUrl with the cancel status.
    NotifyStartEndReadFromCache(true);
    Cancel(NS_MSG_ERROR_MSG_NOT_OFFLINE);

    // Dispatch error notification, so ReadFromImapConnection() returns *before*
    // the error is sent to the listener's OnStopRequest(). This avoids
    // endless recursion where the caller relies on async execution.
    nsCOMPtr<nsIRunnable> event = new nsReadFromImapConnectionFailure(this);
    NS_DispatchToCurrentThread(event);
    return NS_MSG_ERROR_MSG_NOT_OFFLINE;
  }

  nsCOMPtr<nsILoadGroup> loadGroup;
  GetLoadGroup(getter_AddRefs(loadGroup));
  if (!loadGroup)  // if we don't have one, the url will snag one from the msg
                   // window...
    mailnewsUrl->GetLoadGroup(getter_AddRefs(loadGroup));

  // okay, add the mock channel to the load group..
  if (loadGroup)
    loadGroup->AddRequest((nsIRequest*)this, nullptr /* context isupports */);

  // loading the url consists of asking the server to add the url to it's imap
  // event queue....
  nsCOMPtr<nsIMsgIncomingServer> server;
  rv = mailnewsUrl->GetServer(getter_AddRefs(server));
  NS_ENSURE_SUCCESS(rv, rv);
  nsCOMPtr<nsIImapIncomingServer> imapServer(do_QueryInterface(server, &rv));
  NS_ENSURE_SUCCESS(rv, rv);

  // Assume AsyncRead is always called from the UI thread.....
  return imapServer->GetImapConnectionAndLoadUrl(imapUrl, m_channelListener);
}

// for messages stored in our offline cache, we have special code to handle
// that... If it's in the local cache, we return true and we can abort the
// download because this method does the rest of the work.
bool nsImapMockChannel::ReadFromLocalCache() {
  nsresult rv = NS_OK;

  nsCOMPtr<nsIImapUrl> imapUrl = do_QueryInterface(m_url);
  nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_url, &rv);

  bool useLocalCache = false;
  mailnewsUrl->GetMsgIsInLocalCache(&useLocalCache);
  if (!useLocalCache) {
    return false;
  }

  nsAutoCString messageIdString;

  SetupPartExtractorListener(imapUrl, m_channelListener);

  imapUrl->GetListOfMessageIds(messageIdString);
  nsCOMPtr<nsIMsgFolder> folder;
  rv = mailnewsUrl->GetFolder(getter_AddRefs(folder));
  NS_ENSURE_SUCCESS(rv, false);
  if (!folder) {
    return false;
  }
  // we want to create a file channel and read the msg from there.
  nsMsgKey msgKey = strtoul(messageIdString.get(), nullptr, 10);
  nsCOMPtr<nsIMsgDBHdr> hdr;
  rv = folder->GetMessageHeader(msgKey, getter_AddRefs(hdr));
  NS_ENSURE_SUCCESS(rv, false);
  nsCOMPtr<nsIInputStream> msgStream;
  rv = folder->GetLocalMsgStream(hdr, getter_AddRefs(msgStream));
  NS_ENSURE_SUCCESS(rv, false);
  // dougt - This may break the ablity to "cancel" a read from offline
  // mail reading. fileChannel->SetLoadGroup(m_loadGroup);
  RefPtr<nsImapCacheStreamListener> cacheListener =
      new nsImapCacheStreamListener();
  cacheListener->Init(m_channelListener, this);

  // create a stream pump that will async read the message.
  nsCOMPtr<nsIInputStreamPump> pump;
  rv = NS_NewInputStreamPump(getter_AddRefs(pump), msgStream.forget());
  NS_ENSURE_SUCCESS(rv, false);
  rv = pump->AsyncRead(cacheListener);
  NS_ENSURE_SUCCESS(rv, false);

  // if the msg is unread, we should mark it read on the server. This lets
  // the code running this url know we're loading from the cache, if it cares.
  imapUrl->SetMsgLoadingFromCache(true);
  return true;
}

NS_IMETHODIMP nsImapMockChannel::AsyncOpen(nsIStreamListener* aListener) {
  nsCOMPtr<nsIStreamListener> listener = aListener;
  nsresult rv =
      nsContentSecurityManager::doContentSecurityCheck(this, listener);
  NS_ENSURE_SUCCESS(rv, rv);

  int32_t port;
  if (!m_url) return NS_ERROR_NULL_POINTER;
  rv = m_url->GetPort(&port);
  if (NS_FAILED(rv)) return rv;

  rv = NS_CheckPortSafety(port, "imap");
  if (NS_FAILED(rv)) return rv;

  // set the stream listener and then load the url
  NS_ASSERTION(!m_channelListener, "shouldn't already have a listener");
  m_channelListener = listener;
  nsCOMPtr<nsIImapUrl> imapUrl(do_QueryInterface(m_url));

  nsImapAction imapAction;
  imapUrl->GetImapAction(&imapAction);

  bool externalLink = true;
  imapUrl->GetExternalLinkUrl(&externalLink);

  if (externalLink) {
    // for security purposes, only allow imap urls originating from external
    // sources perform a limited set of actions. Currently the allowed set
    // includes: 1) folder selection 2) message fetch 3) message part fetch

    if (!(imapAction == nsIImapUrl::nsImapSelectFolder ||
          imapAction == nsIImapUrl::nsImapMsgFetch ||
          imapAction == nsIImapUrl::nsImapOpenMimePart ||
          imapAction == nsIImapUrl::nsImapMsgFetchPeek))
      return NS_ERROR_FAILURE;  // abort the running of this url....it failed a
                                // security check
  }

  if (ReadFromLocalCache()) {
    (void)NotifyStartEndReadFromCache(true);
    return NS_OK;
  }

  // okay, it's not in the local cache, now check the memory cache...
  // but we can't download for offline use from the memory cache
  if (imapAction != nsIImapUrl::nsImapMsgDownloadForOffline) {
    rv = OpenCacheEntry();
    if (NS_SUCCEEDED(rv)) return rv;
  }

  SetupPartExtractorListener(imapUrl, m_channelListener);
  // if for some reason open cache entry failed then just default to opening an
  // imap connection for the url
  return ReadFromImapConnection();
}

nsresult nsImapMockChannel::SetupPartExtractorListener(
    nsIImapUrl* aUrl, nsIStreamListener* aConsumer) {
  // if the url we are loading refers to a specific part then we need
  // libmime to extract that part from the message for us.
  bool refersToPart = false;
  aUrl->GetMimePartSelectorDetected(&refersToPart);
  if (refersToPart) {
    nsCOMPtr<nsIStreamConverterService> converter =
        do_GetService("@mozilla.org/streamConverters;1");
    if (converter && aConsumer) {
      nsCOMPtr<nsIStreamListener> newConsumer;
      converter->AsyncConvertData("message/rfc822", "*/*", aConsumer,
                                  static_cast<nsIChannel*>(this),
                                  getter_AddRefs(newConsumer));
      if (newConsumer) m_channelListener = newConsumer;
    }
  }

  return NS_OK;
}

NS_IMETHODIMP nsImapMockChannel::GetLoadFlags(nsLoadFlags* aLoadFlags) {
  //*aLoadFlags = nsIRequest::LOAD_NORMAL;
  *aLoadFlags = mLoadFlags;
  return NS_OK;
}

NS_IMETHODIMP nsImapMockChannel::SetLoadFlags(nsLoadFlags aLoadFlags) {
  mLoadFlags = aLoadFlags;
  return NS_OK;  // don't fail when trying to set this
}

NS_IMETHODIMP nsImapMockChannel::GetContentType(nsACString& aContentType) {
  if (mContentType.IsEmpty()) {
    nsImapAction imapAction = 0;
    if (m_url) {
      nsCOMPtr<nsIImapUrl> imapUrl = do_QueryInterface(m_url);
      if (imapUrl) {
        imapUrl->GetImapAction(&imapAction);
      }
    }
    if (imapAction == nsIImapUrl::nsImapSelectFolder)
      aContentType.AssignLiteral("x-application-imapfolder");
    else
      aContentType.AssignLiteral("message/rfc822");
  } else
    aContentType = mContentType;
  return NS_OK;
}

NS_IMETHODIMP nsImapMockChannel::SetContentType(
    const nsACString& aContentType) {
  nsAutoCString charset;
  nsresult rv =
      NS_ParseResponseContentType(aContentType, mContentType, charset);
  if (NS_FAILED(rv) || mContentType.IsEmpty())
    mContentType.AssignLiteral(UNKNOWN_CONTENT_TYPE);
  return rv;
}

NS_IMETHODIMP nsImapMockChannel::GetContentCharset(
    nsACString& aContentCharset) {
  aContentCharset.Assign(mCharset);
  return NS_OK;
}

NS_IMETHODIMP nsImapMockChannel::SetContentCharset(
    const nsACString& aContentCharset) {
  mCharset.Assign(aContentCharset);
  return NS_OK;
}

NS_IMETHODIMP
nsImapMockChannel::GetContentDisposition(uint32_t* aContentDisposition) {
  *aContentDisposition = mContentDisposition;
  return NS_OK;
}

NS_IMETHODIMP
nsImapMockChannel::SetContentDisposition(uint32_t aContentDisposition) {
  mContentDisposition = aContentDisposition;
  return NS_OK;
}

NS_IMETHODIMP
nsImapMockChannel::GetContentDispositionFilename(
    nsAString& aContentDispositionFilename) {
  return NS_ERROR_NOT_AVAILABLE;
}

NS_IMETHODIMP
nsImapMockChannel::SetContentDispositionFilename(
    const nsAString& aContentDispositionFilename) {
  return NS_ERROR_NOT_AVAILABLE;
}

NS_IMETHODIMP
nsImapMockChannel::GetContentDispositionHeader(
    nsACString& aContentDispositionHeader) {
  return NS_ERROR_NOT_AVAILABLE;
}

NS_IMETHODIMP nsImapMockChannel::GetContentLength(int64_t* aContentLength) {
  *aContentLength = mContentLength;
  return NS_OK;
}

NS_IMETHODIMP
nsImapMockChannel::SetContentLength(int64_t aContentLength) {
  mContentLength = aContentLength;
  return NS_OK;
}

NS_IMETHODIMP nsImapMockChannel::GetOwner(nsISupports** aPrincipal) {
  NS_IF_ADDREF(*aPrincipal = mOwner);
  return NS_OK;
}

NS_IMETHODIMP nsImapMockChannel::SetOwner(nsISupports* aPrincipal) {
  mOwner = aPrincipal;
  return NS_OK;
}

NS_IMETHODIMP nsImapMockChannel::GetSecurityInfo(
    nsITransportSecurityInfo** aSecurityInfo) {
  NS_IF_ADDREF(*aSecurityInfo = mSecurityInfo);
  return NS_OK;
}

NS_IMETHODIMP nsImapMockChannel::SetSecurityInfo(
    nsITransportSecurityInfo* aSecurityInfo) {
  mSecurityInfo = aSecurityInfo;
  return NS_OK;
}

NS_IMETHODIMP
nsImapMockChannel::GetIsDocument(bool* aIsDocument) {
  return NS_GetIsDocumentChannel(this, aIsDocument);
}

////////////////////////////////////////////////////////////////////////////////
// From nsIRequest
////////////////////////////////////////////////////////////////////////////////

NS_IMETHODIMP nsImapMockChannel::GetName(nsACString& result) {
  if (m_url) return m_url->GetSpec(result);
  result.Truncate();
  return NS_OK;
}

NS_IMETHODIMP nsImapMockChannel::IsPending(bool* result) {
  *result = m_channelListener != nullptr;
  return NS_OK;
}

NS_IMETHODIMP nsImapMockChannel::GetStatus(nsresult* status) {
  *status = m_cancelStatus;
  return NS_OK;
}

NS_IMETHODIMP nsImapMockChannel::SetWritingToCache(bool aWriting) {
  mWritingToCache = aWriting;
  return NS_OK;
}

NS_IMETHODIMP nsImapMockChannel::GetWritingToCache(bool* result) {
  *result = mWritingToCache;
  return NS_OK;
}

NS_IMETHODIMP nsImapMockChannel::SetImapProtocol(nsIImapProtocol* aProtocol) {
  mProtocol = do_GetWeakReference(aProtocol);
  return NS_OK;
}

NS_IMETHODIMP nsImapMockChannel::SetCanceledReason(const nsACString& aReason) {
  return SetCanceledReasonImpl(aReason);
}

NS_IMETHODIMP nsImapMockChannel::GetCanceledReason(nsACString& aReason) {
  return GetCanceledReasonImpl(aReason);
}

NS_IMETHODIMP nsImapMockChannel::CancelWithReason(nsresult aStatus,
                                                  const nsACString& aReason) {
  return CancelWithReasonImpl(aStatus, aReason);
}

NS_IMETHODIMP nsImapMockChannel::Cancel(nsresult status) {
  MOZ_DIAGNOSTIC_ASSERT(
      NS_IsMainThread(),
      "nsImapMockChannel::Cancel should only be called from UI thread");
  MOZ_LOG(IMAPCache, LogLevel::Debug,
          ("nsImapMockChannel::%s: entering", __func__));
  m_cancelStatus = status;
  nsCOMPtr<nsIImapProtocol> imapProtocol = do_QueryReferent(mProtocol);

  // if we aren't reading from the cache and we get canceled...doom our cache
  // entry if write is still in progress...
  if (m_url) {
    nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_url);
    MOZ_LOG(IMAPCache, LogLevel::Debug,
            ("%s: Doom cache entry only if writing=%d(bool), url=%s", __func__,
             mWritingToCache, m_url->GetSpecOrDefault().get()));
    if (mWritingToCache) DoomCacheEntry(mailnewsUrl);
  }

  // The associated ImapProtocol thread must be unblocked before being killed.
  // Otherwise, it will be deadlocked.
  ResumeAndNotifyOne();

  // Required for killing ImapProtocol thread
  if (imapProtocol) imapProtocol->TellThreadToDie(false);

  return NS_OK;
}

NS_IMETHODIMP nsImapMockChannel::GetCanceled(bool* aCanceled) {
  nsresult status = NS_ERROR_FAILURE;
  GetStatus(&status);
  *aCanceled = NS_FAILED(status);
  return NS_OK;
}

/**
 * Suspends the current request.  This may have the effect of closing
 * any underlying transport (in order to free up resources), although
 * any open streams remain logically opened and will continue delivering
 * data when the transport is resumed.
 *
 * Calling cancel() on a suspended request must not send any
 * notifications (such as onstopRequest) until the request is resumed.
 *
 * NOTE: some implementations are unable to immediately suspend, and
 * may continue to deliver events already posted to an event queue. In
 * general, callers should be capable of handling events even after
 * suspending a request.
 */
NS_IMETHODIMP nsImapMockChannel::Suspend() {
  MOZ_LOG(IMAP, LogLevel::Debug, ("Suspending [this=%p].", this));

  mozilla::MonitorAutoLock lock(mSuspendedMonitor);
  NS_ENSURE_TRUE(!mSuspended, NS_ERROR_NOT_AVAILABLE);
  mSuspended = true;

  MOZ_LOG(IMAP, LogLevel::Debug, ("Suspended [this=%p].", this));

  return NS_OK;
}

/**
 * Resumes the current request.  This may have the effect of re-opening
 * any underlying transport and will resume the delivery of data to
 * any open streams.
 */
NS_IMETHODIMP nsImapMockChannel::Resume() {
  MOZ_LOG(IMAP, LogLevel::Debug, ("Resuming [this=%p].", this));

  nsresult rv = ResumeAndNotifyOne();

  MOZ_LOG(IMAP, LogLevel::Debug, ("Resumed [this=%p].", this));

  return rv;
}

nsresult nsImapMockChannel::ResumeAndNotifyOne() {
  mozilla::MonitorAutoLock lock(mSuspendedMonitor);
  NS_ENSURE_TRUE(mSuspended, NS_ERROR_NOT_AVAILABLE);
  mSuspended = false;
  lock.Notify();

  return NS_OK;
}

NS_IMETHODIMP
nsImapMockChannel::GetNotificationCallbacks(
    nsIInterfaceRequestor** aNotificationCallbacks) {
  NS_IF_ADDREF(*aNotificationCallbacks = mCallbacks.get());
  return NS_OK;
}

NS_IMETHODIMP
nsImapMockChannel::SetNotificationCallbacks(
    nsIInterfaceRequestor* aNotificationCallbacks) {
  mCallbacks = aNotificationCallbacks;
  return NS_OK;
}

NS_IMETHODIMP
nsImapMockChannel::OnTransportStatus(nsITransport* transport, nsresult status,
                                     int64_t progress, int64_t progressMax) {
  if (NS_FAILED(m_cancelStatus) || (mLoadFlags & LOAD_BACKGROUND) || !m_url)
    return NS_OK;

  // these transport events should not generate any status messages
  if (status == NS_NET_STATUS_RECEIVING_FROM ||
      status == NS_NET_STATUS_SENDING_TO)
    return NS_OK;

  if (!mProgressEventSink) {
    NS_QueryNotificationCallbacks(mCallbacks, m_loadGroup, mProgressEventSink);
    if (!mProgressEventSink) return NS_OK;
  }

  nsAutoCString host;
  m_url->GetHost(host);

  nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(m_url);
  if (mailnewsUrl) {
    nsCOMPtr<nsIMsgIncomingServer> server;
    mailnewsUrl->GetServer(getter_AddRefs(server));
    if (server) server->GetHostName(host);
  }
  mProgressEventSink->OnStatus(this, status, NS_ConvertUTF8toUTF16(host).get());

  return NS_OK;
}

nsIMAPMailboxInfo::nsIMAPMailboxInfo(const nsACString& aName, char aDelimiter) {
  mMailboxName.Assign(aName);
  mDelimiter = aDelimiter;
  mChildrenListed = false;
}

nsIMAPMailboxInfo::~nsIMAPMailboxInfo() {}

void nsIMAPMailboxInfo::SetChildrenListed(bool childrenListed) {
  mChildrenListed = childrenListed;
}

bool nsIMAPMailboxInfo::GetChildrenListed() { return mChildrenListed; }

const nsACString& nsIMAPMailboxInfo::GetMailboxName() { return mMailboxName; }

char nsIMAPMailboxInfo::GetDelimiter() { return mDelimiter; }
