mailnews/imap/src/nsImapMailFolder.cpp
author Rob Lemley <rob@thunderbird.net>
Mon, 27 Apr 2020 19:51:09 -0400
changeset 38064 39e1d01fb0c37b7f70bc92d58a9a06f8148e6981
parent 38059 fd16d22d04fa421ab6552ba8e6b7bde56235e9d4
child 38198 01d4d3db44e6c509076df5fda8f37064d9652c78
permissions -rw-r--r--
Bug 1577518 - Update libgcrypt to version 1.8.5. r=kaie

/* -*- 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/. */

#include "msgCore.h"
#include "prmem.h"
#include "nsMsgImapCID.h"
#include "nsImapMailFolder.h"
#include "nsIImapService.h"
#include "nsIFile.h"
#include "nsIUrlListener.h"
#include "nsCOMPtr.h"
#include "nsMsgDBCID.h"
#include "nsMsgFolderFlags.h"
#include "nsISeekableStream.h"
#include "nsThreadUtils.h"
#include "nsIImapUrl.h"
#include "nsImapUtils.h"
#include "nsMsgUtils.h"
#include "nsIMsgMailSession.h"
#include "nsMsgKeyArray.h"
#include "nsMsgBaseCID.h"
#include "nsMsgLocalCID.h"
#include "nsITransactionManager.h"
#include "nsImapUndoTxn.h"
#include "nsIIMAPHostSessionList.h"
#include "nsIMsgCopyService.h"
#include "nsICopyMsgStreamListener.h"
#include "nsImapStringBundle.h"
#include "nsIMsgFolderCacheElement.h"
#include "nsTextFormatter.h"
#include "nsIPrefBranch.h"
#include "nsIPrefService.h"
#include "nsMsgI18N.h"
#include "nsIMsgFilter.h"
#include "nsIMsgFilterService.h"
#include "nsIMsgSearchCustomTerm.h"
#include "nsIMsgSearchTerm.h"
#include "nsImapMoveCoalescer.h"
#include "nsIPrompt.h"
#include "nsIDocShell.h"
#include "nsUnicharUtils.h"
#include "nsIImapFlagAndUidState.h"
#include "nsIImapHeaderXferInfo.h"
#include "nsIMessenger.h"
#include "nsIMsgSearchAdapter.h"
#include "nsIImapMockChannel.h"
#include "nsIProgressEventSink.h"
#include "nsIMsgWindow.h"
#include "nsIMsgFolder.h"  // TO include biffState enum. Change to bool later...
#include "nsIMsgLocalMailFolder.h"
#include "nsIMsgOfflineImapOperation.h"
#include "nsImapOfflineSync.h"
#include "nsIImapMailFolderSink.h"
#include "nsIImapServerSink.h"
#include "nsIMsgAccountManager.h"
#include "nsQuickSort.h"
#include "nsIImapMockChannel.h"
#include "nsNetUtil.h"
#include "nsIMAPNamespace.h"
#include "nsIMsgFolderCompactor.h"
#include "nsMsgMessageFlags.h"
#include "nsISpamSettings.h"
#include <time.h>
#include "nsIMsgMailNewsUrl.h"
#include "nsEmbedCID.h"
#include "nsIMsgComposeService.h"
#include "nsMsgCompCID.h"
#include "nsDirectoryServiceDefs.h"
#include "nsIDirectoryEnumerator.h"
#include "nsIMsgIdentity.h"
#include "nsIMsgFolderNotificationService.h"
#include "nsNativeCharsetUtils.h"
#include "nsIExternalProtocolService.h"
#include "nsCExternalHandlerService.h"
#include "prprf.h"
#include "nsIMutableArray.h"
#include "nsArrayUtils.h"
#include "nsArrayEnumerator.h"
#include "nsAutoSyncManager.h"
#include "nsIMsgFilterCustomAction.h"
#include "nsMsgReadStateTxn.h"
#include "nsStringEnumerator.h"
#include "nsIMsgStatusFeedback.h"
#include "nsMsgLineBuffer.h"
#include "mozilla/Logging.h"
#include "mozilla/Attributes.h"
#include "nsStringStream.h"
#include "nsIStreamListener.h"

static NS_DEFINE_CID(kParseMailMsgStateCID, NS_PARSEMAILMSGSTATE_CID);
static NS_DEFINE_CID(kCImapHostSessionList, NS_IIMAPHOSTSESSIONLIST_CID);

#define MAILNEWS_CUSTOM_HEADERS "mailnews.customHeaders"

using namespace mozilla;

extern LazyLogModule gAutoSyncLog;  // defined in nsAutoSyncManager.cpp
extern LazyLogModule IMAP;          // defined in nsImapProtocol.cpp
extern LazyLogModule IMAP_CS;  // For CONDSTORE, defined in nsImapProtocol.cpp
extern LazyLogModule FILTERLOGMODULE;  // defined in nsMsgFilterService.cpp
LazyLogModule IMAP_KW("IMAP_KW");      // for logging keyword (tag) processing

/*
    Copies the contents of srcDir into destDir.
    destDir will be created if it doesn't exist.
*/

static nsresult RecursiveCopy(nsIFile *srcDir, nsIFile *destDir) {
  nsresult rv;
  bool isDir;

  rv = srcDir->IsDirectory(&isDir);
  if (NS_FAILED(rv)) return rv;
  if (!isDir) return NS_ERROR_INVALID_ARG;

  bool exists;
  rv = destDir->Exists(&exists);
  if (NS_SUCCEEDED(rv) && !exists)
    rv = destDir->Create(nsIFile::DIRECTORY_TYPE, 0775);
  if (NS_FAILED(rv)) return rv;

  nsCOMPtr<nsIDirectoryEnumerator> dirIterator;
  rv = srcDir->GetDirectoryEntries(getter_AddRefs(dirIterator));
  if (NS_FAILED(rv)) return rv;

  bool hasMore = false;
  while (NS_SUCCEEDED(dirIterator->HasMoreElements(&hasMore)) && hasMore) {
    nsCOMPtr<nsIFile> dirEntry;
    rv = dirIterator->GetNextFile(getter_AddRefs(dirEntry));
    if (NS_SUCCEEDED(rv) && dirEntry) {
      rv = dirEntry->IsDirectory(&isDir);
      if (NS_SUCCEEDED(rv)) {
        if (isDir) {
          nsCOMPtr<nsIFile> newChild;
          rv = destDir->Clone(getter_AddRefs(newChild));
          if (NS_SUCCEEDED(rv)) {
            nsAutoString leafName;
            dirEntry->GetLeafName(leafName);
            newChild->AppendRelativePath(leafName);
            rv = newChild->Exists(&exists);
            if (NS_SUCCEEDED(rv) && !exists)
              rv = newChild->Create(nsIFile::DIRECTORY_TYPE, 0775);
            rv = RecursiveCopy(dirEntry, newChild);
          }
        } else
          rv = dirEntry->CopyTo(destDir, EmptyString());
      }
    }
  }

  return rv;
}

//
//  nsMsgQuota
//
NS_IMPL_ISUPPORTS(nsMsgQuota, nsIMsgQuota)

nsMsgQuota::nsMsgQuota(const nsACString &aName, const uint32_t &aUsage,
                       const uint32_t &aLimit)
    : mName(aName), mUsage(aUsage), mLimit(aLimit) {}

nsMsgQuota::~nsMsgQuota() {}

/**
 * Note: These quota access function are not called but still must be defined
 * for the linker.
 */
NS_IMETHODIMP nsMsgQuota::GetName(nsACString &aName) {
  aName = mName;
  return NS_OK;
}

NS_IMETHODIMP nsMsgQuota::SetName(const nsACString &aName) {
  mName = aName;
  return NS_OK;
}

NS_IMETHODIMP nsMsgQuota::GetUsage(uint32_t *aUsage) {
  *aUsage = mUsage;
  return NS_OK;
}

NS_IMETHODIMP nsMsgQuota::SetUsage(uint32_t aUsage) {
  mUsage = aUsage;
  return NS_OK;
}

NS_IMETHODIMP nsMsgQuota::GetLimit(uint32_t *aLimit) {
  *aLimit = mLimit;
  return NS_OK;
}

NS_IMETHODIMP nsMsgQuota::SetLimit(uint32_t aLimit) {
  mLimit = aLimit;
  return NS_OK;
}

//
//  nsImapMailFolder
//
nsImapMailFolder::nsImapMailFolder()
    : m_initialized(false),
      m_haveDiscoveredAllFolders(false),
      m_curMsgUid(0),
      m_nextMessageByteLength(0),
      m_urlRunning(false),
      m_verifiedAsOnlineFolder(false),
      m_explicitlyVerify(false),
      m_folderIsNamespace(false),
      m_folderNeedsSubscribing(false),
      m_folderNeedsAdded(false),
      m_folderNeedsACLListed(true),
      m_performingBiff(false),
      m_updatingFolder(false),
      m_compactingOfflineStore(false),
      m_expunging(false),
      m_applyIncomingFilters(false),
      m_downloadingFolderForOfflineUse(false),
      m_filterListRequiresBody(false),
      m_folderQuotaCommandIssued(false),
      m_folderQuotaDataIsValid(false) {
  m_boxFlags = 0;
  m_uidValidity = kUidUnknown;
  m_numServerRecentMessages = 0;
  m_numServerUnseenMessages = 0;
  m_numServerTotalMessages = 0;
  m_nextUID = nsMsgKey_None;
  m_hierarchyDelimiter = kOnlineHierarchySeparatorUnknown;
  m_folderACL = nullptr;
  m_aclFlags = 0;
  m_supportedUserFlags = 0;
  m_namespace = nullptr;
  m_pendingPlaybackReq = nullptr;
}

nsImapMailFolder::~nsImapMailFolder() {
  delete m_folderACL;

  // cleanup any pending request
  delete m_pendingPlaybackReq;
}

NS_IMPL_ADDREF_INHERITED(nsImapMailFolder, nsMsgDBFolder)
NS_IMPL_RELEASE_INHERITED(nsImapMailFolder, nsMsgDBFolder)
NS_IMPL_QUERY_HEAD(nsImapMailFolder)
NS_IMPL_QUERY_BODY(nsIMsgImapMailFolder)
NS_IMPL_QUERY_BODY(nsICopyMessageListener)
NS_IMPL_QUERY_BODY(nsIImapMailFolderSink)
NS_IMPL_QUERY_BODY(nsIImapMessageSink)
NS_IMPL_QUERY_BODY(nsIUrlListener)
NS_IMPL_QUERY_BODY(nsIMsgFilterHitNotify)
NS_IMPL_QUERY_TAIL_INHERITING(nsMsgDBFolder)

nsresult nsImapMailFolder::AddDirectorySeparator(nsIFile *path) {
  if (mURI.Equals(kImapRootURI)) {
    // don't concat the full separator with .sbd
  } else {
    // see if there's a dir with the same name ending with .sbd
    nsAutoString leafName;
    path->GetLeafName(leafName);
    leafName.AppendLiteral(FOLDER_SUFFIX);
    path->SetLeafName(leafName);
  }

  return NS_OK;
}

static bool nsShouldIgnoreFile(nsString &name) {
  if (StringEndsWith(name, NS_LITERAL_STRING(SUMMARY_SUFFIX),
                     nsCaseInsensitiveStringComparator())) {
    name.SetLength(name.Length() -
                   SUMMARY_SUFFIX_LENGTH);  // truncate the string
    return false;
  }
  return true;
}

nsresult nsImapMailFolder::CreateChildFromURI(const nsCString &uri,
                                              nsIMsgFolder **folder) {
  nsImapMailFolder *newFolder = new nsImapMailFolder;
  newFolder->Init(uri.get());
  NS_ADDREF(*folder = newFolder);
  return NS_OK;
}

NS_IMETHODIMP nsImapMailFolder::AddSubfolder(const nsAString &aName,
                                             nsIMsgFolder **aChild) {
  NS_ENSURE_ARG_POINTER(aChild);

  int32_t flags = 0;
  nsresult rv;

  nsAutoCString uri(mURI);
  uri.Append('/');

  // If AddSubFolder starts getting called for folders other than virtual
  // folders, we'll have to do convert those names to modified utf-7. For now,
  // the account manager code that loads the virtual folders for each account,
  // expects utf8 not modified utf-7.
  nsAutoCString escapedName;
  rv = NS_MsgEscapeEncodeURLPath(aName, escapedName);
  NS_ENSURE_SUCCESS(rv, rv);

  uri += escapedName.get();

  nsCOMPtr<nsIMsgFolder> msgFolder;
  rv = GetChildWithURI(uri, false /*deep*/, true /*case Insensitive*/,
                       getter_AddRefs(msgFolder));
  if (NS_SUCCEEDED(rv) && msgFolder) return NS_MSG_FOLDER_EXISTS;

  nsCOMPtr<nsIMsgFolder> folder;
  rv = GetOrCreateFolder(uri, getter_AddRefs(folder));
  NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);

  nsCOMPtr<nsIFile> path;
  rv = CreateDirectoryForFolder(getter_AddRefs(path));
  NS_ENSURE_SUCCESS(rv, rv);

  folder->GetFlags((uint32_t *)&flags);

  flags |= nsMsgFolderFlags::Mail;

  nsCOMPtr<nsIImapIncomingServer> imapServer;
  GetImapIncomingServer(getter_AddRefs(imapServer));
  if (imapServer) {
    bool setNewFoldersForOffline = false;
    rv = imapServer->GetOfflineDownload(&setNewFoldersForOffline);
    if (NS_SUCCEEDED(rv) && setNewFoldersForOffline)
      flags |= nsMsgFolderFlags::Offline;
  }

  folder->SetParent(this);

  folder->SetFlags(flags);

  mSubFolders.AppendObject(folder);
  folder.forget(aChild);

  nsCOMPtr<nsIMsgImapMailFolder> imapChild = do_QueryInterface(*aChild);
  if (imapChild) {
    imapChild->SetOnlineName(NS_LossyConvertUTF16toASCII(aName));
    imapChild->SetHierarchyDelimiter(m_hierarchyDelimiter);
  }
  NotifyItemAdded(*aChild);
  return rv;
}

nsresult nsImapMailFolder::AddSubfolderWithPath(nsAString &name,
                                                nsIFile *dbPath,
                                                nsIMsgFolder **child,
                                                bool brandNew) {
  NS_ENSURE_ARG_POINTER(child);
  nsresult rv;

  nsAutoCString uri(mURI);
  uri.Append('/');
  AppendUTF16toUTF8(name, uri);

  bool isServer;
  rv = GetIsServer(&isServer);
  NS_ENSURE_SUCCESS(rv, rv);

  bool isInbox = isServer && name.LowerCaseEqualsLiteral("inbox");

  // will make sure mSubFolders does not have duplicates because of bogus msf
  // files.
  nsCOMPtr<nsIMsgFolder> msgFolder;
  rv = GetChildWithURI(uri, false /*deep*/, isInbox /*case Insensitive*/,
                       getter_AddRefs(msgFolder));
  if (NS_SUCCEEDED(rv) && msgFolder) return NS_MSG_FOLDER_EXISTS;

  nsCOMPtr<nsIMsgFolder> folder;
  rv = GetOrCreateFolder(uri, getter_AddRefs(folder));
  NS_ENSURE_SUCCESS(rv, rv);

  folder->SetFilePath(dbPath);
  nsCOMPtr<nsIMsgImapMailFolder> imapFolder = do_QueryInterface(folder, &rv);
  mozilla::Unused << imapFolder;
  NS_ENSURE_SUCCESS(rv, rv);

  uint32_t flags = 0;
  folder->GetFlags(&flags);

  folder->SetParent(this);
  flags |= nsMsgFolderFlags::Mail;

  uint32_t pFlags;
  GetFlags(&pFlags);
  bool isParentInbox = pFlags & nsMsgFolderFlags::Inbox;

  nsCOMPtr<nsIImapIncomingServer> imapServer;
  rv = GetImapIncomingServer(getter_AddRefs(imapServer));
  NS_ENSURE_SUCCESS(rv, rv);

  // Only set these if these are top level children or parent is inbox
  if (isInbox)
    flags |= nsMsgFolderFlags::Inbox;
  else if (isServer || isParentInbox) {
    nsMsgImapDeleteModel deleteModel;
    imapServer->GetDeleteModel(&deleteModel);
    if (deleteModel == nsMsgImapDeleteModels::MoveToTrash) {
      nsAutoString trashName;
      GetTrashFolderName(trashName);
      if (name.Equals(trashName)) flags |= nsMsgFolderFlags::Trash;
    }
  }

  // Make the folder offline if it is newly created and the offline_download
  // pref is true, unless it's the Trash or Junk folder.
  if (brandNew &&
      !(flags & (nsMsgFolderFlags::Trash | nsMsgFolderFlags::Junk))) {
    bool setNewFoldersForOffline = false;
    rv = imapServer->GetOfflineDownload(&setNewFoldersForOffline);
    if (NS_SUCCEEDED(rv) && setNewFoldersForOffline)
      flags |= nsMsgFolderFlags::Offline;
  }

  folder->SetFlags(flags);

  if (folder) mSubFolders.AppendObject(folder);
  folder.forget(child);
  return NS_OK;
}

nsresult nsImapMailFolder::CreateSubFolders(nsIFile *path) {
  nsresult rv = NS_OK;
  nsCOMPtr<nsIMsgIncomingServer> server;
  rv = GetServer(getter_AddRefs(server));
  NS_ENSURE_SUCCESS(rv, rv);

  nsCOMPtr<nsIDirectoryEnumerator> directoryEnumerator;
  rv = path->GetDirectoryEntries(getter_AddRefs(directoryEnumerator));
  NS_ENSURE_SUCCESS(rv, rv);

  bool hasMore = false;
  while (NS_SUCCEEDED(directoryEnumerator->HasMoreElements(&hasMore)) &&
         hasMore) {
    nsCOMPtr<nsIFile> currentFolderPath;
    rv = directoryEnumerator->GetNextFile(getter_AddRefs(currentFolderPath));
    if (NS_FAILED(rv) || !currentFolderPath) continue;

    nsAutoString currentFolderNameStr;    // online name
    nsAutoString currentFolderDBNameStr;  // possibly munged name
    currentFolderPath->GetLeafName(currentFolderNameStr);
    if (nsShouldIgnoreFile(currentFolderNameStr)) continue;

    // OK, here we need to get the online name from the folder cache if we can.
    // If we can, use that to create the sub-folder
    nsCOMPtr<nsIFile> curFolder =
        do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
    NS_ENSURE_SUCCESS(rv, rv);
    nsCOMPtr<nsIFile> dbFile = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
    NS_ENSURE_SUCCESS(rv, rv);
    dbFile->InitWithFile(currentFolderPath);
    curFolder->InitWithFile(currentFolderPath);
    // don't strip off the .msf in currentFolderPath.
    currentFolderPath->SetLeafName(currentFolderNameStr);
    currentFolderDBNameStr = currentFolderNameStr;
    nsAutoString utf7LeafName = currentFolderNameStr;

    if (curFolder) {
      nsCOMPtr<nsIMsgFolderCacheElement> cacheElement;
      rv = GetFolderCacheElemFromFile(dbFile, getter_AddRefs(cacheElement));
      if (NS_SUCCEEDED(rv) && cacheElement) {
        nsCString onlineFullUtf7Name;

        uint32_t folderFlags;
        rv = cacheElement->GetInt32Property("flags", (int32_t *)&folderFlags);
        if (NS_SUCCEEDED(rv) &&
            folderFlags & nsMsgFolderFlags::Virtual)  // ignore virtual folders
          continue;
        int32_t hierarchyDelimiter;
        rv = cacheElement->GetInt32Property("hierDelim", &hierarchyDelimiter);
        if (NS_SUCCEEDED(rv) &&
            hierarchyDelimiter == kOnlineHierarchySeparatorUnknown) {
          currentFolderPath->Remove(false);
          continue;  // blow away .msf files for folders with unknown delimiter.
        }
        rv = cacheElement->GetStringProperty("onlineName", onlineFullUtf7Name);
        if (NS_SUCCEEDED(rv) && !onlineFullUtf7Name.IsEmpty()) {
          CopyMUTF7toUTF16(onlineFullUtf7Name, currentFolderNameStr);
          char delimiter = 0;
          GetHierarchyDelimiter(&delimiter);
          int32_t leafPos = currentFolderNameStr.RFindChar(delimiter);
          if (leafPos > 0) currentFolderNameStr.Cut(0, leafPos + 1);

          // take the utf7 full online name, and determine the utf7 leaf name
          CopyASCIItoUTF16(onlineFullUtf7Name, utf7LeafName);
          leafPos = utf7LeafName.RFindChar(delimiter);
          if (leafPos > 0) utf7LeafName.Cut(0, leafPos + 1);
        }
      }
    }
    // make the imap folder remember the file spec it was created with.
    nsCOMPtr<nsIFile> msfFilePath =
        do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
    NS_ENSURE_SUCCESS(rv, rv);
    msfFilePath->InitWithFile(currentFolderPath);
    if (NS_SUCCEEDED(rv) && msfFilePath) {
      // leaf name is the db name w/o .msf (nsShouldIgnoreFile strips it off)
      // so this trims the .msf off the file spec.
      msfFilePath->SetLeafName(currentFolderDBNameStr);
    }
    // use the utf7 name as the uri for the folder.
    nsCOMPtr<nsIMsgFolder> child;
    AddSubfolderWithPath(utf7LeafName, msfFilePath, getter_AddRefs(child));
    if (child) {
      // use the unicode name as the "pretty" name. Set it so it won't be
      // automatically computed from the URI, which is in utf7 form.
      if (!currentFolderNameStr.IsEmpty())
        child->SetPrettyName(currentFolderNameStr);
      child->SetMsgDatabase(nullptr);
    }
  }
  return rv;
}

NS_IMETHODIMP nsImapMailFolder::GetSubFolders(nsISimpleEnumerator **aResult) {
  bool isServer;
  nsresult rv = GetIsServer(&isServer);
  NS_ENSURE_SUCCESS(rv, rv);

  if (!m_initialized) {
    nsCOMPtr<nsIFile> pathFile;
    rv = GetFilePath(getter_AddRefs(pathFile));
    if (NS_FAILED(rv)) return rv;

    // host directory does not need .sbd tacked on
    if (!isServer) {
      rv = AddDirectorySeparator(pathFile);
      if (NS_FAILED(rv)) return rv;
    }

    m_initialized = true;  // need to set this here to avoid infinite recursion
                           // from CreateSubfolders.
    // we have to treat the root folder specially, because it's name
    // doesn't end with .sbd

    int32_t newFlags = nsMsgFolderFlags::Mail;
    bool isDirectory = false;
    pathFile->IsDirectory(&isDirectory);
    if (isDirectory) {
      newFlags |= (nsMsgFolderFlags::Directory | nsMsgFolderFlags::Elided);
      if (!mIsServer) SetFlag(newFlags);
      rv = CreateSubFolders(pathFile);
    }
    if (isServer) {
      nsCOMPtr<nsIMsgFolder> inboxFolder;

      GetFolderWithFlags(nsMsgFolderFlags::Inbox, getter_AddRefs(inboxFolder));
      if (!inboxFolder) {
        // create an inbox if we don't have one.
        CreateClientSubfolderInfo(NS_LITERAL_CSTRING("INBOX"),
                                  kOnlineHierarchySeparatorUnknown, 0, true);
      }
    }

    int32_t count = mSubFolders.Count();
    nsCOMPtr<nsISimpleEnumerator> dummy;
    for (int32_t i = 0; i < count; i++)
      mSubFolders[i]->GetSubFolders(getter_AddRefs(dummy));

    UpdateSummaryTotals(false);
    if (NS_FAILED(rv)) return rv;
  }

  return aResult ? NS_NewArrayEnumerator(aResult, mSubFolders,
                                         NS_GET_IID(nsIMsgFolder))
                 : NS_ERROR_NULL_POINTER;
}

// Makes sure the database is open and exists.  If the database is valid then
// returns NS_OK.  Otherwise returns a failure error value.
nsresult nsImapMailFolder::GetDatabase() {
  nsresult rv = NS_OK;
  if (!mDatabase) {
    nsCOMPtr<nsIMsgDBService> msgDBService =
        do_GetService(NS_MSGDB_SERVICE_CONTRACTID, &rv);
    NS_ENSURE_SUCCESS(rv, rv);

    // Create the database, blowing it away if it needs to be rebuilt
    rv = msgDBService->OpenFolderDB(this, false, getter_AddRefs(mDatabase));
    if (NS_FAILED(rv))
      rv = msgDBService->CreateNewDB(this, getter_AddRefs(mDatabase));

    NS_ENSURE_SUCCESS(rv, rv);

    // UpdateNewMessages/UpdateSummaryTotals can null mDatabase, so we save a
    // local copy
    nsCOMPtr<nsIMsgDatabase> database(mDatabase);
    UpdateNewMessages();
    if (mAddListener) database->AddListener(this);
    UpdateSummaryTotals(true);
    mDatabase = database;
  }
  return rv;
}

NS_IMETHODIMP nsImapMailFolder::UpdateFolder(nsIMsgWindow *inMsgWindow) {
  return UpdateFolderWithListener(inMsgWindow, nullptr);
}

NS_IMETHODIMP nsImapMailFolder::UpdateFolderWithListener(
    nsIMsgWindow *aMsgWindow, nsIUrlListener *aUrlListener) {
  nsresult rv;
  bool selectFolder = false;

  // If this is the inbox, filters will be applied. Otherwise, we test the
  // inherited folder property "applyIncomingFilters" (which defaults to empty).
  // If this inherited property has the string value "true", we will apply
  // filters even if this is not the inbox folder.
  nsCString applyIncomingFilters;
  GetInheritedStringProperty("applyIncomingFilters", applyIncomingFilters);
  m_applyIncomingFilters = applyIncomingFilters.EqualsLiteral("true");

  nsString folderName;
  GetPrettyName(folderName);
  MOZ_LOG(FILTERLOGMODULE, LogLevel::Debug,
          ("(Imap) nsImapMailFolder::UpdateFolderWithListener() on folder '%s'",
           NS_ConvertUTF16toUTF8(folderName).get()));
  if (mFlags & nsMsgFolderFlags::Inbox || m_applyIncomingFilters) {
    MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
            ("(Imap) Preparing filter run on folder '%s'",
             NS_ConvertUTF16toUTF8(folderName).get()));

    if (!m_filterList) {
      rv = GetFilterList(aMsgWindow, getter_AddRefs(m_filterList));
      if (NS_FAILED(rv)) {
        MOZ_LOG(FILTERLOGMODULE, LogLevel::Error,
                ("(Imap) Loading of filter list failed"));
      }
    }

    // if there's no msg window, but someone is updating the inbox, we're
    // doing something biff-like, and may download headers, so make biff notify.
    if (!aMsgWindow && mFlags & nsMsgFolderFlags::Inbox)
      SetPerformingBiff(true);
  }

  if (m_filterList) {
    nsCString listId;
    m_filterList->GetListId(listId);
    MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
            ("(Imap) Preparing filter list %s", listId.get()));
    nsCOMPtr<nsIMsgIncomingServer> server;
    rv = GetServer(getter_AddRefs(server));
    NS_ENSURE_SUCCESS(rv, rv);

    bool canFileMessagesOnServer = true;
    rv = server->GetCanFileMessagesOnServer(&canFileMessagesOnServer);
    // the mdn filter is for filing return receipts into the sent folder
    // some servers (like AOL mail servers)
    // can't file to the sent folder, so we don't add the filter for those
    // servers
    if (canFileMessagesOnServer) {
      rv = server->ConfigureTemporaryFilters(m_filterList);
      NS_ENSURE_SUCCESS(rv, rv);
    }

    // If a body filter is enabled for an offline folder, delay the filter
    // application until after message has been downloaded.
    m_filterListRequiresBody = false;

    if (mFlags & nsMsgFolderFlags::Offline) {
      nsCOMPtr<nsIMsgFilterService> filterService =
          do_GetService(NS_MSGFILTERSERVICE_CONTRACTID, &rv);
      uint32_t filterCount = 0;
      m_filterList->GetFilterCount(&filterCount);
      for (uint32_t index = 0; index < filterCount && !m_filterListRequiresBody;
           ++index) {
        nsCOMPtr<nsIMsgFilter> filter;
        m_filterList->GetFilterAt(index, getter_AddRefs(filter));
        if (!filter) continue;
        nsMsgFilterTypeType filterType;
        filter->GetFilterType(&filterType);
        if (!(filterType & nsMsgFilterType::Incoming)) continue;
        bool enabled = false;
        filter->GetEnabled(&enabled);
        if (!enabled) continue;
        nsCOMPtr<nsIMutableArray> searchTerms;
        uint32_t numSearchTerms = 0;
        filter->GetSearchTerms(getter_AddRefs(searchTerms));
        if (searchTerms) searchTerms->Count(&numSearchTerms);
        for (uint32_t termIndex = 0;
             termIndex < numSearchTerms && !m_filterListRequiresBody;
             termIndex++) {
          nsCOMPtr<nsIMsgSearchTerm> term =
              do_QueryElementAt(searchTerms, termIndex, &rv);
          NS_ENSURE_SUCCESS(rv, rv);
          nsMsgSearchAttribValue attrib;
          rv = term->GetAttrib(&attrib);
          NS_ENSURE_SUCCESS(rv, rv);
          if (attrib == nsMsgSearchAttrib::Body)
            m_filterListRequiresBody = true;
          else if (attrib == nsMsgSearchAttrib::Custom) {
            nsAutoCString customId;
            rv = term->GetCustomId(customId);
            nsCOMPtr<nsIMsgSearchCustomTerm> customTerm;
            if (NS_SUCCEEDED(rv) && filterService)
              rv = filterService->GetCustomTerm(customId,
                                                getter_AddRefs(customTerm));
            bool needsBody = false;
            if (NS_SUCCEEDED(rv) && customTerm)
              rv = customTerm->GetNeedsBody(&needsBody);
            if (NS_SUCCEEDED(rv) && needsBody) m_filterListRequiresBody = true;
          }
        }

        // Also check if filter actions need the body, as this
        // is supported in custom actions.
        uint32_t numActions = 0;
        filter->GetActionCount(&numActions);
        for (uint32_t actionIndex = 0;
             actionIndex < numActions && !m_filterListRequiresBody;
             actionIndex++) {
          nsCOMPtr<nsIMsgRuleAction> action;
          rv = filter->GetActionAt(actionIndex, getter_AddRefs(action));
          if (NS_FAILED(rv) || !action) continue;

          nsCOMPtr<nsIMsgFilterCustomAction> customAction;
          rv = action->GetCustomAction(getter_AddRefs(customAction));
          if (NS_FAILED(rv) || !customAction) continue;

          bool needsBody = false;
          customAction->GetNeedsBody(&needsBody);
          if (needsBody) m_filterListRequiresBody = true;
        }
      }
    }
    MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
            ("(Imap) Filters require the message body: %s",
             (m_filterListRequiresBody ? "true" : "false")));
  }

  selectFolder = true;

  bool isServer;
  rv = GetIsServer(&isServer);
  if (NS_SUCCEEDED(rv) && isServer) {
    if (!m_haveDiscoveredAllFolders) {
      bool hasSubFolders = false;
      GetHasSubFolders(&hasSubFolders);
      if (!hasSubFolders) {
        rv = CreateClientSubfolderInfo(NS_LITERAL_CSTRING("Inbox"),
                                       kOnlineHierarchySeparatorUnknown, 0,
                                       false);
        NS_ENSURE_SUCCESS(rv, rv);
      }
      m_haveDiscoveredAllFolders = true;
    }
    selectFolder = false;
  }
  rv = GetDatabase();
  if (NS_FAILED(rv)) {
    ThrowAlertMsg("errorGettingDB", aMsgWindow);
    return rv;
  }
  bool canOpenThisFolder = true;
  GetCanOpenFolder(&canOpenThisFolder);

  bool hasOfflineEvents = false;
  GetFlag(nsMsgFolderFlags::OfflineEvents, &hasOfflineEvents);

  if (!WeAreOffline()) {
    if (hasOfflineEvents) {
      // hold a reference to the offline sync object. If ProcessNextOperation
      // runs a url, a reference will be added to it. Otherwise, it will get
      // destroyed when the refptr goes out of scope.
      RefPtr<nsImapOfflineSync> goOnline =
          new nsImapOfflineSync(aMsgWindow, this, this);
      if (goOnline) {
        m_urlListener = aUrlListener;
        return goOnline->ProcessNextOperation();
      }
    }
  }

  // Check it we're password protecting the local store.
  if (!PromptForMasterPasswordIfNecessary()) return NS_ERROR_FAILURE;

  if (!canOpenThisFolder) selectFolder = false;
  // don't run select if we can't select the folder...
  if (NS_SUCCEEDED(rv) && !m_urlRunning && selectFolder) {
    nsCOMPtr<nsIImapService> imapService =
        do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
    NS_ENSURE_SUCCESS(rv, rv);

    nsCOMPtr<nsIURI> url;
    rv = imapService->SelectFolder(this, m_urlListener, aMsgWindow,
                                   getter_AddRefs(url));
    if (NS_SUCCEEDED(rv)) {
      m_urlRunning = true;
      m_updatingFolder = true;
    }
    if (url) {
      nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(url, &rv);
      NS_ENSURE_SUCCESS(rv, rv);
      mailnewsUrl->RegisterListener(this);
      m_urlListener = aUrlListener;
    }

    // Allow IMAP folder auto-compact to occur when online or offline.
    if (aMsgWindow) AutoCompact(aMsgWindow);

    if (rv == NS_MSG_ERROR_OFFLINE || rv == NS_BINDING_ABORTED) {
      rv = NS_OK;
      NotifyFolderEvent(kFolderLoaded);
    }
  } else if (NS_SUCCEEDED(rv))  // tell the front end that the folder is loaded
                                // if we're not going to
  {                             // actually run a url.
    if (!m_updatingFolder)  // if we're already running an update url, we'll let
                            // that one send the folder loaded
      NotifyFolderEvent(kFolderLoaded);
  }
  return rv;
}

NS_IMETHODIMP nsImapMailFolder::GetMessages(nsISimpleEnumerator **result) {
  NS_ENSURE_ARG_POINTER(result);
  if (!mDatabase) GetDatabase();
  if (mDatabase) return mDatabase->EnumerateMessages(result);
  return NS_ERROR_UNEXPECTED;
}

NS_IMETHODIMP nsImapMailFolder::CreateSubfolder(const nsAString &folderName,
                                                nsIMsgWindow *msgWindow) {
  if (folderName.IsEmpty()) return NS_MSG_ERROR_INVALID_FOLDER_NAME;

  nsresult rv;
  nsAutoString trashName;
  GetTrashFolderName(trashName);
  if (folderName.Equals(trashName))  // Trash , a special folder
  {
    ThrowAlertMsg("folderExists", msgWindow);
    return NS_MSG_FOLDER_EXISTS;
  }
  if (mIsServer &&
      folderName.LowerCaseEqualsLiteral("inbox"))  // Inbox, a special folder
  {
    ThrowAlertMsg("folderExists", msgWindow);
    return NS_MSG_FOLDER_EXISTS;
  }

  nsCOMPtr<nsIImapService> imapService =
      do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
  NS_ENSURE_SUCCESS(rv, rv);

  return imapService->CreateFolder(this, folderName, this, nullptr);
}

NS_IMETHODIMP nsImapMailFolder::CreateClientSubfolderInfo(
    const nsACString &folderName, char hierarchyDelimiter, int32_t flags,
    bool suppressNotification) {
  nsresult rv = NS_OK;

  // Get a directory based on our current path.
  nsCOMPtr<nsIFile> path;
  rv = CreateDirectoryForFolder(getter_AddRefs(path));
  if (NS_FAILED(rv)) return rv;

  NS_ConvertASCIItoUTF16 leafName(folderName);
  nsAutoString folderNameStr;
  nsAutoString parentName = leafName;
  // use RFind, because folder can start with a delimiter and
  // not be a leaf folder.
  int32_t folderStart = leafName.RFindChar('/');
  if (folderStart > 0) {
    nsCOMPtr<nsIMsgImapMailFolder> imapFolder;
    nsAutoCString uri(mURI);
    leafName.Assign(Substring(parentName, folderStart + 1));
    parentName.SetLength(folderStart);

    rv = CreateDirectoryForFolder(getter_AddRefs(path));
    NS_ENSURE_SUCCESS(rv, rv);
    uri.Append('/');
    uri.Append(NS_LossyConvertUTF16toASCII(parentName));
    nsCOMPtr<nsIMsgFolder> folder;
    rv = GetOrCreateFolder(uri, getter_AddRefs(folder));
    NS_ENSURE_SUCCESS(rv, rv);
    imapFolder = do_QueryInterface(folder, &rv);
    NS_ENSURE_SUCCESS(rv, rv);
    nsAutoCString leafnameC;
    LossyCopyUTF16toASCII(leafName, leafnameC);
    return imapFolder->CreateClientSubfolderInfo(leafnameC, hierarchyDelimiter,
                                                 flags, suppressNotification);
  }

  // if we get here, it's really a leaf, and "this" is the parent.
  folderNameStr = leafName;

  // Create an empty database for this mail folder, set its name from the user
  nsCOMPtr<nsIMsgDatabase> mailDBFactory;
  nsCOMPtr<nsIMsgFolder> child;

  nsCOMPtr<nsIMsgDBService> msgDBService =
      do_GetService(NS_MSGDB_SERVICE_CONTRACTID, &rv);
  NS_ENSURE_SUCCESS(rv, rv);
  nsCOMPtr<nsIMsgDatabase> unusedDB;
  nsCOMPtr<nsIFile> dbFile;

  // warning, path will be changed
  rv = CreateFileForDB(folderNameStr, path, getter_AddRefs(dbFile));
  NS_ENSURE_SUCCESS(rv, rv);

  // Now let's create the actual new folder
  rv = AddSubfolderWithPath(folderNameStr, dbFile, getter_AddRefs(child), true);
  NS_ENSURE_SUCCESS(rv, rv);
  rv = msgDBService->OpenMailDBFromFile(dbFile, child, true, true,
                                        getter_AddRefs(unusedDB));
  if (rv == NS_MSG_ERROR_FOLDER_SUMMARY_MISSING) rv = NS_OK;

  if (NS_SUCCEEDED(rv) && unusedDB) {
    // need to set the folder name
    nsCOMPtr<nsIDBFolderInfo> folderInfo;
    rv = unusedDB->GetDBFolderInfo(getter_AddRefs(folderInfo));
    nsCOMPtr<nsIMsgImapMailFolder> imapFolder = do_QueryInterface(child, &rv);
    if (NS_SUCCEEDED(rv)) {
      nsAutoCString onlineName(m_onlineFolderName);
      if (!onlineName.IsEmpty()) onlineName.Append(hierarchyDelimiter);
      onlineName.Append(NS_LossyConvertUTF16toASCII(folderNameStr));
      imapFolder->SetVerifiedAsOnlineFolder(true);
      imapFolder->SetOnlineName(onlineName);
      imapFolder->SetHierarchyDelimiter(hierarchyDelimiter);
      imapFolder->SetBoxFlags(flags);

      // Now that the child is created and the boxflags are set we can be sure
      // all special folder flags are known. The child may get its flags already
      // in AddSubfolderWithPath if they were in FolderCache, but that's
      // not always the case.
      uint32_t flags = 0;
      child->GetFlags(&flags);

      // Set the offline use flag for the newly created folder if the
      // offline_download preference is true, unless it's the Trash or Junk
      // folder.
      if (!(flags & (nsMsgFolderFlags::Trash | nsMsgFolderFlags::Junk))) {
        nsCOMPtr<nsIImapIncomingServer> imapServer;
        rv = GetImapIncomingServer(getter_AddRefs(imapServer));
        NS_ENSURE_SUCCESS(rv, rv);
        bool setNewFoldersForOffline = false;
        rv = imapServer->GetOfflineDownload(&setNewFoldersForOffline);
        if (NS_SUCCEEDED(rv) && setNewFoldersForOffline)
          flags |= nsMsgFolderFlags::Offline;
      } else {
        flags &= ~nsMsgFolderFlags::Offline;  // clear offline flag if set
      }

      flags |= nsMsgFolderFlags::Elided;
      child->SetFlags(flags);

      nsString unicodeName;
      rv = CopyMUTF7toUTF16(nsCString(folderName), unicodeName);
      if (NS_SUCCEEDED(rv)) child->SetPrettyName(unicodeName);

      // store the online name as the mailbox name in the db folder info
      // I don't think anyone uses the mailbox name, so we'll use it
      // to restore the online name when blowing away an imap db.
      if (folderInfo)
        folderInfo->SetMailboxName(NS_ConvertASCIItoUTF16(onlineName));
    }

    unusedDB->SetSummaryValid(true);
    unusedDB->Commit(nsMsgDBCommitType::kLargeCommit);
    unusedDB->Close(true);
    // don't want to hold onto this newly created db.
    child->SetMsgDatabase(nullptr);
  }

  if (!suppressNotification) {
    if (NS_SUCCEEDED(rv) && child) {
      NotifyItemAdded(child);
      child->NotifyFolderEvent(kFolderCreateCompleted);
      nsCOMPtr<nsIMsgFolderNotificationService> notifier(
          do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID));
      if (notifier) notifier->NotifyFolderAdded(child);
    } else {
      NotifyFolderEvent(kFolderCreateFailed);
    }
  }
  return rv;
}

NS_IMETHODIMP nsImapMailFolder::List() {
  nsresult rv;
  nsCOMPtr<nsIImapService> imapService =
      do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
  NS_ENSURE_SUCCESS(rv, rv);
  return imapService->ListFolder(this, this, nullptr);
}

NS_IMETHODIMP nsImapMailFolder::RemoveSubFolder(nsIMsgFolder *which) {
  nsresult rv;
  nsCOMPtr<nsIMutableArray> folders(
      do_CreateInstance(NS_ARRAY_CONTRACTID, &rv));
  NS_ENSURE_TRUE(folders, rv);
  nsCOMPtr<nsISupports> folderSupport = do_QueryInterface(which, &rv);
  NS_ENSURE_SUCCESS(rv, rv);
  folders->AppendElement(folderSupport);
  rv = nsMsgDBFolder::DeleteSubFolders(folders, nullptr);
  which->DeleteStorage();
  return rv;
}

NS_IMETHODIMP nsImapMailFolder::CreateStorageIfMissing(
    nsIUrlListener *urlListener) {
  nsresult rv = NS_OK;
  nsCOMPtr<nsIMsgFolder> msgParent;
  GetParent(getter_AddRefs(msgParent));

  // parent is probably not set because *this* was probably created by rdf
  // and not by folder discovery. So, we have to compute the parent.
  if (!msgParent) {
    nsAutoCString folderName(mURI);

    int32_t leafPos = folderName.RFindChar('/');
    nsAutoCString parentName(folderName);

    if (leafPos > 0) {
      // If there is a hierarchy, there is a parent.
      // Don't strip off slash if it's the first character
      parentName.SetLength(leafPos);
      rv = GetOrCreateFolder(parentName, getter_AddRefs(msgParent));
      NS_ENSURE_SUCCESS(rv, rv);
    }
  }
  if (msgParent) {
    nsString folderName;
    GetName(folderName);
    nsresult rv;
    nsCOMPtr<nsIImapService> imapService =
        do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
    NS_ENSURE_SUCCESS(rv, rv);
    nsCOMPtr<nsIURI> uri;
    imapService->EnsureFolderExists(msgParent, folderName, urlListener,
                                    getter_AddRefs(uri));
  }
  return rv;
}

NS_IMETHODIMP nsImapMailFolder::GetVerifiedAsOnlineFolder(
    bool *aVerifiedAsOnlineFolder) {
  NS_ENSURE_ARG_POINTER(aVerifiedAsOnlineFolder);
  *aVerifiedAsOnlineFolder = m_verifiedAsOnlineFolder;
  return NS_OK;
}

NS_IMETHODIMP nsImapMailFolder::SetVerifiedAsOnlineFolder(
    bool aVerifiedAsOnlineFolder) {
  m_verifiedAsOnlineFolder = aVerifiedAsOnlineFolder;
  // mark ancestors as verified as well
  if (aVerifiedAsOnlineFolder) {
    nsCOMPtr<nsIMsgFolder> parent;
    do {
      GetParent(getter_AddRefs(parent));
      if (parent) {
        nsCOMPtr<nsIMsgImapMailFolder> imapParent = do_QueryInterface(parent);
        if (imapParent) {
          bool verifiedOnline;
          imapParent->GetVerifiedAsOnlineFolder(&verifiedOnline);
          if (verifiedOnline) break;
          imapParent->SetVerifiedAsOnlineFolder(true);
        }
      }
    } while (parent);
  }
  return NS_OK;
}

NS_IMETHODIMP nsImapMailFolder::GetOnlineDelimiter(char *onlineDelimiter) {
  return GetHierarchyDelimiter(onlineDelimiter);
}

NS_IMETHODIMP nsImapMailFolder::SetHierarchyDelimiter(
    char aHierarchyDelimiter) {
  m_hierarchyDelimiter = aHierarchyDelimiter;
  return NS_OK;
}

NS_IMETHODIMP nsImapMailFolder::GetHierarchyDelimiter(
    char *aHierarchyDelimiter) {
  NS_ENSURE_ARG_POINTER(aHierarchyDelimiter);
  if (mIsServer) {
    // if it's the root folder, we don't know the delimiter. So look at the
    // first child.
    int32_t count = mSubFolders.Count();
    if (count > 0) {
      nsCOMPtr<nsIMsgImapMailFolder> childFolder(
          do_QueryInterface(mSubFolders[0]));
      if (childFolder) {
        nsresult rv = childFolder->GetHierarchyDelimiter(aHierarchyDelimiter);
        // some code uses m_hierarchyDelimiter directly, so we should set it.
        m_hierarchyDelimiter = *aHierarchyDelimiter;
        return rv;
      }
    }
  }
  ReadDBFolderInfo(false);  // update cache first.
  *aHierarchyDelimiter = m_hierarchyDelimiter;
  return NS_OK;
}

NS_IMETHODIMP nsImapMailFolder::SetBoxFlags(int32_t aBoxFlags) {
  ReadDBFolderInfo(false);

  m_boxFlags = aBoxFlags;
  uint32_t newFlags = mFlags;

  newFlags |= nsMsgFolderFlags::ImapBox;

  if (m_boxFlags & kNoinferiors)
    newFlags |= nsMsgFolderFlags::ImapNoinferiors;
  else
    newFlags &= ~nsMsgFolderFlags::ImapNoinferiors;
  if (m_boxFlags & kNoselect)
    newFlags |= nsMsgFolderFlags::ImapNoselect;
  else
    newFlags &= ~nsMsgFolderFlags::ImapNoselect;
  if (m_boxFlags & kPublicMailbox)
    newFlags |= nsMsgFolderFlags::ImapPublic;
  else
    newFlags &= ~nsMsgFolderFlags::ImapPublic;
  if (m_boxFlags & kOtherUsersMailbox)
    newFlags |= nsMsgFolderFlags::ImapOtherUser;
  else
    newFlags &= ~nsMsgFolderFlags::ImapOtherUser;
  if (m_boxFlags & kPersonalMailbox)
    newFlags |= nsMsgFolderFlags::ImapPersonal;
  else
    newFlags &= ~nsMsgFolderFlags::ImapPersonal;

  // The following are all flags returned by XLIST.
  // nsImapIncomingServer::DiscoveryDone checks for these folders.
  if (m_boxFlags & kImapDrafts) newFlags |= nsMsgFolderFlags::Drafts;

  if (m_boxFlags & kImapSpam) newFlags |= nsMsgFolderFlags::Junk;

  if (m_boxFlags & kImapSent) newFlags |= nsMsgFolderFlags::SentMail;

  if (m_boxFlags & kImapInbox) newFlags |= nsMsgFolderFlags::Inbox;

  if (m_boxFlags & kImapXListTrash) {
    nsCOMPtr<nsIImapIncomingServer> imapServer;
    nsMsgImapDeleteModel deleteModel = nsMsgImapDeleteModels::MoveToTrash;
    (void)GetImapIncomingServer(getter_AddRefs(imapServer));
    if (imapServer) imapServer->GetDeleteModel(&deleteModel);
    if (deleteModel == nsMsgImapDeleteModels::MoveToTrash)
      newFlags |= nsMsgFolderFlags::Trash;
  }
  // Treat the GMail all mail folder as the archive folder.
  if (m_boxFlags & (kImapAllMail | kImapArchive))
    newFlags |= nsMsgFolderFlags::Archive;

  SetFlags(newFlags);
  return NS_OK;
}

NS_IMETHODIMP nsImapMailFolder::GetBoxFlags(int32_t *aBoxFlags) {
  NS_ENSURE_ARG_POINTER(aBoxFlags);
  *aBoxFlags = m_boxFlags;
  return NS_OK;
}

NS_IMETHODIMP nsImapMailFolder::GetExplicitlyVerify(bool *aExplicitlyVerify) {
  NS_ENSURE_ARG_POINTER(aExplicitlyVerify);
  *aExplicitlyVerify = m_explicitlyVerify;
  return NS_OK;
}

NS_IMETHODIMP nsImapMailFolder::SetExplicitlyVerify(bool aExplicitlyVerify) {
  m_explicitlyVerify = aExplicitlyVerify;
  return NS_OK;
}

NS_IMETHODIMP nsImapMailFolder::GetNoSelect(bool *aResult) {
  NS_ENSURE_ARG_POINTER(aResult);
  return GetFlag(nsMsgFolderFlags::ImapNoselect, aResult);
}

NS_IMETHODIMP nsImapMailFolder::ApplyRetentionSettings() {
  int32_t numDaysToKeepOfflineMsgs = -1;

  // Check if we've limited the offline storage by age.
  nsCOMPtr<nsIImapIncomingServer> imapServer;
  nsresult rv = GetImapIncomingServer(getter_AddRefs(imapServer));
  NS_ENSURE_SUCCESS(rv, rv);
  imapServer->GetAutoSyncMaxAgeDays(&numDaysToKeepOfflineMsgs);

  nsCOMPtr<nsIMsgDatabase> holdDBOpen;
  if (numDaysToKeepOfflineMsgs > 0) {
    bool dbWasCached = mDatabase != nullptr;
    rv = GetDatabase();
    NS_ENSURE_SUCCESS(rv, rv);
    nsCOMPtr<nsISimpleEnumerator> hdrs;
    rv = mDatabase->EnumerateMessages(getter_AddRefs(hdrs));
    NS_ENSURE_SUCCESS(rv, rv);
    bool hasMore = false;

    PRTime cutOffDay =
        MsgConvertAgeInDaysToCutoffDate(numDaysToKeepOfflineMsgs);

    nsCOMPtr<nsIMsgDBHdr> pHeader;
    // so now cutOffDay is the PRTime cut-off point. Any offline msg with
    // a date less than that will get marked for pending removal.
    while (NS_SUCCEEDED(rv = hdrs->HasMoreElements(&hasMore)) && hasMore) {
      nsCOMPtr<nsISupports> supports;
      rv = hdrs->GetNext(getter_AddRefs(supports));
      NS_ENSURE_SUCCESS(rv, rv);
      pHeader = do_QueryInterface(supports, &rv);
      NS_ENSURE_SUCCESS(rv, rv);

      uint32_t msgFlags;
      PRTime msgDate;
      pHeader->GetFlags(&msgFlags);
      if (msgFlags & nsMsgMessageFlags::Offline) {
        pHeader->GetDate(&msgDate);
        MarkPendingRemoval(pHeader, msgDate < cutOffDay);
        // I'm horribly tempted to break out of the loop if we've found
        // a message after the cut-off date, because messages will most likely
        // be in date order in the db, but there are always edge cases.
      }
    }
    if (!dbWasCached) {
      holdDBOpen = mDatabase;
      mDatabase = nullptr;
    }
  }
  return nsMsgDBFolder::ApplyRetentionSettings();
}

/**
 * The listener will get called when both the online expunge and the offline
 * store compaction are finished (if the latter is needed).
 */
NS_IMETHODIMP nsImapMailFolder::Compact(nsIUrlListener *aListener,
                                        nsIMsgWindow *aMsgWindow) {
  GetDatabase();
  // now's a good time to apply the retention settings. If we do delete any
  // messages, the expunge is going to have to wait until the delete to
  // finish before it can run, but the multiple-connection protection code
  // should handle that.
  if (mDatabase) ApplyRetentionSettings();

  m_urlListener = aListener;
  // We should be able to compact the offline store now that this should
  // just be called by the UI.
  if (aMsgWindow && (mFlags & nsMsgFolderFlags::Offline)) {
    m_compactingOfflineStore = true;
    CompactOfflineStore(aMsgWindow, this);
  }
  if (WeAreOffline()) return NS_OK;
  m_expunging = true;
  return Expunge(this, aMsgWindow);
}

NS_IMETHODIMP
nsImapMailFolder::NotifyCompactCompleted() {
  if (!m_expunging && m_urlListener) {
    m_urlListener->OnStopRunningUrl(nullptr, NS_OK);
    m_urlListener = nullptr;
  }
  m_compactingOfflineStore = false;
  return NS_OK;
}

NS_IMETHODIMP nsImapMailFolder::MarkPendingRemoval(nsIMsgDBHdr *aHdr,
                                                   bool aMark) {
  NS_ENSURE_ARG_POINTER(aHdr);
  uint32_t offlineMessageSize;
  aHdr->GetOfflineMessageSize(&offlineMessageSize);
  aHdr->SetStringProperty("pendingRemoval", aMark ? "1" : "");
  if (!aMark) return NS_OK;
  nsresult rv = GetDatabase();
  NS_ENSURE_SUCCESS(rv, rv);
  nsCOMPtr<nsIDBFolderInfo> dbFolderInfo;
  rv = mDatabase->GetDBFolderInfo(getter_AddRefs(dbFolderInfo));
  NS_ENSURE_SUCCESS(rv, rv);
  return dbFolderInfo->ChangeExpungedBytes(offlineMessageSize);
}

NS_IMETHODIMP nsImapMailFolder::Expunge(nsIUrlListener *aListener,
                                        nsIMsgWindow *aMsgWindow) {
  nsresult rv;
  nsCOMPtr<nsIImapService> imapService =
      do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
  NS_ENSURE_SUCCESS(rv, rv);

  return imapService->Expunge(this, aListener, aMsgWindow, nullptr);
}

NS_IMETHODIMP nsImapMailFolder::CompactAll(nsIUrlListener *aListener,
                                           nsIMsgWindow *aMsgWindow,
                                           bool aCompactOfflineAlso) {
  nsresult rv;
  nsCOMPtr<nsIMutableArray> folderArray, offlineFolderArray;

  nsCOMPtr<nsIMsgFolder> rootFolder;
  nsCOMPtr<nsIArray> allDescendents;
  rv = GetRootFolder(getter_AddRefs(rootFolder));
  if (NS_SUCCEEDED(rv) && rootFolder) {
    rootFolder->GetDescendants(getter_AddRefs(allDescendents));
    uint32_t cnt = 0;
    rv = allDescendents->GetLength(&cnt);
    NS_ENSURE_SUCCESS(rv, rv);
    folderArray = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
    NS_ENSURE_TRUE(folderArray, rv);
    if (aCompactOfflineAlso) {
      offlineFolderArray = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
      NS_ENSURE_TRUE(offlineFolderArray, rv);
    }
    for (uint32_t i = 0; i < cnt; i++) {
      nsCOMPtr<nsIMsgFolder> folder = do_QueryElementAt(allDescendents, i, &rv);
      NS_ENSURE_SUCCESS(rv, rv);
      uint32_t folderFlags;
      folder->GetFlags(&folderFlags);
      if (!(folderFlags &
            (nsMsgFolderFlags::Virtual | nsMsgFolderFlags::ImapNoselect))) {
        rv = folderArray->AppendElement(folder);
        if (aCompactOfflineAlso) offlineFolderArray->AppendElement(folder);
      }
    }
    rv = folderArray->GetLength(&cnt);
    NS_ENSURE_SUCCESS(rv, rv);
    if (cnt == 0) return NotifyCompactCompleted();
  }
  nsCOMPtr<nsIMsgFolderCompactor> folderCompactor =
      do_CreateInstance(NS_MSGLOCALFOLDERCOMPACTOR_CONTRACTID, &rv);
  NS_ENSURE_SUCCESS(rv, rv);
  return folderCompactor->CompactFolders(folderArray, offlineFolderArray,
                                         aListener, aMsgWindow);
}

NS_IMETHODIMP nsImapMailFolder::UpdateStatus(nsIUrlListener *aListener,
                                             nsIMsgWindow *aMsgWindow) {
  nsresult rv;
  nsCOMPtr<nsIImapService> imapService =
      do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
  NS_ENSURE_SUCCESS(rv, rv);

  nsCOMPtr<nsIURI> uri;
  rv = imapService->UpdateFolderStatus(this, aListener, getter_AddRefs(uri));
  if (uri && !aMsgWindow) {
    nsCOMPtr<nsIMsgMailNewsUrl> mailNewsUrl = do_QueryInterface(uri, &rv);
    NS_ENSURE_SUCCESS(rv, rv);
    // if no msg window, we won't put up error messages (this is almost
    // certainly a biff-inspired status)
    mailNewsUrl->SetSuppressErrorMsgs(true);
  }
  return rv;
}

NS_IMETHODIMP nsImapMailFolder::EmptyTrash(nsIMsgWindow *aMsgWindow,
                                           nsIUrlListener *aListener) {
  nsCOMPtr<nsIMsgFolder> trashFolder;
  nsresult rv = GetTrashFolder(getter_AddRefs(trashFolder));
  if (NS_SUCCEEDED(rv)) {
    nsCOMPtr<nsIMsgAccountManager> accountManager =
        do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv);
    NS_ENSURE_SUCCESS(rv, rv);
    // if we are emptying trash on exit and we are an aol server then don't
    // perform this operation because it's causing a hang that we haven't been
    // able to figure out yet this is an rtm fix and we'll look for the right
    // solution post rtm.
    bool empytingOnExit = false;
    accountManager->GetEmptyTrashInProgress(&empytingOnExit);
    if (empytingOnExit) {
      nsCOMPtr<nsIImapIncomingServer> imapServer;
      rv = GetImapIncomingServer(getter_AddRefs(imapServer));
      if (imapServer) {
        bool isAOLServer = false;
        imapServer->GetIsAOLServer(&isAOLServer);
        if (isAOLServer)
          return NS_ERROR_FAILURE;  // we will not be performing an empty
                                    // trash....
      }                             // if we fetched an imap server
    }  // if emptying trash on exit which is done through the account manager.

    if (WeAreOffline()) {
      nsCOMPtr<nsIMsgDatabase> trashDB;
      rv = trashFolder->GetMsgDatabase(getter_AddRefs(trashDB));
      if (trashDB) {
        nsMsgKey fakeKey;
        trashDB->GetNextFakeOfflineMsgKey(&fakeKey);

        nsCOMPtr<nsIMsgOfflineImapOperation> op;
        rv = trashDB->GetOfflineOpForKey(fakeKey, true, getter_AddRefs(op));
        trashFolder->SetFlag(nsMsgFolderFlags::OfflineEvents);
        op->SetOperation(nsIMsgOfflineImapOperation::kDeleteAllMsgs);
      }
      return rv;
    }

    nsCOMPtr<nsIImapService> imapService =
        do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
    NS_ENSURE_SUCCESS(rv, rv);

    if (aListener)
      rv = imapService->DeleteAllMessages(trashFolder, aListener, nullptr);
    else {
      nsCOMPtr<nsIUrlListener> urlListener = do_QueryInterface(trashFolder);
      rv = imapService->DeleteAllMessages(trashFolder, urlListener, nullptr);
    }
    // Return an error if this failed. We want the empty trash on exit code
    // to know if this fails so that it doesn't block waiting for empty trash to
    // finish.
    NS_ENSURE_SUCCESS(rv, rv);

    bool hasSubfolders = false;
    rv = trashFolder->GetHasSubFolders(&hasSubfolders);
    NS_ENSURE_SUCCESS(rv, rv);
    if (hasSubfolders) {
      nsCOMPtr<nsISimpleEnumerator> enumerator;
      nsCOMPtr<nsISupports> item;
      nsCOMArray<nsIMsgFolder> array;

      rv = trashFolder->GetSubFolders(getter_AddRefs(enumerator));
      NS_ENSURE_SUCCESS(rv, rv);

      bool hasMore;
      while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMore)) && hasMore) {
        rv = enumerator->GetNext(getter_AddRefs(item));
        if (NS_SUCCEEDED(rv)) {
          nsCOMPtr<nsIMsgFolder> folder(do_QueryInterface(item, &rv));
          if (NS_SUCCEEDED(rv)) array.AppendObject(folder);
        }
      }
      for (int32_t i = array.Count() - 1; i >= 0; i--) {
        trashFolder->PropagateDelete(array[i], true, aMsgWindow);
        // Remove the object, presumably to free it up before we delete the
        // next.
        array.RemoveObjectAt(i);
      }
    }

    nsCOMPtr<nsIDBFolderInfo> transferInfo;
    rv = trashFolder->GetDBTransferInfo(getter_AddRefs(transferInfo));
    NS_ENSURE_SUCCESS(rv, rv);
    // Bulk-delete all the messages by deleting the msf file and storage.
    // This is a little kludgy.
    rv = trashFolder->DeleteStorage();
    NS_ENSURE_SUCCESS(rv, rv);
    trashFolder->SetDBTransferInfo(transferInfo);
    trashFolder->SetSizeOnDisk(0);

    // The trash folder has effectively been deleted.
    nsCOMPtr<nsIMsgFolderNotificationService> notifier(
        do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID));
    if (notifier) notifier->NotifyFolderDeleted(trashFolder);

    return NS_OK;
  }
  return rv;
}

NS_IMETHODIMP nsImapMailFolder::DeleteStorage() {
  nsresult rv = nsMsgDBFolder::DeleteStorage();

  // Should notify nsIMsgFolderListeners about the folder getting deleted?
  return rv;
}

NS_IMETHODIMP nsImapMailFolder::Rename(const nsAString &newName,
                                       nsIMsgWindow *msgWindow) {
  if (mFlags & nsMsgFolderFlags::Virtual)
    return nsMsgDBFolder::Rename(newName, msgWindow);
  nsresult rv;
  nsAutoString newNameStr(newName);
  if (newNameStr.FindChar(m_hierarchyDelimiter, 0) != kNotFound) {
    nsCOMPtr<nsIDocShell> docShell;
    if (msgWindow) msgWindow->GetRootDocShell(getter_AddRefs(docShell));
    if (docShell) {
      nsCOMPtr<nsIStringBundle> bundle;
      rv = IMAPGetStringBundle(getter_AddRefs(bundle));
      if (NS_SUCCEEDED(rv) && bundle) {
        AutoTArray<nsString, 1> formatStrings;
        formatStrings.AppendElement()->Append(m_hierarchyDelimiter);
        nsString alertString;
        rv = bundle->FormatStringFromName("imapSpecialChar2", formatStrings,
                                          alertString);
        nsCOMPtr<nsIPrompt> dialog(do_GetInterface(docShell));
        // setting up the dialog title
        nsCOMPtr<nsIMsgIncomingServer> server;
        rv = GetServer(getter_AddRefs(server));
        NS_ENSURE_SUCCESS(rv, rv);
        nsString dialogTitle;
        nsString accountName;
        rv = server->GetPrettyName(accountName);
        NS_ENSURE_SUCCESS(rv, rv);
        AutoTArray<nsString, 1> titleParams = {accountName};
        rv = bundle->FormatStringFromName("imapAlertDialogTitle", titleParams,
                                          dialogTitle);

        if (dialog && !alertString.IsEmpty())
          dialog->Alert(dialogTitle.get(), alertString.get());
      }
    }
    return NS_ERROR_FAILURE;
  }
  nsCOMPtr<nsIImapIncomingServer> incomingImapServer;
  GetImapIncomingServer(getter_AddRefs(incomingImapServer));
  if (incomingImapServer) RecursiveCloseActiveConnections(incomingImapServer);

  nsCOMPtr<nsIImapService> imapService =
      do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
  NS_ENSURE_SUCCESS(rv, rv);
  return imapService->RenameLeaf(this, newName, this, msgWindow, nullptr);
}

NS_IMETHODIMP nsImapMailFolder::RecursiveCloseActiveConnections(
    nsIImapIncomingServer *incomingImapServer) {
  NS_ENSURE_ARG(incomingImapServer);

  nsCOMPtr<nsIMsgImapMailFolder> folder;
  int32_t count = mSubFolders.Count();
  for (int32_t i = 0; i < count; i++) {
    folder = do_QueryInterface(mSubFolders[i]);
    if (folder) folder->RecursiveCloseActiveConnections(incomingImapServer);

    incomingImapServer->CloseConnectionForFolder(mSubFolders[i]);
  }
  return NS_OK;
}

// this is called *after* we've done the rename on the server.
NS_IMETHODIMP nsImapMailFolder::PrepareToRename() {
  nsCOMPtr<nsIMsgImapMailFolder> folder;
  int32_t count = mSubFolders.Count();
  for (int32_t i = 0; i < count; i++) {
    folder = do_QueryInterface(mSubFolders[i]);
    if (folder) folder->PrepareToRename();
  }

  SetOnlineName(EmptyCString());
  return NS_OK;
}

NS_IMETHODIMP nsImapMailFolder::RenameLocal(const nsACString &newName,
                                            nsIMsgFolder *parent) {
  // XXX Here it's assumed that IMAP folder names are stored locally
  // in modified UTF-7 (ASCII-only) as is stored remotely.  If we ever change
  // this, we have to work with nsString instead of nsCString
  // (ref. bug 264071)
  nsAutoCString leafname(newName);
  nsAutoCString parentName;
  // newName always in the canonical form "greatparent/parentname/leafname"
  int32_t leafpos = leafname.RFindChar('/');
  if (leafpos > 0) leafname.Cut(0, leafpos + 1);
  m_msgParser = nullptr;
  PrepareToRename();
  CloseAndBackupFolderDB(leafname);

  nsresult rv = NS_OK;
  nsCOMPtr<nsIFile> oldPathFile;
  rv = GetFilePath(getter_AddRefs(oldPathFile));
  if (NS_FAILED(rv)) return rv;

  nsCOMPtr<nsIFile> parentPathFile;
  rv = parent->GetFilePath(getter_AddRefs(parentPathFile));
  NS_ENSURE_SUCCESS(rv, rv);

  bool isDirectory = false;
  parentPathFile->IsDirectory(&isDirectory);
  if (!isDirectory) AddDirectorySeparator(parentPathFile);

  nsCOMPtr<nsIFile> dirFile;

  int32_t count = mSubFolders.Count();
  if (count > 0) rv = CreateDirectoryForFolder(getter_AddRefs(dirFile));

  nsCOMPtr<nsIFile> oldSummaryFile;
  rv = GetSummaryFileLocation(oldPathFile, getter_AddRefs(oldSummaryFile));
  NS_ENSURE_SUCCESS(rv, rv);

  nsAutoCString newNameStr;
  oldSummaryFile->Remove(false);
  if (count > 0) {
    newNameStr = leafname;
    NS_MsgHashIfNecessary(newNameStr);
    newNameStr.AppendLiteral(FOLDER_SUFFIX8);
    nsAutoCString leafName;
    dirFile->GetNativeLeafName(leafName);
    if (!leafName.Equals(newNameStr))
      return dirFile->MoveToNative(
          nullptr,
          newNameStr);  // in case of rename operation leaf names will differ

    parentPathFile->AppendNative(
        newNameStr);  // only for move we need to progress further in case the
                      // parent differs
    bool isDirectory = false;
    parentPathFile->IsDirectory(&isDirectory);
    if (!isDirectory) {
      rv = parentPathFile->Create(nsIFile::DIRECTORY_TYPE, 0700);
      NS_ENSURE_SUCCESS(rv, rv);
    } else {
      NS_ERROR("Directory already exists.");
    }
    rv = RecursiveCopy(dirFile, parentPathFile);
    NS_ENSURE_SUCCESS(rv, rv);
    dirFile->Remove(true);  // moving folders
  }
  return rv;
}

NS_IMETHODIMP nsImapMailFolder::GetPrettyName(nsAString &prettyName) {
  return GetName(prettyName);
}

NS_IMETHODIMP nsImapMailFolder::UpdateSummaryTotals(bool force) {
  // bug 72871 inserted the mIsServer check for IMAP
  return mIsServer ? NS_OK : nsMsgDBFolder::UpdateSummaryTotals(force);
}

NS_IMETHODIMP nsImapMailFolder::GetDeletable(bool *deletable) {
  NS_ENSURE_ARG_POINTER(deletable);

  bool isServer;
  GetIsServer(&isServer);

  *deletable = !(isServer || (mFlags & nsMsgFolderFlags::SpecialUse));
  return NS_OK;
}

NS_IMETHODIMP nsImapMailFolder::GetSizeOnDisk(int64_t *size) {
  NS_ENSURE_ARG_POINTER(size);

  bool isServer = false;
  nsresult rv = GetIsServer(&isServer);
  // If this is the rootFolder, return 0 as a safe value.
  if (NS_FAILED(rv) || isServer) mFolderSize = 0;

  *size = mFolderSize;
  return NS_OK;
}

NS_IMETHODIMP
nsImapMailFolder::GetCanCreateSubfolders(bool *aResult) {
  NS_ENSURE_ARG_POINTER(aResult);
  *aResult = !(mFlags &
               (nsMsgFolderFlags::ImapNoinferiors | nsMsgFolderFlags::Virtual));

  bool isServer = false;
  GetIsServer(&isServer);
  if (!isServer) {
    nsCOMPtr<nsIImapIncomingServer> imapServer;
    nsresult rv = GetImapIncomingServer(getter_AddRefs(imapServer));
    bool dualUseFolders = true;
    if (NS_SUCCEEDED(rv) && imapServer)
      imapServer->GetDualUseFolders(&dualUseFolders);
    if (!dualUseFolders && *aResult)
      *aResult = (mFlags & nsMsgFolderFlags::ImapNoselect);
  }
  return NS_OK;
}

NS_IMETHODIMP
nsImapMailFolder::GetCanSubscribe(bool *aResult) {
  NS_ENSURE_ARG_POINTER(aResult);
  *aResult = false;

  bool isImapServer = false;
  nsresult rv = GetIsServer(&isImapServer);
  if (NS_FAILED(rv)) return rv;
  // you can only subscribe to imap servers, not imap folders
  *aResult = isImapServer;
  return NS_OK;
}

nsresult nsImapMailFolder::GetServerKey(nsACString &serverKey) {
  // look for matching imap folders, then pop folders
  nsCOMPtr<nsIMsgIncomingServer> server;
  nsresult rv = GetServer(getter_AddRefs(server));
  if (NS_SUCCEEDED(rv)) rv = server->GetKey(serverKey);
  return rv;
}

NS_IMETHODIMP
nsImapMailFolder::GetImapIncomingServer(
    nsIImapIncomingServer **aImapIncomingServer) {
  NS_ENSURE_ARG(aImapIncomingServer);
  nsCOMPtr<nsIMsgIncomingServer> server;
  if (NS_SUCCEEDED(GetServer(getter_AddRefs(server))) && server) {
    nsCOMPtr<nsIImapIncomingServer> incomingServer = do_QueryInterface(server);
    incomingServer.forget(aImapIncomingServer);
    return NS_OK;
  }
  return NS_ERROR_NULL_POINTER;
}

NS_IMETHODIMP
nsImapMailFolder::AddMessageDispositionState(
    nsIMsgDBHdr *aMessage, nsMsgDispositionState aDispositionFlag) {
  nsMsgDBFolder::AddMessageDispositionState(aMessage, aDispositionFlag);

  // set the mark message answered flag on the server for this message...
  if (aMessage) {
    nsMsgKey msgKey;
    aMessage->GetMessageKey(&msgKey);

    if (aDispositionFlag == nsIMsgFolder::nsMsgDispositionState_Replied)
      StoreImapFlags(kImapMsgAnsweredFlag, true, {msgKey}, nullptr);
    else if (aDispositionFlag == nsIMsgFolder::nsMsgDispositionState_Forwarded)
      StoreImapFlags(kImapMsgForwardedFlag, true, {msgKey}, nullptr);
  }
  return NS_OK;
}

NS_IMETHODIMP
nsImapMailFolder::MarkMessagesRead(nsIArray *messages, bool markRead) {
  // tell the folder to do it, which will mark them read in the db.
  nsresult rv = nsMsgDBFolder::MarkMessagesRead(messages, markRead);
  if (NS_SUCCEEDED(rv)) {
    nsAutoCString messageIds;
    nsTArray<nsMsgKey> keysToMarkRead;
    rv = BuildIdsAndKeyArray(messages, messageIds, keysToMarkRead);
    NS_ENSURE_SUCCESS(rv, rv);

    StoreImapFlags(kImapMsgSeenFlag, markRead, keysToMarkRead, nullptr);
    rv = GetDatabase();
    if (NS_SUCCEEDED(rv)) mDatabase->Commit(nsMsgDBCommitType::kLargeCommit);
  }
  return rv;
}

NS_IMETHODIMP
nsImapMailFolder::SetLabelForMessages(nsIArray *aMessages,
                                      nsMsgLabelValue aLabel) {
  NS_ENSURE_ARG(aMessages);

  nsresult rv = nsMsgDBFolder::SetLabelForMessages(aMessages, aLabel);
  if (NS_SUCCEEDED(rv)) {
    nsAutoCString messageIds;
    nsTArray<nsMsgKey> keysToLabel;
    nsresult rv = BuildIdsAndKeyArray(aMessages, messageIds, keysToLabel);
    NS_ENSURE_SUCCESS(rv, rv);
    StoreImapFlags((aLabel << 9), true, keysToLabel, nullptr);
    rv = GetDatabase();
    if (NS_SUCCEEDED(rv)) mDatabase->Commit(nsMsgDBCommitType::kLargeCommit);
  }
  return rv;
}

NS_IMETHODIMP
nsImapMailFolder::MarkAllMessagesRead(nsIMsgWindow *aMsgWindow) {
  nsresult rv = GetDatabase();
  if (NS_SUCCEEDED(rv)) {
    nsTArray<nsMsgKey> thoseMarked;
    EnableNotifications(allMessageCountNotifications, false);
    rv = mDatabase->MarkAllRead(thoseMarked);
    EnableNotifications(allMessageCountNotifications, true);
    if (NS_SUCCEEDED(rv) && thoseMarked.Length() > 0) {
      rv = StoreImapFlags(kImapMsgSeenFlag, true, thoseMarked, nullptr);
      mDatabase->Commit(nsMsgDBCommitType::kLargeCommit);

      // Setup a undo-state
      if (aMsgWindow)
        rv = AddMarkAllReadUndoAction(aMsgWindow, thoseMarked.Elements(),
                                      thoseMarked.Length());
    }
  }
  return rv;
}

NS_IMETHODIMP nsImapMailFolder::MarkThreadRead(nsIMsgThread *thread) {
  nsresult rv = GetDatabase();
  if (NS_SUCCEEDED(rv)) {
    nsTArray<nsMsgKey> keys;
    rv = mDatabase->MarkThreadRead(thread, nullptr, keys);
    if (NS_SUCCEEDED(rv) && keys.Length() > 0) {
      rv = StoreImapFlags(kImapMsgSeenFlag, true, keys, nullptr);
      mDatabase->Commit(nsMsgDBCommitType::kLargeCommit);
    }
  }
  return rv;
}

NS_IMETHODIMP nsImapMailFolder::ReadFromFolderCacheElem(
    nsIMsgFolderCacheElement *element) {
  nsresult rv = nsMsgDBFolder::ReadFromFolderCacheElem(element);
  int32_t hierarchyDelimiter = kOnlineHierarchySeparatorUnknown;
  nsCString onlineName;

  element->GetInt32Property("boxFlags", &m_boxFlags);
  if (NS_SUCCEEDED(
          element->GetInt32Property("hierDelim", &hierarchyDelimiter)) &&
      hierarchyDelimiter != kOnlineHierarchySeparatorUnknown)
    m_hierarchyDelimiter = (char)hierarchyDelimiter;
  rv = element->GetStringProperty("onlineName", onlineName);
  if (NS_SUCCEEDED(rv) && !onlineName.IsEmpty())
    m_onlineFolderName.Assign(onlineName);

  m_aclFlags = kAclInvalid;  // init to invalid value.
  element->GetInt32Property("aclFlags", (int32_t *)&m_aclFlags);
  element->GetInt32Property("serverTotal", &m_numServerTotalMessages);
  element->GetInt32Property("serverUnseen", &m_numServerUnseenMessages);
  element->GetInt32Property("serverRecent", &m_numServerRecentMessages);
  element->GetInt32Property("nextUID", &m_nextUID);
  int32_t lastSyncTimeInSec;
  if (NS_FAILED(element->GetInt32Property("lastSyncTimeInSec",
                                          (int32_t *)&lastSyncTimeInSec)))
    lastSyncTimeInSec = 0U;

  // make sure that auto-sync state object is created
  InitAutoSyncState();
  m_autoSyncStateObj->SetLastSyncTimeInSec(lastSyncTimeInSec);

  return rv;
}

NS_IMETHODIMP nsImapMailFolder::WriteToFolderCacheElem(
    nsIMsgFolderCacheElement *element) {
  nsresult rv = nsMsgDBFolder::WriteToFolderCacheElem(element);
  element->SetInt32Property("boxFlags", m_boxFlags);
  element->SetInt32Property("hierDelim", (int32_t)m_hierarchyDelimiter);
  element->SetStringProperty("onlineName", m_onlineFolderName);
  element->SetInt32Property("aclFlags", (int32_t)m_aclFlags);
  element->SetInt32Property("serverTotal", m_numServerTotalMessages);
  element->SetInt32Property("serverUnseen", m_numServerUnseenMessages);
  element->SetInt32Property("serverRecent", m_numServerRecentMessages);
  if (m_nextUID != (int32_t)nsMsgKey_None)
    element->SetInt32Property("nextUID", m_nextUID);

  // store folder's last sync time
  if (m_autoSyncStateObj) {
    PRTime lastSyncTime;
    m_autoSyncStateObj->GetLastSyncTime(&lastSyncTime);
    // store in sec
    element->SetInt32Property("lastSyncTimeInSec",
                              (int32_t)(lastSyncTime / PR_USEC_PER_SEC));
  }

  return rv;
}

NS_IMETHODIMP
nsImapMailFolder::MarkMessagesFlagged(nsIArray *messages, bool markFlagged) {
  nsresult rv;
  // tell the folder to do it, which will mark them read in the db.
  rv = nsMsgDBFolder::MarkMessagesFlagged(messages, markFlagged);
  if (NS_SUCCEEDED(rv)) {
    nsAutoCString messageIds;
    nsTArray<nsMsgKey> keysToMarkFlagged;
    rv = BuildIdsAndKeyArray(messages, messageIds, keysToMarkFlagged);
    if (NS_FAILED(rv)) return rv;
    rv = StoreImapFlags(kImapMsgFlaggedFlag, markFlagged, keysToMarkFlagged,
                        nullptr);
    NS_ENSURE_SUCCESS(rv, rv);
    rv = GetDatabase();
    NS_ENSURE_SUCCESS(rv, rv);
    mDatabase->Commit(nsMsgDBCommitType::kLargeCommit);
  }
  return rv;
}

NS_IMETHODIMP nsImapMailFolder::SetOnlineName(
    const nsACString &aOnlineFolderName) {
  nsresult rv;
  nsCOMPtr<nsIMsgDatabase> db;
  nsCOMPtr<nsIDBFolderInfo> folderInfo;
  rv = GetDBFolderInfoAndDB(getter_AddRefs(folderInfo), getter_AddRefs(db));
  // do this after GetDBFolderInfoAndDB, because it crunches m_onlineFolderName
  // (not sure why)
  m_onlineFolderName = aOnlineFolderName;
  if (NS_SUCCEEDED(rv) && folderInfo) {
    nsAutoString onlineName;
    CopyASCIItoUTF16(aOnlineFolderName, onlineName);
    rv = folderInfo->SetProperty("onlineName", onlineName);
    rv = folderInfo->SetMailboxName(onlineName);
    // so, when are we going to commit this? Definitely not every time!
    // We could check if the online name has changed.
    db->Commit(nsMsgDBCommitType::kLargeCommit);
  }
  folderInfo = nullptr;
  return rv;
}

NS_IMETHODIMP nsImapMailFolder::GetOnlineName(nsACString &aOnlineFolderName) {
  ReadDBFolderInfo(false);  // update cache first.
  aOnlineFolderName = m_onlineFolderName;
  return NS_OK;
}

NS_IMETHODIMP
nsImapMailFolder::GetDBFolderInfoAndDB(nsIDBFolderInfo **folderInfo,
                                       nsIMsgDatabase **db) {
  NS_ENSURE_ARG_POINTER(folderInfo);
  NS_ENSURE_ARG_POINTER(db);

  nsresult rv = GetDatabase();
  if (NS_FAILED(rv)) return rv;

  NS_ADDREF(*db = mDatabase);

  rv = (*db)->GetDBFolderInfo(folderInfo);
  if (NS_FAILED(rv))
    return rv;  // GetDBFolderInfo can't return NS_OK if !folderInfo

  nsCString onlineName;
  rv = (*folderInfo)->GetCharProperty("onlineName", onlineName);
  if (NS_FAILED(rv)) return rv;

  if (!onlineName.IsEmpty())
    m_onlineFolderName.Assign(onlineName);
  else {
    nsAutoString autoOnlineName;
    (*folderInfo)->GetMailboxName(autoOnlineName);
    if (autoOnlineName.IsEmpty()) {
      nsCString uri;
      rv = GetURI(uri);
      NS_ENSURE_SUCCESS(rv, rv);

      nsCString hostname;
      rv = GetHostname(hostname);
      NS_ENSURE_SUCCESS(rv, rv);

      nsCString onlineCName;
      rv = nsImapURI2FullName(kImapRootURI, hostname.get(), uri.get(),
                              getter_Copies(onlineCName));
      if (m_hierarchyDelimiter != '/')
        onlineCName.ReplaceChar('/', m_hierarchyDelimiter);
      m_onlineFolderName.Assign(onlineCName);
      CopyASCIItoUTF16(onlineCName, autoOnlineName);
    }
    (*folderInfo)->SetProperty("onlineName", autoOnlineName);
  }
  return rv;
}

/* static */ nsresult nsImapMailFolder::BuildIdsAndKeyArray(
    nsIArray *messages, nsCString &msgIds, nsTArray<nsMsgKey> &keyArray) {
  NS_ENSURE_ARG_POINTER(messages);
  nsresult rv;
  uint32_t count = 0;
  uint32_t i;
  rv = messages->GetLength(&count);
  if (NS_FAILED(rv)) return rv;

  // build up message keys.
  for (i = 0; i < count; i++) {
    nsMsgKey key;
    nsCOMPtr<nsIMsgDBHdr> msgDBHdr = do_QueryElementAt(messages, i, &rv);
    if (msgDBHdr) rv = msgDBHdr->GetMessageKey(&key);
    if (NS_SUCCEEDED(rv)) keyArray.AppendElement(key);
  }
  return AllocateUidStringFromKeys(keyArray, msgIds);
}

/* static */ nsresult nsImapMailFolder::AllocateUidStringFromKeys(
    const nsTArray<nsMsgKey> &keys, nsCString &msgIds) {
  if (keys.IsEmpty()) return NS_ERROR_INVALID_ARG;
  nsresult rv = NS_OK;
  uint32_t startSequence;
  startSequence = keys[0];
  uint32_t curSequenceEnd = startSequence;
  uint32_t total = keys.Length();
  // sort keys and then generate ranges instead of singletons!
  nsTArray<nsMsgKey> sorted(keys);
  sorted.Sort();
  for (uint32_t keyIndex = 0; keyIndex < total; keyIndex++) {
    uint32_t curKey = sorted[keyIndex];
    uint32_t nextKey =
        (keyIndex + 1 < total) ? sorted[keyIndex + 1] : 0xFFFFFFFF;
    bool lastKey = (nextKey == 0xFFFFFFFF);

    if (lastKey) curSequenceEnd = curKey;
    if (nextKey == (uint32_t)curSequenceEnd + 1 && !lastKey) {
      curSequenceEnd = nextKey;
      continue;
    }
    if (curSequenceEnd > startSequence) {
      AppendUid(msgIds, startSequence);
      msgIds += ':';
      AppendUid(msgIds, curSequenceEnd);
      if (!lastKey) msgIds += ',';
      startSequence = nextKey;
      curSequenceEnd = startSequence;
    } else {
      startSequence = nextKey;
      curSequenceEnd = startSequence;
      AppendUid(msgIds, sorted[keyIndex]);
      if (!lastKey) msgIds += ',';
    }
  }
  return rv;
}

nsresult nsImapMailFolder::MarkMessagesImapDeleted(nsTArray<nsMsgKey> *keyArray,
                                                   bool deleted,
                                                   nsIMsgDatabase *db) {
  for (uint32_t kindex = 0; kindex < keyArray->Length(); kindex++) {
    nsMsgKey key = keyArray->ElementAt(kindex);
    db->MarkImapDeleted(key, deleted, nullptr);
  }
  return NS_OK;
}

NS_IMETHODIMP nsImapMailFolder::DeleteMessages(
    nsIArray *messages, nsIMsgWindow *msgWindow, bool deleteStorage,
    bool isMove, nsIMsgCopyServiceListener *listener, bool allowUndo) {
  // Stopgap. Build a parallel array of message headers while we complete
  // removal of nsIArray usage (Bug 1583030).
  uint32_t messageCount;
  messages->GetLength(&messageCount);
  nsTArray<RefPtr<nsIMsgDBHdr>> msgHeaders;
  msgHeaders.SetCapacity(messageCount);
  for (uint32_t i = 0; i < messageCount; ++i) {
    msgHeaders.AppendElement(do_QueryElementAt(messages, i));
  }

  // *** jt - assuming delete is move to the trash folder for now
  nsAutoCString uri;
  bool deleteImmediatelyNoTrash = false;
  nsAutoCString messageIds;
  nsTArray<nsMsgKey> srcKeyArray;
  bool deleteMsgs = true;  // used for toggling delete status - default is true
  nsMsgImapDeleteModel deleteModel = nsMsgImapDeleteModels::MoveToTrash;
  imapMessageFlagsType messageFlags = kImapMsgDeletedFlag;

  nsCOMPtr<nsIImapIncomingServer> imapServer;
  nsresult rv = GetFlag(nsMsgFolderFlags::Trash, &deleteImmediatelyNoTrash);
  rv = GetImapIncomingServer(getter_AddRefs(imapServer));

  if (NS_SUCCEEDED(rv) && imapServer) {
    imapServer->GetDeleteModel(&deleteModel);
    if (deleteModel != nsMsgImapDeleteModels::MoveToTrash || deleteStorage)
      deleteImmediatelyNoTrash = true;
    // if we're deleting a message, we should pseudo-interrupt the msg
    // load of the current message.
    bool interrupted = false;
    imapServer->PseudoInterruptMsgLoad(this, msgWindow, &interrupted);
  }

  rv = BuildIdsAndKeyArray(messages, messageIds, srcKeyArray);
  if (NS_FAILED(rv)) return rv;

  nsCOMPtr<nsIMsgFolder> rootFolder;
  nsCOMPtr<nsIMsgFolder> trashFolder;

  if (!deleteImmediatelyNoTrash) {
    rv = GetRootFolder(getter_AddRefs(rootFolder));
    if (NS_SUCCEEDED(rv) && rootFolder) {
      rootFolder->GetFolderWithFlags(nsMsgFolderFlags::Trash,
                                     getter_AddRefs(trashFolder));
      NS_ASSERTION(trashFolder, "couldn't find trash");
      // if we can't find the trash, we'll just have to do an imap delete and
      // pretend this is the trash
      if (!trashFolder) deleteImmediatelyNoTrash = true;
    }
  }

  if ((NS_SUCCEEDED(rv) && deleteImmediatelyNoTrash) ||
      deleteModel == nsMsgImapDeleteModels::IMAPDelete) {
    if (allowUndo) {
      // need to take care of these two delete models
      RefPtr<nsImapMoveCopyMsgTxn> undoMsgTxn = new nsImapMoveCopyMsgTxn;
      if (!undoMsgTxn ||
          NS_FAILED(undoMsgTxn->Init(this, &srcKeyArray, messageIds.get(),
                                     nullptr, true, isMove)))
        return NS_ERROR_OUT_OF_MEMORY;

      undoMsgTxn->SetTransactionType(nsIMessenger::eDeleteMsg);
      // we're adding this undo action before the delete is successful. This is
      // evil, but 4.5 did it as well.
      nsCOMPtr<nsITransactionManager> txnMgr;
      if (msgWindow) msgWindow->GetTransactionManager(getter_AddRefs(txnMgr));
      if (txnMgr) txnMgr->DoTransaction(undoMsgTxn);
    }

    if (deleteModel == nsMsgImapDeleteModels::IMAPDelete && !deleteStorage) {
      uint32_t cnt, flags;
      rv = messages->GetLength(&cnt);
      NS_ENSURE_SUCCESS(rv, rv);
      deleteMsgs = false;
      for (uint32_t i = 0; i < cnt; i++) {
        nsCOMPtr<nsIMsgDBHdr> msgHdr = do_QueryElementAt(messages, i);
        if (msgHdr) {
          msgHdr->GetFlags(&flags);
          if (!(flags & nsMsgMessageFlags::IMAPDeleted)) {
            deleteMsgs = true;
            break;
          }
        }
      }
    }
    // if copy service listener is also a url listener, pass that
    // url listener into StoreImapFlags.
    nsCOMPtr<nsIUrlListener> urlListener = do_QueryInterface(listener);
    if (deleteMsgs) messageFlags |= kImapMsgSeenFlag;
    rv = StoreImapFlags(messageFlags, deleteMsgs, srcKeyArray, urlListener);

    if (NS_SUCCEEDED(rv)) {
      if (mDatabase) {
        nsCOMPtr<nsIMsgDatabase> database(mDatabase);
        if (deleteModel == nsMsgImapDeleteModels::IMAPDelete)
          MarkMessagesImapDeleted(&srcKeyArray, deleteMsgs, database);
        else {
          EnableNotifications(allMessageCountNotifications,
                              false);  //"remove it immediately" model
          // Notify if this is an actual delete.
          if (!isMove) {
            nsCOMPtr<nsIMsgFolderNotificationService> notifier(
                do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID));
            if (notifier) notifier->NotifyMsgsDeleted(msgHeaders);
          }
          DeleteStoreMessages(msgHeaders);
          database->DeleteMessages(srcKeyArray, nullptr);
          EnableNotifications(allMessageCountNotifications, true);
        }
        if (listener) {
          listener->OnStartCopy();
          listener->OnStopCopy(NS_OK);
        }
        NotifyFolderEvent(kDeleteOrMoveMsgCompleted);
      }
    }
    return rv;
  }

  // have to move the messages to the trash
  if (trashFolder) {
    nsCOMPtr<nsIMsgFolder> srcFolder;
    nsCOMPtr<nsISupports> srcSupport;

    rv = QueryInterface(NS_GET_IID(nsIMsgFolder), getter_AddRefs(srcFolder));
    nsCOMPtr<nsIMsgCopyService> copyService =
        do_GetService(NS_MSGCOPYSERVICE_CONTRACTID, &rv);
    NS_ENSURE_SUCCESS(rv, rv);
    rv = copyService->CopyMessages(srcFolder, messages, trashFolder, true,
                                   listener, msgWindow, allowUndo);
  }

  return rv;
}

// check if folder is the trash, or a descendent of the trash
// so we can tell if the folders we're deleting from it should
// be *really* deleted.
bool nsImapMailFolder::TrashOrDescendentOfTrash(nsIMsgFolder *folder) {
  NS_ENSURE_TRUE(folder, false);
  nsCOMPtr<nsIMsgFolder> parent;
  nsCOMPtr<nsIMsgFolder> curFolder = folder;
  nsresult rv;
  uint32_t flags = 0;
  do {
    rv = curFolder->GetFlags(&flags);
    if (NS_FAILED(rv)) return false;
    if (flags & nsMsgFolderFlags::Trash) return true;
    curFolder->GetParent(getter_AddRefs(parent));
    if (!parent) return false;
    curFolder = parent;
  } while (NS_SUCCEEDED(rv) && curFolder);
  return false;
}
NS_IMETHODIMP
nsImapMailFolder::DeleteSubFolders(nsIArray *folders, nsIMsgWindow *msgWindow) {
  nsCOMPtr<nsIMsgFolder> curFolder;
  nsCOMPtr<nsIUrlListener> urlListener;
  nsCOMPtr<nsIMsgFolder> trashFolder;
  int32_t i;
  uint32_t folderCount = 0;
  nsresult rv;
  // "this" is the folder we're deleting from
  bool deleteNoTrash = TrashOrDescendentOfTrash(this) || !DeleteIsMoveToTrash();
  bool confirmed = false;
  bool confirmDeletion = true;

  nsCOMPtr<nsIMutableArray> foldersRemaining(
      do_CreateInstance(NS_ARRAY_CONTRACTID));
  folders->GetLength(&folderCount);

  for (i = folderCount - 1; i >= 0; i--) {
    curFolder = do_QueryElementAt(folders, i, &rv);
    if (NS_SUCCEEDED(rv)) {
      uint32_t folderFlags;
      curFolder->GetFlags(&folderFlags);
      if (folderFlags & nsMsgFolderFlags::Virtual) {
        RemoveSubFolder(curFolder);
        // since the folder pane only allows single selection, we can do this
        deleteNoTrash = confirmed = true;
        confirmDeletion = false;
      } else
        foldersRemaining->InsertElementAt(curFolder, 0);
    }
  }

  foldersRemaining->GetLength(&folderCount);

  nsCOMPtr<nsIImapService> imapService =
      do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
  NS_ENSURE_SUCCESS(rv, rv);
  if (!deleteNoTrash) {
    rv = GetTrashFolder(getter_AddRefs(trashFolder));
    // If we can't find the trash folder and we are supposed to move it to the
    // trash return failure.
    if (NS_FAILED(rv) || !trashFolder) return NS_ERROR_FAILURE;
    bool canHaveSubFoldersOfTrash = true;
    trashFolder->GetCanCreateSubfolders(&canHaveSubFoldersOfTrash);
    if (canHaveSubFoldersOfTrash)  // UW server doesn't set NOINFERIORS - check
                                   // dual use pref
    {
      nsCOMPtr<nsIImapIncomingServer> imapServer;
      rv = GetImapIncomingServer(getter_AddRefs(imapServer));
      NS_ENSURE_SUCCESS(rv, rv);
      bool serverSupportsDualUseFolders;
      imapServer->GetDualUseFolders(&serverSupportsDualUseFolders);
      if (!serverSupportsDualUseFolders) canHaveSubFoldersOfTrash = false;
    }
    if (!canHaveSubFoldersOfTrash) deleteNoTrash = true;
    nsCOMPtr<nsIPrefBranch> prefBranch(
        do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
    NS_ENSURE_SUCCESS(rv, rv);
    prefBranch->GetBoolPref("mailnews.confirm.moveFoldersToTrash",
                            &confirmDeletion);
  }
  if (!confirmed &&
      (confirmDeletion || deleteNoTrash))  // let us alert the user if we are
                                           // deleting folder immediately
  {
    nsCOMPtr<nsIStringBundle> bundle;
    rv = IMAPGetStringBundle(getter_AddRefs(bundle));
    NS_ENSURE_SUCCESS(rv, rv);

    nsAutoString folderName;
    rv = curFolder->GetName(folderName);
    NS_ENSURE_SUCCESS(rv, rv);
    AutoTArray<nsString, 1> formatStrings = {folderName};

    nsAutoString deleteFolderDialogTitle;
    rv = bundle->GetStringFromName("imapDeleteFolderDialogTitle",
                                   deleteFolderDialogTitle);
    NS_ENSURE_SUCCESS(rv, rv);

    nsAutoString deleteFolderButtonLabel;
    rv = bundle->GetStringFromName("imapDeleteFolderButtonLabel",
                                   deleteFolderButtonLabel);
    NS_ENSURE_SUCCESS(rv, rv);

    nsAutoString confirmationStr;
    rv = bundle->FormatStringFromName(
        (deleteNoTrash) ? "imapDeleteNoTrash" : "imapMoveFolderToTrash",
        formatStrings, confirmationStr);
    NS_ENSURE_SUCCESS(rv, rv);
    if (!msgWindow) return NS_ERROR_NULL_POINTER;
    nsCOMPtr<nsIDocShell> docShell;
    msgWindow->GetRootDocShell(getter_AddRefs(docShell));
    nsCOMPtr<nsIPrompt> dialog;
    if (docShell) dialog = do_GetInterface(docShell);
    if (dialog) {
      int32_t buttonPressed = 0;
      // Default the dialog to "cancel".
      const uint32_t buttonFlags =
          (nsIPrompt::BUTTON_TITLE_IS_STRING * nsIPrompt::BUTTON_POS_0) +
          (nsIPrompt::BUTTON_TITLE_CANCEL * nsIPrompt::BUTTON_POS_1);

      bool dummyValue = false;
      rv = dialog->ConfirmEx(deleteFolderDialogTitle.get(),
                             confirmationStr.get(), buttonFlags,
                             deleteFolderButtonLabel.get(), nullptr, nullptr,
                             nullptr, &dummyValue, &buttonPressed);
      NS_ENSURE_SUCCESS(rv, rv);
      confirmed = !buttonPressed;  // "ok" is in position 0
    }
  } else
    confirmed = true;

  if (confirmed) {
    for (i = 0; i < (int32_t)folderCount; i++) {
      curFolder = do_QueryElementAt(foldersRemaining, i, &rv);
      if (NS_SUCCEEDED(rv)) {
        urlListener = do_QueryInterface(curFolder);
        if (deleteNoTrash)
          rv = imapService->DeleteFolder(curFolder, urlListener, msgWindow,
                                         nullptr);
        else {
          bool confirm = false;
          bool match = false;
          rv =
              curFolder->MatchOrChangeFilterDestination(nullptr, false, &match);
          if (match) {
            curFolder->ConfirmFolderDeletionForFilter(msgWindow, &confirm);
            if (!confirm) return NS_OK;
          }
          rv = imapService->MoveFolder(curFolder, trashFolder, urlListener,
                                       msgWindow, nullptr);
        }
      }
    }
  }
  // delete subfolders only if you are  deleting things from trash
  return confirmed && deleteNoTrash
             ? nsMsgDBFolder::DeleteSubFolders(foldersRemaining, msgWindow)
             : rv;
}

// FIXME: helper function to know whether we should check all IMAP folders
// for new mail; this is necessary because of a legacy hidden preference
// mail.check_all_imap_folders_for_new (now replaced by per-server preference
// mail.server.%serverkey%.check_all_folders_for_new), still present in some
// profiles.
/*static*/
bool nsImapMailFolder::ShouldCheckAllFolders(
    nsIImapIncomingServer *imapServer) {
  // Check legacy global preference to see if we should check all folders for
  // new messages, or just the inbox and marked ones.
  bool checkAllFolders = false;
  nsresult rv;
  nsCOMPtr<nsIPrefBranch> prefBranch =
      do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
  NS_ENSURE_SUCCESS(rv, false);
  // This pref might not exist, which is OK.
  (void)prefBranch->GetBoolPref("mail.check_all_imap_folders_for_new",
                                &checkAllFolders);

  if (checkAllFolders) return true;

  // If the legacy preference doesn't exist or has its default value (False),
  // the true preference is read.
  imapServer->GetCheckAllFoldersForNew(&checkAllFolders);
  return checkAllFolders;
}

// Called by Biff, or when user presses GetMsg button.
NS_IMETHODIMP nsImapMailFolder::GetNewMessages(nsIMsgWindow *aWindow,
                                               nsIUrlListener *aListener) {
  nsCOMPtr<nsIMsgFolder> rootFolder;
  nsresult rv = GetRootFolder(getter_AddRefs(rootFolder));
  if (NS_SUCCEEDED(rv) && rootFolder) {
    nsCOMPtr<nsIImapIncomingServer> imapServer;
    rv = GetImapIncomingServer(getter_AddRefs(imapServer));
    NS_ENSURE_SUCCESS(rv, rv);
    bool performingBiff = false;
    nsCOMPtr<nsIMsgIncomingServer> incomingServer =
        do_QueryInterface(imapServer, &rv);
    NS_ENSURE_SUCCESS(rv, rv);
    incomingServer->GetPerformingBiff(&performingBiff);
    m_urlListener = aListener;

    // See if we should check all folders for new messages, or just the inbox
    // and marked ones
    bool checkAllFolders = ShouldCheckAllFolders(imapServer);

    // Get new messages for inbox
    nsCOMPtr<nsIMsgFolder> inbox;
    rv = rootFolder->GetFolderWithFlags(nsMsgFolderFlags::Inbox,
                                        getter_AddRefs(inbox));
    if (inbox) {
      nsCOMPtr<nsIMsgImapMailFolder> imapFolder = do_QueryInterface(inbox, &rv);
      NS_ENSURE_SUCCESS(rv, rv);
      imapFolder->SetPerformingBiff(performingBiff);
      inbox->SetGettingNewMessages(true);
      rv = inbox->UpdateFolder(aWindow);
    }
    // Get new messages for other folders if marked, or all of them if the pref
    // is set
    rv = imapServer->GetNewMessagesForNonInboxFolders(
        rootFolder, aWindow, checkAllFolders, performingBiff);
  }
  return rv;
}

NS_IMETHODIMP nsImapMailFolder::Shutdown(bool shutdownChildren) {
  m_filterList = nullptr;
  m_initialized = false;
  // mPath is used to decide if folder pathname needs to be reconstructed in
  // GetPath().
  mPath = nullptr;
  m_moveCoalescer = nullptr;
  m_msgParser = nullptr;
  if (m_playbackTimer) {
    m_playbackTimer->Cancel();
    m_playbackTimer = nullptr;
  }
  m_pendingOfflineMoves.Clear();
  return nsMsgDBFolder::Shutdown(shutdownChildren);
}

nsresult nsImapMailFolder::GetBodysToDownload(
    nsTArray<nsMsgKey> *keysOfMessagesToDownload) {
  NS_ENSURE_ARG(keysOfMessagesToDownload);
  NS_ENSURE_TRUE(mDatabase, NS_ERROR_FAILURE);

  nsCOMPtr<nsISimpleEnumerator> enumerator;
  nsresult rv = mDatabase->EnumerateMessages(getter_AddRefs(enumerator));
  if (NS_SUCCEEDED(rv) && enumerator) {
    bool hasMore;
    while (NS_SUCCEEDED(rv = enumerator->HasMoreElements(&hasMore)) &&
           hasMore) {
      nsCOMPtr<nsISupports> supports;
      rv = enumerator->GetNext(getter_AddRefs(supports));
      NS_ENSURE_SUCCESS(rv, rv);
      nsCOMPtr<nsIMsgDBHdr> pHeader = do_QueryInterface(supports, &rv);
      NS_ENSURE_SUCCESS(rv, rv);
      bool shouldStoreMsgOffline = false;
      nsMsgKey msgKey;
      pHeader->GetMessageKey(&msgKey);
      // MsgFitsDownloadCriteria ignores nsMsgFolderFlags::Offline, which we
      // want
      if (m_downloadingFolderForOfflineUse)
        MsgFitsDownloadCriteria(msgKey, &shouldStoreMsgOffline);
      else
        ShouldStoreMsgOffline(msgKey, &shouldStoreMsgOffline);
      if (shouldStoreMsgOffline)
        keysOfMessagesToDownload->AppendElement(msgKey);
    }
  }
  return rv;
}

NS_IMETHODIMP nsImapMailFolder::OnNewIdleMessages() {
  nsresult rv;
  nsCOMPtr<nsIImapIncomingServer> imapServer;
  rv = GetImapIncomingServer(getter_AddRefs(imapServer));
  NS_ENSURE_SUCCESS(rv, rv);

  bool checkAllFolders = ShouldCheckAllFolders(imapServer);

  // only trigger biff if we're checking all new folders for new messages, or
  // this particular folder, but excluding trash,junk, sent, and no select
  // folders, by default.
  if ((checkAllFolders &&
       !(mFlags &
         (nsMsgFolderFlags::Trash | nsMsgFolderFlags::Junk |
          nsMsgFolderFlags::SentMail | nsMsgFolderFlags::ImapNoselect))) ||
      (mFlags & (nsMsgFolderFlags::CheckNew | nsMsgFolderFlags::Inbox)))
    SetPerformingBiff(true);
  return UpdateFolder(nullptr);
}

NS_IMETHODIMP nsImapMailFolder::UpdateImapMailboxInfo(
    nsIImapProtocol *aProtocol, nsIMailboxSpec *aSpec) {
  nsresult rv;
  ChangeNumPendingTotalMessages(-mNumPendingTotalMessages);
  ChangeNumPendingUnread(-mNumPendingUnreadMessages);
  m_numServerRecentMessages = 0;  // clear this since we selected the folder.
  m_numServerUnseenMessages = 0;  // clear this since we selected the folder.

  if (!mDatabase) GetDatabase();

  bool folderSelected;
  rv = aSpec->GetFolderSelected(&folderSelected);
  NS_ENSURE_SUCCESS(rv, rv);
  nsTArray<nsMsgKey> existingKeys;
  nsTArray<nsMsgKey> keysToDelete;
  uint32_t numNewUnread;
  nsCOMPtr<nsIDBFolderInfo> dbFolderInfo;
  int32_t imapUIDValidity = 0;
  if (mDatabase) {
    rv = mDatabase->GetDBFolderInfo(getter_AddRefs(dbFolderInfo));
    if (NS_SUCCEEDED(rv) && dbFolderInfo) {
      dbFolderInfo->GetImapUidValidity(&imapUIDValidity);
      uint64_t mailboxHighestModSeq;
      aSpec->GetHighestModSeq(&mailboxHighestModSeq);
      MOZ_LOG(IMAP_CS, mozilla::LogLevel::Debug,
              ("UpdateImapMailboxInfo(): Store highest MODSEQ=%" PRIu64
               " for folder=%s",
               mailboxHighestModSeq, m_onlineFolderName.get()));
      char intStrBuf[40];
      PR_snprintf(intStrBuf, sizeof(intStrBuf), "%llu", mailboxHighestModSeq);
      dbFolderInfo->SetCharProperty(kModSeqPropertyName,
                                    nsDependentCString(intStrBuf));
    }
    RefPtr<nsMsgKeyArray> keys = new nsMsgKeyArray;
    if (!keys) return NS_ERROR_OUT_OF_MEMORY;
    rv = mDatabase->ListAllKeys(keys);
    NS_ENSURE_SUCCESS(rv, rv);
    existingKeys.AppendElements(keys->m_keys);
    mDatabase->ListAllOfflineDeletes(&existingKeys);
  }
  int32_t folderValidity;
  aSpec->GetFolder_UIDVALIDITY(&folderValidity);
  nsCOMPtr<nsIImapFlagAndUidState> flagState;
  aSpec->GetFlagState(getter_AddRefs(flagState));

  // remember what the supported user flags are.
  uint32_t supportedUserFlags;
  aSpec->GetSupportedUserFlags(&supportedUserFlags);
  SetSupportedUserFlags(supportedUserFlags);

  m_uidValidity = folderValidity;

  if (imapUIDValidity != folderValidity) {
    NS_ASSERTION(imapUIDValidity == kUidUnknown,
                 "uid validity seems to have changed, blowing away db");
    nsCOMPtr<nsIFile> pathFile;
    rv = GetFilePath(getter_AddRefs(pathFile));
    if (NS_FAILED(rv)) return rv;

    nsCOMPtr<nsIMsgDBService> msgDBService =
        do_GetService(NS_MSGDB_SERVICE_CONTRACTID, &rv);
    NS_ENSURE_SUCCESS(rv, rv);

    nsCOMPtr<nsIDBFolderInfo> transferInfo;
    if (dbFolderInfo)
      dbFolderInfo->GetTransferInfo(getter_AddRefs(transferInfo));

    // A backup message database might have been created earlier, for example
    // if the user requested a reindex. We'll use the earlier one if we can,
    // otherwise we'll try to backup at this point.
    nsresult rvbackup = OpenBackupMsgDatabase();
    if (mDatabase) {
      dbFolderInfo = nullptr;
      if (NS_FAILED(rvbackup)) {
        CloseAndBackupFolderDB(EmptyCString());
        if (NS_FAILED(OpenBackupMsgDatabase()) && mBackupDatabase) {
          mBackupDatabase->RemoveListener(this);
          mBackupDatabase = nullptr;
        }
      } else
        mDatabase->ForceClosed();
    }
    mDatabase = nullptr;

    nsCOMPtr<nsIFile> summaryFile;
    rv = GetSummaryFileLocation(pathFile, getter_AddRefs(summaryFile));
    // Remove summary file.
    if (NS_SUCCEEDED(rv) && summaryFile) summaryFile->Remove(false);

    // Create a new summary file, update the folder message counts, and
    // Close the summary file db.
    rv = msgDBService->CreateNewDB(this, getter_AddRefs(mDatabase));

    if (NS_FAILED(rv) && mDatabase) {
      mDatabase->ForceClosed();
      mDatabase = nullptr;
    } else if (NS_SUCCEEDED(rv) && mDatabase) {
      if (transferInfo) SetDBTransferInfo(transferInfo);

      SummaryChanged();
      if (mDatabase) {
        if (mAddListener) mDatabase->AddListener(this);
        rv = mDatabase->GetDBFolderInfo(getter_AddRefs(dbFolderInfo));
      }
    }
    // store the new UIDVALIDITY value

    if (NS_SUCCEEDED(rv) && dbFolderInfo) {
      dbFolderInfo->SetImapUidValidity(folderValidity);
      // need to forget highest mod seq when uid validity rolls.
      MOZ_LOG(IMAP_CS, mozilla::LogLevel::Debug,
              ("UpdateImapMailboxInfo(): UIDVALIDITY changed, reset highest "
               "MODSEQ and UID for folder=%s",
               m_onlineFolderName.get()));
      dbFolderInfo->SetCharProperty(kModSeqPropertyName, EmptyCString());
      dbFolderInfo->SetUint32Property(kHighestRecordedUIDPropertyName, 0);
    }
    // delete all my msgs, the keys are bogus now
    // add every message in this folder
    existingKeys.Clear();
    //      keysToDelete.CopyArray(&existingKeys);

    if (flagState) {
      nsTArray<nsMsgKey> no_existingKeys;
      FindKeysToAdd(no_existingKeys, m_keysToFetch, numNewUnread, flagState);
    }
    if (NS_FAILED(rv)) pathFile->Remove(false);

  } else if (!flagState /*&& !NET_IsOffline() */)  // if there are no messages
                                                   // on the server
    keysToDelete = existingKeys;
  else /* if ( !NET_IsOffline()) */
  {
    uint32_t boxFlags;
    aSpec->GetBox_flags(&boxFlags);
    // FindKeysToDelete and FindKeysToAdd require sorted lists
    existingKeys.Sort();
    FindKeysToDelete(existingKeys, keysToDelete, flagState, boxFlags);
    // if this is the result of an expunge then don't grab headers
    if (!(boxFlags & kJustExpunged))
      FindKeysToAdd(existingKeys, m_keysToFetch, numNewUnread, flagState);
  }
  m_totalKeysToFetch = m_keysToFetch.Length();
  if (!keysToDelete.IsEmpty() && mDatabase) {
    nsTArray<RefPtr<nsIMsgDBHdr>> hdrsToDelete;
    MsgGetHeadersFromKeys2(mDatabase, keysToDelete, hdrsToDelete);
    // Notify nsIMsgFolderListeners of a mass delete, but only if we actually
    // have headers
    if (!hdrsToDelete.IsEmpty()) {
      nsCOMPtr<nsIMsgFolderNotificationService> notifier(
          do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID));
      if (notifier) notifier->NotifyMsgsDeleted(hdrsToDelete);
    }
    DeleteStoreMessages(hdrsToDelete);
    EnableNotifications(nsIMsgFolder::allMessageCountNotifications, false);
    mDatabase->DeleteMessages(keysToDelete, nullptr);
    EnableNotifications(nsIMsgFolder::allMessageCountNotifications, true);
  }
  int32_t numUnreadFromServer;
  aSpec->GetNumUnseenMessages(&numUnreadFromServer);

  bool partialUIDFetch;
  flagState->GetPartialUIDFetch(&partialUIDFetch);

  // For partial UID fetches, we can only trust the numUnread from the server.
  if (partialUIDFetch) numNewUnread = numUnreadFromServer;

  // If we are performing biff for this folder, tell the
  // stand-alone biff about the new high water mark
  if (m_performingBiff && numNewUnread) {
    // We must ensure that the server knows that we are performing biff.
    // Otherwise the stand-alone biff won't fire.
    nsCOMPtr<nsIMsgIncomingServer> server;
    if (NS_SUCCEEDED(GetServer(getter_AddRefs(server))) && server)
      server->SetPerformingBiff(true);
    SetNumNewMessages(numNewUnread);
  }
  SyncFlags(flagState);
  if (mDatabase && (int32_t)(mNumUnreadMessages + m_keysToFetch.Length()) >
                       numUnreadFromServer)
    mDatabase->SyncCounts();

  if (!m_keysToFetch.IsEmpty() && aProtocol)
    PrepareToAddHeadersToMailDB(aProtocol);
  else {
    bool gettingNewMessages;
    GetGettingNewMessages(&gettingNewMessages);
    if (gettingNewMessages)
      ProgressStatusString(aProtocol, "imapNoNewMessages", nullptr);
    SetPerformingBiff(false);
  }
  aSpec->GetNumMessages(&m_numServerTotalMessages);
  aSpec->GetNumUnseenMessages(&m_numServerUnseenMessages);
  aSpec->GetNumRecentMessages(&m_numServerRecentMessages);

  // some servers don't return UIDNEXT on SELECT - don't crunch
  // existing values in that case.
  int32_t nextUID;
  aSpec->GetNextUID(&nextUID);
  if (nextUID != (int32_t)nsMsgKey_None) m_nextUID = nextUID;

  return rv;
}

NS_IMETHODIMP nsImapMailFolder::UpdateImapMailboxStatus(
    nsIImapProtocol *aProtocol, nsIMailboxSpec *aSpec) {
  NS_ENSURE_ARG_POINTER(aSpec);
  int32_t numUnread, numTotal;
  aSpec->GetNumUnseenMessages(&numUnread);
  aSpec->GetNumMessages(&numTotal);
  aSpec->GetNumRecentMessages(&m_numServerRecentMessages);
  int32_t prevNextUID = m_nextUID;
  aSpec->GetNextUID(&m_nextUID);
  bool summaryChanged = false;

  // If m_numServerUnseenMessages is 0, it means
  // this is the first time we've done a Status.
  // In that case, we count all the previous pending unread messages we know
  // about as unread messages. We may want to do similar things with total
  // messages, but the total messages include deleted messages if the folder
  // hasn't been expunged.
  int32_t previousUnreadMessages =
      (m_numServerUnseenMessages)
          ? m_numServerUnseenMessages
          : mNumPendingUnreadMessages + mNumUnreadMessages;
  if (numUnread != previousUnreadMessages || m_nextUID != prevNextUID) {
    int32_t unreadDelta =
        numUnread - (mNumPendingUnreadMessages + mNumUnreadMessages);
    if (numUnread - previousUnreadMessages != unreadDelta)
      NS_WARNING("unread count should match server count");
    ChangeNumPendingUnread(unreadDelta);
    if (unreadDelta > 0 &&
        !(mFlags & (nsMsgFolderFlags::Trash | nsMsgFolderFlags::Junk))) {
      SetHasNewMessages(true);
      SetNumNewMessages(unreadDelta);
      SetBiffState(nsMsgBiffState_NewMail);
    }
    summaryChanged = true;
  }
  SetPerformingBiff(false);
  if (m_numServerUnseenMessages != numUnread ||
      m_numServerTotalMessages != numTotal) {
    if (numUnread > m_numServerUnseenMessages ||
        m_numServerTotalMessages > numTotal)
      NotifyHasPendingMsgs();
    summaryChanged = true;
    m_numServerUnseenMessages = numUnread;
    m_numServerTotalMessages = numTotal;
  }
  if (summaryChanged) SummaryChanged();

  return NS_OK;
}

NS_IMETHODIMP nsImapMailFolder::ParseMsgHdrs(
    nsIImapProtocol *aProtocol, nsIImapHeaderXferInfo *aHdrXferInfo) {
  NS_ENSURE_ARG_POINTER(aHdrXferInfo);
  int32_t numHdrs;
  nsCOMPtr<nsIImapHeaderInfo> headerInfo;
  nsCOMPtr<nsIImapUrl> aImapUrl;
  nsImapAction imapAction = nsIImapUrl::nsImapTest;  // unused value.
  if (!mDatabase) GetDatabase();

  nsresult rv = aHdrXferInfo->GetNumHeaders(&numHdrs);
  if (aProtocol) {
    (void)aProtocol->GetRunningImapURL(getter_AddRefs(aImapUrl));
    if (aImapUrl) aImapUrl->GetImapAction(&imapAction);
  }
  for (uint32_t i = 0; NS_SUCCEEDED(rv) && (int32_t)i < numHdrs; i++) {
    rv = aHdrXferInfo->GetHeader(i, getter_AddRefs(headerInfo));
    NS_ENSURE_SUCCESS(rv, rv);
    if (!headerInfo) break;
    int32_t msgSize;
    nsMsgKey msgKey;
    bool containsKey;
    const char *msgHdrs;
    headerInfo->GetMsgSize(&msgSize);
    headerInfo->GetMsgUid(&msgKey);
    if (msgKey == nsMsgKey_None)  // not a valid uid.
      continue;
    if (imapAction == nsIImapUrl::nsImapMsgPreview) {
      nsCOMPtr<nsIMsgDBHdr> msgHdr;
      headerInfo->GetMsgHdrs(&msgHdrs);
      // create an input stream based on the hdr string.
      nsCOMPtr<nsIStringInputStream> inputStream =
          do_CreateInstance("@mozilla.org/io/string-input-stream;1", &rv);
      NS_ENSURE_SUCCESS(rv, rv);
      inputStream->ShareData(msgHdrs, strlen(msgHdrs));
      GetMessageHeader(msgKey, getter_AddRefs(msgHdr));
      if (msgHdr) {
        GetMsgPreviewTextFromStream(msgHdr, inputStream);
      }
      continue;
    }
    if (mDatabase &&
        NS_SUCCEEDED(mDatabase->ContainsKey(msgKey, &containsKey)) &&
        containsKey) {
      NS_ERROR("downloading hdrs for hdr we already have");
      continue;
    }
    nsresult rv = SetupHeaderParseStream(msgSize, EmptyCString(), nullptr);
    NS_ENSURE_SUCCESS(rv, rv);
    headerInfo->GetMsgHdrs(&msgHdrs);
    rv = ParseAdoptedHeaderLine(msgHdrs, msgKey);
    NS_ENSURE_SUCCESS(rv, rv);
    rv = NormalEndHeaderParseStream(aProtocol, aImapUrl);
  }
  return rv;
}

nsresult nsImapMailFolder::SetupHeaderParseStream(
    uint32_t aSize, const nsACString &content_type, nsIMailboxSpec *boxSpec) {
  if (!mDatabase) GetDatabase();
  m_nextMessageByteLength = aSize;
  if (!m_msgParser) {
    nsresult rv;
    m_msgParser = do_CreateInstance(kParseMailMsgStateCID, &rv);
    NS_ENSURE_SUCCESS(rv, rv);
  } else
    m_msgParser->Clear();

  m_msgParser->SetMailDB(mDatabase);
  if (mBackupDatabase) m_msgParser->SetBackupMailDB(mBackupDatabase);
  return m_msgParser->SetState(nsIMsgParseMailMsgState::ParseHeadersState);
}

nsresult nsImapMailFolder::ParseAdoptedHeaderLine(const char *aMessageLine,
                                                  nsMsgKey aMsgKey) {
  // we can get blocks that contain more than one line,
  // but they never contain partial lines
  const char *str = aMessageLine;
  m_curMsgUid = aMsgKey;
  m_msgParser->SetNewKey(m_curMsgUid);
  // m_envelope_pos, for local folders,
  // is the msg key. Setting this will set the msg key for the new header.

  int32_t len = strlen(str);
  char *currentEOL = PL_strstr(str, MSG_LINEBREAK);
  const char *currentLine = str;
  while (currentLine < (str + len)) {
    if (currentEOL) {
      m_msgParser->ParseAFolderLine(
          currentLine, (currentEOL + MSG_LINEBREAK_LEN) - currentLine);
      currentLine = currentEOL + MSG_LINEBREAK_LEN;
      currentEOL = PL_strstr(currentLine, MSG_LINEBREAK);
    } else {
      m_msgParser->ParseAFolderLine(currentLine, PL_strlen(currentLine));
      currentLine = str + len + 1;
    }
  }
  return NS_OK;
}

nsresult nsImapMailFolder::NormalEndHeaderParseStream(
    nsIImapProtocol *aProtocol, nsIImapUrl *imapUrl) {
  nsCOMPtr<nsIMsgDBHdr> newMsgHdr;
  nsresult rv;
  NS_ENSURE_TRUE(m_msgParser, NS_ERROR_NULL_POINTER);

  nsMailboxParseState parseState;
  m_msgParser->GetState(&parseState);
  if (parseState == nsIMsgParseMailMsgState::ParseHeadersState)
    m_msgParser->ParseAFolderLine(CRLF, 2);
  rv = m_msgParser->GetNewMsgHdr(getter_AddRefs(newMsgHdr));
  NS_ENSURE_SUCCESS(rv, rv);

  char *headers;
  int32_t headersSize;

  nsCOMPtr<nsIMsgWindow> msgWindow;
  nsCOMPtr<nsIMsgMailNewsUrl> msgUrl;
  if (imapUrl) {
    msgUrl = do_QueryInterface(imapUrl, &rv);
    NS_ENSURE_SUCCESS(rv, rv);
    msgUrl->GetMsgWindow(getter_AddRefs(msgWindow));
  }

  nsCOMPtr<nsIMsgIncomingServer> server;
  rv = GetServer(getter_AddRefs(server));
  NS_ENSURE_SUCCESS(rv, rv);

  nsCOMPtr<nsIImapIncomingServer> imapServer = do_QueryInterface(server);
  rv = imapServer->GetIsGMailServer(&m_isGmailServer);
  NS_ENSURE_SUCCESS(rv, rv);

  newMsgHdr->SetMessageKey(m_curMsgUid);
  TweakHeaderFlags(aProtocol, newMsgHdr);
  uint32_t messageSize;
  if (NS_SUCCEEDED(newMsgHdr->GetMessageSize(&messageSize)))
    mFolderSize += messageSize;
  m_msgMovedByFilter = false;

  nsMsgKey highestUID = 0;
  nsCOMPtr<nsIDBFolderInfo> dbFolderInfo;
  if (mDatabase) mDatabase->GetDBFolderInfo(getter_AddRefs(dbFolderInfo));
  if (dbFolderInfo)
    dbFolderInfo->GetUint32Property(kHighestRecordedUIDPropertyName, 0,
                                    &highestUID);

  // If this is the inbox, try to apply filters. Otherwise, test the inherited
  // folder property "applyIncomingFilters" (which defaults to empty). If this
  // inherited property has the string value "true", then apply filters even
  // if this is not the Inbox folder.
  if (mFlags & nsMsgFolderFlags::Inbox || m_applyIncomingFilters) {
    // Use highwater to determine whether to filter?
    bool filterOnHighwater = false;
    nsCOMPtr<nsIPrefBranch> prefBranch(
        do_GetService(NS_PREFSERVICE_CONTRACTID));
    if (prefBranch)
      prefBranch->GetBoolPref("mail.imap.filter_on_new", &filterOnHighwater);

    uint32_t msgFlags;
    newMsgHdr->GetFlags(&msgFlags);

    // clang-format off
    bool doFilter = filterOnHighwater
      // Filter on largest UUID and not deleted.
      ? m_curMsgUid > highestUID && !(msgFlags & nsMsgMessageFlags::IMAPDeleted)
      // Filter on unread and not deleted.
      : !(msgFlags & (nsMsgMessageFlags::Read | nsMsgMessageFlags::IMAPDeleted));
    // clang-format on

    if (doFilter)
      MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
              ("(Imap) New message parsed, and filters will be run on it"));
    else
      MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
              ("(Imap) New message parsed, but filters will not be run on it"));

    if (doFilter) {
      int32_t duplicateAction = nsIMsgIncomingServer::keepDups;
      if (server) server->GetIncomingDuplicateAction(&duplicateAction);
      if ((duplicateAction != nsIMsgIncomingServer::keepDups) &&
          mFlags & nsMsgFolderFlags::Inbox) {
        bool isDup;
        server->IsNewHdrDuplicate(newMsgHdr, &isDup);
        if (isDup) {
          // we want to do something similar to applying filter hits.
          // if a dup is marked read, it shouldn't trigger biff.
          // Same for deleting it or moving it to trash.
          switch (duplicateAction) {
            case nsIMsgIncomingServer::deleteDups: {
              uint32_t newFlags;
              newMsgHdr->OrFlags(
                  nsMsgMessageFlags::Read | nsMsgMessageFlags::IMAPDeleted,
                  &newFlags);
              StoreImapFlags(kImapMsgSeenFlag | kImapMsgDeletedFlag, true,
                             {m_curMsgUid}, nullptr);
              m_msgMovedByFilter = true;
            } break;
            case nsIMsgIncomingServer::moveDupsToTrash: {
              nsCOMPtr<nsIMsgFolder> trash;
              GetTrashFolder(getter_AddRefs(trash));
              if (trash) {
                nsCString trashUri;
                trash->GetURI(trashUri);
                nsresult err = MoveIncorporatedMessage(
                    newMsgHdr, mDatabase, trashUri, nullptr, msgWindow);
                if (NS_SUCCEEDED(err)) m_msgMovedByFilter = true;
              }
            } break;
            case nsIMsgIncomingServer::markDupsRead: {
              uint32_t newFlags;
              newMsgHdr->OrFlags(nsMsgMessageFlags::Read, &newFlags);
              StoreImapFlags(kImapMsgSeenFlag, true, {m_curMsgUid}, nullptr);
            } break;
          }
          int32_t numNewMessages;
          GetNumNewMessages(false, &numNewMessages);
          SetNumNewMessages(numNewMessages - 1);
        }
      }
      rv = m_msgParser->GetAllHeaders(&headers, &headersSize);

      if (NS_SUCCEEDED(rv) && headers && !m_msgMovedByFilter &&
          !m_filterListRequiresBody) {
        if (m_filterList) {
          GetMoveCoalescer();  // not sure why we're doing this here.
          MOZ_LOG(FILTERLOGMODULE, LogLevel::Debug,
                  ("(Imap) ApplyFilterToHdr from "
                   "nsImapMailFolder::NormalEndHeaderParseStream()"));
          m_filterList->ApplyFiltersToHdr(
              nsMsgFilterType::InboxRule, newMsgHdr, this, mDatabase,
              nsDependentCSubstring(headers, headersSize), this, msgWindow);
          NotifyFolderEvent(kFiltersApplied);
        }
      }
    }
  }
  // here we need to tweak flags from uid state..
  if (mDatabase && (!m_msgMovedByFilter || ShowDeletedMessages())) {
    nsCOMPtr<nsIMsgFolderNotificationService> notifier(
        do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID));
    // Check if this header corresponds to a pseudo header
    // we have from doing a pseudo-offline move and then downloading
    // the real header from the server. In that case, we notify
    // db/folder listeners that the pseudo-header has become the new
    // header, i.e., the key has changed.
    nsCString newMessageId;
    nsMsgKey pseudoKey = nsMsgKey_None;
    newMsgHdr->GetMessageId(getter_Copies(newMessageId));
    m_pseudoHdrs.Get(newMessageId, &pseudoKey);
    if (notifier && pseudoKey != nsMsgKey_None) {
      notifier->NotifyMsgKeyChanged(pseudoKey, newMsgHdr);
      m_pseudoHdrs.Remove(newMessageId);
    }
    mDatabase->AddNewHdrToDB(newMsgHdr, true);
    if (notifier) notifier->NotifyMsgAdded(newMsgHdr);
    // mark the header as not yet reported classified
    OrProcessingFlags(m_curMsgUid, nsMsgProcessingFlags::NotReportedClassified);
  }
  // adjust highestRecordedUID
  if (dbFolderInfo) {
    if (m_curMsgUid > highestUID) {
      MOZ_LOG(IMAP_CS, mozilla::LogLevel::Debug,
              ("NormalEndHeaderParseStream(): Store new highest UID=%" PRIu32
               " for folder=%s",
               m_curMsgUid, m_onlineFolderName.get()));
      dbFolderInfo->SetUint32Property(kHighestRecordedUIDPropertyName,
                                      m_curMsgUid);
    }
  }

  if (m_isGmailServer) {
    nsCOMPtr<nsIImapFlagAndUidState> flagState;
    aProtocol->GetFlagAndUidState(getter_AddRefs(flagState));
    nsCString msgIDValue;
    nsCString threadIDValue;
    nsCString labelsValue;
    flagState->GetCustomAttribute(m_curMsgUid, NS_LITERAL_CSTRING("X-GM-MSGID"),
                                  msgIDValue);
    flagState->GetCustomAttribute(m_curMsgUid, NS_LITERAL_CSTRING("X-GM-THRID"),
                                  threadIDValue);
    flagState->GetCustomAttribute(
        m_curMsgUid, NS_LITERAL_CSTRING("X-GM-LABELS"), labelsValue);
    newMsgHdr->SetStringProperty("X-GM-MSGID", msgIDValue.get());
    newMsgHdr->SetStringProperty("X-GM-THRID", threadIDValue.get());
    newMsgHdr->SetStringProperty("X-GM-LABELS", labelsValue.get());
  }

  m_msgParser->Clear();  // clear out parser, because it holds onto a msg hdr.
  m_msgParser->SetMailDB(nullptr);  // tell it to let go of the db too.
  // I don't think we want to do this - it does bad things like set the size
  // incorrectly.
  //    m_msgParser->FinishHeader();
  return NS_OK;
}

NS_IMETHODIMP nsImapMailFolder::AbortHeaderParseStream(
    nsIImapProtocol *aProtocol) {
  nsresult rv = NS_ERROR_FAILURE;
  return rv;
}

NS_IMETHODIMP nsImapMailFolder::BeginCopy(nsIMsgDBHdr *message) {
  NS_ENSURE_TRUE(m_copyState, NS_ERROR_NULL_POINTER);
  nsresult rv;
  if (m_copyState->m_tmpFile)  // leftover file spec nuke it
  {
    rv = m_copyState->m_tmpFile->Remove(false);
    if (NS_FAILED(rv)) {
      nsCString nativePath = m_copyState->m_tmpFile->HumanReadablePath();
      MOZ_LOG(IMAP, mozilla::LogLevel::Info,
              ("couldn't remove prev temp file %s: %" PRIx32, nativePath.get(),
               static_cast<uint32_t>(rv)));
    }
    m_copyState->m_tmpFile = nullptr;
  }
  if (message) m_copyState->m_message = message;

  rv = GetSpecialDirectoryWithFileName(NS_OS_TEMP_DIR, "nscpmsg.txt",
                                       getter_AddRefs(m_copyState->m_tmpFile));
  if (NS_FAILED(rv))
    MOZ_LOG(IMAP, mozilla::LogLevel::Info,
            ("couldn't find nscpmsg.txt:%" PRIx32, static_cast<uint32_t>(rv)));
  NS_ENSURE_SUCCESS(rv, rv);

  // create a unique file, since multiple copies may be open on multiple folders
  rv = m_copyState->m_tmpFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 00600);
  if (NS_FAILED(rv)) {
    MOZ_LOG(IMAP, mozilla::LogLevel::Info,
            ("couldn't create temp nscpmsg.txt:%" PRIx32,
             static_cast<uint32_t>(rv)));
    // Last ditch attempt to create a temp file, because virus checker might
    // be locking the previous temp file, and CreateUnique fails if the file
    // is locked. Use the message key to make a unique name.
    if (message) {
      nsCString tmpFileName("nscpmsg-");
      nsMsgKey msgKey;
      message->GetMessageKey(&msgKey);
      tmpFileName.AppendInt(msgKey);
      tmpFileName.AppendLiteral(".txt");
      m_copyState->m_tmpFile->SetNativeLeafName(tmpFileName);
      rv = m_copyState->m_tmpFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE,
                                                00600);
      if (NS_FAILED(rv)) {
        MOZ_LOG(IMAP, mozilla::LogLevel::Info,
                ("couldn't create temp nscpmsg.txt: %" PRIx32,
                 static_cast<uint32_t>(rv)));
        OnCopyCompleted(m_copyState->m_srcSupport, rv);
        return rv;
      }
    }
  }

  nsCOMPtr<nsIOutputStream> fileOutputStream;
  rv = MsgNewBufferedFileOutputStream(
      getter_AddRefs(m_copyState->m_msgFileStream), m_copyState->m_tmpFile, -1,
      00600);
  if (NS_FAILED(rv))
    MOZ_LOG(IMAP, mozilla::LogLevel::Info,
            ("couldn't create output file stream: %" PRIx32,
             static_cast<uint32_t>(rv)));

  if (!m_copyState->m_dataBuffer)
    m_copyState->m_dataBuffer = (char *)PR_CALLOC(COPY_BUFFER_SIZE + 1);
  NS_ENSURE_TRUE(m_copyState->m_dataBuffer, NS_ERROR_OUT_OF_MEMORY);
  m_copyState->m_dataBufferSize = COPY_BUFFER_SIZE;
  return NS_OK;
}

NS_IMETHODIMP nsImapMailFolder::CopyDataToOutputStreamForAppend(
    nsIInputStream *aIStream, int32_t aLength, nsIOutputStream *outputStream) {
  uint32_t readCount;
  uint32_t writeCount;
  if (!m_copyState) m_copyState = new nsImapMailCopyState();

  if (aLength + m_copyState->m_leftOver > m_copyState->m_dataBufferSize) {
    char *newBuffer = (char *)PR_REALLOC(m_copyState->m_dataBuffer,
                                         aLength + m_copyState->m_leftOver + 1);
    NS_ENSURE_TRUE(newBuffer, NS_ERROR_OUT_OF_MEMORY);
    m_copyState->m_dataBuffer = newBuffer;
    m_copyState->m_dataBufferSize = aLength + m_copyState->m_leftOver;
  }

  char *start, *end;
  uint32_t linebreak_len = 1;

  nsresult rv = aIStream->Read(
      m_copyState->m_dataBuffer + m_copyState->m_leftOver, aLength, &readCount);
  if (NS_FAILED(rv)) return rv;

  m_copyState->m_leftOver += readCount;
  m_copyState->m_dataBuffer[m_copyState->m_leftOver] = '\0';

  start = m_copyState->m_dataBuffer;
  if (m_copyState->m_eatLF) {
    if (*start == '\n') start++;
    m_copyState->m_eatLF = false;
  }
  end = PL_strpbrk(start, "\r\n");
  if (end && *end == '\r' && *(end + 1) == '\n') linebreak_len = 2;

  while (start && end) {
    if (PL_strncasecmp(start, "X-Mozilla-Status:", 17) &&
        PL_strncasecmp(start, "X-Mozilla-Status2:", 18) &&
        PL_strncmp(start, "From - ", 7)) {
      rv = outputStream->Write(start, end - start, &writeCount);
      rv = outputStream->Write(CRLF, 2, &writeCount);
    }
    start = end + linebreak_len;
    if (start >= m_copyState->m_dataBuffer + m_copyState->m_leftOver) {
      m_copyState->m_leftOver = 0;
      break;
    }
    linebreak_len = 1;

    end = PL_strpbrk(start, "\r\n");
    if (end && *end == '\r') {
      if (*(end + 1) == '\n')
        linebreak_len = 2;
      else if (!*(end + 1))  // block might have split CRLF so remember if
        m_copyState->m_eatLF = true;  // we should eat LF
    }

    if (start && !end) {
      m_copyState->m_leftOver -= (start - m_copyState->m_dataBuffer);
      memcpy(m_copyState->m_dataBuffer, start,
             m_copyState->m_leftOver + 1);  // including null
    }
  }
  return rv;
}

NS_IMETHODIMP nsImapMailFolder::CopyDataDone() {
  m_copyState = nullptr;
  return NS_OK;
}

// sICopyMessageListener methods, BeginCopy, CopyData, EndCopy, EndMove,
// StartMessage, EndMessage
NS_IMETHODIMP nsImapMailFolder::CopyData(nsIInputStream *aIStream,
                                         int32_t aLength) {
  NS_ENSURE_TRUE(
      m_copyState && m_copyState->m_msgFileStream && m_copyState->m_dataBuffer,
      NS_ERROR_NULL_POINTER);
  nsresult rv = CopyDataToOutputStreamForAppend(aIStream, aLength,
                                                m_copyState->m_msgFileStream);
  if (NS_FAILED(rv)) {
    MOZ_LOG(IMAP, mozilla::LogLevel::Info,
            ("CopyData failed: %" PRIx32, static_cast<uint32_t>(rv)));
    OnCopyCompleted(m_copyState->m_srcSupport, rv);
  }
  return rv;
}

NS_IMETHODIMP nsImapMailFolder::EndCopy(bool copySucceeded) {
  nsresult rv = copySucceeded ? NS_OK : NS_ERROR_FAILURE;
  if (copySucceeded && m_copyState && m_copyState->m_msgFileStream) {
    nsCOMPtr<nsIUrlListener> urlListener;
    m_copyState->m_msgFileStream->Close();
    // m_tmpFile can be stale because we wrote to it
    nsCOMPtr<nsIFile> tmpFile;
    m_copyState->m_tmpFile->Clone(getter_AddRefs(tmpFile));
    m_copyState->m_tmpFile = tmpFile;
    nsCOMPtr<nsIImapService> imapService =
        do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
    NS_ENSURE_SUCCESS(rv, rv);

    rv =
        QueryInterface(NS_GET_IID(nsIUrlListener), getter_AddRefs(urlListener));
    nsCOMPtr<nsISupports> copySupport;
    if (m_copyState) copySupport = do_QueryInterface(m_copyState);
    rv = imapService->AppendMessageFromFile(
        m_copyState->m_tmpFile, this, EmptyCString(), true,
        m_copyState->m_selectedState, urlListener, nullptr, copySupport,
        m_copyState->m_msgWindow);
  }
  if (NS_FAILED(rv) || !copySucceeded)
    MOZ_LOG(IMAP, mozilla::LogLevel::Info,
            ("EndCopy failed: %" PRIx32, static_cast<uint32_t>(rv)));
  return rv;
}

NS_IMETHODIMP nsImapMailFolder::EndMove(bool moveSucceeded) { return NS_OK; }
// this is the beginning of the next message copied
NS_IMETHODIMP nsImapMailFolder::StartMessage() { return NS_OK; }

// just finished the current message.
NS_IMETHODIMP nsImapMailFolder::EndMessage(nsMsgKey key) { return NS_OK; }

NS_IMETHODIMP nsImapMailFolder::ApplyFilterHit(nsIMsgFilter *filter,
                                               nsIMsgWindow *msgWindow,
                                               bool *applyMore) {
  //
  //  This routine is called indirectly from ApplyFiltersToHdr in two
  //  circumstances, controlled by m_filterListRequiresBody:
  //
  //  If false, after headers are parsed in NormalEndHeaderParseStream.
  //  If true, after the message body is downloaded in NormalEndMsgWriteStream.
  //
  //  In NormalEndHeaderParseStream, the message has not been added to the
  //  database, and it is important that database notifications and count
  //  updates do not occur. In NormalEndMsgWriteStream, the message has been
  //  added to the database, and database notifications and count updates
  //  should be performed.
  //

  NS_ENSURE_ARG_POINTER(filter);
  NS_ENSURE_ARG_POINTER(applyMore);

  nsresult rv = NS_OK;

  nsCOMPtr<nsIMsgDBHdr> msgHdr;
  if (m_filterListRequiresBody)
    GetMessageHeader(m_curMsgUid, getter_AddRefs(msgHdr));
  else if (m_msgParser)
    m_msgParser->GetNewMsgHdr(getter_AddRefs(msgHdr));
  NS_ENSURE_TRUE(msgHdr,
                 NS_ERROR_NULL_POINTER);  // fatal error, cannot apply filters

  bool deleteToTrash = DeleteIsMoveToTrash();

  nsTArray<RefPtr<nsIMsgRuleAction>> filterActionList;
  rv = filter->GetSortedActionList(filterActionList);
  NS_ENSURE_SUCCESS(rv, rv);

  uint32_t numActions = filterActionList.Length();

  nsCString msgId;
  msgHdr->GetMessageId(getter_Copies(msgId));
  nsMsgKey msgKey;
  msgHdr->GetMessageKey(&msgKey);
  MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
          ("(Imap) Applying %" PRIu32
           " filter actions on message with key %" PRIu32,
           numActions, msgKeyToInt(msgKey)));
  MOZ_LOG(FILTERLOGMODULE, LogLevel::Debug,
          ("(Imap) Message ID: %s", msgId.get()));

  bool loggingEnabled = false;
  if (m_filterList && numActions)
    (void)m_filterList->GetLoggingEnabled(&loggingEnabled);

  bool msgIsNew = true;

  rv = GetDatabase();
  NS_ENSURE_SUCCESS(rv, rv);

  nsresult finalResult = NS_OK;  // result of all actions
  for (uint32_t actionIndex = 0; actionIndex < numActions; actionIndex++) {
    nsCOMPtr<nsIMsgRuleAction> filterAction(filterActionList[actionIndex]);
    if (!filterAction) {
      MOZ_LOG(FILTERLOGMODULE, LogLevel::Warning,
              ("(Imap) Filter action at index %" PRIu32 " invalid, skipping",
               actionIndex));
      continue;
    }

    rv = NS_OK;  // result of the current action
    nsMsgRuleActionType actionType;
    if (NS_SUCCEEDED(filterAction->GetType(&actionType))) {
      MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
              ("(Imap) Running filter action at index %" PRIu32
               ", action type = %i",
               actionIndex, actionType));
      if (loggingEnabled) (void)filter->LogRuleHit(filterAction, msgHdr);

      nsCString actionTargetFolderUri;
      if (actionType == nsMsgFilterAction::MoveToFolder ||
          actionType == nsMsgFilterAction::CopyToFolder) {
        rv = filterAction->GetTargetFolderUri(actionTargetFolderUri);
        if (NS_FAILED(rv) || actionTargetFolderUri.IsEmpty()) {
          // clang-format off
          MOZ_LOG(FILTERLOGMODULE, LogLevel::Warning,
                  ("(Imap) Target URI for Copy/Move action is empty, skipping"));
          // clang-format on
          NS_ASSERTION(false, "actionTargetFolderUri is empty");
          continue;
        }
      }

      uint32_t msgFlags;
      msgHdr->GetFlags(&msgFlags);
      bool isRead = (msgFlags & nsMsgMessageFlags::Read);

      switch (actionType) {
        case nsMsgFilterAction::Delete: {
          if (deleteToTrash) {
            // set value to trash folder
            nsCOMPtr<nsIMsgFolder> mailTrash;
            rv = GetTrashFolder(getter_AddRefs(mailTrash));
            if (NS_SUCCEEDED(rv) && mailTrash) {
              rv = mailTrash->GetURI(actionTargetFolderUri);
              if (NS_FAILED(rv)) break;
            }
            // msgHdr->OrFlags(nsMsgMessageFlags::Read, &newFlags);  // mark
            // read in trash.
          } else {
            mDatabase->MarkHdrRead(msgHdr, true, nullptr);
            mDatabase->MarkImapDeleted(msgKey, true, nullptr);
            rv = StoreImapFlags(kImapMsgSeenFlag | kImapMsgDeletedFlag, true,
                                {msgKey}, nullptr);
            if (NS_FAILED(rv)) break;
            // this will prevent us from adding the header to the db.
            m_msgMovedByFilter = true;
          }
          msgIsNew = false;
        }
          // note that delete falls through to move.
          [[fallthrough]];
        case nsMsgFilterAction::MoveToFolder: {
          // if moving to a different file, do it.
          nsCString uri;
          rv = GetURI(uri);
          if (NS_FAILED(rv)) break;

          if (!actionTargetFolderUri.Equals(uri)) {
            msgHdr->GetFlags(&msgFlags);
            if (msgFlags & nsMsgMessageFlags::MDNReportNeeded && !isRead) {
              mDatabase->MarkMDNNeeded(msgKey, false, nullptr);
              mDatabase->MarkMDNSent(msgKey, true, nullptr);
            }
            nsresult rv = MoveIncorporatedMessage(
                msgHdr, mDatabase, actionTargetFolderUri, filter, msgWindow);
            if (NS_SUCCEEDED(rv)) {
              m_msgMovedByFilter = true;
            } else {
              if (loggingEnabled) {
                (void)filter->LogRuleHitFail(
                    filterAction, msgHdr, rv,
                    NS_LITERAL_CSTRING("filterFailureMoveFailed"));
              }
            }
          }
          // don't apply any more filters, even if it was a move to the same
          // folder
          *applyMore = false;
        } break;
        case nsMsgFilterAction::CopyToFolder: {
          nsCString uri;
          rv = GetURI(uri);
          if (NS_FAILED(rv)) break;

          if (!actionTargetFolderUri.Equals(uri)) {
            // XXXshaver I'm not actually 100% what the right semantics are for
            // MDNs and copied messages, but I suspect deep down inside that
            // we probably want to suppress them only on the copies.
            msgHdr->GetFlags(&msgFlags);
            if (msgFlags & nsMsgMessageFlags::MDNReportNeeded && !isRead) {
              mDatabase->MarkMDNNeeded(msgKey, false, nullptr);
              mDatabase->MarkMDNSent(msgKey, true, nullptr);
            }

            nsCOMPtr<nsIMutableArray> messageArray(
                do_CreateInstance(NS_ARRAY_CONTRACTID, &rv));
            if (NS_FAILED(rv) || !messageArray) break;
            messageArray->AppendElement(msgHdr);

            nsCOMPtr<nsIMsgFolder> dstFolder;
            rv = GetExistingFolder(actionTargetFolderUri,
                                   getter_AddRefs(dstFolder));
            if (NS_FAILED(rv)) break;

            nsCOMPtr<nsIMsgCopyService> copyService =
                do_GetService(NS_MSGCOPYSERVICE_CONTRACTID, &rv);
            if (NS_FAILED(rv)) break;
            rv = copyService->CopyMessages(this, messageArray, dstFolder, false,
                                           nullptr, msgWindow, false);
            if (NS_FAILED(rv)) {
              if (loggingEnabled) {
                (void)filter->LogRuleHitFail(
                    filterAction, msgHdr, rv,
                    NS_LITERAL_CSTRING("filterFailureCopyFailed"));
              }
            }
          }
        } break;
        case nsMsgFilterAction::MarkRead: {
          mDatabase->MarkHdrRead(msgHdr, true, nullptr);
          rv = StoreImapFlags(kImapMsgSeenFlag, true, {msgKey}, nullptr);
          msgIsNew = false;
        } break;
        case nsMsgFilterAction::MarkUnread: {
          mDatabase->MarkHdrRead(msgHdr, false, nullptr);
          rv = StoreImapFlags(kImapMsgSeenFlag, false, {msgKey}, nullptr);
          msgIsNew = true;
        } break;
        case nsMsgFilterAction::MarkFlagged: {
          mDatabase->MarkHdrMarked(msgHdr, true, nullptr);
          rv = StoreImapFlags(kImapMsgFlaggedFlag, true, {msgKey}, nullptr);
        } break;
        case nsMsgFilterAction::KillThread:
        case nsMsgFilterAction::WatchThread: {
          nsCOMPtr<nsIMsgThread> msgThread;
          nsMsgKey threadKey;
          mDatabase->GetThreadContainingMsgHdr(msgHdr,
                                               getter_AddRefs(msgThread));
          if (msgThread) {
            msgThread->GetThreadKey(&threadKey);
            if (actionType == nsMsgFilterAction::KillThread)
              rv = mDatabase->MarkThreadIgnored(msgThread, threadKey, true,
                                                nullptr);
            else
              rv = mDatabase->MarkThreadWatched(msgThread, threadKey, true,
                                                nullptr);
          } else {
            if (actionType == nsMsgFilterAction::KillThread)
              rv = msgHdr->SetUint32Property("ProtoThreadFlags",
                                             nsMsgMessageFlags::Ignored);
            else
              rv = msgHdr->SetUint32Property("ProtoThreadFlags",
                                             nsMsgMessageFlags::Watched);
          }
          if (actionType == nsMsgFilterAction::KillThread) {
            mDatabase->MarkHdrRead(msgHdr, true, nullptr);
            rv = StoreImapFlags(kImapMsgSeenFlag, true, {msgKey}, nullptr);
            msgIsNew = false;
          }
        } break;
        case nsMsgFilterAction::KillSubthread: {
          mDatabase->MarkHeaderKilled(msgHdr, true, nullptr);
          mDatabase->MarkHdrRead(msgHdr, true, nullptr);
          rv = StoreImapFlags(kImapMsgSeenFlag, true, {msgKey}, nullptr);
          msgIsNew = false;
        } break;
        case nsMsgFilterAction::ChangePriority: {
          nsMsgPriorityValue filterPriority;  // a int32_t
          filterAction->GetPriority(&filterPriority);
          rv = mDatabase->SetUint32PropertyByHdr(
              msgHdr, "priority", static_cast<uint32_t>(filterPriority));
        } break;
        case nsMsgFilterAction::Label: {
          nsMsgLabelValue filterLabel;
          filterAction->GetLabel(&filterLabel);
          mDatabase->SetUint32PropertyByHdr(msgHdr, "label",
                                            static_cast<uint32_t>(filterLabel));
          rv = StoreImapFlags((filterLabel << 9), true, {msgKey}, nullptr);
        } break;
        case nsMsgFilterAction::AddTag: {
          nsCString keyword;
          filterAction->GetStrValue(keyword);
          nsCOMPtr<nsIMutableArray> messageArray(
              do_CreateInstance(NS_ARRAY_CONTRACTID, &rv));
          if (NS_FAILED(rv) || !messageArray) break;
          messageArray->AppendElement(msgHdr);
          rv = AddKeywordsToMessages(messageArray, keyword);
        } break;
        case nsMsgFilterAction::JunkScore: {
          nsAutoCString junkScoreStr;
          int32_t junkScore;
          filterAction->GetJunkScore(&junkScore);
          junkScoreStr.AppendInt(junkScore);
          rv = mDatabase->SetStringProperty(msgKey, "junkscore",
                                            junkScoreStr.get());
          mDatabase->SetStringProperty(msgKey, "junkscoreorigin", "filter");

          // If score is available, set up to store junk status on server.
          if (junkScore == nsIJunkMailPlugin::IS_SPAM_SCORE ||
              junkScore == nsIJunkMailPlugin::IS_HAM_SCORE) {
            nsTArray<nsMsgKey> *keysToClassify = m_moveCoalescer->GetKeyBucket(
                (junkScore == nsIJunkMailPlugin::IS_SPAM_SCORE) ? 0 : 1);
            NS_ASSERTION(keysToClassify, "error getting key bucket");
            if (keysToClassify) keysToClassify->AppendElement(msgKey);
            if (msgIsNew && junkScore == nsIJunkMailPlugin::IS_SPAM_SCORE) {
              msgIsNew = false;
              mDatabase->MarkHdrNotNew(msgHdr, nullptr);
              // nsMsgDBFolder::SendFlagNotifications by the call to
              // SetBiffState(nsMsgBiffState_NoMail) will reset numNewMessages
              // only if the message is also read and database notifications
              // are active, but we are not going to mark it read in this
              // action, preferring to leave the choice to the user.
              // So correct numNewMessages.
              if (m_filterListRequiresBody) {
                msgHdr->GetFlags(&msgFlags);
                if (!(msgFlags & nsMsgMessageFlags::Read)) {
                  int32_t numNewMessages;
                  GetNumNewMessages(false, &numNewMessages);
                  SetNumNewMessages(--numNewMessages);
                  SetHasNewMessages(numNewMessages != 0);
                }
              }
            }
          }
        } break;
        case nsMsgFilterAction::Forward: {
          nsCString forwardTo;
          filterAction->GetStrValue(forwardTo);
          nsCOMPtr<nsIMsgIncomingServer> server;
          rv = GetServer(getter_AddRefs(server));
          if (NS_FAILED(rv)) break;
          if (!forwardTo.IsEmpty()) {
            nsCOMPtr<nsIMsgComposeService> compService =
                do_GetService(NS_MSGCOMPOSESERVICE_CONTRACTID, &rv);
            if (NS_FAILED(rv)) break;
            rv = compService->ForwardMessage(
                NS_ConvertASCIItoUTF16(forwardTo), msgHdr, msgWindow, server,
                nsIMsgComposeService::kForwardAsDefault);
          }
        } break;

        case nsMsgFilterAction::Reply: {
          nsCString replyTemplateUri;
          filterAction->GetStrValue(replyTemplateUri);
          nsCOMPtr<nsIMsgIncomingServer> server;
          rv = GetServer(getter_AddRefs(server));
          if (NS_FAILED(rv)) break;
          if (!replyTemplateUri.IsEmpty()) {
            nsCOMPtr<nsIMsgComposeService> compService =
                do_GetService(NS_MSGCOMPOSESERVICE_CONTRACTID, &rv);
            if (NS_SUCCEEDED(rv) && compService) {
              rv = compService->ReplyWithTemplate(
                  msgHdr, replyTemplateUri.get(), msgWindow, server);
              if (NS_FAILED(rv)) {
                NS_WARNING("ReplyWithTemplate failed");
                if (rv == NS_ERROR_ABORT) {
                  (void)filter->LogRuleHitFail(
                      filterAction, msgHdr, rv,
                      NS_LITERAL_CSTRING("filterFailureSendingReplyAborted"));
                } else {
                  (void)filter->LogRuleHitFail(
                      filterAction, msgHdr, rv,
                      NS_LITERAL_CSTRING("filterFailureSendingReplyError"));
                }
              }
            }
          }
        } break;

        case nsMsgFilterAction::StopExecution: {
          // don't apply any more filters
          *applyMore = false;
          rv = NS_OK;
        } break;

        case nsMsgFilterAction::Custom: {
          nsCOMPtr<nsIMsgFilterCustomAction> customAction;
          rv = filterAction->GetCustomAction(getter_AddRefs(customAction));
          if (NS_FAILED(rv)) break;

          nsAutoCString value;
          rv = filterAction->GetStrValue(value);
          if (NS_FAILED(rv)) break;

          nsCOMPtr<nsIMutableArray> messageArray(
              do_CreateInstance(NS_ARRAY_CONTRACTID, &rv));
          NS_ENSURE_TRUE(messageArray, rv);
          if (NS_FAILED(rv) || !messageArray) break;
          messageArray->AppendElement(msgHdr);

          rv = customAction->Apply(messageArray, value, nullptr,
                                   nsMsgFilterType::InboxRule, msgWindow);
          // allow custom action to affect new
          msgHdr->GetFlags(&msgFlags);
          if (!(msgFlags & nsMsgMessageFlags::New)) msgIsNew = false;
        } break;

        default:
          NS_ERROR("unexpected filter action");
          rv = NS_ERROR_UNEXPECTED;
          break;
      }
    }
    if (NS_FAILED(rv)) {
      finalResult = rv;
      MOZ_LOG(FILTERLOGMODULE, LogLevel::Error,
              ("(Imap) Action execution failed with error: %" PRIx32,
               static_cast<uint32_t>(rv)));
      if (loggingEnabled) {
        (void)filter->LogRuleHitFail(filterAction, msgHdr, rv,
                                     NS_LITERAL_CSTRING("filterFailureAction"));
      }
    } else {
      MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
              ("(Imap) Action execution succeeded"));
    }
  }
  if (!msgIsNew) {
    int32_t numNewMessages;
    GetNumNewMessages(false, &numNewMessages);
    // When database notifications are active, new counts will be reset
    // to zero in nsMsgDBFolder::SendFlagNotifications by the call to
    // SetBiffState(nsMsgBiffState_NoMail), so don't repeat them here.
    if (!m_filterListRequiresBody) SetNumNewMessages(--numNewMessages);
    if (mDatabase) mDatabase->MarkHdrNotNew(msgHdr, nullptr);
    MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
            ("(Imap) Message will not be marked new"));
  }
  MOZ_LOG(FILTERLOGMODULE, LogLevel::Info,
          ("(Imap) Finished executing actions"));
  return finalResult;
}

NS_IMETHODIMP nsImapMailFolder::SetImapFlags(const char *uids, int32_t flags,
                                             nsIURI **url) {
  nsresult rv;
  nsCOMPtr<nsIImapService> imapService =
      do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
  NS_ENSURE_SUCCESS(rv, rv);

  return imapService->SetMessageFlags(this, this, url, nsAutoCString(uids),
                                      flags, true);
}

// "this" is the parent folder
NS_IMETHODIMP nsImapMailFolder::PlaybackOfflineFolderCreate(
    const nsAString &aFolderName, nsIMsgWindow *aWindow, nsIURI **url) {
  nsresult rv;
  nsCOMPtr<nsIImapService> imapService =
      do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
  NS_ENSURE_SUCCESS(rv, rv);
  return imapService->CreateFolder(this, aFolderName, this, url);
}

NS_IMETHODIMP
nsImapMailFolder::ReplayOfflineMoveCopy(const nsTArray<nsMsgKey> &aMsgKeys,
                                        bool isMove, nsIMsgFolder *aDstFolder,
                                        nsIUrlListener *aUrlListener,
                                        nsIMsgWindow *aWindow) {
  nsresult rv;

  nsCOMPtr<nsIMsgImapMailFolder> imapFolder = do_QueryInterface(aDstFolder);
  if (imapFolder) {
    nsImapMailFolder *destImapFolder =
        static_cast<nsImapMailFolder *>(aDstFolder);
    nsCOMPtr<nsIMsgDatabase> dstFolderDB;
    aDstFolder->GetMsgDatabase(getter_AddRefs(dstFolderDB));
    if (dstFolderDB) {
      // find the fake header in the destination db, and use that to
      // set the pending attributes on the real headers. To do this,
      // we need to iterate over the offline ops in the destination db,
      // looking for ones with matching keys and source folder uri.
      // If we find that offline op, its "key" will be the key of the fake
      // header, so we just need to get the header for that key
      // from the dest db.
      nsTArray<nsMsgKey> offlineOps;
      if (NS_SUCCEEDED(dstFolderDB->ListAllOfflineOpIds(&offlineOps))) {
        nsTArray<RefPtr<nsIMsgDBHdr>> messages;
        nsCString srcFolderUri;
        GetURI(srcFolderUri);
        nsCOMPtr<nsIMsgOfflineImapOperation> currentOp;
        for (uint32_t opIndex = 0; opIndex < offlineOps.Length(); opIndex++) {
          dstFolderDB->GetOfflineOpForKey(offlineOps[opIndex], false,
                                          getter_AddRefs(currentOp));
          if (currentOp) {
            nsCString opSrcUri;
            currentOp->GetSourceFolderURI(getter_Copies(opSrcUri));
            if (opSrcUri.Equals(srcFolderUri)) {
              nsMsgKey srcMessageKey;
              currentOp->GetSrcMessageKey(&srcMessageKey);
              for (auto key : aMsgKeys) {
                if (srcMessageKey == key) {
                  nsCOMPtr<nsIMsgDBHdr> fakeDestHdr;
                  dstFolderDB->GetMsgHdrForKey(offlineOps[opIndex],
                                               getter_AddRefs(fakeDestHdr));
                  if (fakeDestHdr) messages.AppendElement(fakeDestHdr);
                  break;
                }
              }
            }
          }
        }
        // 3rd parameter: Set offline flag.
        destImapFolder->SetPendingAttributes(messages, isMove, true);
      }
    }
    // if we can't get the dst folder db, we should still try to playback
    // the offline move/copy.
  }

  nsCOMPtr<nsIImapService> imapService =
      do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
  NS_ENSURE_SUCCESS(rv, rv);
  nsCOMPtr<nsIURI> resultUrl;
  nsAutoCString uids;
  AllocateUidStringFromKeys(aMsgKeys, uids);
  rv = imapService->OnlineMessageCopy(this, uids, aDstFolder, true, isMove,
                                      aUrlListener, getter_AddRefs(resultUrl),
                                      nullptr, aWindow);
  if (resultUrl) {
    nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(resultUrl, &rv);
    NS_ENSURE_SUCCESS(rv, rv);
    nsCOMPtr<nsIUrlListener> folderListener = do_QueryInterface(aDstFolder);
    if (folderListener) mailnewsUrl->RegisterListener(folderListener);
  }
  return rv;
}

NS_IMETHODIMP nsImapMailFolder::AddMoveResultPseudoKey(nsMsgKey aMsgKey) {
  nsresult rv = GetDatabase();
  NS_ENSURE_SUCCESS(rv, rv);

  nsCOMPtr<nsIMsgDBHdr> pseudoHdr;
  rv = mDatabase->GetMsgHdrForKey(aMsgKey, getter_AddRefs(pseudoHdr));
  NS_ENSURE_SUCCESS(rv, rv);
  nsCString messageId;
  pseudoHdr->GetMessageId(getter_Copies(messageId));
  // err on the side of caution and ignore messages w/o messageid.
  if (messageId.IsEmpty()) return NS_OK;
  m_pseudoHdrs.Put(messageId, aMsgKey);
  return NS_OK;
}

NS_IMETHODIMP nsImapMailFolder::StoreImapFlags(int32_t flags, bool addFlags,
                                               const nsTArray<nsMsgKey> &keys,
                                               nsIUrlListener *aUrlListener) {
  nsresult rv;
  if (!WeAreOffline()) {
    nsCOMPtr<nsIImapService> imapService =
        do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
    NS_ENSURE_SUCCESS(rv, rv);
    nsAutoCString msgIds;
    AllocateUidStringFromKeys(keys, msgIds);
    if (addFlags)
      imapService->AddMessageFlags(this, aUrlListener ? aUrlListener : this,
                                   nullptr, msgIds, flags, true);
    else
      imapService->SubtractMessageFlags(this,
                                        aUrlListener ? aUrlListener : this,
                                        nullptr, msgIds, flags, true);
  } else {
    rv = GetDatabase();
    if (NS_SUCCEEDED(rv) && mDatabase) {
      for (auto key : keys) {
        nsCOMPtr<nsIMsgOfflineImapOperation> op;
        rv = mDatabase->GetOfflineOpForKey(key, true, getter_AddRefs(op));
        SetFlag(nsMsgFolderFlags::OfflineEvents);
        if (NS_SUCCEEDED(rv) && op) {
          imapMessageFlagsType newFlags;
          op->GetNewFlags(&newFlags);
          op->SetFlagOperation(addFlags ? newFlags | flags : newFlags & ~flags);
        }
      }
      mDatabase->Commit(
          nsMsgDBCommitType::kLargeCommit);  // flush offline flags
    }
  }
  return rv;
}

NS_IMETHODIMP nsImapMailFolder::LiteSelect(nsIUrlListener *aUrlListener,
                                           nsIMsgWindow *aMsgWindow) {
  nsresult rv;
  nsCOMPtr<nsIImapService> imapService =
      do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
  NS_ENSURE_SUCCESS(rv, rv);
  return imapService->LiteSelectFolder(this, aUrlListener, aMsgWindow, nullptr);
}

nsresult nsImapMailFolder::GetFolderOwnerUserName(nsACString &userName) {
  if ((mFlags & nsMsgFolderFlags::ImapPersonal) ||
      !(mFlags &
        (nsMsgFolderFlags::ImapPublic | nsMsgFolderFlags::ImapOtherUser))) {
    // this is one of our personal mail folders
    // return our username on this host
    nsCOMPtr<nsIMsgIncomingServer> server;
    nsresult rv = GetServer(getter_AddRefs(server));
    return NS_FAILED(rv) ? rv : server->GetUsername(userName);
  }

  // the only other type of owner is if it's in the other users' namespace
  if (!(mFlags & nsMsgFolderFlags::ImapOtherUser)) return NS_OK;

  if (m_ownerUserName.IsEmpty()) {
    nsCString onlineName;
    GetOnlineName(onlineName);
    m_ownerUserName = nsIMAPNamespaceList::GetFolderOwnerNameFromPath(
        GetNamespaceForFolder(), onlineName.get());
  }
  userName = m_ownerUserName;
  return NS_OK;
}

nsIMAPNamespace *nsImapMailFolder::GetNamespaceForFolder() {
  if (!m_namespace) {
#ifdef DEBUG_bienvenu
    // Make sure this isn't causing us to open the database
    NS_ASSERTION(m_hierarchyDelimiter != kOnlineHierarchySeparatorUnknown,
                 "haven't set hierarchy delimiter");
#endif
    nsCString serverKey;
    nsCString onlineName;
    GetServerKey(serverKey);
    GetOnlineName(onlineName);
    char hierarchyDelimiter;
    GetHierarchyDelimiter(&hierarchyDelimiter);

    m_namespace = nsIMAPNamespaceList::GetNamespaceForFolder(
        serverKey.get(), onlineName.get(), hierarchyDelimiter);
    NS_ASSERTION(m_namespace, "didn't get namespace for folder");
    if (m_namespace) {
      nsIMAPNamespaceList::SuggestHierarchySeparatorForNamespace(
          m_namespace, hierarchyDelimiter);
      m_folderIsNamespace = nsIMAPNamespaceList::GetFolderIsNamespace(
          serverKey.get(), onlineName.get(), hierarchyDelimiter, m_namespace);
    }
  }
  return m_namespace;
}

void nsImapMailFolder::SetNamespaceForFolder(nsIMAPNamespace *ns) {
#ifdef DEBUG_bienvenu
  NS_ASSERTION(ns, "null namespace");
#endif
  m_namespace = ns;
}

NS_IMETHODIMP nsImapMailFolder::FolderPrivileges(nsIMsgWindow *window) {
  NS_ENSURE_ARG_POINTER(window);
  nsresult rv = NS_OK;  // if no window...
  if (!m_adminUrl.IsEmpty()) {
    nsCOMPtr<nsIExternalProtocolService> extProtService =
        do_GetService(NS_EXTERNALPROTOCOLSERVICE_CONTRACTID);
    if (extProtService) {
      nsAutoCString scheme;
      nsCOMPtr<nsIURI> uri;
      if (NS_FAILED(rv = NS_NewURI(getter_AddRefs(uri), m_adminUrl.get())))
        return rv;
      uri->GetScheme(scheme);
      if (!scheme.IsEmpty()) {
        // if the URL scheme does not correspond to an exposed protocol, then we
        // need to hand this link click over to the external protocol handler.
        bool isExposed;
        rv = extProtService->IsExposedProtocol(scheme.get(), &isExposed);
        if (NS_SUCCEEDED(rv) && !isExposed)
          return extProtService->LoadURI(uri, nullptr);
      }
    }
  } else {
    nsCOMPtr<nsIImapService> imapService =
        do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
    NS_ENSURE_SUCCESS(rv, rv);
    rv = imapService->GetFolderAdminUrl(this, window, this, nullptr);
    if (NS_SUCCEEDED(rv)) m_urlRunning = true;
  }
  return rv;
}

NS_IMETHODIMP nsImapMailFolder::GetHasAdminUrl(bool *aBool) {
  NS_ENSURE_ARG_POINTER(aBool);
  nsCOMPtr<nsIImapIncomingServer> imapServer;
  nsresult rv = GetImapIncomingServer(getter_AddRefs(imapServer));
  nsCString manageMailAccountUrl;
  if (NS_SUCCEEDED(rv) && imapServer)
    rv = imapServer->GetManageMailAccountUrl(manageMailAccountUrl);
  *aBool = (NS_SUCCEEDED(rv) && !manageMailAccountUrl.IsEmpty());
  return rv;
}

NS_IMETHODIMP nsImapMailFolder::GetAdminUrl(nsACString &aResult) {
  aResult = m_adminUrl;
  return NS_OK;
}

NS_IMETHODIMP nsImapMailFolder::SetAdminUrl(const nsACString &adminUrl) {
  m_adminUrl = adminUrl;
  return NS_OK;
}

NS_IMETHODIMP nsImapMailFolder::GetHdrParser(
    nsIMsgParseMailMsgState **aHdrParser) {
  NS_ENSURE_ARG_POINTER(aHdrParser);
  NS_IF_ADDREF(*aHdrParser = m_msgParser);
  return NS_OK;
}

// this is used to issue an arbitrary imap command on the passed in msgs.
// It assumes the command needs to be run in the selected state.
NS_IMETHODIMP nsImapMailFolder::IssueCommandOnMsgs(const nsACString &command,
                                                   const char *uids,
                                                   nsIMsgWindow *aWindow,
                                                   nsIURI **url) {
  nsresult rv;
  nsCOMPtr<nsIImapService> imapService =
      do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
  NS_ENSURE_SUCCESS(rv, rv);
  return imapService->IssueCommandOnMsgs(this, aWindow, command,
                                         nsDependentCString(uids), url);
}

NS_IMETHODIMP nsImapMailFolder::FetchCustomMsgAttribute(
    const nsACString &attribute, const char *uids, nsIMsgWindow *aWindow,
    nsIURI **url) {
  nsresult rv;
  nsCOMPtr<nsIImapService> imapService =
      do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
  NS_ENSURE_SUCCESS(rv, rv);

  return imapService->FetchCustomMsgAttribute(this, aWindow, attribute,
                                              nsDependentCString(uids), url);
}

nsresult nsImapMailFolder::MoveIncorporatedMessage(
    nsIMsgDBHdr *mailHdr, nsIMsgDatabase *sourceDB,
    const nsACString &destFolderUri, nsIMsgFilter *filter,
    nsIMsgWindow *msgWindow) {
  nsresult rv;
  if (m_moveCoalescer) {
    nsCOMPtr<nsIMsgFolder> destIFolder;
    rv = GetOrCreateFolder(destFolderUri, getter_AddRefs(destIFolder));
    NS_ENSURE_SUCCESS(rv, rv);

    if (destIFolder) {
      // check if the destination is a real folder (by checking for null parent)
      // and if it can file messages (e.g., servers or news folders can't file
      // messages). Or read only imap folders...
      bool canFileMessages = true;
      nsCOMPtr<nsIMsgFolder> parentFolder;
      destIFolder->GetParent(getter_AddRefs(parentFolder));
      if (parentFolder) destIFolder->GetCanFileMessages(&canFileMessages);
      if (filter && (!parentFolder || !canFileMessages)) {
        filter->SetEnabled(false);
        m_filterList->SaveToDefaultFile();
        destIFolder->ThrowAlertMsg("filterDisabled", msgWindow);
        return NS_MSG_NOT_A_MAIL_FOLDER;
      }
      // put the header into the source db, since it needs to be there when we
      // copy it and we need a valid header to pass to
      // StartAsyncCopyMessagesInto
      nsMsgKey keyToFilter;
      mailHdr->GetMessageKey(&keyToFilter);

      if (sourceDB && destIFolder) {
        bool imapDeleteIsMoveToTrash = DeleteIsMoveToTrash();
        m_moveCoalescer->AddMove(destIFolder, keyToFilter);
        // For each folder, we need to keep track of the ids we want to move to
        // that folder - we used to store them in the MSG_FolderInfo and then
        // when we'd finished downloading headers, we'd iterate through all the
        // folders looking for the ones that needed messages moved into them -
        // perhaps instead we could keep track of nsIMsgFolder,
        // nsTArray<nsMsgKey> pairs here in the imap code. nsTArray<nsMsgKey>
        // *idsToMoveFromInbox = msgFolder->GetImapIdsToMoveFromInbox();
        // idsToMoveFromInbox->AppendElement(keyToFilter);
        if (imapDeleteIsMoveToTrash) {
        }
        bool isRead = false;
        mailHdr->GetIsRead(&isRead);
        if (imapDeleteIsMoveToTrash) rv = NS_OK;
      }
    }
  } else
    rv = NS_ERROR_UNEXPECTED;

  // we have to return an error because we do not actually move the message
  // it is done async and that can fail
  return rv;
}

/**
 * This method assumes that key arrays and flag states are sorted by increasing
 * key.
 */
void nsImapMailFolder::FindKeysToDelete(const nsTArray<nsMsgKey> &existingKeys,
                                        nsTArray<nsMsgKey> &keysToDelete,
                                        nsIImapFlagAndUidState *flagState,
                                        uint32_t boxFlags) {
  bool showDeletedMessages = ShowDeletedMessages();
  int32_t numMessageInFlagState;
  bool partialUIDFetch;
  uint32_t uidOfMessage;
  imapMessageFlagsType flags;

  flagState->GetNumberOfMessages(&numMessageInFlagState);
  flagState->GetPartialUIDFetch(&partialUIDFetch);

  // if we're doing a partialUIDFetch, just delete the keys from the db
  // that have the deleted flag set (if not using imap delete model)
  // and return.
  if (partialUIDFetch) {
    if (!showDeletedMessages) {
      for (uint32_t i = 0; (int32_t)i < numMessageInFlagState; i++) {
        flagState->GetUidOfMessage(i, &uidOfMessage);
        // flag state will be zero filled up to first real uid, so ignore those.
        if (uidOfMessage) {
          flagState->GetMessageFlags(i, &flags);
          if (flags & kImapMsgDeletedFlag)
            keysToDelete.AppendElement(uidOfMessage);
        }
      }
    } else if (boxFlags & kJustExpunged) {
      // we've just issued an expunge with a partial flag state. We should
      // delete headers with the imap deleted flag set, because we can't
      // tell from the expunge response which messages were deleted.
      nsCOMPtr<nsISimpleEnumerator> hdrs;
      nsresult rv = GetMessages(getter_AddRefs(hdrs));
      NS_ENSURE_SUCCESS_VOID(rv);
      bool hasMore = false;
      nsCOMPtr<nsIMsgDBHdr> pHeader;
      while (NS_SUCCEEDED(rv = hdrs->HasMoreElements(&hasMore)) && hasMore) {
        nsCOMPtr<nsISupports> supports;
        rv = hdrs->GetNext(getter_AddRefs(supports));
        NS_ENSURE_SUCCESS_VOID(rv);
        pHeader = do_QueryInterface(supports, &rv);
        NS_ENSURE_SUCCESS_VOID(rv);
        uint32_t msgFlags;
        pHeader->GetFlags(&msgFlags);
        if (msgFlags & nsMsgMessageFlags::IMAPDeleted) {
          nsMsgKey msgKey;
          pHeader->GetMessageKey(&msgKey);
          keysToDelete.AppendElement(msgKey);
        }
      }
    }
    return;
  }
  // otherwise, we have a complete set of uid's and flags, so we delete
  // anything that's in existingKeys but not in the flag state, as well
  // as messages with the deleted flag set.
  uint32_t total = existingKeys.Length();
  int onlineIndex = 0;  // current index into flagState
  for (uint32_t keyIndex = 0; keyIndex < total; keyIndex++) {
    while (
        (onlineIndex < numMessageInFlagState) &&
        NS_SUCCEEDED(flagState->GetUidOfMessage(onlineIndex, &uidOfMessage)) &&
        (existingKeys[keyIndex] > uidOfMessage))
      onlineIndex++;

    flagState->GetUidOfMessage(onlineIndex, &uidOfMessage);
    flagState->GetMessageFlags(onlineIndex, &flags);
    // delete this key if it is not there or marked deleted
    if ((onlineIndex >= numMessageInFlagState) ||
        (existingKeys[keyIndex] != uidOfMessage) ||
        ((flags & kImapMsgDeletedFlag) && !showDeletedMessages)) {
      nsMsgKey doomedKey = existingKeys[keyIndex];
      if ((int32_t)doomedKey <= 0 && doomedKey != nsMsgKey_None) continue;

      keysToDelete.AppendElement(existingKeys[keyIndex]);
    }

    flagState->GetUidOfMessage(onlineIndex, &uidOfMessage);
    if (existingKeys[keyIndex] == uidOfMessage) onlineIndex++;
  }
}

void nsImapMailFolder::FindKeysToAdd(const nsTArray<nsMsgKey> &existingKeys,
                                     nsTArray<nsMsgKey> &keysToFetch,
                                     uint32_t &numNewUnread,
                                     nsIImapFlagAndUidState *flagState) {
  bool showDeletedMessages = ShowDeletedMessages();
  int dbIndex = 0;  // current index into existingKeys
  int32_t existTotal, numberOfKnownKeys;
  int32_t messageIndex;

  numNewUnread = 0;
  existTotal = numberOfKnownKeys = existingKeys.Length();
  flagState->GetNumberOfMessages(&messageIndex);
  bool partialUIDFetch;
  flagState->GetPartialUIDFetch(&partialUIDFetch);

  for (int32_t flagIndex = 0; flagIndex < messageIndex; flagIndex++) {
    uint32_t uidOfMessage;
    flagState->GetUidOfMessage(flagIndex, &uidOfMessage);
    while ((flagIndex < numberOfKnownKeys) && (dbIndex < existTotal) &&
           existingKeys[dbIndex] < uidOfMessage)
      dbIndex++;

    if ((flagIndex >= numberOfKnownKeys) || (dbIndex >= existTotal) ||
        (existingKeys[dbIndex] != uidOfMessage)) {
      numberOfKnownKeys++;

      imapMessageFlagsType flags;
      flagState->GetMessageFlags(flagIndex, &flags);
      NS_ASSERTION(uidOfMessage != nsMsgKey_None, "got invalid msg key");
      if (uidOfMessage && uidOfMessage != nsMsgKey_None &&
          (showDeletedMessages || !(flags & kImapMsgDeletedFlag))) {
        if (mDatabase) {
          bool dbContainsKey;
          if (NS_SUCCEEDED(
                  mDatabase->ContainsKey(uidOfMessage, &dbContainsKey)) &&
              dbContainsKey) {
            // this is expected in the partial uid fetch case because the
            // flag state does not contain all messages, so the db has
            // messages the flag state doesn't know about.
            if (!partialUIDFetch) NS_ERROR("db has key - flagState messed up?");
            continue;
          }
        }
        keysToFetch.AppendElement(uidOfMessage);
        if (!(flags & kImapMsgSeenFlag)) numNewUnread++;
      }
    }
  }
}

NS_IMETHODIMP nsImapMailFolder::GetMsgHdrsToDownload(
    bool *aMoreToDownload, int32_t *aTotalCount, nsTArray<nsMsgKey> &aKeys) {
  NS_ENSURE_ARG_POINTER(aMoreToDownload);
  NS_ENSURE_ARG_POINTER(aTotalCount);
  aKeys.Clear();

  *aMoreToDownload = false;
  *aTotalCount = m_totalKeysToFetch;
  if (m_keysToFetch.IsEmpty()) {
    return NS_OK;
  }

  // if folder isn't open in a window, no reason to limit the number of headers
  // we download.
  nsCOMPtr<nsIMsgMailSession> session =
      do_GetService(NS_MSGMAILSESSION_CONTRACTID);
  bool folderOpen = false;
  if (session) session->IsFolderOpenInWindow(this, &folderOpen);

  int32_t hdrChunkSize = 200;
  if (folderOpen) {
    nsresult rv;
    nsCOMPtr<nsIPrefBranch> prefBranch(
        do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
    NS_ENSURE_SUCCESS(rv, rv);
    if (prefBranch)
      prefBranch->GetIntPref("mail.imap.hdr_chunk_size", &hdrChunkSize);
  }
  int32_t numKeysToFetch = m_keysToFetch.Length();
  int32_t startIndex = 0;
  if (folderOpen && hdrChunkSize > 0 &&
      (int32_t)m_keysToFetch.Length() > hdrChunkSize) {
    numKeysToFetch = hdrChunkSize;
    *aMoreToDownload = true;
    startIndex = m_keysToFetch.Length() - hdrChunkSize;
  }
  aKeys.AppendElements(&m_keysToFetch[startIndex], numKeysToFetch);
  // Remove these for the incremental header download case, so that
  // we know we don't have to download them again.
  m_keysToFetch.RemoveElementsAt(startIndex, numKeysToFetch);

  return NS_OK;
}

void nsImapMailFolder::PrepareToAddHeadersToMailDB(nsIImapProtocol *aProtocol) {
  // now, tell it we don't need any bodies.
  nsTArray<nsMsgKey> noBodies;
  aProtocol->NotifyBodysToDownload(noBodies);
}

void nsImapMailFolder::TweakHeaderFlags(nsIImapProtocol *aProtocol,
                                        nsIMsgDBHdr *tweakMe) {
  if (mDatabase && aProtocol && tweakMe) {
    tweakMe->SetMessageKey(m_curMsgUid);
    tweakMe->SetMessageSize(m_nextMessageByteLength);

    bool foundIt = false;
    imapMessageFlagsType imap_flags;

    nsCString customFlags;
    nsresult rv = aProtocol->GetFlagsForUID(m_curMsgUid, &foundIt, &imap_flags,
                                            getter_Copies(customFlags));
    if (NS_SUCCEEDED(rv) && foundIt) {
      // make a mask and clear these message flags
      uint32_t mask = nsMsgMessageFlags::Read | nsMsgMessageFlags::Replied |
                      nsMsgMessageFlags::Marked |
                      nsMsgMessageFlags::IMAPDeleted |
                      nsMsgMessageFlags::Labels;
      uint32_t dbHdrFlags;

      tweakMe->GetFlags(&dbHdrFlags);
      tweakMe->AndFlags(~mask, &dbHdrFlags);

      // set the new value for these flags
      uint32_t newFlags = 0;
      if (imap_flags & kImapMsgSeenFlag)
        newFlags |= nsMsgMessageFlags::Read;
      else  // if (imap_flags & kImapMsgRecentFlag)
        newFlags |= nsMsgMessageFlags::New;

      // Okay here is the MDN needed logic (if DNT header seen):
      /* if server support user defined flag:
         XXX TODO: Fix badly formatted comment which doesn't reflect the code.
                    MDNSent flag set => clear kMDNNeeded flag
                    MDNSent flag not set => do nothing, leave kMDNNeeded on
                    else if
                    not nsMsgMessageFlags::New => clear kMDNNeeded flag
                   nsMsgMessageFlags::New => do nothing, leave kMDNNeeded on
               */
      uint16_t userFlags;
      rv = aProtocol->GetSupportedUserFlags(&userFlags);
      if (NS_SUCCEEDED(rv) && (userFlags & (kImapMsgSupportUserFlag |
                                            kImapMsgSupportMDNSentFlag))) {
        if (imap_flags & kImapMsgMDNSentFlag) {
          newFlags |= nsMsgMessageFlags::MDNReportSent;
          if (dbHdrFlags & nsMsgMessageFlags::MDNReportNeeded)
            tweakMe->AndFlags(~nsMsgMessageFlags::MDNReportNeeded, &dbHdrFlags);
        }
      }

      if (imap_flags & kImapMsgAnsweredFlag)
        newFlags |= nsMsgMessageFlags::Replied;
      if (imap_flags & kImapMsgFlaggedFlag)
        newFlags |= nsMsgMessageFlags::Marked;
      if (imap_flags & kImapMsgDeletedFlag)
        newFlags |= nsMsgMessageFlags::IMAPDeleted;
      if (imap_flags & kImapMsgForwardedFlag)
        newFlags |= nsMsgMessageFlags::Forwarded;

      // db label flags are 0x0E000000 and imap label flags are 0x0E00
      // so we need to shift 16 bits to the left to convert them.
      if (imap_flags & kImapMsgLabelFlags) {
        // we need to set label attribute on header because the dbview code
        // does msgHdr->GetLabel when asked to paint a row
        tweakMe->SetLabel((imap_flags & kImapMsgLabelFlags) >> 9);
        newFlags |= (imap_flags & kImapMsgLabelFlags) << 16;
      }
      if (newFlags) tweakMe->OrFlags(newFlags, &dbHdrFlags);
      if (!customFlags.IsEmpty())
        (void)HandleCustomFlags(m_curMsgUid, tweakMe, userFlags, customFlags);
    }
  }
}

NS_IMETHODIMP
nsImapMailFolder::SetupMsgWriteStream(nsIFile *aFile, bool addDummyEnvelope) {
  nsresult rv;
  aFile->Remove(false);
  rv = MsgNewBufferedFileOutputStream(
      getter_AddRefs(m_tempMessageStream), aFile,
      PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE, 00700);
  if (m_tempMessageStream && addDummyEnvelope) {
    nsAutoCString result;
    char *ct;
    uint32_t writeCount;
    time_t now = time((time_t *)0);
    ct = ctime(&now);
    ct[24] = 0;
    result = "From - ";
    result += ct;
    result += MSG_LINEBREAK;

    m_tempMessageStream->Write(result.get(), result.Length(), &writeCount);
    result = "X-Mozilla-Status: 0001";
    result += MSG_LINEBREAK;
    m_tempMessageStream->Write(result.get(), result.Length(), &writeCount);
    result = "X-Mozilla-Status2: 00000000";
    result += MSG_LINEBREAK;
    m_tempMessageStream->Write(result.get(), result.Length(), &writeCount);
  }
  return rv;
}

NS_IMETHODIMP nsImapMailFolder::DownloadMessagesForOffline(
    nsIArray *messages, nsIMsgWindow *window) {
  nsAutoCString messageIds;
  nsTArray<nsMsgKey> srcKeyArray;
  nsresult rv = BuildIdsAndKeyArray(messages, messageIds, srcKeyArray);
  if (NS_FAILED(rv) || messageIds.IsEmpty()) return rv;

  nsCOMPtr<nsIImapService> imapService =
      do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
  NS_ENSURE_SUCCESS(rv, rv);

  rv = AcquireSemaphore(static_cast<nsIMsgFolder *>(this));
  if (NS_FAILED(rv)) {
    ThrowAlertMsg("operationFailedFolderBusy", window);
    return rv;
  }
  return imapService->DownloadMessagesForOffline(messageIds, this, this,
                                                 window);
}

NS_IMETHODIMP nsImapMailFolder::DownloadAllForOffline(nsIUrlListener *listener,
                                                      nsIMsgWindow *msgWindow) {
  nsresult rv;
  nsCOMPtr<nsIURI> runningURI;
  bool noSelect;
  GetFlag(nsMsgFolderFlags::ImapNoselect, &noSelect);

  if (!noSelect) {
    nsAutoCString messageIdsToDownload;
    nsTArray<nsMsgKey> msgsToDownload;

    GetDatabase();
    m_downloadingFolderForOfflineUse = true;

    rv = AcquireSemaphore(static_cast<nsIMsgFolder *>(this));
    if (NS_FAILED(rv)) {
      m_downloadingFolderForOfflineUse = false;
      ThrowAlertMsg("operationFailedFolderBusy", msgWindow);
      return rv;
    }
    nsCOMPtr<nsIImapService> imapService =
        do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
    NS_ENSURE_SUCCESS(rv, rv);

    // Selecting the folder with nsIImapUrl::shouldStoreMsgOffline true will
    // cause us to fetch any message bodies we don't have.
    m_urlListener = listener;
    rv = imapService->SelectFolder(this, this, msgWindow,
                                   getter_AddRefs(runningURI));
    if (NS_SUCCEEDED(rv)) {
      nsCOMPtr<nsIImapUrl> imapUrl(do_QueryInterface(runningURI));
      if (imapUrl) imapUrl->SetStoreResultsOffline(true);
      m_urlRunning = true;
    }
  } else
    rv = NS_MSG_FOLDER_UNREADABLE;
  return rv;
}

NS_IMETHODIMP
nsImapMailFolder::ParseAdoptedMsgLine(const char *adoptedMessageLine,
                                      nsMsgKey uidOfMessage,
                                      nsIImapUrl *aImapUrl) {
  NS_ENSURE_ARG_POINTER(aImapUrl);
  uint32_t count = 0;
  nsresult rv;
  // remember the uid of the message we're downloading.
  m_curMsgUid = uidOfMessage;
  if (!m_offlineHeader) {
    rv = GetMessageHeader(uidOfMessage, getter_AddRefs(m_offlineHeader));
    if (NS_SUCCEEDED(rv) && !m_offlineHeader) rv = NS_ERROR_UNEXPECTED;
    NS_ENSURE_SUCCESS(rv, rv);
    rv = StartNewOfflineMessage();
    NS_ENSURE_SUCCESS(rv, rv);
  }
  // adoptedMessageLine is actually a string with a lot of message lines,
  // separated by native line terminators we need to count the number of
  // MSG_LINEBREAK's to determine how much to increment m_numOfflineMsgLines by.
  const char *nextLine = adoptedMessageLine;
  do {
    m_numOfflineMsgLines++;
    nextLine = PL_strstr(nextLine, MSG_LINEBREAK);
    if (nextLine) nextLine += MSG_LINEBREAK_LEN;
  } while (nextLine && *nextLine);

  if (m_tempMessageStream) {
    nsCOMPtr<nsISeekableStream> seekable(
        do_QueryInterface(m_tempMessageStream));
    if (seekable) seekable->Seek(PR_SEEK_END, 0);
    rv = m_tempMessageStream->Write(adoptedMessageLine,
                                    PL_strlen(adoptedMessageLine), &count);
    NS_ENSURE_SUCCESS(rv, rv);
  }
  return NS_OK;
}

void nsImapMailFolder::EndOfflineDownload() {
  if (m_tempMessageStream) {
    m_tempMessageStream->Close();
    m_tempMessageStream = nullptr;
    ReleaseSemaphore(static_cast<nsIMsgFolder *>(this));
    if (mDatabase) mDatabase->Commit(nsMsgDBCommitType::kLargeCommit);
  }
  m_offlineHeader = nullptr;
}

NS_IMETHODIMP
nsImapMailFolder::NormalEndMsgWriteStream(nsMsgKey uidOfMessage, bool markRead,
                                          nsIImapUrl *imapUrl,
                                          int32_t updatedMessageSize) {
  if (updatedMessageSize != -1) {
    // retrieve the message header to update size, if we don't already have it
    nsCOMPtr<nsIMsgDBHdr> msgHeader = m_offlineHeader;
    if (!msgHeader) GetMessageHeader(uidOfMessage, getter_AddRefs(msgHeader));
    if (msgHeader) {
      uint32_t msgSize;
      msgHeader->GetMessageSize(&msgSize);
      MOZ_LOG(IMAP, mozilla::LogLevel::Debug,
              ("Updating stored message size from %u, new size %d", msgSize,
               updatedMessageSize));
      msgHeader->SetMessageSize(updatedMessageSize);
      // only commit here if this isn't an offline message
      // offline header gets committed in EndNewOfflineMessage() called below
      if (mDatabase && !m_offlineHeader)
        mDatabase->Commit(nsMsgDBCommitType::kLargeCommit);
    } else
      NS_WARNING(
          "Failed to get message header when trying to update message size");
  }

  if (m_offlineHeader) EndNewOfflineMessage();

  m_curMsgUid = uidOfMessage;

  // Apply filter now if it needed a body
  if (m_filterListRequiresBody) {
    if (m_filterList) {
      nsCOMPtr<nsIMsgDBHdr> newMsgHdr;
      GetMessageHeader(uidOfMessage, getter_AddRefs(newMsgHdr));
      GetMoveCoalescer();
      nsCOMPtr<nsIMsgWindow> msgWindow;
      if (imapUrl) {
        nsresult rv;
        nsCOMPtr<nsIMsgMailNewsUrl> msgUrl;
        msgUrl = do_QueryInterface(imapUrl, &rv);
        if (msgUrl && NS_SUCCEEDED(rv))
          msgUrl->GetMsgWindow(getter_AddRefs(msgWindow));
      }
      m_filterList->ApplyFiltersToHdr(nsMsgFilterType::InboxRule, newMsgHdr,
                                      this, mDatabase, EmptyCString(), this,
                                      msgWindow);
      NotifyFolderEvent(kFiltersApplied);
    }
    // Process filter plugins and other items normally done at the end of
    // HeaderFetchCompleted.
    bool pendingMoves = m_moveCoalescer && m_moveCoalescer->HasPendingMoves();
    PlaybackCoalescedOperations();

    bool filtersRun;
    CallFilterPlugins(nullptr, &filtersRun);
    int32_t numNewBiffMsgs = 0;
    if (m_performingBiff) GetNumNewMessages(false, &numNewBiffMsgs);

    if (!filtersRun && m_performingBiff && mDatabase && numNewBiffMsgs > 0 &&
        (!pendingMoves || !ShowPreviewText())) {
      // If we are performing biff for this folder, tell the
      // stand-alone biff about the new high water mark
      // We must ensure that the server knows that we are performing biff.
      // Otherwise the stand-alone biff won't fire.
      nsCOMPtr<nsIMsgIncomingServer> server;
      if (NS_SUCCEEDED(GetServer(getter_AddRefs(server))) && server)
        server->SetPerformingBiff(true);

      SetBiffState(nsIMsgFolder::nsMsgBiffState_NewMail);
      if (server) server->SetPerformingBiff(false);
      m_performingBiff = false;
    }

    if (m_filterList) (void)m_filterList->FlushLogIfNecessary();
  }

  return NS_OK;
}

NS_IMETHODIMP
nsImapMailFolder::AbortMsgWriteStream() {
  m_offlineHeader = nullptr;
  return NS_ERROR_FAILURE;
}

// message move/copy related methods
NS_IMETHODIMP
nsImapMailFolder::OnlineCopyCompleted(nsIImapProtocol *aProtocol,
                                      ImapOnlineCopyState aCopyState) {
  NS_ENSURE_ARG_POINTER(aProtocol);

  nsresult rv;
  if (aCopyState == ImapOnlineCopyStateType::kSuccessfulCopy) {
    nsCOMPtr<nsIImapUrl> imapUrl;
    rv = aProtocol->GetRunningImapURL(getter_AddRefs(imapUrl));
    if (NS_FAILED(rv) || !imapUrl) return NS_ERROR_FAILURE;
    nsImapAction action;
    rv = imapUrl->GetImapAction(&action);
    if (NS_FAILED(rv)) return rv;
    if (action != nsIImapUrl::nsImapOnlineToOfflineMove)
      return NS_ERROR_FAILURE;  // don't assert here...
    nsCString messageIds;
    rv = imapUrl->GetListOfMessageIds(messageIds);
    if (NS_FAILED(rv)) return rv;
    nsCOMPtr<nsIImapService> imapService =
        do_GetService(NS_IMAPSERVICE_CONTRACTID, &rv);
    NS_ENSURE_SUCCESS(rv, rv);
    return imapService->AddMessageFlags(this, nullptr, nullptr, messageIds,
                                        kImapMsgDeletedFlag, true);
  }
  /* unhandled copystate */
  if (m_copyState)  // whoops, this is the wrong folder - should use the source
                    // folder
  {
    nsCOMPtr<nsIMsgFolder> srcFolder;
    srcFolder = do_QueryInterface(m_copyState->m_srcSupport, &rv);
    if (srcFolder) srcFolder->NotifyFolderEvent(kDeleteOrMoveMsgCompleted);
  } else
    rv = NS_ERROR_FAILURE;

  return rv;
}

NS_IMETHODIMP
nsImapMailFolder::CloseMockChannel(nsIImapMockChannel *aChannel) {
  aChannel->Close();
  return NS_OK;
}

NS_IMETHODIMP
nsImapMailFolder::ReleaseUrlCacheEntry(nsIMsgMailNewsUrl *aUrl) {
  NS_ENSURE_ARG_POINTER(aUrl);
  return aUrl->SetMemCacheEntry(nullptr);
}

NS_IMETHODIMP
nsImapMailFolder::BeginMessageUpload() { return NS_ERROR_FAILURE; }

nsresult nsImapMailFolder::HandleCustomFlags(nsMsgKey uidOfMessage,
                                             nsIMsgDBHdr *dbHdr,
                                             uint16_t userFlags,
                                             nsCString &keywords) {
  nsresult rv = GetDatabase();
  NS_ENSURE_SUCCESS(rv, rv);

  ToLowerCase(keywords);
  bool messageClassified = true;
  // Mac Mail uses "NotJunk"
  if (keywords.Find("NonJunk", /* ignoreCase = */ true) != kNotFound ||
      keywords.Find("NotJunk", /* ignoreCase = */ true) != kNotFound) {
    nsAutoCString msgJunkScore;
    msgJunkScore.AppendInt(nsIJunkMailPlugin::IS_HAM_SCORE);
    mDatabase->SetStringProperty(uidOfMessage, "junkscore", msgJunkScore.get());
  }
  // ### TODO: we really should parse the keywords into space delimited keywords
  // before checking
  else if (keywords.Find("Junk", /* ignoreCase = */ true) != kNotFound) {
    uint32_t newFlags;
    dbHdr->AndFlags(~nsMsgMessageFlags::New, &newFlags);
    nsAutoCString msgJunkScore;
    msgJunkScore.AppendInt(nsIJunkMailPlugin::IS_SPAM_SCORE);
    mDatabase->SetStringProperty(uidOfMessage, "junkscore", msgJunkScore.get());
  } else
    messageClassified = false;
  if (messageClassified) {
    // only set the junkscore origin if it wasn't set before.
    nsCString existingProperty;
    dbHdr->GetStringProperty("junkscoreorigin",
                             getter_Copies(existingProperty));
    if (existingProperty.IsEmpty())
      dbHdr->SetStringProperty("junkscoreorigin", "imapflag");
  }

  if (!(userFlags & kImapMsgSupportUserFlag)) {
    nsCString localKeywords;
    nsCString prevKeywords;
    dbHdr->GetStringProperty("keywords", getter_Copies(localKeywords));
    dbHdr->GetStringProperty("prevkeywords", getter_Copies(prevKeywords));
    // localKeywords: tags currently stored in database for the message.
    // keywords: tags stored in server and obtained when flags for the message
    //           were last fetched. (Parameter of this function.)
    // prevKeywords: saved keywords from previous call of this function.
    // clang-format off
    MOZ_LOG(IMAP_KW, mozilla::LogLevel::Debug,
            ("UID=%" PRIu32 ", localKeywords=|%s| keywords=|%s|, prevKeywords=|%s|",
             uidOfMessage, localKeywords.get(), keywords.get(), prevKeywords.get()));
    // clang-format on

    // Store keywords to detect changes on next call of this function.
    dbHdr->SetStringProperty("prevkeywords", keywords.get());

    // Parse the space separated strings into arrays.
    nsTArray<nsCString> localKeywordArray;
    nsTArray<nsCString> keywordArray;
    nsTArray<nsCString> prevKeywordArray;
    ParseString(localKeywords, ' ', localKeywordArray);
    ParseString(keywords, ' ', keywordArray);
    ParseString(prevKeywords, ' ', prevKeywordArray);

    // If keyword not received now but was the last time, remove it from
    // the localKeywords. This means the keyword was removed by another user
    // sharing the folder.
    for (uint32_t i = 0; i < prevKeywordArray.Length(); i++) {
      bool inRcvd = keywordArray.Contains(prevKeywordArray[i]);
      bool inLocal = localKeywordArray.Contains(prevKeywordArray[i]);
      if (!inRcvd && inLocal)
        localKeywordArray.RemoveElement(prevKeywordArray[i]);
    }

    // Combine local and rcvd keyword arrays into a single string
    // so it can be passed to SetStringProperty(). If element of
    // local already in rcvd, avoid duplicates in combined string.
    nsAutoCString combinedKeywords;
    for (uint32_t i = 0; i < localKeywordArray.Length(); i++) {
      if (!keywordArray.Contains(localKeywordArray[i])) {
        combinedKeywords.Append(localKeywordArray[i]);
        combinedKeywords.Append(' ');
      }
    }
    for (uint32_t i = 0; i < keywordArray.Length(); i++) {
      combinedKeywords.Append(keywordArray[i]);
      combinedKeywords.Append(' ');
    }
    MOZ_LOG(IMAP_KW, mozilla::LogLevel::Debug,
            ("combinedKeywords stored = |%s|", combinedKeywords.get()));
    // combinedKeywords are tags being stored in database for the message.
    return dbHdr->SetStringProperty("keywords", combinedKeywords.get());
  }
  return (userFlags & kImapMsgSupportUserFlag)
             ? dbHdr->SetStringProperty("keywords", keywords.get())
             : NS_OK;
}

// synchronize the message flags in the database with the server flags
nsresult nsImapMailFolder::SyncFlags(nsIImapFlagAndUidState *flagState) {
  nsresult rv = GetDatabase();  // we need a database for this
  NS_ENSURE_SUCCESS(rv, rv);
  bool partialUIDFetch;
  flagState->GetPartialUIDFetch(&partialUIDFetch);

  // update all of the database flags
  int32_t messageIndex;
  uint32_t messageSize;

  // Take this opportunity to recalculate the folder size, if we're not a
  // partial (condstore) fetch.
  int64_t newFolderSize = 0;

  flagState->GetNumberOfMessages(&messageIndex);

  uint16_t supportedUserFlags;
  flagState->GetSupportedUserFlags(&supportedUserFlags);

  for (int32_t flagIndex = 0; flagIndex < messageIndex; flagIndex++) {
    uint32_t uidOfMessage;
    flagState->GetUidOfMessage(flagIndex, &uidOfMessage);
    imapMessageFlagsType flags;
    flagState->GetMessageFlags(flagIndex, &flags);
    nsCOMPtr<nsIMsgDBHdr> dbHdr;
    bool containsKey;
    rv = mDatabase->ContainsKey(uidOfMessage, &containsKey);
    // if we don't have the header, don't diddle the flags.
    // GetMsgHdrForKey will create the header if it doesn't exist.
    if (NS_FAILED(rv) || !containsKey) continue;

    rv = mDatabase->GetMsgHdrForKey(uidOfMessage, getter_AddRefs(dbHdr));
    if (NS_SUCCEEDED(dbHdr->GetMessageSize(&messageSize)))
      newFolderSize += messageSize;

    nsCString keywords;
    if (NS_SUCCEEDED(
            flagState->GetCustomFlags(uidOfMessage, getter_Copies(keywords))))
      HandleCustomFlags(uidOfMessage, dbHdr, supportedUserFlags, keywords);

    NotifyMessageFlagsFromHdr(dbHdr, uidOfMessage, flags);
  }
  if (!partialUIDFetch && newFolderSize != mFolderSize) {
    int64_t oldFolderSize = mFolderSize;
    mFolderSize = newFolderSize;
    NotifyIntPropertyChanged(kFolderSize, oldFolderSize, mFolderSize);
  }

  return NS_OK;
}

// helper routine to sync the flags on a given header
nsresult nsImapMailFolder::NotifyMessageFlagsFromHdr(nsIMsgDBHdr *dbHdr,
                                                     nsMsgKey msgKey,
                                                     uint32_t flags) {
  nsresult rv = GetDatabase();
  NS_ENSURE_SUCCESS(rv, rv);

  // Although it may seem strange to keep a local reference of mDatabase here,
  // the current lifetime management of databases requires that methods
  // sometimes null the database when they think they opened it. Unfortunately
  // experience shows this happens when we don't expect, so for crash protection
  // best practice with the current flawed database management is to keep a
  // local reference when there will be complex calls in a method. See bug
  // 1312254.
  nsCOMPtr<nsIMsgDatabase> database(mDatabase);
  NS_ENSURE_STATE(database);

  database->MarkHdrRead(dbHdr, (flags & kImapMsgSeenFlag) != 0, nullptr);
  database->MarkHdrReplied(dbHdr, (flags & kImapMsgAnsweredFlag) != 0, nullptr);
  database->MarkHdrMarked(dbHdr, (flags & kImapMsgFlaggedFlag) != 0, nullptr);
  database->MarkImapDeleted(msgKey, (flags & kImapMsgDeletedFlag) != 0,
                            nullptr);

  uint32_t supportedFlags;
  GetSupportedUserFlags(&supportedFlags);
  if (supportedFlags & kImapMsgSupportForwardedFlag)
    database->MarkForwarded(msgKey, (flags & kImapMsgForwardedFlag) != 0,
                            nullptr);
  // this turns on labels, but it doesn't handle the case where the user
  // unlabels a message on one machine, and expects it to be unlabeled
  // on their other machines. If I turn that on, I'll be removing all the labels
  // that were assigned before we started storing them on the server, which will
  // make some people very unhappy.
  if (flags & kImapMsgLabelFlags)
    database->SetLabel(msgKey, (flags & kImapMsgLabelFlags) >> 9);
  else {
    if (supportedFlags & kImapMsgLabelFlags) database->SetLabel(msgKey, 0);
  }
  if (supportedFlags & kImapMsgSupportMDNSentFlag)
    database->MarkMDNSent(msgKey, (flags & kImapMsgMDNSentFlag) != 0, nullptr);

  return NS_OK;
}

// message flags operation - this is called from the imap protocol,
// proxied over from the imap thread to the ui thread, when a flag changes
NS_IMETHODIMP
nsImapMailFolder::NotifyMessageFlags(uint32_t aFlags,
                                     const nsACString &aKeywords,
                                     nsMsgKey aMsgKey,
                                     uint64_t aHighestModSeq) {
  if (NS_SUCCEEDED(GetDatabase()) && mDatabase) {
    bool msgDeleted = aFlags & kImapMsgDeletedFlag;
    if (aHighestModSeq || msgDeleted) {
      nsCOMPtr<nsIDBFolderInfo> dbFolderInfo;
      mDatabase->GetDBFolderInfo(getter_AddRefs(dbFolderInfo));
      if (dbFolderInfo) {
        if (aHighestModSeq) {
          char intStrBuf[40];
          PR_snprintf(intStrBuf, sizeof(intStrBuf), "%llu", aHighestModSeq);
          MOZ_LOG(IMAP_CS, mozilla::LogLevel::Debug,
                  ("NotifyMessageFlags(): Store highest MODSEQ=%" PRIu64
                   " for folder=%s",
                   aHighestModSeq, m_onlineFolderName.get()));
          dbFolderInfo->SetCharProperty(kModSeqPropertyName,
                                        nsDependentCString(intStrBuf));
        }
        if (msgDeleted) {
          uint32_t oldDeletedCount;
          dbFolderInfo->GetUint32Property(kDeletedHdrCountPropertyName, 0,
                                          &oldDeletedCount);
          dbFolderInfo->SetUint32Property(kDeletedHdrCountPropertyName,
                                          oldDeletedCount + 1);
        }
      }
    }
    nsCOMPtr<nsIMsgDBHdr> dbHdr;
    bool containsKey;
    nsresult rv = mDatabase->ContainsKey(aMsgKey, &containsKey);
    // if we don't have the header, don't diddle the flags.
    // GetMsgHdrForKey will create the header if it doesn't exist.
    if (NS_FAILED(rv) || !containsKey) return rv;
    rv = mDatabase->GetMsgHdrForKey(aMsgKey, getter_AddRefs(dbHdr));
    if (NS_SUCCEEDED(rv) && dbHdr) {
      uint32_t supportedUserFlags;
      GetSupportedUserFlags(&supportedUserFlags);
      NotifyMessageFlagsFromHdr(dbHdr, aMsgKey, aFlags);
      nsCString keywords(aKeywords);
      HandleCustomFlags(aMsgKey, dbHdr, supportedUserFlags, keywords);
    }
  }
  return NS_OK;
}

NS_IMETHODIMP
nsImapMailFolder::NotifyMessageDeleted(const char *onlineFolderName,
                                       bool deleteAllMsgs,
                                       const char *msgIdString) {
  if (deleteAllMsgs) return NS_OK;

  if (!msgIdString) return NS_OK;

  nsTArray<nsMsgKey> affectedMessages;
  ParseUidString(msgIdString, affectedMessages);

  if (!ShowDeletedMessages()) {
    GetDatabase();
    NS_ENSURE_TRUE(mDatabase, NS_OK);
    if (!ShowDeletedMessages()) {
      if (!affectedMessages.IsEmpty())  // perhaps Search deleted these messages
      {
        DeleteStoreMessages(affectedMessages);
        mDatabase->DeleteMessages(affectedMessages, nullptr);
      }
    } else  // && !imapDeleteIsMoveToTrash // TODO: can this ever be executed?
      SetIMAPDeletedFlag(mDatabase, affectedMessages, false);
  }
  return NS_OK;
}

bool nsImapMailFolder::ShowDeletedMessages() {
  nsresult rv;
  nsCOMPtr<nsIImapHostSessionList> hostSession =
      do_GetService(kCImapHostSessionList, &rv);
  NS_ENSURE_SUCCESS(rv, false);

  bool showDeleted = false;
  nsCString serverKey;
  GetServerKey(serverKey);
  hostSession->GetShowDeletedMessagesForHost(serverKey.get(), showDeleted);

  return showDeleted;
}

bool nsImapMailFolder::DeleteIsMoveToTrash() {
  nsresult err;
  nsCOMPtr<nsIImapHostSessionList> hostSession =
      do_GetService(kCImapHostSessionList, &err);
  NS_ENSURE_SUCCESS(err, true);
  bool rv = true;

  nsCString serverKey;
  GetServerKey(serverKey);
  hostSession->GetDeleteIsMoveToTrashForHost(serverKey.get(), rv);
  return rv;
}

nsresult nsImapMailFolder::GetTrashFolder(nsIMsgFolder **pTrashFolder) {
  NS_ENSURE_ARG_POINTER(pTrashFolder);
  nsCOMPtr<nsIMsgFolder> rootFolder;
  nsresult rv = GetRootFolder(getter_AddRefs(rootFolder));
  if (NS_SUCCEEDED(rv) && rootFolder) {
    rv = rootFolder->GetFolderWithFlags(nsMsgFolderFlags::Trash, pTrashFolder);
    if (!*pTrashFolder) rv = NS_ERROR_FAILURE;
  }
  return rv;
}

// store nsMsgMessageFlags::IMAPDeleted in the specified mailhdr records
void nsImapMailFolder::SetIMAPDeletedFlag(nsIMsgDatabase *mailDB,
                                          const nsTArray<nsMsgKey> &msgids,
                                          bool markDeleted) {
  nsresult markStatus = NS_OK;
  uint32_t total = msgids.Length();

  for (uint32_t msgIndex = 0; NS_SUCCEEDED(markStatus) && (msgIndex < total);
       msgIndex++)
    markStatus =
        mailDB->MarkImapDeleted(msgids[msgIndex], markDeleted, nullptr);
}

NS_IMETHODIMP
nsImapMailFolder::GetMessageSizeFromDB(const char *id, uint32_t *size) {
  NS_ENSURE_ARG_POINTER(size);

  *size = 0;
  nsresult rv = GetDatabase();
  NS_ENSURE_SUCCESS(rv, rv);
  if (id) {
    nsMsgKey key = msgKeyFromInt(ParseUint64Str(id));
    nsCOMPtr<nsIMsgDBHdr> mailHdr;
    rv = mDatabase->GetMsgHdrForKey(key, getter_AddRefs(mailHdr));
    if (NS_SUCCEEDED(rv) && mailHdr) rv = mailHdr->GetMessageSize(size);
  }
  return rv;
}

NS_IMETHODIMP
nsImapMailFolder::SetContentModified(nsIImapUrl *aImapUrl,
                                     nsImapContentModifiedType modified) {
  return aImapUrl->SetContentModified(modified);
}

NS_IMETHODIMP
nsImapMailFolder::GetCurMoveCopyMessageInfo(nsIImapUrl *runningUrl,
                                            PRTime *aDate,
                                            nsACString &aKeywords,
                                            uint32_t *aResult) {
  nsCOMPtr<nsISupports> copyState;
  runningUrl->GetCopyState(getter_AddRefs(copyState));
  if (copyState) {
    nsCOMPtr<nsImapMailCopyState> mailCopyState = do_QueryInterface(copyState);
    uint32_t supportedFlags = 0;
    GetSupportedUserFlags(&supportedFlags);
    if (mailCopyState && mailCopyState->m_message) {
      nsMsgLabelValue label;
      mailCopyState->m_message->GetFlags(aResult);
      if (supportedFlags & (kImapMsgSupportUserFlag | kImapMsgLabelFlags)) {
        mailCopyState->m_message->GetLabel(&label);
        if (label != 0) *aResult |= label << 25;
      }
      if (aDate) mailCopyState->m_message->GetDate(aDate);
      if (supportedFlags & kImapMsgSupportUserFlag) {
        // setup the custom imap keywords, which includes the message keywords
        // plus any junk status
        nsCString junkscore;
        mailCopyState->m_message->GetStringProperty("junkscore",
                                                    getter_Copies(junkscore));
        bool isJunk = false, isNotJunk = false;
        if (!junkscore.IsEmpty()) {
          if (junkscore.EqualsLiteral("0"))
            isNotJunk = true;
          else
            isJunk = true;
        }

        nsCString keywords;  // MsgFindKeyword can't use nsACString
        mailCopyState->m_message->GetStringProperty("keywords",
                                                    getter_Copies(keywords));
        int32_t start;
        int32_t length;
        bool hasJunk = MsgFindKeyword(NS_LITERAL_CSTRING("junk"), keywords,
                                      &start, &length);
        if (hasJunk && !isJunk)
          keywords.Cut(start, length);
        else if (!hasJunk && isJunk)
          keywords.AppendLiteral(" Junk");
        bool hasNonJunk = MsgFindKeyword(NS_LITERAL_CSTRING("nonjunk"),
                                         keywords, &start, &length);
        if (!hasNonJunk)
          hasNonJunk = MsgFindKeyword(NS_LITERAL_CSTRING("notjunk"), keywords,
                                      &start, &length);
        if (hasNonJunk && !isNotJunk)
          keywords.Cut(start, length);
        else if (!hasNonJunk && isNotJunk)
          keywords.AppendLiteral(" NonJunk");

        // Cleanup extra spaces
        while (!keywords.IsEmpty() && keywords.First() == ' ')
          keywords.Cut(0, 1);
        while (!keywords.IsEmpty() && keywords.Last() == ' ')
          keywords.Cut(keywords.Length() - 1, 1);
        while (!keywords.IsEmpty() &&
               (start = keywords.Find(NS_LITERAL_CSTRING("  "))) >= 0)
          keywords.Cut(start, 1);
        aKeywords.Assign(keywords);
      }
    }
    // if we don't have a source header, and it's not the drafts folder,
    // then mark the message read, since it must be an append to the
    // fcc or templates folder.
    else if (mailCopyState) {
      *aResult = mailCopyState->m_newMsgFlags;
      if (supportedFlags & kImapMsgSupportUserFlag)
        aKeywords.Assign(mailCopyState->m_newMsgKeywords);
    }
  }
  return NS_OK;
}

NS_IMETHODIMP
nsImapMailFolder::OnStartRunningUrl(nsIURI *aUrl) {
  NS_ASSERTION(aUrl, "sanity check - need to be be running non-null url");
  nsCOMPtr<nsIMsgMailNewsUrl> mailUrl = do_QueryInterface(aUrl);
  if (mailUrl) {
    bool updatingFolder;
    mailUrl->GetUpdatingFolder(&updatingFolder);
    m_updatingFolder = updatingFolder;
  }
  m_urlRunning = true;
  return NS_OK;
}

NS_IMETHODIMP
nsImapMailFolder::OnStopRunningUrl(nsIURI *aUrl, nsresult aExitCode) {
  nsresult rv;
  bool endedOfflineDownload = false;
  nsImapAction imapAction = nsIImapUrl::nsImapTest;
  m_urlRunning = false;
  m_updatingFolder = false;
  nsCOMPtr<nsIMsgMailSession> session =
      do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv);
  NS_ENSURE_SUCCESS(rv, rv);
  if (aUrl) {
    nsCOMPtr<nsIImapUrl> imapUrl = do_QueryInterface(aUrl, &rv);
    NS_ENSURE_SUCCESS(rv, rv);
    bool downloadingForOfflineUse;
    imapUrl->GetStoreResultsOffline(&downloadingForOfflineUse);
    bool hasSemaphore = false;
    // if we have the folder locked, clear it.
    TestSemaphore(static_cast<nsIMsgFolder *>(this), &hasSemaphore);
    if (hasSemaphore) ReleaseSemaphore(static_cast<nsIMsgFolder *>(this));
    if (downloadingForOfflineUse) {
      endedOfflineDownload = true;
      EndOfflineDownload();
    }
    nsCOMPtr<nsIMsgWindow> msgWindow;
    nsCOMPtr<nsIMsgMailNewsUrl> mailUrl = do_QueryInterface(aUrl);
    bool folderOpen = false;
    if (mailUrl) mailUrl->GetMsgWindow(getter_AddRefs(msgWindow));
    if (session) session->IsFolderOpenInWindow(this, &folderOpen);
#ifdef DEBUG_bienvenu
    printf("stop running url %s\n", aUrl->GetSpecOrDefault().get());
#endif

    if (imapUrl) {
      DisplayStatusMsg(imapUrl, EmptyString());
      imapUrl->GetImapAction(&imapAction);
      if (imapAction == nsIImapUrl::nsImapMsgFetch ||
          imapAction == nsIImapUrl::nsImapMsgDownloadForOffline) {
        ReleaseSemaphore(static_cast<nsIMsgFolder *>(this));
        if (!endedOfflineDownload) EndOfflineDownload();
      }

      // Notify move, copy or delete (online operations)
      // Not sure whether nsImapDeleteMsg is even used, deletes in all three
      // models use nsImapAddMsgFlags.
      nsCOMPtr<nsIMsgFolderNotificationService> notifier(
          do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID));
      if (notifier && m_copyState) {
        if (imapAction == nsIImapUrl::nsImapOnlineMove)
          notifier->NotifyMsgsMoveCopyCompleted(true, m_copyState->m_messages,
                                                this, nullptr);
        else if (imapAction == nsIImapUrl::nsImapOnlineCopy)
          notifier->NotifyMsgsMoveCopyCompleted(false, m_copyState->m_messages,
                                                this, nullptr);
        else if (imapAction == nsIImapUrl::nsImapDeleteMsg) {
          // Stopgap. Build a parallel array of message headers while we
          // complete removal of nsIArray usage (Bug 1583030).
          uint32_t messageCount;
          m_copyState->m_messages->GetLength(&messageCount);
          nsTArray<RefPtr<nsIMsgDBHdr>> msgHeaders;
          msgHeaders.SetCapacity(messageCount);
          for (uint32_t i = 0; i < messageCount; ++i) {
            msgHeaders.AppendElement(
                do_QueryElementAt(m_copyState->m_messages, i));
          }

          notifier->NotifyMsgsDeleted(msgHeaders);
        }
      }

      switch (imapAction) {
        case nsIImapUrl::nsImapDeleteMsg:
        case nsIImapUrl::nsImapOnlineMove:
        case nsIImapUrl::nsImapOnlineCopy:
          if (NS_SUCCEEDED(aExitCode)) {
            if (folderOpen)
              UpdateFolder(msgWindow);
            else
              UpdatePendingCounts();
          }

          if (m_copyState) {
            nsCOMPtr<nsIMsgFolder> srcFolder =
                do_QueryInterface(m_copyState->m_srcSupport, &rv);
            if (m_copyState->m_isMove && !m_copyState->m_isCrossServerOp) {
              if (NS_SUCCEEDED(aExitCode)) {
                nsCOMPtr<nsIMsgDatabase> srcDB;
                if (srcFolder)
                  rv = srcFolder->GetMsgDatabase(getter_AddRefs(srcDB));
                if (NS_SUCCEEDED(rv) && srcDB) {
                  RefPtr<nsImapMoveCopyMsgTxn> msgTxn;
                  nsTArray<nsMsgKey> srcKeyArray;
                  if (m_copyState->m_allowUndo) {
                    msgTxn = m_copyState->m_undoMsgTxn;
                    if (msgTxn) msgTxn->GetSrcKeyArray(srcKeyArray);
                  } else {
                    nsAutoCString messageIds;
                    rv = BuildIdsAndKeyArray(m_copyState->m_messages,
                                             messageIds, srcKeyArray);
                    NS_ENSURE_SUCCESS(rv, rv);
                  }

                  if (!ShowDeletedMessages()) {
                    // We only reach here for same-server operations
                    // (!m_copyState->m_isCrossServerOp in if above), so we can
                    // assume that the src is also imap that uses offline
                    // storage.
                    DeleteStoreMessages(srcKeyArray, srcFolder);
                    srcDB->DeleteMessages(srcKeyArray, nullptr);
                  } else
                    MarkMessagesImapDeleted(&srcKeyArray, true, srcDB);
                }
                srcFolder->EnableNotifications(allMessageCountNotifications,
                                               true);
                // even if we're showing deleted messages,
                // we still need to notify FE so it will show the imap deleted
                // flag
                srcFolder->NotifyFolderEvent(kDeleteOrMoveMsgCompleted);
                // is there a way to see that we think we have new msgs?
                nsCOMPtr<nsIPrefBranch> prefBranch(
                    do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
                if (NS_SUCCEEDED(rv)) {
                  bool showPreviewText;
                  prefBranch->GetBoolPref("mail.biff.alert.show_preview",
                                          &showPreviewText);
                  // if we're showing preview text, update ourselves if we got a
                  // new unread message copied so that we can download the new
                  // headers and have a chance to preview the msg bodies.
                  if (!folderOpen && showPreviewText &&
                      m_copyState->m_unreadCount > 0 &&
                      !(mFlags &
                        (nsMsgFolderFlags::Trash | nsMsgFolderFlags::Junk)))
                    UpdateFolder(msgWindow);
                }
              } else {
                srcFolder->EnableNotifications(allMessageCountNotifications,
                                               true);
                srcFolder->NotifyFolderEvent(kDeleteOrMoveMsgFailed);
              }
            }
            if (m_copyState->m_msgWindow &&
                m_copyState->m_undoMsgTxn &&  // may be null from filters
                NS_SUCCEEDED(
                    aExitCode))  // we should do this only if move/copy succeeds
            {
              nsCOMPtr<nsITransactionManager> txnMgr;
              m_copyState->m_msgWindow->GetTransactionManager(
                  getter_AddRefs(txnMgr));
              if (txnMgr) {
                RefPtr<nsImapMoveCopyMsgTxn> txn = m_copyState->m_undoMsgTxn;
                mozilla::DebugOnly<nsresult> rv2 = txnMgr->DoTransaction(txn);
                NS_ASSERTION(NS_SUCCEEDED(rv2), "doing transaction failed");
              }
            }
            (void)OnCopyCompleted(m_copyState->m_srcSupport, aExitCode);
          }

          // we're the dest folder of a move/copy - if we're not open in the ui,
          // then we should clear our nsMsgDatabase pointer. Otherwise, the db
          // would be open until the user selected it and then selected another
          // folder. but don't do this for the trash or inbox - we'll leave them
          // open
          if (!folderOpen &&
              !(mFlags & (nsMsgFolderFlags::Trash | nsMsgFolderFlags::Inbox)))
            SetMsgDatabase(nullptr);
          break;
        case nsIImapUrl::nsImapSubtractMsgFlags: {
          // this isn't really right - we'd like to know we were
          // deleting a message to start with, but it probably
          // won't do any harm.
          imapMessageFlagsType flags = 0;
          imapUrl->GetMsgFlags(&flags);
          // we need to subtract the delete flag in db only in case when we show
          // deleted msgs
          if (flags & kImapMsgDeletedFlag && ShowDeletedMessages()) {
            nsCOMPtr<nsIMsgDatabase> db;
            rv = GetMsgDatabase(getter_AddRefs(db));
            if (NS_SUCCEEDED(rv) && db) {
              nsTArray<nsMsgKey> keyArray;
              nsCString keyString;
              imapUrl->GetListOfMessageIds(keyString);
              ParseUidString(keyString.get(), keyArray);
              MarkMessagesImapDeleted(&keyArray, false, db);
              db->Commit(nsMsgDBCommitType::kLargeCommit);
            }
          }
        } break;
        case nsIImapUrl::nsImapAddMsgFlags: {
          imapMessageFlagsType flags = 0;
          imapUrl->GetMsgFlags(&flags);
          if (flags & kImapMsgDeletedFlag) {
            // we need to delete headers from db only when we don't show deleted
            // msgs
            if (!ShowDeletedMessages()) {
              nsCOMPtr<nsIMsgDatabase> db;
              rv = GetMsgDatabase(getter_AddRefs(db));
              if (NS_SUCCEEDED(rv) && db) {
                nsTArray<nsMsgKey> keyArray;
                nsCString keyString;
                imapUrl->GetListOfMessageIds(keyString);
                ParseUidString(keyString.get(), keyArray);

                // For pluggable stores that do not support compaction, we need
                // to delete the messages now.
                bool supportsCompaction = false;
                nsCOMPtr<nsIMsgPluggableStore> offlineStore;
                (void)GetMsgStore(getter_AddRefs(offlineStore));
                if (offlineStore)
                  offlineStore->GetSupportsCompaction(&supportsCompaction);

                nsTArray<RefPtr<nsIMsgDBHdr>> msgHdrs;
                if (notifier || !supportsCompaction) {
                  MsgGetHeadersFromKeys2(db, keyArray, msgHdrs);
                }

                // Notify listeners of delete.
                if (notifier && !msgHdrs.IsEmpty()) {
                  // XXX Currently, the DeleteMessages below gets executed twice
                  // on deletes. Once in DeleteMessages, once here. The second
                  // time, it silently fails to delete. This is why we're also
                  // checking whether the array is empty.
                  notifier->NotifyMsgsDeleted(msgHdrs);
                }

                if (!supportsCompaction && !msgHdrs.IsEmpty())
                  DeleteStoreMessages(msgHdrs);

                db->DeleteMessages(keyArray, nullptr);
                db->SetSummaryValid(true);
                db->Commit(nsMsgDBCommitType::kLargeCommit);
              }
            }
          }
        } break;
        case nsIImapUrl::nsImapAppendMsgFromFile:
        case nsIImapUrl::nsImapAppendDraftFromFile:
          if (m_copyState) {
            if (NS_SUCCEEDED(aExitCode)) {
              UpdatePendingCounts();

              m_copyState->m_curIndex++;
              if (m_copyState->m_curIndex >= m_copyState->m_totalCount) {
                nsCOMPtr<nsIUrlListener> saveUrlListener = m_urlListener;
                if (folderOpen) {
                  // This gives a way for the caller to get notified
                  // when the UpdateFolder url is done.
                  if (m_copyState->m_listener)
                    m_urlListener = do_QueryInterface(m_copyState->m_listener);
                }
                if (m_copyState->m_msgWindow && m_copyState->m_undoMsgTxn) {
                  nsCOMPtr<nsITransactionManager> txnMgr;
                  m_copyState->m_msgWindow->GetTransactionManager(
                      getter_AddRefs(txnMgr));
                  if (txnMgr) {
                    RefPtr<nsImapMoveCopyMsgTxn> txn =
                        m_copyState->m_undoMsgTxn;
                    txnMgr->DoTransaction(txn);
                  }
                }
                (void)OnCopyCompleted(m_copyState->m_srcSupport, aExitCode);
                if (folderOpen ||
                    imapAction == nsIImapUrl::nsImapAppendDraftFromFile) {
                  UpdateFolderWithListener(msgWindow, m_urlListener);
                  m_urlListener = saveUrlListener;
                }
              }
            } else
              // clear the copyState if copy has failed
              (void)OnCopyCompleted(m_copyState->m_srcSupport, aExitCode);
          }
          break;
        case nsIImapUrl::nsImapMoveFolderHierarchy:
          if (m_copyState)  // delete folder gets here, but w/o an m_copyState
          {
            nsCOMPtr<nsIMsgCopyService> copyService =
                do_GetService(NS_MSGCOPYSERVICE_CONTRACTID, &rv);
            NS_ENSURE_SUCCESS(rv, rv);
            nsCOMPtr<nsIMsgFolder> srcFolder =
                do_QueryInterface(m_copyState->m_srcSupport);
            if (srcFolder) {
              copyService->NotifyCompletion(m_copyState->m_srcSupport, this,
                                            aExitCode);
            }
            m_copyState = nullptr;
          }
          break;
        case nsIImapUrl::nsImapRenameFolder:
          if (NS_FAILED(aExitCode)) {
            NotifyFolderEvent(kRenameCompleted);
          }
          break;
        case nsIImapUrl::nsImapDeleteAllMsgs:
          if (NS_SUCCEEDED(aExitCode)) {
            if (folderOpen)
              UpdateFolder(msgWindow);
            else {
              ChangeNumPendingTotalMessages(-mNumPendingTotalMessages);
              ChangeNumPendingUnread(-mNumPendingUnreadMessages);
              m_numServerUnseenMessages = 0;
            }
          }
          break;
        case nsIImapUrl::nsImapListFolder:
          if (NS_SUCCEEDED(aExitCode)) {
            // listing folder will open db; don't leave the db open.
            SetMsgDatabase(nullptr);
            if (!m_verifiedAsOnlineFolder) {
              // If folder is not verified, we remove it.
              nsCOMPtr<nsIMsgFolder> parent;
              rv = GetParent(getter_AddRefs(parent));
              if (NS_SUCCEEDED(rv) && parent) {
                nsCOMPtr<nsIMsgImapMailFolder> imapParent =
                    do_QueryInterface(parent);
                if (imapParent) imapParent->RemoveSubFolder(this);
              }
            }
          }
          break;
        case nsIImapUrl::nsImapRefreshFolderUrls:
          // we finished getting an admin url for the folder.
          if (!m_adminUrl.IsEmpty()) FolderPrivileges(msgWindow);
          break;
        case nsIImapUrl::nsImapCreateFolder:
          if (NS_FAILED(aExitCode))  // if success notification already done
          {
            NotifyFolderEvent(kFolderCreateFailed);
          }
          break;
        case nsIImapUrl::nsImapSubscribe:
          if (NS_SUCCEEDED(aExitCode) && msgWindow) {
            nsCString canonicalFolderName;
            imapUrl->CreateCanonicalSourceFolderPathString(
                getter_Copies(canonicalFolderName));
            nsCOMPtr<nsIMsgFolder> rootFolder;
            nsresult rv = GetRootFolder(getter_AddRefs(rootFolder));
            if (NS_SUCCEEDED(rv) && rootFolder) {
              nsCOMPtr<nsIMsgImapMailFolder> imapRoot =
                  do_QueryInterface(rootFolder);
              if (imapRoot) {
                nsCOMPtr<nsIMsgImapMailFolder> foundFolder;
                rv = imapRoot->FindOnlineSubFolder(canonicalFolderName,
                                                   getter_AddRefs(foundFolder));
                if (NS_SUCCEEDED(rv) && foundFolder) {
                  nsCString uri;
                  nsCOMPtr<nsIMsgFolder> msgFolder =
                      do_QueryInterface(foundFolder);
                  if (msgFolder) {
                    msgFolder->GetURI(uri);
                    nsCOMPtr<nsIMsgWindowCommands> windowCommands;
                    msgWindow->GetWindowCommands(
                        getter_AddRefs(windowCommands));
                    if (windowCommands) windowCommands->SelectFolder(uri);
                  }
                }
              }
            }
          }
          break;
        case nsIImapUrl::nsImapExpungeFolder:
          m_expunging = false;
          break;
        default:
          break;
      }
    }
    // give base class a chance to send folder loaded notification...
    rv = nsMsgDBFolder::OnStopRunningUrl(aUrl, aExitCode);
  }
  // if we're not running a url, we must not be getting new mail.
  SetGettingNewMessages(false);
  // don't send OnStopRunning notification if still compacting offline store.
  if (m_urlListener && (imapAction != nsIImapUrl::nsImapExpungeFolder ||
                        !m_compactingOfflineStore)) {
    nsCOMPtr<nsIUrlListener> saveListener = m_urlListener;
    m_urlListener = nullptr;
    saveListener->OnStopRunningUrl(aUrl, aExitCode);
  }
  return rv;
}

void nsImapMailFolder::UpdatePendingCounts() {
  if (m_copyState) {
    ChangePendingTotal(
        m_copyState->m_isCrossServerOp ? 1 : m_copyState->m_totalCount);

    // count the moves that were unread
    int numUnread = m_copyState->m_unreadCount;
    if (numUnread) {
      m_numServerUnseenMessages +=
          numUnread;  // adjust last status count by this delta.
      ChangeNumPendingUnread(numUnread);
    }
    SummaryChanged();
  }
}

NS_IMETHODIMP
nsImapMailFolder::ClearFolderRights() {
  SetFolderNeedsACLListed(false);
  delete m_folderACL;
  m_folderACL = new nsMsgIMAPFolderACL(this);
  return NS_OK;
}

NS_IMETHODIMP
nsImapMailFolder::AddFolderRights(const nsACString &userName,
                                  const nsACString &rights) {
  SetFolderNeedsACLListed(false);
  GetFolderACL()->SetFolderRightsForUser(userName, rights);
  return NS_OK;
}

NS_IMETHODIMP
nsImapMailFolder::RefreshFolderRights() {
  if (GetFolderACL()->GetIsFolderShared())
    SetFlag(nsMsgFolderFlags::PersonalShared);
  else
    ClearFlag(nsMsgFolderFlags::PersonalShared);
  return NS_OK;
}

NS_IMETHODIMP
nsImapMailFolder::SetCopyResponseUid(const char *msgIdString,
                                     nsIImapUrl *aUrl) {  // CopyMessages() only
  nsresult rv = NS_OK;
  RefPtr<nsImapMoveCopyMsgTxn> msgTxn;
  nsCOMPtr<nsISupports> copyState;

  if (aUrl) aUrl->GetCopyState(getter_AddRefs(copyState));

  if (copyState) {
    nsCOMPtr<nsImapMailCopyState> mailCopyState =
        do_QueryInterface(copyState, &rv);
    if (NS_FAILED(rv)) return rv;
    if (mailCopyState->m_undoMsgTxn) msgTxn = mailCopyState->m_undoMsgTxn;
  } else if (aUrl && m_pendingOfflineMoves.Length()) {
    nsCString urlSourceMsgIds, undoTxnSourceMsgIds;
    aUrl->GetListOfMessageIds(urlSourceMsgIds);
    RefPtr<nsImapMoveCopyMsgTxn> imapUndo = m_pendingOfflineMoves[0];
    if (imapUndo) {
      imapUndo->GetSrcMsgIds(undoTxnSourceMsgIds);
      if (undoTxnSourceMsgIds.Equals(urlSourceMsgIds)) msgTxn = imapUndo;
      // ### we should handle batched moves, but lets keep it simple for a2.
      m_pendingOfflineMoves.Clear();
    }
  }
  if (msgTxn) msgTxn->SetCopyResponseUid(msgIdString);
  return NS_OK;
}

NS_IMETHODIMP