summaryrefslogtreecommitdiffstats
path: root/comm/mailnews/imap/src/nsImapMailFolder.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'comm/mailnews/imap/src/nsImapMailFolder.cpp')
-rw-r--r--comm/mailnews/imap/src/nsImapMailFolder.cpp9095
1 files changed, 9095 insertions, 0 deletions
diff --git a/comm/mailnews/imap/src/nsImapMailFolder.cpp b/comm/mailnews/imap/src/nsImapMailFolder.cpp
new file mode 100644
index 0000000000..1ec8482383
--- /dev/null
+++ b/comm/mailnews/imap/src/nsImapMailFolder.cpp
@@ -0,0 +1,9095 @@
+/* -*- 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 "nsImapMailFolder.h"
+#include "nsIImapService.h"
+#include "nsIFile.h"
+#include "nsAnonymousTemporaryFile.h"
+#include "nsIUrlListener.h"
+#include "nsCOMPtr.h"
+#include "nsMsgFolderFlags.h"
+#include "nsISeekableStream.h"
+#include "nsThreadUtils.h"
+#include "nsIImapUrl.h"
+#include "nsImapUtils.h"
+#include "nsMsgUtils.h"
+#include "nsIMsgMailSession.h"
+#include "nsITransactionManager.h"
+#include "nsImapUndoTxn.h"
+#include "../public/nsIImapHostSessionList.h"
+#include "nsIMsgCopyService.h"
+#include "nsICopyMessageStreamListener.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 "nsDirectoryServiceDefs.h"
+#include "nsIDirectoryEnumerator.h"
+#include "nsIMsgIdentity.h"
+#include "nsIMsgFolderNotificationService.h"
+#include "nsNativeCharsetUtils.h"
+#include "nsIExternalProtocolService.h"
+#include "nsCExternalHandlerService.h"
+#include "prprf.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 "mozilla/SlicedInputStream.h"
+#include "nsStringStream.h"
+#include "nsIStreamListener.h"
+#include "nsITimer.h"
+#include "nsReadableUtils.h"
+#include "UrlListener.h"
+#include "nsIObserverService.h"
+
+#define NS_PARSEMAILMSGSTATE_CID \
+ { /* 2B79AC51-1459-11d3-8097-006008128C4E */ \
+ 0x2b79ac51, 0x1459, 0x11d3, { \
+ 0x80, 0x97, 0x0, 0x60, 0x8, 0x12, 0x8c, 0x4e \
+ } \
+ }
+static NS_DEFINE_CID(kParseMailMsgStateCID, NS_PARSEMAILMSGSTATE_CID);
+
+#define NS_IIMAPHOSTSESSIONLIST_CID \
+ { \
+ 0x479ce8fc, 0xe725, 0x11d2, { \
+ 0xa5, 0x05, 0x00, 0x60, 0xb0, 0xfc, 0x04, 0xb7 \
+ } \
+ }
+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) {
+ bool isDir;
+ nsresult rv = srcDir->IsDirectory(&isDir);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!isDir) return NS_ERROR_INVALID_ARG;
+
+ bool exists;
+ rv = destDir->Exists(&exists);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!exists) {
+ rv = destDir->Create(nsIFile::DIRECTORY_TYPE, 0775);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ nsCOMPtr<nsIDirectoryEnumerator> dirIterator;
+ rv = srcDir->GetDirectoryEntries(getter_AddRefs(dirIterator));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool hasMore = false;
+ while (NS_SUCCEEDED(dirIterator->HasMoreElements(&hasMore)) && hasMore) {
+ nsCOMPtr<nsIFile> dirEntry;
+ rv = dirIterator->GetNextFile(getter_AddRefs(dirEntry));
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!dirEntry) continue;
+ rv = dirEntry->IsDirectory(&isDir);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (isDir) {
+ nsCOMPtr<nsIFile> newChild;
+ rv = destDir->Clone(getter_AddRefs(newChild));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsAutoString leafName;
+ dirEntry->GetLeafName(leafName);
+ newChild->AppendRelativePath(leafName);
+ rv = newChild->Exists(&exists);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!exists) {
+ rv = newChild->Create(nsIFile::DIRECTORY_TYPE, 0775);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ rv = RecursiveCopy(dirEntry, newChild);
+ } else {
+ rv = dirEntry->CopyTo(destDir, EmptyString());
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ return rv;
+}
+
+//
+// nsMsgQuota
+//
+NS_IMPL_ISUPPORTS(nsMsgQuota, nsIMsgQuota)
+
+nsMsgQuota::nsMsgQuota(const nsACString& aName, const uint64_t& aUsage,
+ const uint64_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(uint64_t* aUsage) {
+ *aUsage = mUsage;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgQuota::SetUsage(uint64_t aUsage) {
+ mUsage = aUsage;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgQuota::GetLimit(uint64_t* aLimit) {
+ *aLimit = mLimit;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgQuota::SetLimit(uint64_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_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_FROM_CSTRING(SUMMARY_SUFFIX),
+ nsCaseInsensitiveStringComparator)) {
+ name.SetLength(name.Length() -
+ SUMMARY_SUFFIX_LENGTH); // truncate the string
+ return false;
+ }
+ return true;
+}
+
+nsresult nsImapMailFolder::CreateChildFromURI(const nsACString& uri,
+ nsIMsgFolder** folder) {
+ nsImapMailFolder* newFolder = new nsImapMailFolder;
+ newFolder->Init(uri);
+ 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('/');
+
+ 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);
+
+ // Ensure the containing dir exists.
+ 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);
+
+ // New child needs to inherit hierarchyDelimiter.
+ nsCOMPtr<nsIMsgImapMailFolder> imapChild = do_QueryInterface(*aChild);
+ if (imapChild) {
+ imapChild->SetHierarchyDelimiter(m_hierarchyDelimiter);
+ }
+ NotifyFolderAdded(*aChild);
+ return rv;
+}
+
+// Creates a new child nsIMsgFolder locally, with no IMAP traffic.
+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;
+}
+
+// Create child nsIMsgFolders by scanning the filesystem to find .msf files.
+// No IMAP traffic.
+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);
+
+ // For each .msf file in the directory...
+ 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);
+ // Skip if not an .msf file.
+ // (NOTE: nsShouldIgnoreFile() strips the trailing ".msf" here)
+ 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 utfLeafName = currentFolderNameStr;
+
+ if (curFolder) {
+ nsCOMPtr<nsIMsgFolderCacheElement> cacheElement;
+ rv = GetFolderCacheElemFromFile(dbFile, getter_AddRefs(cacheElement));
+ if (NS_SUCCEEDED(rv) && cacheElement) {
+ nsCString onlineFullUtfName;
+
+ uint32_t folderFlags;
+ rv = cacheElement->GetCachedUInt32("flags", &folderFlags);
+ if (NS_SUCCEEDED(rv) &&
+ folderFlags & nsMsgFolderFlags::Virtual) // ignore virtual folders
+ continue;
+ int32_t hierarchyDelimiter;
+ rv = cacheElement->GetCachedInt32("hierDelim", &hierarchyDelimiter);
+ if (NS_SUCCEEDED(rv) &&
+ hierarchyDelimiter == kOnlineHierarchySeparatorUnknown) {
+ currentFolderPath->Remove(false);
+ continue; // blow away .msf files for folders with unknown delimiter.
+ }
+ rv = cacheElement->GetCachedString("onlineName", onlineFullUtfName);
+ if (NS_SUCCEEDED(rv) && !onlineFullUtfName.IsEmpty()) {
+ CopyFolderNameToUTF16(onlineFullUtfName, currentFolderNameStr);
+ char delimiter = 0;
+ GetHierarchyDelimiter(&delimiter);
+ int32_t leafPos = currentFolderNameStr.RFindChar(delimiter);
+ if (leafPos > 0) currentFolderNameStr.Cut(0, leafPos + 1);
+
+ // Take the full online name, and determine the leaf name.
+ CopyUTF8toUTF16(onlineFullUtfName, utfLeafName);
+ leafPos = utfLeafName.RFindChar(delimiter);
+ if (leafPos > 0) utfLeafName.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 name as the uri for the folder.
+ nsCOMPtr<nsIMsgFolder> child;
+ AddSubfolderWithPath(utfLeafName, 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.
+ if (!currentFolderNameStr.IsEmpty())
+ child->SetPrettyName(currentFolderNameStr);
+ child->SetMsgDatabase(nullptr);
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapMailFolder::GetSubFolders(
+ nsTArray<RefPtr<nsIMsgFolder>>& folders) {
+ 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("INBOX"_ns, kOnlineHierarchySeparatorUnknown,
+ 0, true);
+ }
+ }
+
+ // Force initialisation recursively.
+ for (nsIMsgFolder* f : mSubFolders) {
+ nsTArray<RefPtr<nsIMsgFolder>> dummy;
+ rv = f->GetSubFolders(dummy);
+ if (NS_FAILED(rv)) {
+ break;
+ }
+ }
+
+ UpdateSummaryTotals(false);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ return nsMsgDBFolder::GetSubFolders(folders);
+}
+
+// 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("@mozilla.org/msgDatabase/msgDBService;1", &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;
+ // 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("@mozilla.org/messenger/services/filters;1", &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;
+ nsTArray<RefPtr<nsIMsgSearchTerm>> searchTerms;
+ filter->GetSearchTerms(searchTerms);
+ for (nsIMsgSearchTerm* term : searchTerms) {
+ 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;
+ }
+ if (m_filterListRequiresBody) {
+ break;
+ }
+ }
+
+ // 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")));
+ }
+
+ bool isServer;
+ rv = GetIsServer(&isServer);
+ if (NS_SUCCEEDED(rv) && isServer) {
+ if (!m_haveDiscoveredAllFolders) {
+ bool hasSubFolders = false;
+ GetHasSubFolders(&hasSubFolders);
+ if (!hasSubFolders) {
+ rv = CreateClientSubfolderInfo(
+ "Inbox"_ns, kOnlineHierarchySeparatorUnknown, 0, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ m_haveDiscoveredAllFolders = true;
+ }
+ }
+
+ rv = GetDatabase();
+ if (NS_FAILED(rv)) {
+ ThrowAlertMsg("errorGettingDB", aMsgWindow);
+ return rv;
+ }
+
+ 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();
+ goOnline->Init(aMsgWindow, this, this, false);
+ if (goOnline) {
+ m_urlListener = aUrlListener;
+ return goOnline->ProcessNextOperation();
+ }
+ }
+ }
+
+ // Check it we're password protecting the local store.
+ if (!PromptForMasterPasswordIfNecessary()) return NS_ERROR_FAILURE;
+
+ bool canOpenThisFolder = true;
+ GetCanOpenFolder(&canOpenThisFolder);
+ // Don't run select if we can't select the folder...
+ if (!m_urlRunning && canOpenThisFolder && !isServer) {
+ nsCOMPtr<nsIImapService> imapService =
+ do_GetService("@mozilla.org/messenger/imapservice;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ /* Do a discovery in its own url if needed. Do before SELECT url. */
+ nsCOMPtr<nsIImapHostSessionList> hostSession =
+ do_GetService(kCImapHostSessionList, &rv);
+ if (NS_SUCCEEDED(rv) && hostSession) {
+ bool foundMailboxesAlready = false;
+ nsCString serverKey;
+ GetServerKey(serverKey);
+ hostSession->GetHaveWeEverDiscoveredFoldersForHost(serverKey.get(),
+ foundMailboxesAlready);
+ if (!foundMailboxesAlready) {
+ bool discoveryInProgress = false;
+ // See if discovery in progress and not yet finished.
+ hostSession->GetDiscoveryForHostInProgress(serverKey.get(),
+ discoveryInProgress);
+ if (!discoveryInProgress) {
+ nsCOMPtr<nsIMsgFolder> rootFolder;
+ rv = GetRootFolder(getter_AddRefs(rootFolder));
+ if (NS_SUCCEEDED(rv) && rootFolder) {
+ rv = imapService->DiscoverAllFolders(rootFolder, this, aMsgWindow);
+ if (NS_SUCCEEDED(rv))
+ hostSession->SetDiscoveryForHostInProgress(serverKey.get(), true);
+ }
+ }
+ }
+ }
+
+ 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 {
+ // 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::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("@mozilla.org/messenger/imapservice;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIURI> url;
+ return imapService->CreateFolder(this, folderName, this, getter_AddRefs(url));
+}
+
+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_ConvertUTF8toUTF16 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_ConvertUTF16toUTF8(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;
+ CopyUTF16toUTF8(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("@mozilla.org/msgDatabase/msgDBService;1", &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_ConvertUTF16toUTF8(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 = CopyFolderNameToUTF16(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_ConvertUTF8toUTF16(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) {
+ NotifyFolderAdded(child);
+ child->NotifyFolderEvent(kFolderCreateCompleted);
+ nsCOMPtr<nsIMsgFolderNotificationService> notifier(
+ do_GetService("@mozilla.org/messenger/msgnotificationservice;1"));
+ if (notifier) notifier->NotifyFolderAdded(child);
+ } else {
+ NotifyFolderEvent(kFolderCreateFailed);
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapMailFolder::List() {
+ nsresult rv;
+ nsCOMPtr<nsIImapService> imapService =
+ do_GetService("@mozilla.org/messenger/imapservice;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return imapService->ListFolder(this, this);
+}
+
+NS_IMETHODIMP nsImapMailFolder::RemoveLocalSelf() {
+ // Kill the local folder and its storage.
+ return nsMsgDBFolder::DeleteSelf(nullptr);
+}
+
+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("@mozilla.org/messenger/imapservice;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ imapService->EnsureFolderExists(msgParent, folderName, nullptr,
+ urlListener);
+ }
+ 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<nsIMsgEnumerator> hdrs;
+ rv = mDatabase->EnumerateMessages(getter_AddRefs(hdrs));
+ NS_ENSURE_SUCCESS(rv, rv);
+ bool hasMore = false;
+
+ PRTime cutOffDay =
+ MsgConvertAgeInDaysToCutoffDate(numDaysToKeepOfflineMsgs);
+
+ // 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<nsIMsgDBHdr> header;
+ rv = hdrs->GetNext(getter_AddRefs(header));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t msgFlags;
+ PRTime msgDate;
+ header->GetFlags(&msgFlags);
+ if (msgFlags & nsMsgMessageFlags::Offline) {
+ header->GetDate(&msgDate);
+ MarkPendingRemoval(header, 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).
+ */
+nsresult nsImapMailFolder::ExpungeAndCompact(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();
+
+ // Things to hold in existence until both expunge and compact are complete.
+ RefPtr<nsImapMailFolder> folder = this;
+ nsCOMPtr<nsIUrlListener> finalListener = aListener;
+ nsCOMPtr<nsIMsgWindow> msgWindow = aMsgWindow;
+
+ // doCompact implements OnStopRunningUrl()
+ // NOTE: The caller will be expecting that their listener will be invoked, so
+ // we need to be careful that all execution paths in here do that. We either
+ // call it directly, or pass it along to the foldercompactor to call.
+ auto doCompact = [folder, finalListener, msgWindow](
+ nsIURI* url, nsresult status) -> nsresult {
+ nsCOMPtr<nsIMsgPluggableStore> msgStore;
+ nsresult rv = folder->GetMsgStore(getter_AddRefs(msgStore));
+ if (NS_FAILED(rv)) {
+ if (finalListener) {
+ return finalListener->OnStopRunningUrl(nullptr, rv);
+ }
+ return rv;
+ }
+ bool storeSupportsCompaction;
+ msgStore->GetSupportsCompaction(&storeSupportsCompaction);
+ if (storeSupportsCompaction && folder->mFlags & nsMsgFolderFlags::Offline) {
+ nsCOMPtr<nsIMsgFolderCompactor> folderCompactor =
+ do_CreateInstance("@mozilla.org/messenger/foldercompactor;1", &rv);
+ if (NS_FAILED(rv)) {
+ if (finalListener) {
+ return finalListener->OnStopRunningUrl(nullptr, rv);
+ }
+ return rv;
+ }
+ return folderCompactor->CompactFolders({folder}, finalListener,
+ msgWindow);
+ }
+ // Not going to run a compaction, so signal that we're all done.
+ if (finalListener) {
+ return finalListener->OnStopRunningUrl(nullptr, NS_OK);
+ }
+ return NS_OK;
+ };
+
+ if (WeAreOffline()) {
+ // Can't run an expunge. Kick off the next stage (compact) immediately.
+ return doCompact(nullptr, NS_OK);
+ }
+
+ // Run the expunge, followed by the compaction.
+ RefPtr<UrlListener> expungeListener = new UrlListener();
+ expungeListener->mStopFn = doCompact;
+ return Expunge(expungeListener, aMsgWindow);
+}
+
+// IMAP compact implies an Expunge.
+NS_IMETHODIMP nsImapMailFolder::Compact(nsIUrlListener* aListener,
+ nsIMsgWindow* aMsgWindow) {
+ return ExpungeAndCompact(aListener, aMsgWindow);
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::NotifyCompactCompleted() { 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"_ns : ""_ns);
+ 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("@mozilla.org/messenger/imapservice;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return imapService->Expunge(this, aListener, aMsgWindow);
+}
+
+NS_IMETHODIMP nsImapMailFolder::CompactAll(nsIUrlListener* aListener,
+ nsIMsgWindow* aMsgWindow) {
+ nsresult rv;
+
+ nsCOMPtr<nsIMsgFolderCompactor> folderCompactor =
+ do_CreateInstance("@mozilla.org/messenger/foldercompactor;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgFolder> rootFolder;
+ rv = GetRootFolder(getter_AddRefs(rootFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgWindow> msgWindow = aMsgWindow;
+
+ // Set up a callable which will start the compaction phase.
+ auto doCompact = [folderCompactor, rootFolder,
+ listener = nsCOMPtr<nsIUrlListener>(aListener),
+ msgWindow]() {
+ // Collect all the compactable folders.
+ nsTArray<RefPtr<nsIMsgFolder>> foldersToCompact;
+ nsTArray<RefPtr<nsIMsgFolder>> allDescendants;
+ rootFolder->GetDescendants(allDescendants);
+ for (auto folder : allDescendants) {
+ uint32_t flags;
+ folder->GetFlags(&flags);
+ if (flags &
+ (nsMsgFolderFlags::Virtual | nsMsgFolderFlags::ImapNoselect)) {
+ continue;
+ }
+ // Folder can be compacted?
+ nsCOMPtr<nsIMsgPluggableStore> msgStore;
+ folder->GetMsgStore(getter_AddRefs(msgStore));
+ if (!msgStore) {
+ continue;
+ }
+ bool storeSupportsCompaction;
+ msgStore->GetSupportsCompaction(&storeSupportsCompaction);
+ if (storeSupportsCompaction) {
+ foldersToCompact.AppendElement(folder);
+ }
+ }
+ nsresult rv =
+ folderCompactor->CompactFolders(foldersToCompact, listener, msgWindow);
+ if (NS_FAILED(rv) && listener) {
+ // Make sure the listener hears about the failure.
+ listener->OnStopRunningUrl(nullptr, rv);
+ }
+ };
+
+ // Collect all the expungeable folders.
+ nsTArray<RefPtr<nsIMsgImapMailFolder>> foldersToExpunge;
+ nsTArray<RefPtr<nsIMsgFolder>> allDescendants;
+ rootFolder->GetDescendants(allDescendants);
+ for (auto folder : allDescendants) {
+ nsCOMPtr<nsIMsgImapMailFolder> imapFolder(do_QueryInterface(folder));
+ if (!imapFolder) {
+ continue;
+ }
+ uint32_t folderFlags;
+ folder->GetFlags(&folderFlags);
+ if (!(folderFlags &
+ (nsMsgFolderFlags::Virtual | nsMsgFolderFlags::ImapNoselect))) {
+ foldersToExpunge.AppendElement(imapFolder);
+ }
+ }
+
+ if (!WeAreOffline() && !foldersToExpunge.IsEmpty()) {
+ // Kick off expunge on all the folders (the IMAP protocol will handle
+ // queuing them up as needed).
+
+ // A listener to track the completed expunges.
+ RefPtr<UrlListener> l = new UrlListener();
+ l->mStopFn = [expungeCount = foldersToExpunge.Length(), doCompact](
+ nsIURI* url, nsresult status) mutable -> nsresult {
+ // NOTE: we're ignoring expunge result code - nothing much we can do
+ // here to recover, so just plough on.
+ --expungeCount;
+ if (expungeCount == 0) {
+ // All the expunges are done so start compacting.
+ doCompact();
+ }
+ return NS_OK;
+ };
+ // Go!
+ for (auto& imapFolder : foldersToExpunge) {
+ rv = imapFolder->Expunge(l, aMsgWindow);
+ if (NS_FAILED(rv)) {
+ // Make sure expungeCount is kept in sync!
+ l->OnStopRunningUrl(nullptr, rv);
+ }
+ }
+ } else {
+ // No expunging. Start the compaction immediately.
+ doCompact();
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailFolder::UpdateStatus(nsIUrlListener* aListener,
+ nsIMsgWindow* aMsgWindow) {
+ nsresult rv;
+ nsCOMPtr<nsIImapService> imapService =
+ do_GetService("@mozilla.org/messenger/imapservice;1", &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(nsIUrlListener* aListener) {
+ nsCOMPtr<nsIMsgFolder> trashFolder;
+ nsresult rv = GetTrashFolder(getter_AddRefs(trashFolder));
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIMsgAccountManager> accountManager =
+ do_GetService("@mozilla.org/messenger/account-manager;1", &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) {
+ nsCOMPtr<nsIMsgOfflineOpsDatabase> opsDb =
+ do_QueryInterface(trashDB, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsMsgKey fakeKey;
+ opsDb->GetNextFakeOfflineMsgKey(&fakeKey);
+
+ nsCOMPtr<nsIMsgOfflineImapOperation> op;
+ rv = opsDb->GetOfflineOpForKey(fakeKey, true, getter_AddRefs(op));
+ trashFolder->SetFlag(nsMsgFolderFlags::OfflineEvents);
+ op->SetOperation(nsIMsgOfflineImapOperation::kDeleteAllMsgs);
+ }
+ return rv;
+ }
+
+ nsCOMPtr<nsIImapService> imapService =
+ do_GetService("@mozilla.org/messenger/imapservice;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (aListener)
+ rv = imapService->DeleteAllMessages(trashFolder, aListener);
+ else {
+ nsCOMPtr<nsIUrlListener> urlListener = do_QueryInterface(trashFolder);
+ rv = imapService->DeleteAllMessages(trashFolder, urlListener);
+ }
+ // 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);
+
+ // Delete any subfolders under Trash.
+ nsTArray<RefPtr<nsIMsgFolder>> subFolders;
+ rv = trashFolder->GetSubFolders(subFolders);
+ NS_ENSURE_SUCCESS(rv, rv);
+ while (!subFolders.IsEmpty()) {
+ RefPtr<nsIMsgFolder> f = subFolders.PopLastElement();
+ rv = trashFolder->PropagateDelete(f, true);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ 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);
+ if (transferInfo) trashFolder->SetDBTransferInfo(transferInfo);
+ trashFolder->SetSizeOnDisk(0);
+
+ // The trash folder has effectively been deleted.
+ nsCOMPtr<nsIMsgFolderNotificationService> notifier(
+ do_GetService("@mozilla.org/messenger/msgnotificationservice;1"));
+ 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("@mozilla.org/messenger/imapservice;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return imapService->RenameLeaf(this, newName, this, msgWindow);
+}
+
+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) {
+ 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));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ 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);
+ NS_ENSURE_TRUE(incomingServer, NS_ERROR_NO_INTERFACE);
+ 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(
+ const nsTArray<RefPtr<nsIMsgDBHdr>>& 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::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->GetCachedUInt32("boxFlags", (uint32_t*)&m_boxFlags);
+ if (NS_SUCCEEDED(element->GetCachedInt32("hierDelim", &hierarchyDelimiter)) &&
+ hierarchyDelimiter != kOnlineHierarchySeparatorUnknown)
+ m_hierarchyDelimiter = (char)hierarchyDelimiter;
+ rv = element->GetCachedString("onlineName", onlineName);
+ if (NS_SUCCEEDED(rv) && !onlineName.IsEmpty())
+ m_onlineFolderName.Assign(onlineName);
+
+ m_aclFlags = kAclInvalid; // init to invalid value.
+ element->GetCachedUInt32("aclFlags", &m_aclFlags);
+ element->GetCachedInt32("serverTotal", &m_numServerTotalMessages);
+ element->GetCachedInt32("serverUnseen", &m_numServerUnseenMessages);
+ element->GetCachedInt32("serverRecent", &m_numServerRecentMessages);
+ element->GetCachedInt32("nextUID", &m_nextUID);
+ int32_t lastSyncTimeInSec;
+ if (NS_FAILED(element->GetCachedInt32("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->SetCachedUInt32("boxFlags", (uint32_t)m_boxFlags);
+ element->SetCachedInt32("hierDelim", (int32_t)m_hierarchyDelimiter);
+ element->SetCachedString("onlineName", m_onlineFolderName);
+ element->SetCachedUInt32("aclFlags", m_aclFlags);
+ element->SetCachedInt32("serverTotal", m_numServerTotalMessages);
+ element->SetCachedInt32("serverUnseen", m_numServerUnseenMessages);
+ element->SetCachedInt32("serverRecent", m_numServerRecentMessages);
+ if (m_nextUID != (int32_t)nsMsgKey_None)
+ element->SetCachedInt32("nextUID", m_nextUID);
+
+ // store folder's last sync time
+ if (m_autoSyncStateObj) {
+ PRTime lastSyncTime;
+ m_autoSyncStateObj->GetLastSyncTime(&lastSyncTime);
+ // store in sec
+ element->SetCachedInt32("lastSyncTimeInSec",
+ (int32_t)(lastSyncTime / PR_USEC_PER_SEC));
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::MarkMessagesFlagged(
+ const nsTArray<RefPtr<nsIMsgDBHdr>>& 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;
+ CopyUTF8toUTF16(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));
+ // Note: check for unknown separator '^' only became needed
+ // with UTF8=ACCEPT modification and haven't found why. Online name
+ // contained the '^' delimiter and gmail said "NO" when folder under
+ // [Gmail] is created and selected.
+ if ((m_hierarchyDelimiter != '/') &&
+ (m_hierarchyDelimiter != kOnlineHierarchySeparatorUnknown))
+ onlineCName.ReplaceChar('/', m_hierarchyDelimiter);
+ // XXX: What if online name contains slashes? Breaks?
+ m_onlineFolderName.Assign(onlineCName);
+ CopyUTF8toUTF16(onlineCName, autoOnlineName);
+ }
+ (*folderInfo)->SetProperty("onlineName", autoOnlineName);
+ }
+ return rv;
+}
+
+/* static */
+nsresult nsImapMailFolder::BuildIdsAndKeyArray(
+ const nsTArray<RefPtr<nsIMsgDBHdr>>& messages, nsCString& msgIds,
+ nsTArray<nsMsgKey>& keyArray) {
+ keyArray.Clear();
+ keyArray.SetCapacity(messages.Length());
+ // build up message keys.
+ for (auto msgDBHdr : messages) {
+ nsMsgKey key;
+ nsresult 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.Clone());
+ 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(
+ nsTArray<RefPtr<nsIMsgDBHdr>> const& msgHeaders, nsIMsgWindow* msgWindow,
+ bool deleteStorage, bool isMove, nsIMsgCopyServiceListener* listener,
+ bool allowUndo) {
+ // *** 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(msgHeaders, 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) {
+ deleteMsgs = false;
+ for (nsIMsgDBHdr* msgHdr : msgHeaders) {
+ if (!msgHdr) {
+ continue;
+ }
+ uint32_t flags;
+ 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(
+ "@mozilla.org/messenger/msgnotificationservice;1"));
+ 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("@mozilla.org/messenger/messagecopyservice;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = copyService->CopyMessages(srcFolder, msgHeaders, 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::DeleteSelf(nsIMsgWindow* msgWindow) {
+ nsCOMPtr<nsIMsgFolder> trashFolder;
+ nsresult rv;
+ uint32_t folderFlags;
+
+ // No IMAP shenanigans required for virtual folders.
+ GetFlags(&folderFlags);
+ if (folderFlags & nsMsgFolderFlags::Virtual) {
+ return nsMsgDBFolder::DeleteSelf(nullptr);
+ }
+
+ // "this" is the folder we're deleting from
+ bool deleteNoTrash = TrashOrDescendentOfTrash(this) || !DeleteIsMoveToTrash();
+ bool confirmDeletion = true;
+
+ nsCOMPtr<nsIImapService> imapService =
+ do_GetService("@mozilla.org/messenger/imapservice;1", &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 we are deleting folder immediately, ask user for confirmation.
+ bool confirmed = false;
+ if (confirmDeletion || deleteNoTrash) {
+ nsCOMPtr<nsIStringBundle> bundle;
+ rv = IMAPGetStringBundle(getter_AddRefs(bundle));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString folderName;
+ rv = 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) {
+ if (deleteNoTrash) {
+ rv = imapService->DeleteFolder(this, this, msgWindow);
+ nsMsgDBFolder::DeleteSelf(msgWindow);
+ } else {
+ bool match = false;
+ rv = MatchOrChangeFilterDestination(nullptr, false, &match);
+ if (match) {
+ bool confirm = false;
+ ConfirmFolderDeletionForFilter(msgWindow, &confirm);
+ if (!confirm) return NS_OK;
+ }
+ rv = imapService->MoveFolder(this, trashFolder, this, msgWindow);
+ }
+ }
+ return 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<nsIMsgEnumerator> enumerator;
+ nsresult rv = mDatabase->EnumerateMessages(getter_AddRefs(enumerator));
+ if (NS_SUCCEEDED(rv) && enumerator) {
+ bool hasMore;
+ nsCOMPtr<nsIMsgDBHdr> header;
+ nsMsgKey msgKey;
+ while (NS_SUCCEEDED(rv = enumerator->HasMoreElements(&hasMore)) &&
+ hasMore) {
+ rv = enumerator->GetNext(getter_AddRefs(header));
+ NS_ENSURE_SUCCESS(rv, rv);
+ bool shouldStoreMsgOffline = false;
+ header->GetMessageKey(&msgKey);
+ // MsgFitsDownloadCriteria ignores nsMsgFolderFlags::Offline, which we
+ // want
+ if (m_downloadingFolderForOfflineUse)
+ MsgFitsDownloadCriteria(msgKey, &shouldStoreMsgOffline);
+ else
+ ShouldStoreMsgOffline(msgKey, &shouldStoreMsgOffline);
+ if (shouldStoreMsgOffline)
+ keysOfMessagesToDownload->AppendElement(msgKey);
+ }
+ if (MOZ_LOG_TEST(gAutoSyncLog, mozilla::LogLevel::Debug) && header) {
+ // Log this only if folder is not empty.
+ uint32_t msgFlags = 0;
+ header->GetFlags(&msgFlags);
+ MOZ_LOG(gAutoSyncLog, mozilla::LogLevel::Debug,
+ ("%s: num keys to download=%zu, last key=%d, last msg flag=0x%x "
+ "nsMsgMessageFlags::Offline=0x%x",
+ __func__, keysOfMessagesToDownload->Length(), msgKey, msgFlags,
+ nsMsgMessageFlags::Offline));
+ }
+ }
+ 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.
+
+ 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));
+ }
+ nsTArray<nsMsgKey> keys;
+ rv = mDatabase->ListAllKeys(keys);
+ NS_ENSURE_SUCCESS(rv, rv);
+ existingKeys.AppendElements(keys);
+ nsCOMPtr<nsIMsgOfflineOpsDatabase> opsDb =
+ do_QueryInterface(mDatabase, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ opsDb->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("@mozilla.org/msgDatabase/msgDBService;1", &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.Clone();
+ 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;
+ MsgGetHeadersFromKeys(mDatabase, keysToDelete, hdrsToDelete);
+ // Notify nsIMsgFolderListeners of a mass delete, but only if we actually
+ // have headers
+ if (!hdrsToDelete.IsEmpty()) {
+ nsCOMPtr<nsIMsgFolderNotificationService> notifier(
+ do_GetService("@mozilla.org/messenger/msgnotificationservice;1"));
+ 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 (i.e., occurs when CONDSTORE in effect), we can
+ // only trust the numUnread from the server. However, even that will only be
+ // correct if a recent imap STATUS occurred as indicated by
+ // numUnreadFromServer greater than -1.
+ 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 &&
+ static_cast<int32_t>(numNewUnread) != -1) {
+ // 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 && numUnreadFromServer > -1 &&
+ (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);
+ if (numUnreadFromServer > -1) m_numServerUnseenMessages = numUnreadFromServer;
+ 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;
+}
+
+/**
+ * Called after successful imap STATUS response occurs. Have valid unseen value
+ * if folderstatus URL produced an imap STATUS. If a NOOP occurs instead (doing
+ * folderstatus from a connection SELECTed on the same folder) there is no
+ * UNSEEN returned by NOOP.
+ */
+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 == -1) {
+ // A noop occurred so don't know server's UNSEEN number, keep using the
+ // previously known unread count.
+ MOZ_LOG(IMAP, mozilla::LogLevel::Debug,
+ ("%s: folder=%s, unread was -1, set numUnread to previousUnread=%d",
+ __func__, m_onlineFolderName.get(), previousUnreadMessages));
+ numUnread = previousUnreadMessages;
+ }
+ 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;
+ nsCString 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.get(), msgHdrs.Length());
+ 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.get(), 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("@mozilla.org/messenger/msgnotificationservice;1"));
+ // 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;
+ newMsgHdr->GetMessageId(getter_Copies(newMessageId));
+ nsMsgKey pseudoKey =
+ m_pseudoHdrs.MaybeGet(newMessageId).valueOr(nsMsgKey_None);
+ 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, "X-GM-MSGID"_ns, msgIDValue);
+ flagState->GetCustomAttribute(m_curMsgUid, "X-GM-THRID"_ns, threadIDValue);
+ flagState->GetCustomAttribute(m_curMsgUid, "X-GM-LABELS"_ns, labelsValue);
+ newMsgHdr->SetStringProperty("X-GM-MSGID", msgIDValue);
+ newMsgHdr->SetStringProperty("X-GM-THRID", threadIDValue);
+ newMsgHdr->SetStringProperty("X-GM-LABELS", labelsValue);
+ }
+
+ 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() {
+ 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;
+ }
+
+ rv = NS_OpenAnonymousTemporaryNsIFile(getter_AddRefs(m_copyState->m_tmpFile));
+ if (NS_FAILED(rv)) {
+ MOZ_LOG(IMAP, mozilla::LogLevel::Info,
+ ("Couldn't create temp file: %" PRIx32, static_cast<uint32_t>(rv)));
+ OnCopyCompleted(m_copyState->m_srcSupport, rv);
+ }
+ NS_ENSURE_SUCCESS(rv, 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("@mozilla.org/messenger/imapservice;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv =
+ QueryInterface(NS_GET_IID(nsIUrlListener), getter_AddRefs(urlListener));
+ rv = imapService->AppendMessageFromFile(
+ m_copyState->m_tmpFile, this, EmptyCString(), true,
+ m_copyState->m_selectedState, urlListener, m_copyState,
+ 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,
+ "filterFailureMoveFailed"_ns);
+ }
+ }
+ }
+ // 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<nsIMsgFolder> dstFolder;
+ rv = GetExistingFolder(actionTargetFolderUri,
+ getter_AddRefs(dstFolder));
+ if (NS_FAILED(rv)) break;
+
+ nsCOMPtr<nsIMsgCopyService> copyService = do_GetService(
+ "@mozilla.org/messenger/messagecopyservice;1", &rv);
+ if (NS_FAILED(rv)) break;
+ rv = copyService->CopyMessages(this, {&*msgHdr}, dstFolder, false,
+ nullptr, msgWindow, false);
+ if (NS_FAILED(rv)) {
+ if (loggingEnabled) {
+ (void)filter->LogRuleHitFail(filterAction, msgHdr, rv,
+ "filterFailureCopyFailed"_ns);
+ }
+ }
+ }
+ } 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::AddTag: {
+ nsCString keyword;
+ filterAction->GetStrValue(keyword);
+ rv = AddKeywordsToMessages({&*msgHdr}, keyword);
+ } break;
+ case nsMsgFilterAction::JunkScore: {
+ nsAutoCString junkScoreStr;
+ int32_t junkScore;
+ filterAction->GetJunkScore(&junkScore);
+ junkScoreStr.AppendInt(junkScore);
+ rv = mDatabase->SetStringProperty(msgKey, "junkscore", junkScoreStr);
+ mDatabase->SetStringProperty(msgKey, "junkscoreorigin", "filter"_ns);
+
+ // 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("@mozilla.org/messengercompose;1", &rv);
+ if (NS_FAILED(rv)) break;
+ rv = compService->ForwardMessage(
+ NS_ConvertUTF8toUTF16(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("@mozilla.org/messengercompose;1", &rv);
+ if (NS_SUCCEEDED(rv) && compService) {
+ rv = compService->ReplyWithTemplate(msgHdr, replyTemplateUri,
+ msgWindow, server);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("ReplyWithTemplate failed");
+ if (rv == NS_ERROR_ABORT) {
+ (void)filter->LogRuleHitFail(
+ filterAction, msgHdr, rv,
+ "filterFailureSendingReplyAborted"_ns);
+ } else {
+ (void)filter->LogRuleHitFail(
+ filterAction, msgHdr, rv,
+ "filterFailureSendingReplyError"_ns);
+ }
+ }
+ }
+ }
+ } 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;
+
+ rv = customAction->ApplyAction({&*msgHdr}, 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,
+ "filterFailureAction"_ns);
+ }
+ } 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("@mozilla.org/messenger/imapservice;1", &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("@mozilla.org/messenger/imapservice;1", &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,
+ bool srcFolderOffline) {
+ nsresult rv;
+
+ nsCOMPtr<nsIMsgImapMailFolder> imapFolder = do_QueryInterface(aDstFolder);
+ if (imapFolder) {
+ nsImapMailFolder* destImapFolder =
+ static_cast<nsImapMailFolder*>(aDstFolder);
+ nsCOMPtr<nsIMsgDatabase> dstFolderDB;
+ aDstFolder->GetMsgDatabase(getter_AddRefs(dstFolderDB));
+ nsCOMPtr<nsIMsgOfflineOpsDatabase> opsDb =
+ do_QueryInterface(dstFolderDB, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (opsDb) {
+ // 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(opsDb->ListAllOfflineOpIds(offlineOps))) {
+ nsTArray<RefPtr<nsIMsgDBHdr>> messages;
+ nsCString srcFolderUri;
+ GetURI(srcFolderUri);
+ nsCOMPtr<nsIMsgOfflineImapOperation> currentOp;
+ for (uint32_t opIndex = 0; opIndex < offlineOps.Length(); opIndex++) {
+ opsDb->GetOfflineOpForKey(offlineOps[opIndex], false,
+ getter_AddRefs(currentOp));
+ if (currentOp) {
+ nsCString opSrcUri;
+ currentOp->GetSourceFolderURI(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: Sets offline flag.
+ destImapFolder->SetPendingAttributes(messages, isMove,
+ srcFolderOffline);
+ }
+ }
+ // if we can't get the dst folder db, we should still try to playback
+ // the offline move/copy.
+ }
+
+ nsCOMPtr<nsIImapService> imapService =
+ do_GetService("@mozilla.org/messenger/imapservice;1", &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.InsertOrUpdate(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("@mozilla.org/messenger/imapservice;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsAutoCString msgIds;
+ AllocateUidStringFromKeys(keys, msgIds);
+ if (addFlags)
+ imapService->AddMessageFlags(this, aUrlListener ? aUrlListener : this,
+ msgIds, flags, true);
+ else
+ imapService->SubtractMessageFlags(
+ this, aUrlListener ? aUrlListener : this, msgIds, flags, true);
+ } else {
+ rv = GetDatabase();
+ if (NS_SUCCEEDED(rv) && mDatabase) {
+ nsCOMPtr<nsIMsgOfflineOpsDatabase> opsDb =
+ do_QueryInterface(mDatabase, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ for (auto key : keys) {
+ nsCOMPtr<nsIMsgOfflineImapOperation> op;
+ rv = opsDb->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);
+ }
+ }
+ opsDb->Commit(nsMsgDBCommitType::kLargeCommit); // flush offline flags
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapMailFolder::LiteSelect(nsIUrlListener* aUrlListener,
+ nsIMsgWindow* aMsgWindow) {
+ nsresult rv;
+ nsCOMPtr<nsIImapService> imapService =
+ do_GetService("@mozilla.org/messenger/imapservice;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIURI> outUri;
+ return imapService->LiteSelectFolder(this, aUrlListener, aMsgWindow,
+ getter_AddRefs(outUri));
+}
+
+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, nullptr, nullptr, false,
+ false);
+ }
+ }
+ } else {
+ nsCOMPtr<nsIImapService> imapService =
+ do_GetService("@mozilla.org/messenger/imapservice;1", &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("@mozilla.org/messenger/imapservice;1", &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("@mozilla.org/messenger/imapservice;1", &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<nsIMsgEnumerator> hdrs;
+ nsresult rv = GetMessages(getter_AddRefs(hdrs));
+ NS_ENSURE_SUCCESS_VOID(rv);
+ bool hasMore = false;
+ while (NS_SUCCEEDED(rv = hdrs->HasMoreElements(&hasMore)) && hasMore) {
+ nsCOMPtr<nsIMsgDBHdr> header;
+ rv = hdrs->GetNext(getter_AddRefs(header));
+ NS_ENSURE_SUCCESS_VOID(rv);
+ uint32_t msgFlags;
+ header->GetFlags(&msgFlags);
+ if (msgFlags & nsMsgMessageFlags::IMAPDeleted) {
+ nsMsgKey msgKey;
+ header->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("@mozilla.org/messenger/services/session;1");
+ 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;
+
+ nsCOMPtr<nsIImapFlagAndUidState> flagState;
+ nsresult rv = aProtocol->GetFlagAndUidState(getter_AddRefs(flagState));
+ NS_ENSURE_SUCCESS_VOID(rv);
+ rv = flagState->HasMessage(m_curMsgUid, &foundIt);
+
+ if (NS_SUCCEEDED(rv) && foundIt) {
+ imapMessageFlagsType imap_flags;
+ nsCString customFlags;
+ flagState->GetMessageFlagsByUid(m_curMsgUid, &imap_flags);
+ if (imap_flags & kImapMsgCustomKeywordFlag) {
+ flagState->GetCustomFlags(m_curMsgUid, getter_Copies(customFlags));
+ }
+
+ // 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;
+ 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);
+ m_tempMessageStreamBytesWritten = 0;
+ 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;
+
+ rv = m_tempMessageStream->Write(result.get(), result.Length(), &writeCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+ m_tempMessageStreamBytesWritten += writeCount;
+
+ result = "X-Mozilla-Status: 0001";
+ result += MSG_LINEBREAK;
+ rv = m_tempMessageStream->Write(result.get(), result.Length(), &writeCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+ m_tempMessageStreamBytesWritten += writeCount;
+
+ result = "X-Mozilla-Status2: 00000000";
+ result += MSG_LINEBREAK;
+ rv = m_tempMessageStream->Write(result.get(), result.Length(), &writeCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+ m_tempMessageStreamBytesWritten += writeCount;
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapMailFolder::DownloadMessagesForOffline(
+ nsTArray<RefPtr<nsIMsgDBHdr>> const& 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("@mozilla.org/messenger/imapservice;1", &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("@mozilla.org/messenger/imapservice;1", &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) {
+ rv = m_tempMessageStream->Write(adoptedMessageLine,
+ PL_strlen(adoptedMessageLine), &count);
+ NS_ENSURE_SUCCESS(rv, rv);
+ m_tempMessageStreamBytesWritten += count;
+ }
+ 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(NS_OK);
+
+ 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("@mozilla.org/messenger/imapservice;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return imapService->AddMessageFlags(this, 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;
+ // ### TODO: we really should parse the keywords into space delimited keywords
+ // before checking
+ // Mac Mail, Yahoo uses "NotJunk"
+ if (FindInReadable("NonJunk"_ns, keywords,
+ nsCaseInsensitiveCStringComparator) ||
+ FindInReadable("NotJunk"_ns, keywords,
+ nsCaseInsensitiveCStringComparator)) {
+ nsAutoCString msgJunkScore;
+ msgJunkScore.AppendInt(nsIJunkMailPlugin::IS_HAM_SCORE);
+ mDatabase->SetStringProperty(uidOfMessage, "junkscore", msgJunkScore);
+ } else if (FindInReadable("Junk"_ns, keywords,
+ nsCaseInsensitiveCStringComparator)) {
+ uint32_t newFlags;
+ dbHdr->AndFlags(~nsMsgMessageFlags::New, &newFlags);
+ nsAutoCString msgJunkScore;
+ msgJunkScore.AppendInt(nsIJunkMailPlugin::IS_SPAM_SCORE);
+ mDatabase->SetStringProperty(uidOfMessage, "junkscore", msgJunkScore);
+ } else
+ messageClassified = false;
+ if (messageClassified) {
+ // only set the junkscore origin if it wasn't set before.
+ nsCString existingProperty;
+ dbHdr->GetStringProperty("junkscoreorigin", existingProperty);
+ if (existingProperty.IsEmpty())
+ dbHdr->SetStringProperty("junkscoreorigin", "imapflag"_ns);
+ }
+
+ if (!(userFlags & kImapMsgSupportUserFlag)) {
+ nsCString localKeywords;
+ nsCString prevKeywords;
+ dbHdr->GetStringProperty("keywords", localKeywords);
+ dbHdr->GetStringProperty("prevkeywords", 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);
+
+ // 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);
+ }
+ return (userFlags & kImapMsgSupportUserFlag)
+ ? dbHdr->SetStringProperty("keywords", keywords)
+ : 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);
+ 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;
+
+ nsCOMPtr<nsIMsgDBHdr> dbHdr;
+ rv = mDatabase->GetMsgHdrForKey(uidOfMessage, getter_AddRefs(dbHdr));
+ if (NS_FAILED(rv)) continue;
+ 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);
+ 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::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_curIndex < mailCopyState->m_messages.Length()) {
+ nsIMsgDBHdr* message =
+ mailCopyState->m_messages[mailCopyState->m_curIndex];
+ message->GetFlags(aResult);
+ if (aDate) message->GetDate(aDate);
+ if (supportedFlags & kImapMsgSupportUserFlag) {
+ // setup the custom imap keywords, which includes the message keywords
+ // plus any junk status
+ nsCString junkscore;
+ message->GetStringProperty("junkscore", 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
+ message->GetStringProperty("keywords", keywords);
+ int32_t start;
+ int32_t length;
+ bool hasJunk = MsgFindKeyword("junk"_ns, keywords, &start, &length);
+ if (hasJunk && !isJunk)
+ keywords.Cut(start, length);
+ else if (!hasJunk && isJunk)
+ keywords.AppendLiteral(" Junk");
+ bool hasNonJunk =
+ MsgFindKeyword("nonjunk"_ns, keywords, &start, &length);
+ if (!hasNonJunk)
+ hasNonJunk = MsgFindKeyword("notjunk"_ns, 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)) >= 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;
+}
+
+// nsIUrlListener implementation.
+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;
+}
+
+// nsIUrlListener implementation.
+// nsImapMailFolder passes itself as a listener when it kicks off operations
+// on the nsIImapService. So, when the operation completes, this gets called
+// to handle all the different operations, using a big switch statement.
+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("@mozilla.org/messenger/services/session;1", &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("@mozilla.org/messenger/msgnotificationservice;1"));
+ if (notifier && m_copyState) {
+ if (imapAction == nsIImapUrl::nsImapOnlineMove) {
+ notifier->NotifyMsgsMoveCopyCompleted(true, m_copyState->m_messages,
+ this, {});
+ } else if (imapAction == nsIImapUrl::nsImapOnlineCopy) {
+ notifier->NotifyMsgsMoveCopyCompleted(false, m_copyState->m_messages,
+ this, {});
+ } else if (imapAction == nsIImapUrl::nsImapDeleteMsg) {
+ notifier->NotifyMsgsDeleted(m_copyState->m_messages);
+ }
+ }
+
+ 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");
+ }
+ }
+ // nsImapUrl can hold a pointer to our m_copyState, so force a
+ // release here (see Bug 1586494).
+ imapUrl->SetCopyState(nullptr);
+ (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) {
+ MsgGetHeadersFromKeys(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_messages.Length()) {
+ nsCOMPtr<nsIUrlListener> saveUrlListener = m_urlListener;
+ if (folderOpen) {
+ // This gives a way for the caller to get notified
+ // when the UpdateFolder url is done.
+ // (if the nsIMsgCopyServiceListener also implements
+ // nsIUrlListener)
+ 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(
+ "@mozilla.org/messenger/messagecopyservice;1", &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) this->RemoveLocalSelf();
+ }
+ }
+ }
+ 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) {
+ nsCOMPtr<nsIObserverService> obsServ =
+ mozilla::services::GetObserverService();
+ obsServ->NotifyObservers(msgFolder, "folder-subscribed",
+ nullptr);
+ }
+ }
+ }
+ }
+ }
+ break;
+ case nsIImapUrl::nsImapExpungeFolder:
+ 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);
+
+ // If we're planning to inform another listener then do that now.
+ // Some folder methods can take a listener to inform when the operation
+ // is complete e.g. UpdateFolderWithListener(), DownloadAllForOffline(),
+ // GetNewMessages(). That listener is stashed in m_urlListener.
+ if (m_urlListener) {
+ nsCOMPtr<nsIUrlListener> saveListener = m_urlListener;
+ m_urlListener = nullptr;
+ saveListener->OnStopRunningUrl(aUrl, aExitCode);
+ }
+ return rv;
+}
+
+void nsImapMailFolder::UpdatePendingCounts() {
+ if (m_copyState) {
+ int32_t delta =
+ m_copyState->m_isCrossServerOp ? 1 : m_copyState->m_messages.Length();
+ if (!m_copyState->m_selectedState && m_copyState->m_messages.IsEmpty()) {
+ // special case from CopyFileMessage():
+ // - copied a single message in from a file
+ // - no previously-existing messages are involved
+ delta = 1;
+ }
+ ChangePendingTotal(delta);
+
+ // 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
+nsImapMailFolder::StartMessage(nsIMsgMailNewsUrl* aUrl) {
+ nsCOMPtr<nsIImapUrl> imapUrl(do_QueryInterface(aUrl));
+ nsCOMPtr<nsISupports> copyState;
+ NS_ENSURE_TRUE(imapUrl, NS_ERROR_FAILURE);
+
+ imapUrl->GetCopyState(getter_AddRefs(copyState));
+ if (copyState) {
+ nsCOMPtr<nsICopyMessageStreamListener> listener =
+ do_QueryInterface(copyState);
+ if (listener) listener->StartMessage();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::EndMessage(nsIMsgMailNewsUrl* aUrl, nsMsgKey uidOfMessage) {
+ nsCOMPtr<nsIImapUrl> imapUrl(do_QueryInterface(aUrl));
+ nsCOMPtr<nsISupports> copyState;
+ NS_ENSURE_TRUE(imapUrl, NS_ERROR_FAILURE);
+ imapUrl->GetCopyState(getter_AddRefs(copyState));
+ if (copyState) {
+ nsCOMPtr<nsICopyMessageStreamListener> listener =
+ do_QueryInterface(copyState);
+ if (listener) listener->EndMessage(uidOfMessage);
+ }
+ return NS_OK;
+}
+
+#define WHITESPACE " \015\012" // token delimiter
+
+NS_IMETHODIMP
+nsImapMailFolder::NotifySearchHit(nsIMsgMailNewsUrl* aUrl,
+ const char* searchHitLine) {
+ NS_ENSURE_ARG_POINTER(aUrl);
+ nsresult rv = GetDatabase();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // expect search results in the form of "* SEARCH <hit> <hit> ..."
+ // expect search results in the form of "* SEARCH <hit> <hit> ..."
+ nsCString tokenString(searchHitLine);
+ char* currentPosition = PL_strcasestr(tokenString.get(), "SEARCH");
+ if (currentPosition) {
+ currentPosition += strlen("SEARCH");
+ bool shownUpdateAlert = false;
+ char* hitUidToken = NS_strtok(WHITESPACE, &currentPosition);
+ while (hitUidToken) {
+ long naturalLong; // %l is 64 bits on OSF1
+ sscanf(hitUidToken, "%ld", &naturalLong);
+ nsMsgKey hitUid = (nsMsgKey)naturalLong;
+
+ nsCOMPtr<nsIMsgDBHdr> hitHeader;
+ rv = mDatabase->GetMsgHdrForKey(hitUid, getter_AddRefs(hitHeader));
+ if (NS_SUCCEEDED(rv) && hitHeader) {
+ nsCOMPtr<nsIMsgSearchSession> searchSession;
+ nsCOMPtr<nsIMsgSearchAdapter> searchAdapter;
+ aUrl->GetSearchSession(getter_AddRefs(searchSession));
+ if (searchSession) {
+ searchSession->GetRunningAdapter(getter_AddRefs(searchAdapter));
+ if (searchAdapter) searchAdapter->AddResultElement(hitHeader);
+ }
+ } else if (!shownUpdateAlert) {
+ }
+
+ hitUidToken = NS_strtok(WHITESPACE, &currentPosition);
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::SetAppendMsgUid(nsMsgKey aKey, nsIImapUrl* aUrl) {
+ nsresult rv;
+ 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) // CopyMessages()
+ {
+ RefPtr<nsImapMoveCopyMsgTxn> msgTxn;
+ msgTxn = mailCopyState->m_undoMsgTxn;
+ msgTxn->AddDstKey(aKey);
+ } else if (mailCopyState->m_listener) // CopyFileMessage();
+ // Draft/Template goes here
+ {
+ mailCopyState->m_appendUID = aKey;
+ mailCopyState->m_listener->SetMessageKey(aKey);
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::GetMessageId(nsIImapUrl* aUrl, nsACString& messageId) {
+ nsresult rv = NS_OK;
+ 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_listener)
+ rv = mailCopyState->m_listener->GetMessageId(messageId);
+ }
+ if (NS_SUCCEEDED(rv) && messageId.Length() > 0) {
+ if (messageId.First() == '<') messageId.Cut(0, 1);
+ if (messageId.Last() == '>') messageId.SetLength(messageId.Length() - 1);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::HeaderFetchCompleted(nsIImapProtocol* aProtocol) {
+ nsCOMPtr<nsIMsgWindow>
+ msgWindow; // we might need this for the filter plugins.
+ if (mBackupDatabase) RemoveBackupMsgDatabase();
+
+ SetSizeOnDisk(mFolderSize);
+ int32_t numNewBiffMsgs = 0;
+ if (m_performingBiff) GetNumNewMessages(false, &numNewBiffMsgs);
+
+ bool pendingMoves = m_moveCoalescer && m_moveCoalescer->HasPendingMoves();
+ PlaybackCoalescedOperations();
+ if (aProtocol) {
+ // check if we should download message bodies because it's the inbox and
+ // the server is specified as one where where we download msg bodies
+ // automatically. Or if we autosyncing all offline folders.
+ nsCOMPtr<nsIImapIncomingServer> imapServer;
+ GetImapIncomingServer(getter_AddRefs(imapServer));
+
+ bool autoDownloadNewHeaders = false;
+ bool autoSyncOfflineStores = false;
+
+ if (imapServer) {
+ imapServer->GetAutoSyncOfflineStores(&autoSyncOfflineStores);
+ imapServer->GetDownloadBodiesOnGetNewMail(&autoDownloadNewHeaders);
+ if (m_filterListRequiresBody) autoDownloadNewHeaders = true;
+ }
+ bool notifiedBodies = false;
+ if (m_downloadingFolderForOfflineUse || autoSyncOfflineStores ||
+ autoDownloadNewHeaders) {
+ nsTArray<nsMsgKey> keysToDownload;
+ GetBodysToDownload(&keysToDownload);
+ if (!keysToDownload.IsEmpty() &&
+ (m_downloadingFolderForOfflineUse || autoDownloadNewHeaders)) {
+ // this is the case when DownloadAllForOffline is called.
+ notifiedBodies = true;
+ aProtocol->NotifyBodysToDownload(keysToDownload);
+ } else {
+ // create auto-sync state object lazily
+ InitAutoSyncState();
+ if (MOZ_LOG_TEST(gAutoSyncLog, mozilla::LogLevel::Debug)) {
+ int32_t flags = 0;
+ GetFlags((uint32_t*)&flags);
+ nsString folderName;
+ GetName(folderName);
+ nsCString utfLeafName;
+ CopyUTF16toUTF8(folderName, utfLeafName);
+ MOZ_LOG(gAutoSyncLog, mozilla::LogLevel::Debug,
+ ("%s: foldername=%s, flags=0x%X, "
+ "isOffline=%s, nsMsgFolderFlags::Offline=0x%X",
+ __func__, utfLeafName.get(), flags,
+ (flags & nsMsgFolderFlags::Offline) ? "true" : "false",
+ nsMsgFolderFlags::Offline));
+ MOZ_LOG(gAutoSyncLog, mozilla::LogLevel::Debug,
+ ("%s: created autosync obj, have keys to download=%s",
+ __func__, keysToDownload.IsEmpty() ? "false" : "true"));
+ }
+ // make enough room for new downloads
+ m_autoSyncStateObj->ManageStorageSpace(); // currently a no-op
+
+ m_autoSyncStateObj->SetServerCounts(
+ m_numServerTotalMessages, m_numServerRecentMessages,
+ m_numServerUnseenMessages, m_nextUID);
+ m_autoSyncStateObj->OnNewHeaderFetchCompleted(keysToDownload);
+ }
+ }
+ if (!notifiedBodies) {
+ nsTArray<nsMsgKey> noBodies;
+ aProtocol->NotifyBodysToDownload(noBodies);
+ }
+
+ nsCOMPtr<nsIURI> runningUri;
+ aProtocol->GetRunningUrl(getter_AddRefs(runningUri));
+ if (runningUri) {
+ nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(runningUri);
+ if (mailnewsUrl) mailnewsUrl->GetMsgWindow(getter_AddRefs(msgWindow));
+ }
+ }
+
+ // delay calling plugins if filter application is also delayed
+ if (!m_filterListRequiresBody) {
+ bool filtersRun;
+ CallFilterPlugins(msgWindow, &filtersRun);
+ 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::SetBiffStateAndUpdate(nsMsgBiffState biffState) {
+ SetBiffState(biffState);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::GetUidValidity(int32_t* uidValidity) {
+ NS_ENSURE_ARG(uidValidity);
+ if ((int32_t)m_uidValidity == kUidUnknown) {
+ nsCOMPtr<nsIMsgDatabase> db;
+ nsCOMPtr<nsIDBFolderInfo> dbFolderInfo;
+ (void)GetDBFolderInfoAndDB(getter_AddRefs(dbFolderInfo),
+ getter_AddRefs(db));
+ if (db) db->GetDBFolderInfo(getter_AddRefs(dbFolderInfo));
+
+ if (dbFolderInfo)
+ dbFolderInfo->GetImapUidValidity((int32_t*)&m_uidValidity);
+ }
+ *uidValidity = m_uidValidity;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::SetUidValidity(int32_t uidValidity) {
+ m_uidValidity = uidValidity;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::FillInFolderProps(nsIMsgImapFolderProps* aFolderProps) {
+ NS_ENSURE_ARG(aFolderProps);
+ const char* folderTypeStringID;
+ const char* folderTypeDescStringID = nullptr;
+ const char* folderQuotaStatusStringID;
+ nsString folderType;
+ nsString folderTypeDesc;
+ nsString folderQuotaStatusDesc;
+ nsCOMPtr<nsIStringBundle> bundle;
+ nsresult rv = IMAPGetStringBundle(getter_AddRefs(bundle));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIImapIncomingServer> imapServer;
+ rv = GetImapIncomingServer(getter_AddRefs(imapServer));
+ // if for some bizarre reason this fails, we'll still fall through to the
+ // normal sharing code
+ if (NS_SUCCEEDED(rv)) {
+ // get the latest committed imap capabilities bit mask.
+ eIMAPCapabilityFlags capability = kCapabilityUndefined;
+ imapServer->GetCapability(&capability);
+ bool haveACL = capability & kACLCapability;
+ bool haveQuota = capability & kQuotaCapability;
+
+ // Figure out what to display in the Quota tab of the folder properties.
+ // Does the server support quotas? This depends on the latest imap
+ // CAPABILITY response.
+ if (haveQuota) {
+ // Have quota capability. Have we asked the server for quota information?
+ if (m_folderQuotaCommandIssued) {
+ // Has the server replied with all the quota info?
+ if (m_folderQuotaDataIsValid) {
+ if (!m_folderQuota.IsEmpty()) {
+ // If so, set quota data to show in the quota tab
+ folderQuotaStatusStringID = nullptr;
+ aFolderProps->SetQuotaData(m_folderQuota);
+ } else {
+ // The server reported no quota limits on this folder.
+ folderQuotaStatusStringID = "imapQuotaStatusNoQuota2";
+ }
+ } else {
+ // The getquotaroot command was sent to the server but the complete
+ // response was not yet received when the folder properties were
+ // requested. This is rare. Request the folder properties again to
+ // obtain the quota data.
+ folderQuotaStatusStringID = "imapQuotaStatusInProgress";
+ }
+ } else {
+ // The folder is not open, so no quota information is available
+ folderQuotaStatusStringID = "imapQuotaStatusFolderNotOpen";
+ }
+ } else {
+ // Either the server doesn't support quotas, or we don't know if it does
+ // (e.g., because we don't have a connection yet). If the latter, we fall
+ // back to saying that no information is available because the folder is
+ // not yet open.
+ folderQuotaStatusStringID = (capability == kCapabilityUndefined)
+ ? "imapQuotaStatusFolderNotOpen"
+ : "imapQuotaStatusNotSupported";
+ }
+
+ if (!folderQuotaStatusStringID) {
+ // Display quota data
+ aFolderProps->ShowQuotaData(true);
+ } else {
+ // Hide quota data and show reason why it is not available
+ aFolderProps->ShowQuotaData(false);
+
+ rv = IMAPGetStringByName(folderQuotaStatusStringID,
+ getter_Copies(folderQuotaStatusDesc));
+ if (NS_SUCCEEDED(rv)) aFolderProps->SetQuotaStatus(folderQuotaStatusDesc);
+ }
+
+ // See if the server supports ACL.
+ // If not, just set the folder description to a string that says
+ // the server doesn't support sharing, and return.
+ if (!haveACL) {
+ rv = IMAPGetStringByName("imapServerDoesntSupportAcl",
+ getter_Copies(folderTypeDesc));
+ if (NS_SUCCEEDED(rv))
+ aFolderProps->SetFolderTypeDescription(folderTypeDesc);
+ aFolderProps->ServerDoesntSupportACL();
+ return NS_OK;
+ }
+ }
+ if (mFlags & nsMsgFolderFlags::ImapPublic) {
+ folderTypeStringID = "imapPublicFolderTypeName";
+ folderTypeDescStringID = "imapPublicFolderTypeDescription";
+ } else if (mFlags & nsMsgFolderFlags::ImapOtherUser) {
+ folderTypeStringID = "imapOtherUsersFolderTypeName";
+ nsCString owner;
+ nsString uniOwner;
+ GetFolderOwnerUserName(owner);
+ if (owner.IsEmpty()) {
+ IMAPGetStringByName(folderTypeStringID, getter_Copies(uniOwner));
+ // Another user's folder, for which we couldn't find an owner name
+ NS_ASSERTION(false, "couldn't get owner name for other user's folder");
+ } else {
+ CopyUTF8toUTF16(owner, uniOwner);
+ }
+ AutoTArray<nsString, 1> params = {uniOwner};
+ bundle->FormatStringFromName("imapOtherUsersFolderTypeDescription", params,
+ folderTypeDesc);
+ } else if (GetFolderACL()->GetIsFolderShared()) {
+ folderTypeStringID = "imapPersonalSharedFolderTypeName";
+ folderTypeDescStringID = "imapPersonalSharedFolderTypeDescription";
+ } else {
+ folderTypeStringID = "imapPersonalSharedFolderTypeName";
+ folderTypeDescStringID = "imapPersonalFolderTypeDescription";
+ }
+
+ rv = IMAPGetStringByName(folderTypeStringID, getter_Copies(folderType));
+ if (NS_SUCCEEDED(rv)) aFolderProps->SetFolderType(folderType);
+
+ if (folderTypeDesc.IsEmpty() && folderTypeDescStringID)
+ IMAPGetStringByName(folderTypeDescStringID, getter_Copies(folderTypeDesc));
+ if (!folderTypeDesc.IsEmpty())
+ aFolderProps->SetFolderTypeDescription(folderTypeDesc);
+
+ nsString rightsString;
+ rv = CreateACLRightsStringForFolder(rightsString);
+ if (NS_SUCCEEDED(rv)) aFolderProps->SetFolderPermissions(rightsString);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailFolder::SetAclFlags(uint32_t aclFlags) {
+ nsresult rv = NS_OK;
+ if (m_aclFlags != aclFlags) {
+ nsCOMPtr<nsIDBFolderInfo> dbFolderInfo;
+ bool dbWasOpen = (mDatabase != nullptr);
+ rv = GetDatabase();
+
+ m_aclFlags = aclFlags;
+ if (mDatabase) {
+ rv = mDatabase->GetDBFolderInfo(getter_AddRefs(dbFolderInfo));
+ if (NS_SUCCEEDED(rv) && dbFolderInfo)
+ dbFolderInfo->SetUint32Property("aclFlags", aclFlags);
+ // if setting the acl flags caused us to open the db, release the ref
+ // because on startup, we might get acl on all folders,which will
+ // leave a lot of db's open.
+ if (!dbWasOpen) {
+ mDatabase->Close(true /* commit changes */);
+ mDatabase = nullptr;
+ }
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapMailFolder::GetAclFlags(uint32_t* aclFlags) {
+ NS_ENSURE_ARG_POINTER(aclFlags);
+ nsresult rv;
+ ReadDBFolderInfo(false); // update cache first.
+ if (m_aclFlags == kAclInvalid) // -1 means invalid value, so get it from db.
+ {
+ nsCOMPtr<nsIDBFolderInfo> dbFolderInfo;
+ bool dbWasOpen = (mDatabase != nullptr);
+ rv = GetDatabase();
+
+ if (mDatabase) {
+ rv = mDatabase->GetDBFolderInfo(getter_AddRefs(dbFolderInfo));
+ if (NS_SUCCEEDED(rv) && dbFolderInfo) {
+ rv = dbFolderInfo->GetUint32Property("aclFlags", 0, aclFlags);
+ m_aclFlags = *aclFlags;
+ }
+ // if getting the acl flags caused us to open the db, release the ref
+ // because on startup, we might get acl on all folders,which will
+ // leave a lot of db's open.
+ if (!dbWasOpen) {
+ mDatabase->Close(true /* commit changes */);
+ mDatabase = nullptr;
+ }
+ }
+ } else
+ *aclFlags = m_aclFlags;
+ return NS_OK;
+}
+
+nsresult nsImapMailFolder::SetSupportedUserFlags(uint32_t userFlags) {
+ nsCOMPtr<nsIDBFolderInfo> dbFolderInfo;
+ nsresult rv = GetDatabase();
+
+ m_supportedUserFlags = userFlags;
+ if (mDatabase) {
+ rv = mDatabase->GetDBFolderInfo(getter_AddRefs(dbFolderInfo));
+ if (NS_SUCCEEDED(rv) && dbFolderInfo)
+ dbFolderInfo->SetUint32Property("imapFlags", userFlags);
+ }
+ return rv;
+}
+
+nsresult nsImapMailFolder::GetSupportedUserFlags(uint32_t* userFlags) {
+ NS_ENSURE_ARG_POINTER(userFlags);
+
+ nsresult rv = NS_OK;
+
+ ReadDBFolderInfo(false); // update cache first.
+ if (m_supportedUserFlags == 0) // 0 means invalid value, so get it from db.
+ {
+ nsCOMPtr<nsIDBFolderInfo> dbFolderInfo;
+ rv = GetDatabase();
+
+ if (mDatabase) {
+ rv = mDatabase->GetDBFolderInfo(getter_AddRefs(dbFolderInfo));
+ if (NS_SUCCEEDED(rv) && dbFolderInfo) {
+ rv = dbFolderInfo->GetUint32Property("imapFlags", 0, userFlags);
+ m_supportedUserFlags = *userFlags;
+ }
+ }
+ } else
+ *userFlags = m_supportedUserFlags;
+ return rv;
+}
+
+NS_IMETHODIMP nsImapMailFolder::GetCanOpenFolder(bool* aBool) {
+ NS_ENSURE_ARG_POINTER(aBool);
+ bool noSelect;
+ GetFlag(nsMsgFolderFlags::ImapNoselect, &noSelect);
+ *aBool = (noSelect) ? false : GetFolderACL()->GetCanIReadFolder();
+ return NS_OK;
+}
+
+///////// nsMsgIMAPFolderACL class ///////////////////////////////
+
+// This string is defined in the ACL RFC to be "anyone"
+#define IMAP_ACL_ANYONE_STRING "anyone"
+
+nsMsgIMAPFolderACL::nsMsgIMAPFolderACL(nsImapMailFolder* folder)
+ : m_rightsHash(24) {
+ NS_ASSERTION(folder, "need folder");
+ m_folder = folder;
+ m_aclCount = 0;
+ BuildInitialACLFromCache();
+}
+
+nsMsgIMAPFolderACL::~nsMsgIMAPFolderACL() {}
+
+// We cache most of our own rights in the MSG_FOLDER_PREF_* flags
+void nsMsgIMAPFolderACL::BuildInitialACLFromCache() {
+ nsAutoCString myrights;
+
+ uint32_t startingFlags;
+ m_folder->GetAclFlags(&startingFlags);
+
+ if (startingFlags & IMAP_ACL_READ_FLAG) myrights += "r";
+ if (startingFlags & IMAP_ACL_STORE_SEEN_FLAG) myrights += "s";
+ if (startingFlags & IMAP_ACL_WRITE_FLAG) myrights += "w";
+ if (startingFlags & IMAP_ACL_INSERT_FLAG) myrights += "i";
+ if (startingFlags & IMAP_ACL_POST_FLAG) myrights += "p";
+ if (startingFlags & IMAP_ACL_CREATE_SUBFOLDER_FLAG) myrights += "c";
+ if (startingFlags & IMAP_ACL_DELETE_FLAG) myrights += "dt";
+ if (startingFlags & IMAP_ACL_ADMINISTER_FLAG) myrights += "a";
+ if (startingFlags & IMAP_ACL_EXPUNGE_FLAG) myrights += "e";
+
+ if (!myrights.IsEmpty()) SetFolderRightsForUser(EmptyCString(), myrights);
+}
+
+void nsMsgIMAPFolderACL::UpdateACLCache() {
+ uint32_t startingFlags = 0;
+ m_folder->GetAclFlags(&startingFlags);
+
+ if (GetCanIReadFolder())
+ startingFlags |= IMAP_ACL_READ_FLAG;
+ else
+ startingFlags &= ~IMAP_ACL_READ_FLAG;
+
+ if (GetCanIStoreSeenInFolder())
+ startingFlags |= IMAP_ACL_STORE_SEEN_FLAG;
+ else
+ startingFlags &= ~IMAP_ACL_STORE_SEEN_FLAG;
+
+ if (GetCanIWriteFolder())
+ startingFlags |= IMAP_ACL_WRITE_FLAG;
+ else
+ startingFlags &= ~IMAP_ACL_WRITE_FLAG;
+
+ if (GetCanIInsertInFolder())
+ startingFlags |= IMAP_ACL_INSERT_FLAG;
+ else
+ startingFlags &= ~IMAP_ACL_INSERT_FLAG;
+
+ if (GetCanIPostToFolder())
+ startingFlags |= IMAP_ACL_POST_FLAG;
+ else
+ startingFlags &= ~IMAP_ACL_POST_FLAG;
+
+ if (GetCanICreateSubfolder())
+ startingFlags |= IMAP_ACL_CREATE_SUBFOLDER_FLAG;
+ else
+ startingFlags &= ~IMAP_ACL_CREATE_SUBFOLDER_FLAG;
+
+ if (GetCanIDeleteInFolder())
+ startingFlags |= IMAP_ACL_DELETE_FLAG;
+ else
+ startingFlags &= ~IMAP_ACL_DELETE_FLAG;
+
+ if (GetCanIAdministerFolder())
+ startingFlags |= IMAP_ACL_ADMINISTER_FLAG;
+ else
+ startingFlags &= ~IMAP_ACL_ADMINISTER_FLAG;
+
+ if (GetCanIExpungeFolder())
+ startingFlags |= IMAP_ACL_EXPUNGE_FLAG;
+ else
+ startingFlags &= ~IMAP_ACL_EXPUNGE_FLAG;
+
+ m_folder->SetAclFlags(startingFlags);
+}
+
+bool nsMsgIMAPFolderACL::SetFolderRightsForUser(const nsACString& userName,
+ const nsACString& rights) {
+ nsCString myUserName;
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ nsresult rv = m_folder->GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, false);
+
+ server->GetUsername(myUserName);
+
+ nsAutoCString ourUserName;
+ if (userName.IsEmpty())
+ ourUserName.Assign(myUserName);
+ else
+ ourUserName.Assign(userName);
+
+ if (ourUserName.IsEmpty()) return false;
+
+ ToLowerCase(ourUserName);
+ nsCString oldValue = m_rightsHash.Get(ourUserName);
+ if (!oldValue.IsEmpty()) {
+ m_rightsHash.Remove(ourUserName);
+ m_aclCount--;
+ NS_ASSERTION(m_aclCount >= 0, "acl count can't go negative");
+ }
+ m_aclCount++;
+ m_rightsHash.InsertOrUpdate(ourUserName, PromiseFlatCString(rights));
+
+ if (myUserName.Equals(ourUserName) ||
+ ourUserName.EqualsLiteral(IMAP_ACL_ANYONE_STRING))
+ // if this is setting an ACL for me, cache it in the folder pref flags
+ UpdateACLCache();
+
+ return true;
+}
+
+NS_IMETHODIMP nsImapMailFolder::GetOtherUsersWithAccess(
+ nsIUTF8StringEnumerator** aResult) {
+ return GetFolderACL()->GetOtherUsers(aResult);
+}
+
+nsresult nsMsgIMAPFolderACL::GetOtherUsers(nsIUTF8StringEnumerator** aResult) {
+ nsCString myUserName;
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ nsresult rv = m_folder->GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+ server->GetUsername(myUserName);
+
+ // We need to filter out myUserName from m_rightsHash.
+ nsTArray<nsCString>* resultArray = new nsTArray<nsCString>;
+ for (auto iter = m_rightsHash.Iter(); !iter.Done(); iter.Next()) {
+ if (!iter.Key().Equals(myUserName)) resultArray->AppendElement(iter.Key());
+ }
+
+ // enumerator will free resultArray
+ return NS_NewAdoptingUTF8StringEnumerator(aResult, resultArray);
+}
+
+nsresult nsImapMailFolder::GetPermissionsForUser(const nsACString& otherUser,
+ nsACString& aResult) {
+ nsCString str;
+ nsresult rv = GetFolderACL()->GetRightsStringForUser(otherUser, str);
+ NS_ENSURE_SUCCESS(rv, rv);
+ aResult = str;
+ return NS_OK;
+}
+
+nsresult nsMsgIMAPFolderACL::GetRightsStringForUser(
+ const nsACString& inUserName, nsCString& rights) {
+ nsCString userName;
+ userName.Assign(inUserName);
+ if (userName.IsEmpty()) {
+ nsCOMPtr<nsIMsgIncomingServer> server;
+
+ nsresult rv = m_folder->GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+ // we need the real user name to match with what the imap server returns
+ // in the acl response.
+ server->GetUsername(userName);
+ }
+ ToLowerCase(userName);
+ rights = m_rightsHash.Get(userName);
+ return NS_OK;
+}
+
+// First looks for individual user; then looks for 'anyone' if the user isn't
+// found. Returns defaultIfNotFound, if neither are found.
+bool nsMsgIMAPFolderACL::GetFlagSetInRightsForUser(const nsACString& userName,
+ char flag,
+ bool defaultIfNotFound) {
+ nsCString flags;
+ nsresult rv = GetRightsStringForUser(userName, flags);
+ NS_ENSURE_SUCCESS(rv, defaultIfNotFound);
+ if (flags.IsEmpty()) {
+ nsCString anyoneFlags;
+ GetRightsStringForUser(nsLiteralCString(IMAP_ACL_ANYONE_STRING),
+ anyoneFlags);
+ if (anyoneFlags.IsEmpty()) return defaultIfNotFound;
+ return (anyoneFlags.FindChar(flag) != kNotFound);
+ }
+ return (flags.FindChar(flag) != kNotFound);
+}
+
+bool nsMsgIMAPFolderACL::GetCanUserLookupFolder(const nsACString& userName) {
+ return GetFlagSetInRightsForUser(userName, 'l', false);
+}
+
+bool nsMsgIMAPFolderACL::GetCanUserReadFolder(const nsACString& userName) {
+ return GetFlagSetInRightsForUser(userName, 'r', false);
+}
+
+bool nsMsgIMAPFolderACL::GetCanUserStoreSeenInFolder(
+ const nsACString& userName) {
+ return GetFlagSetInRightsForUser(userName, 's', false);
+}
+
+bool nsMsgIMAPFolderACL::GetCanUserWriteFolder(const nsACString& userName) {
+ return GetFlagSetInRightsForUser(userName, 'w', false);
+}
+
+bool nsMsgIMAPFolderACL::GetCanUserInsertInFolder(const nsACString& userName) {
+ return GetFlagSetInRightsForUser(userName, 'i', false);
+}
+
+bool nsMsgIMAPFolderACL::GetCanUserPostToFolder(const nsACString& userName) {
+ return GetFlagSetInRightsForUser(userName, 'p', false);
+}
+
+bool nsMsgIMAPFolderACL::GetCanUserCreateSubfolder(const nsACString& userName) {
+ return GetFlagSetInRightsForUser(userName, 'c', false);
+}
+
+bool nsMsgIMAPFolderACL::GetCanUserDeleteInFolder(const nsACString& userName) {
+ return GetFlagSetInRightsForUser(userName, 'd', false) ||
+ GetFlagSetInRightsForUser(userName, 't', false);
+}
+
+bool nsMsgIMAPFolderACL::GetCanUserAdministerFolder(
+ const nsACString& userName) {
+ return GetFlagSetInRightsForUser(userName, 'a', false);
+}
+
+bool nsMsgIMAPFolderACL::GetCanILookupFolder() {
+ return GetFlagSetInRightsForUser(EmptyCString(), 'l', true);
+}
+
+bool nsMsgIMAPFolderACL::GetCanIReadFolder() {
+ return GetFlagSetInRightsForUser(EmptyCString(), 'r', true);
+}
+
+bool nsMsgIMAPFolderACL::GetCanIStoreSeenInFolder() {
+ return GetFlagSetInRightsForUser(EmptyCString(), 's', true);
+}
+
+bool nsMsgIMAPFolderACL::GetCanIWriteFolder() {
+ return GetFlagSetInRightsForUser(EmptyCString(), 'w', true);
+}
+
+bool nsMsgIMAPFolderACL::GetCanIInsertInFolder() {
+ return GetFlagSetInRightsForUser(EmptyCString(), 'i', true);
+}
+
+bool nsMsgIMAPFolderACL::GetCanIPostToFolder() {
+ return GetFlagSetInRightsForUser(EmptyCString(), 'p', true);
+}
+
+bool nsMsgIMAPFolderACL::GetCanICreateSubfolder() {
+ return GetFlagSetInRightsForUser(EmptyCString(), 'c', true);
+}
+
+bool nsMsgIMAPFolderACL::GetCanIDeleteInFolder() {
+ return GetFlagSetInRightsForUser(EmptyCString(), 'd', true) ||
+ GetFlagSetInRightsForUser(EmptyCString(), 't', true);
+}
+
+bool nsMsgIMAPFolderACL::GetCanIAdministerFolder() {
+ return GetFlagSetInRightsForUser(EmptyCString(), 'a', true);
+}
+
+bool nsMsgIMAPFolderACL::GetCanIExpungeFolder() {
+ return GetFlagSetInRightsForUser(EmptyCString(), 'e', true) ||
+ GetFlagSetInRightsForUser(EmptyCString(), 'd', true);
+}
+
+// We use this to see if the ACLs think a folder is shared or not.
+// We will define "Shared" in 5.0 to mean:
+// At least one user other than the currently authenticated user has at least
+// one explicitly-listed ACL right on that folder.
+bool nsMsgIMAPFolderACL::GetIsFolderShared() {
+ // If we have more than one ACL count for this folder, which means that
+ // someone other than ourself has rights on it, then it is "shared."
+ if (m_aclCount > 1) return true;
+
+ // Or, if "anyone" has rights to it, it is shared.
+ nsCString anyonesRights =
+ m_rightsHash.Get(nsLiteralCString(IMAP_ACL_ANYONE_STRING));
+ return (!anyonesRights.IsEmpty());
+}
+
+bool nsMsgIMAPFolderACL::GetDoIHaveFullRightsForFolder() {
+ return (GetCanIReadFolder() && GetCanIWriteFolder() &&
+ GetCanIInsertInFolder() && GetCanIAdministerFolder() &&
+ GetCanICreateSubfolder() && GetCanIDeleteInFolder() &&
+ GetCanILookupFolder() && GetCanIStoreSeenInFolder() &&
+ GetCanIExpungeFolder() && GetCanIPostToFolder());
+}
+
+// Returns a newly allocated string describing these rights
+nsresult nsMsgIMAPFolderACL::CreateACLRightsString(nsAString& aRightsString) {
+ nsString curRight;
+ nsCOMPtr<nsIStringBundle> bundle;
+ nsresult rv = IMAPGetStringBundle(getter_AddRefs(bundle));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (GetDoIHaveFullRightsForFolder()) {
+ nsAutoString result;
+ rv = bundle->GetStringFromName("imapAclFullRights", result);
+ aRightsString.Assign(result);
+ return rv;
+ }
+
+ if (GetCanIReadFolder()) {
+ bundle->GetStringFromName("imapAclReadRight", curRight);
+ aRightsString.Append(curRight);
+ }
+ if (GetCanIWriteFolder()) {
+ if (!aRightsString.IsEmpty()) aRightsString.AppendLiteral(", ");
+ bundle->GetStringFromName("imapAclWriteRight", curRight);
+ aRightsString.Append(curRight);
+ }
+ if (GetCanIInsertInFolder()) {
+ if (!aRightsString.IsEmpty()) aRightsString.AppendLiteral(", ");
+ bundle->GetStringFromName("imapAclInsertRight", curRight);
+ aRightsString.Append(curRight);
+ }
+ if (GetCanILookupFolder()) {
+ if (!aRightsString.IsEmpty()) aRightsString.AppendLiteral(", ");
+ bundle->GetStringFromName("imapAclLookupRight", curRight);
+ aRightsString.Append(curRight);
+ }
+ if (GetCanIStoreSeenInFolder()) {
+ if (!aRightsString.IsEmpty()) aRightsString.AppendLiteral(", ");
+ bundle->GetStringFromName("imapAclSeenRight", curRight);
+ aRightsString.Append(curRight);
+ }
+ if (GetCanIDeleteInFolder()) {
+ if (!aRightsString.IsEmpty()) aRightsString.AppendLiteral(", ");
+ bundle->GetStringFromName("imapAclDeleteRight", curRight);
+ aRightsString.Append(curRight);
+ }
+ if (GetCanIExpungeFolder()) {
+ if (!aRightsString.IsEmpty()) aRightsString.AppendLiteral(", ");
+ bundle->GetStringFromName("imapAclExpungeRight", curRight);
+ aRightsString.Append(curRight);
+ }
+ if (GetCanICreateSubfolder()) {
+ if (!aRightsString.IsEmpty()) aRightsString.AppendLiteral(", ");
+ bundle->GetStringFromName("imapAclCreateRight", curRight);
+ aRightsString.Append(curRight);
+ }
+ if (GetCanIPostToFolder()) {
+ if (!aRightsString.IsEmpty()) aRightsString.AppendLiteral(", ");
+ bundle->GetStringFromName("imapAclPostRight", curRight);
+ aRightsString.Append(curRight);
+ }
+ if (GetCanIAdministerFolder()) {
+ if (!aRightsString.IsEmpty()) aRightsString.AppendLiteral(", ");
+ bundle->GetStringFromName("imapAclAdministerRight", curRight);
+ aRightsString.Append(curRight);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapMailFolder::GetFilePath(nsIFile** aPathName) {
+ // this will return a copy of mPath, which is what we want.
+ // this will also initialize mPath using parseURI if it isn't already done
+ return nsMsgDBFolder::GetFilePath(aPathName);
+}
+
+NS_IMETHODIMP nsImapMailFolder::SetFilePath(nsIFile* aPathName) {
+ return nsMsgDBFolder::SetFilePath(
+ aPathName); // call base class so mPath will get set
+}
+
+nsresult nsImapMailFolder::DisplayStatusMsg(nsIImapUrl* aImapUrl,
+ const nsAString& msg) {
+ nsCOMPtr<nsIImapMockChannel> mockChannel;
+ aImapUrl->GetMockChannel(getter_AddRefs(mockChannel));
+ if (mockChannel) {
+ nsCOMPtr<nsIProgressEventSink> progressSink;
+ mockChannel->GetProgressEventSink(getter_AddRefs(progressSink));
+ if (progressSink) {
+ progressSink->OnStatus(mockChannel, NS_OK,
+ PromiseFlatString(msg).get()); // XXX i18n message
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::ProgressStatusString(nsIImapProtocol* aProtocol,
+ const char* aMsgName,
+ const char16_t* extraInfo) {
+ nsString progressMsg;
+
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ nsresult rv = GetServer(getter_AddRefs(server));
+ if (NS_SUCCEEDED(rv) && server) {
+ nsCOMPtr<nsIImapServerSink> serverSink = do_QueryInterface(server);
+ if (serverSink) serverSink->GetImapStringByName(aMsgName, progressMsg);
+ }
+ if (progressMsg.IsEmpty())
+ IMAPGetStringByName(aMsgName, getter_Copies(progressMsg));
+
+ if (aProtocol && !progressMsg.IsEmpty()) {
+ nsCOMPtr<nsIImapUrl> imapUrl;
+ aProtocol->GetRunningImapURL(getter_AddRefs(imapUrl));
+ if (imapUrl) {
+ if (extraInfo) {
+ nsString printfString;
+ nsTextFormatter::ssprintf(printfString, progressMsg.get(), extraInfo);
+ progressMsg = printfString;
+ }
+
+ DisplayStatusMsg(imapUrl, progressMsg);
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::PercentProgress(nsIImapProtocol* aProtocol,
+ nsACString const& aFmtStringName,
+ nsAString const& aMailboxName,
+ int64_t aCurrentProgress,
+ int64_t aMaxProgress) {
+ if (aProtocol) {
+ nsCOMPtr<nsIImapUrl> imapUrl;
+ aProtocol->GetRunningImapURL(getter_AddRefs(imapUrl));
+ if (imapUrl) {
+ nsCOMPtr<nsIImapMockChannel> mockChannel;
+ imapUrl->GetMockChannel(getter_AddRefs(mockChannel));
+ if (mockChannel) {
+ nsCOMPtr<nsIProgressEventSink> progressSink;
+ mockChannel->GetProgressEventSink(getter_AddRefs(progressSink));
+ if (progressSink) {
+ progressSink->OnProgress(mockChannel, aCurrentProgress, aMaxProgress);
+
+ if (!aFmtStringName.IsEmpty()) {
+ // There's a progress message to format (the progress messages are
+ // all localized and expect three params).
+ nsAutoString current;
+ current.AppendInt(aCurrentProgress);
+ nsAutoString expected;
+ expected.AppendInt(aMaxProgress);
+ nsAutoString mailbox(aMailboxName);
+ AutoTArray<nsString, 3> params = {current, expected, mailbox};
+
+ nsCOMPtr<nsIStringBundle> bundle;
+ nsresult rv = IMAPGetStringBundle(getter_AddRefs(bundle));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsString progressText;
+ rv = bundle->FormatStringFromName(
+ PromiseFlatCString(aFmtStringName).get(), params, progressText);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!progressText.IsEmpty()) {
+ progressSink->OnStatus(mockChannel, NS_OK, progressText.get());
+ }
+ }
+ }
+ }
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::CopyNextStreamMessage(bool copySucceeded,
+ nsISupports* copyState) {
+ // if copy has failed it could be either user interrupted it or for some other
+ // reason don't do any subsequent copies or delete src messages if it is move
+ if (!copySucceeded) return NS_OK;
+ nsresult rv;
+ nsCOMPtr<nsImapMailCopyState> mailCopyState =
+ do_QueryInterface(copyState, &rv);
+ if (NS_FAILED(rv)) {
+ MOZ_LOG(IMAP, mozilla::LogLevel::Info,
+ ("QI copyState failed: %" PRIx32, static_cast<uint32_t>(rv)));
+ return rv; // this can fail...
+ }
+
+ if (!mailCopyState->m_streamCopy) return NS_OK;
+
+ uint32_t idx = mailCopyState->m_curIndex;
+ if (mailCopyState->m_isMove && idx) {
+ nsCOMPtr<nsIMsgFolder> srcFolder(
+ do_QueryInterface(mailCopyState->m_srcSupport, &rv));
+ if (NS_SUCCEEDED(rv) && srcFolder) {
+ // Create "array" of one message header to delete
+ idx--;
+ if (idx < mailCopyState->m_messages.Length()) {
+ RefPtr<nsIMsgDBHdr> msg = mailCopyState->m_messages[idx];
+ srcFolder->DeleteMessages({msg}, nullptr, true, true, nullptr, false);
+ }
+ }
+ }
+
+ if (mailCopyState->m_curIndex < mailCopyState->m_messages.Length()) {
+ MOZ_LOG(IMAP, mozilla::LogLevel::Info,
+ ("CopyNextStreamMessage: %s %u of %u",
+ mailCopyState->m_isMove ? "Moving" : "Copying",
+ mailCopyState->m_curIndex,
+ (uint32_t)mailCopyState->m_messages.Length()));
+ nsIMsgDBHdr* message = mailCopyState->m_messages[mailCopyState->m_curIndex];
+ bool isRead;
+ message->GetIsRead(&isRead);
+ mailCopyState->m_unreadCount = (isRead) ? 0 : 1;
+ rv = CopyStreamMessage(message, this, mailCopyState->m_msgWindow,
+ mailCopyState->m_isMove);
+ } else {
+ // Notify of move/copy completion in case we have some source headers
+ nsCOMPtr<nsIMsgFolderNotificationService> notifier(
+ do_GetService("@mozilla.org/messenger/msgnotificationservice;1"));
+ if (notifier && !mailCopyState->m_messages.IsEmpty()) {
+ notifier->NotifyMsgsMoveCopyCompleted(
+ mailCopyState->m_isMove, mailCopyState->m_messages, this, {});
+ }
+ if (mailCopyState->m_isMove) {
+ nsCOMPtr<nsIMsgFolder> srcFolder(
+ do_QueryInterface(mailCopyState->m_srcSupport, &rv));
+ if (NS_SUCCEEDED(rv) && srcFolder) {
+ // we want to send this notification now that the source messages have
+ // been deleted.
+ nsCOMPtr<nsIMsgLocalMailFolder> popFolder(do_QueryInterface(srcFolder));
+ if (popFolder) // needed if move pop->imap to notify FE
+ srcFolder->NotifyFolderEvent(kDeleteOrMoveMsgCompleted);
+ }
+ }
+ }
+ if (NS_FAILED(rv)) (void)OnCopyCompleted(mailCopyState->m_srcSupport, rv);
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::SetUrlState(nsIImapProtocol* aProtocol,
+ nsIMsgMailNewsUrl* aUrl, bool isRunning,
+ bool aSuspend, nsresult statusCode) {
+ // If we have no path, then the folder has been shutdown, and there's
+ // no point in doing anything...
+ if (!mPath) return NS_OK;
+ if (!isRunning) {
+ ProgressStatusString(aProtocol, "imapDone", nullptr);
+ m_urlRunning = false;
+ // if no protocol, then we're reading from the mem or disk cache
+ // and we don't want to end the offline download just yet.
+ if (aProtocol) {
+ EndOfflineDownload();
+ m_downloadingFolderForOfflineUse = false;
+ }
+ nsCOMPtr<nsIImapUrl> imapUrl(do_QueryInterface(aUrl));
+ if (imapUrl) {
+ nsImapAction imapAction;
+ imapUrl->GetImapAction(&imapAction);
+ // if the server doesn't support copyUID, then SetCopyResponseUid won't
+ // get called, so we need to clear m_pendingOfflineMoves when the online
+ // move operation has finished.
+ if (imapAction == nsIImapUrl::nsImapOnlineMove)
+ m_pendingOfflineMoves.Clear();
+ }
+ }
+ if (aUrl && !aSuspend) return aUrl->SetUrlState(isRunning, statusCode);
+ return statusCode;
+}
+
+// used when copying from local mail folder, or other imap server)
+nsresult nsImapMailFolder::CopyMessagesWithStream(
+ nsIMsgFolder* srcFolder, nsTArray<RefPtr<nsIMsgDBHdr>> const& messages,
+ bool isMove, bool isCrossServerOp, nsIMsgWindow* msgWindow,
+ nsIMsgCopyServiceListener* listener, bool allowUndo) {
+ NS_ENSURE_ARG_POINTER(srcFolder);
+ nsresult rv;
+ rv = InitCopyState(srcFolder, messages, isMove, false, isCrossServerOp, 0,
+ EmptyCString(), listener, msgWindow, allowUndo);
+ if (NS_FAILED(rv)) return rv;
+
+ m_copyState->m_streamCopy = true;
+
+ // ** jt - needs to create server to server move/copy undo msg txn
+ if (m_copyState->m_allowUndo) {
+ nsAutoCString messageIds;
+ nsTArray<nsMsgKey> srcKeyArray;
+ rv = BuildIdsAndKeyArray(messages, messageIds, srcKeyArray);
+
+ RefPtr<nsImapMoveCopyMsgTxn> undoMsgTxn = new nsImapMoveCopyMsgTxn;
+
+ if (!undoMsgTxn ||
+ NS_FAILED(undoMsgTxn->Init(srcFolder, &srcKeyArray, messageIds.get(),
+ this, true, isMove)))
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ if (isMove) {
+ if (mFlags & nsMsgFolderFlags::Trash)
+ undoMsgTxn->SetTransactionType(nsIMessenger::eDeleteMsg);
+ else
+ undoMsgTxn->SetTransactionType(nsIMessenger::eMoveMsg);
+ } else
+ undoMsgTxn->SetTransactionType(nsIMessenger::eCopyMsg);
+ m_copyState->m_undoMsgTxn = undoMsgTxn;
+ }
+ if (NS_SUCCEEDED(rv)) CopyStreamMessage(messages[0], this, msgWindow, isMove);
+ return rv; // we are clearing copy state in CopyMessages on failure
+}
+
+nsresult nsImapMailFolder::GetClearedOriginalOp(
+ nsIMsgOfflineImapOperation* op, nsIMsgOfflineImapOperation** originalOp,
+ nsIMsgDatabase** originalDB) {
+ nsCOMPtr<nsIMsgOfflineImapOperation> returnOp;
+ nsOfflineImapOperationType opType;
+ op->GetOperation(&opType);
+ NS_ASSERTION(opType & nsIMsgOfflineImapOperation::kMoveResult,
+ "not an offline move op");
+
+ nsCString sourceFolderURI;
+ op->GetSourceFolderURI(sourceFolderURI);
+
+ nsresult rv;
+ nsCOMPtr<nsIMsgFolder> sourceFolder;
+ rv = GetOrCreateFolder(sourceFolderURI, getter_AddRefs(sourceFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIDBFolderInfo> folderInfo;
+ sourceFolder->GetDBFolderInfoAndDB(getter_AddRefs(folderInfo), originalDB);
+ if (*originalDB) {
+ nsCOMPtr<nsIMsgOfflineOpsDatabase> opsDb =
+ do_QueryInterface(*originalDB, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsMsgKey originalKey;
+ op->GetMessageKey(&originalKey);
+ rv =
+ opsDb->GetOfflineOpForKey(originalKey, false, getter_AddRefs(returnOp));
+ if (NS_SUCCEEDED(rv) && returnOp) {
+ nsCString moveDestination;
+ nsCString thisFolderURI;
+ GetURI(thisFolderURI);
+ returnOp->GetDestinationFolderURI(moveDestination);
+ if (moveDestination.Equals(thisFolderURI))
+ returnOp->ClearOperation(nsIMsgOfflineImapOperation::kMoveResult);
+ }
+ }
+ returnOp.forget(originalOp);
+ return rv;
+}
+
+nsresult nsImapMailFolder::GetOriginalOp(
+ nsIMsgOfflineImapOperation* op, nsIMsgOfflineImapOperation** originalOp,
+ nsIMsgDatabase** originalDB) {
+ nsCOMPtr<nsIMsgOfflineImapOperation> returnOp;
+ nsCString sourceFolderURI;
+ op->GetSourceFolderURI(sourceFolderURI);
+
+ nsresult rv;
+ nsCOMPtr<nsIMsgFolder> sourceFolder;
+ rv = GetOrCreateFolder(sourceFolderURI, getter_AddRefs(sourceFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIDBFolderInfo> folderInfo;
+ sourceFolder->GetDBFolderInfoAndDB(getter_AddRefs(folderInfo), originalDB);
+ if (*originalDB) {
+ nsCOMPtr<nsIMsgOfflineOpsDatabase> opsDb =
+ do_QueryInterface(*originalDB, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsMsgKey originalKey;
+ op->GetMessageKey(&originalKey);
+ rv =
+ opsDb->GetOfflineOpForKey(originalKey, false, getter_AddRefs(returnOp));
+ }
+ returnOp.forget(originalOp);
+ return rv;
+}
+
+nsresult nsImapMailFolder::FindOpenRange(nsMsgKey& fakeBase,
+ uint32_t srcCount) {
+ nsresult rv = GetDatabase();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsMsgKey newBase = fakeBase - 1;
+ uint32_t freeCount = 0;
+ while (freeCount != srcCount && newBase > 0) {
+ bool containsKey;
+ if (NS_SUCCEEDED(mDatabase->ContainsKey(newBase, &containsKey)) &&
+ !containsKey)
+ freeCount++;
+ else
+ freeCount = 0;
+ newBase--;
+ }
+ if (!newBase) return NS_ERROR_FAILURE;
+ fakeBase = newBase;
+ return NS_OK;
+}
+
+// Helper to synchronously copy a message from one msgStore to another.
+static nsresult CopyStoreMessage(nsIMsgDBHdr* srcHdr, nsIMsgDBHdr* destHdr,
+ uint64_t& bytesCopied) {
+ nsresult rv;
+
+ // Boilerplate setup.
+ nsCOMPtr<nsIMsgFolder> srcFolder;
+ rv = srcHdr->GetFolder(getter_AddRefs(srcFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIMsgFolder> destFolder;
+ rv = destHdr->GetFolder(getter_AddRefs(destFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIMsgPluggableStore> destStore;
+ rv = destFolder->GetMsgStore(getter_AddRefs(destStore));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Copy message into the msgStore.
+ nsCOMPtr<nsIInputStream> srcStream;
+ rv = srcFolder->GetLocalMsgStream(srcHdr, getter_AddRefs(srcStream));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIOutputStream> destStream;
+ rv = destFolder->GetOfflineStoreOutputStream(destHdr,
+ getter_AddRefs(destStream));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = SyncCopyStream(srcStream, destStream, bytesCopied);
+ if (NS_SUCCEEDED(rv)) {
+ rv = destStore->FinishNewMessage(destStream, destHdr);
+ } else {
+ destStore->DiscardNewMessage(destStream, destHdr);
+ }
+ return rv;
+}
+
+// This imap folder is the destination of an offline move/copy.
+// We are either offline, or doing a pseudo-offline delete (where we do an
+// offline delete, load the next message, then playback the offline delete).
+nsresult nsImapMailFolder::CopyMessagesOffline(
+ nsIMsgFolder* srcFolder, nsTArray<RefPtr<nsIMsgDBHdr>> const& messages,
+ bool isMove, nsIMsgWindow* msgWindow, nsIMsgCopyServiceListener* listener) {
+ nsresult rv;
+ nsresult stopit = NS_OK;
+ nsCOMPtr<nsIMsgDatabase> sourceMailDB;
+ nsCOMPtr<nsIDBFolderInfo> srcDbFolderInfo;
+ srcFolder->GetDBFolderInfoAndDB(getter_AddRefs(srcDbFolderInfo),
+ getter_AddRefs(sourceMailDB));
+ bool deleteToTrash = false;
+ bool deleteImmediately = false;
+ uint32_t srcCount = messages.Length();
+ nsCOMPtr<nsIImapIncomingServer> imapServer;
+ rv = GetImapIncomingServer(getter_AddRefs(imapServer));
+
+ nsTArray<RefPtr<nsIMsgDBHdr>> msgHdrsCopied;
+ nsTArray<RefPtr<nsIMsgDBHdr>> destMsgHdrs;
+
+ if (NS_SUCCEEDED(rv) && imapServer) {
+ nsMsgImapDeleteModel deleteModel;
+ imapServer->GetDeleteModel(&deleteModel);
+ deleteToTrash = (deleteModel == nsMsgImapDeleteModels::MoveToTrash);
+ deleteImmediately = (deleteModel == nsMsgImapDeleteModels::DeleteNoTrash);
+ }
+
+ // This array is used only when we are actually removing the messages from the
+ // source database.
+ nsTArray<nsMsgKey> keysToDelete(
+ (isMove && (deleteToTrash || deleteImmediately)) ? srcCount : 0);
+
+ if (sourceMailDB) {
+ // save the future ops in the source DB, if this is not a imap->local
+ // copy/move
+ nsCOMPtr<nsITransactionManager> txnMgr;
+ if (msgWindow) msgWindow->GetTransactionManager(getter_AddRefs(txnMgr));
+ if (txnMgr) txnMgr->BeginBatch(nullptr);
+ nsCOMPtr<nsIMsgDatabase> database;
+ GetMsgDatabase(getter_AddRefs(database));
+ if (database) {
+ // get the highest key in the dest db, so we can make up our fake keys
+ nsMsgKey fakeBase = 1;
+ nsCOMPtr<nsIDBFolderInfo> folderInfo;
+ rv = database->GetDBFolderInfo(getter_AddRefs(folderInfo));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsMsgKey highWaterMark = nsMsgKey_None;
+ folderInfo->GetHighWater(&highWaterMark);
+ fakeBase += highWaterMark;
+ nsMsgKey fakeTop = fakeBase + srcCount;
+ // Check that we have enough room for the fake headers. If fakeTop
+ // is <= highWaterMark, we've overflowed.
+ if (fakeTop <= highWaterMark || fakeTop == nsMsgKey_None) {
+ rv = FindOpenRange(fakeBase, srcCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ // N.B. We must not return out of the for loop - we need the matching
+ // end notifications to be sent.
+ // We don't need to acquire the semaphor since this is synchronous
+ // on the UI thread but we should check if the offline store is locked.
+ bool isLocked;
+ GetLocked(&isLocked);
+ nsTArray<nsMsgKey> addedKeys;
+ nsTArray<nsMsgKey> srcKeyArray;
+ nsCOMArray<nsIMsgDBHdr> addedHdrs;
+ nsCOMArray<nsIMsgDBHdr> srcMsgs;
+ nsOfflineImapOperationType moveCopyOpType;
+ nsOfflineImapOperationType deleteOpType =
+ nsIMsgOfflineImapOperation::kDeletedMsg;
+ if (!deleteToTrash)
+ deleteOpType = nsIMsgOfflineImapOperation::kMsgMarkedDeleted;
+ nsCString messageIds;
+ rv = BuildIdsAndKeyArray(messages, messageIds, srcKeyArray);
+ // put fake message in destination db, delete source if move
+ EnableNotifications(nsIMsgFolder::allMessageCountNotifications, false);
+ nsCString originalSrcFolderURI;
+ srcFolder->GetURI(originalSrcFolderURI);
+ nsCOMPtr<nsIMsgOfflineOpsDatabase> opsDb =
+ do_QueryInterface(sourceMailDB, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ for (uint32_t sourceKeyIndex = 0;
+ NS_SUCCEEDED(stopit) && (sourceKeyIndex < srcCount);
+ sourceKeyIndex++) {
+ bool messageReturningHome = false;
+ RefPtr<nsIMsgDBHdr> message = messages[sourceKeyIndex];
+ nsMsgKey originalKey;
+ if (message) {
+ rv = message->GetMessageKey(&originalKey);
+ } else {
+ NS_ERROR("bad msg in src array");
+ continue;
+ }
+ nsCOMPtr<nsIMsgOfflineImapOperation> sourceOp;
+ rv = opsDb->GetOfflineOpForKey(originalKey, true,
+ getter_AddRefs(sourceOp));
+ if (NS_SUCCEEDED(rv) && sourceOp) {
+ srcFolder->SetFlag(nsMsgFolderFlags::OfflineEvents);
+ nsCOMPtr<nsIMsgDatabase> originalDB;
+ nsOfflineImapOperationType opType;
+ sourceOp->GetOperation(&opType);
+ // if we already have an offline op for this key, then we need to see
+ // if it was moved into the source folder while offline
+ if (opType ==
+ nsIMsgOfflineImapOperation::kMoveResult) // offline move
+ {
+ // gracious me, we are moving something we already moved while
+ // offline! find the original operation and clear it!
+ nsCOMPtr<nsIMsgOfflineImapOperation> originalOp;
+ GetClearedOriginalOp(sourceOp, getter_AddRefs(originalOp),
+ getter_AddRefs(originalDB));
+ nsCOMPtr<nsIMsgOfflineOpsDatabase> opsDbOriginal =
+ do_QueryInterface(originalDB, &rv);
+ if (NS_SUCCEEDED(rv) && originalOp) {
+ nsCString srcFolderURI;
+ srcFolder->GetURI(srcFolderURI);
+ sourceOp->GetSourceFolderURI(originalSrcFolderURI);
+ sourceOp->GetMessageKey(&originalKey);
+ if (isMove) opsDb->RemoveOfflineOp(sourceOp);
+ sourceOp = originalOp;
+ if (originalSrcFolderURI.Equals(srcFolderURI)) {
+ messageReturningHome = true;
+ opsDbOriginal->RemoveOfflineOp(originalOp);
+ }
+ }
+ }
+ if (!messageReturningHome) {
+ nsCString folderURI;
+ GetURI(folderURI);
+ if (isMove) {
+ uint32_t msgSize;
+ uint32_t msgFlags;
+ imapMessageFlagsType newImapFlags = 0;
+ message->GetMessageSize(&msgSize);
+ message->GetFlags(&msgFlags);
+ sourceOp->SetDestinationFolderURI(folderURI); // offline move
+ sourceOp->SetOperation(nsIMsgOfflineImapOperation::kMsgMoved);
+ sourceOp->SetMsgSize(msgSize);
+ newImapFlags = msgFlags & 0x7;
+ if (msgFlags & nsMsgMessageFlags::Forwarded)
+ newImapFlags |= kImapMsgForwardedFlag;
+ sourceOp->SetNewFlags(newImapFlags);
+ } else {
+ sourceOp->AddMessageCopyOperation(folderURI); // offline copy
+ }
+
+ sourceOp->GetOperation(&moveCopyOpType);
+ srcMsgs.AppendObject(message);
+ }
+ } else {
+ stopit = NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIMsgDBHdr> mailHdr;
+ rv =
+ sourceMailDB->GetMsgHdrForKey(originalKey, getter_AddRefs(mailHdr));
+
+ if (NS_SUCCEEDED(rv) && mailHdr) {
+ // Copy the DB hdr into the destination folder.
+ bool successfulCopy = false;
+ nsMsgKey srcDBhighWaterMark;
+ srcDbFolderInfo->GetHighWater(&srcDBhighWaterMark);
+
+ nsCOMPtr<nsIMsgDBHdr> newMailHdr;
+ rv = database->CopyHdrFromExistingHdr(fakeBase + sourceKeyIndex,
+ mailHdr, true,
+ getter_AddRefs(newMailHdr));
+ if (!newMailHdr || NS_FAILED(rv)) {
+ NS_ASSERTION(false, "failed to copy hdr");
+ stopit = rv;
+ }
+
+ if (NS_SUCCEEDED(stopit)) {
+ bool hasMsgOffline = false;
+
+ destMsgHdrs.AppendElement(newMailHdr);
+ srcFolder->HasMsgOffline(originalKey, &hasMsgOffline);
+ newMailHdr->SetUint32Property("pseudoHdr", 1);
+
+ if (hasMsgOffline && !isLocked) {
+ uint64_t bytesCopied;
+ stopit = CopyStoreMessage(mailHdr, newMailHdr, bytesCopied);
+ if (NS_SUCCEEDED(stopit)) {
+ uint32_t unused;
+ newMailHdr->OrFlags(nsMsgMessageFlags::Offline, &unused);
+ newMailHdr->SetOfflineMessageSize(bytesCopied);
+ }
+ } else {
+ database->MarkOffline(fakeBase + sourceKeyIndex, false, nullptr);
+ }
+
+ nsCOMPtr<nsIMsgOfflineOpsDatabase> opsDb =
+ do_QueryInterface(database, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgOfflineImapOperation> destOp;
+ opsDb->GetOfflineOpForKey(fakeBase + sourceKeyIndex, true,
+ getter_AddRefs(destOp));
+ if (destOp) {
+ // check if this is a move back to the original mailbox, in which
+ // case we just delete the offline operation.
+ if (messageReturningHome) {
+ opsDb->RemoveOfflineOp(destOp);
+ } else {
+ SetFlag(nsMsgFolderFlags::OfflineEvents);
+ destOp->SetSourceFolderURI(originalSrcFolderURI);
+ destOp->SetSrcMessageKey(originalKey);
+ addedKeys.AppendElement(fakeBase + sourceKeyIndex);
+ addedHdrs.AppendObject(newMailHdr);
+ }
+ } else {
+ stopit = NS_ERROR_FAILURE;
+ }
+ }
+ successfulCopy = NS_SUCCEEDED(stopit);
+ nsMsgKey msgKey;
+ mailHdr->GetMessageKey(&msgKey);
+ if (isMove && successfulCopy) {
+ if (deleteToTrash || deleteImmediately)
+ keysToDelete.AppendElement(msgKey);
+ else
+ sourceMailDB->MarkImapDeleted(msgKey, true,
+ nullptr); // offline delete
+ }
+ if (successfulCopy) {
+ // This is for both moves and copies
+ msgHdrsCopied.AppendElement(mailHdr);
+ }
+ }
+ } // End message loop.
+ EnableNotifications(nsIMsgFolder::allMessageCountNotifications, true);
+ RefPtr<nsImapOfflineTxn> addHdrMsgTxn = new nsImapOfflineTxn(
+ this, &addedKeys, nullptr, this, isMove,
+ nsIMsgOfflineImapOperation::kAddedHeader, addedHdrs);
+ if (addHdrMsgTxn && txnMgr) txnMgr->DoTransaction(addHdrMsgTxn);
+ RefPtr<nsImapOfflineTxn> undoMsgTxn =
+ new nsImapOfflineTxn(srcFolder, &srcKeyArray, messageIds.get(), this,
+ isMove, moveCopyOpType, srcMsgs);
+ if (undoMsgTxn) {
+ if (isMove) {
+ undoMsgTxn->SetTransactionType(nsIMessenger::eMoveMsg);
+ nsCOMPtr<nsIMsgImapMailFolder> srcIsImap(
+ do_QueryInterface(srcFolder));
+ // remember this undo transaction so we can hook up the result
+ // msg ids in the undo transaction.
+ if (srcIsImap) {
+ nsImapMailFolder* srcImapFolder =
+ static_cast<nsImapMailFolder*>(srcFolder);
+ srcImapFolder->m_pendingOfflineMoves.AppendElement(undoMsgTxn);
+ }
+ } else {
+ undoMsgTxn->SetTransactionType(nsIMessenger::eCopyMsg);
+ }
+ // we're adding this undo action before the delete is successful. This
+ // is evil, but 4.5 did it as well.
+ if (txnMgr) txnMgr->DoTransaction(undoMsgTxn);
+ }
+ undoMsgTxn =
+ new nsImapOfflineTxn(srcFolder, &srcKeyArray, messageIds.get(), this,
+ isMove, deleteOpType, srcMsgs);
+ if (undoMsgTxn) {
+ if (isMove) {
+ if (mFlags & nsMsgFolderFlags::Trash) {
+ undoMsgTxn->SetTransactionType(nsIMessenger::eDeleteMsg);
+ } else {
+ undoMsgTxn->SetTransactionType(nsIMessenger::eMoveMsg);
+ }
+ } else {
+ undoMsgTxn->SetTransactionType(nsIMessenger::eCopyMsg);
+ }
+ if (txnMgr) txnMgr->DoTransaction(undoMsgTxn);
+ }
+
+ if (isMove) sourceMailDB->Commit(nsMsgDBCommitType::kLargeCommit);
+ database->Commit(nsMsgDBCommitType::kLargeCommit);
+ SummaryChanged();
+ srcFolder->SummaryChanged();
+ }
+ if (txnMgr) txnMgr->EndBatch(false);
+ }
+
+ if (!msgHdrsCopied.IsEmpty()) {
+ nsCOMPtr<nsIMsgFolderNotificationService> notifier(
+ do_GetService("@mozilla.org/messenger/msgnotificationservice;1"));
+ if (notifier) {
+ notifier->NotifyMsgsMoveCopyCompleted(isMove, msgHdrsCopied, this,
+ destMsgHdrs);
+ }
+ }
+
+ // NOTE (Bug 1787963):
+ // If we're performing a move, by rights we should be deleting the source
+ // message(s) here. But that would mean they won't be available when we try
+ // to run the offline move operation once we're back online. So we'll just
+ // leave things as they are:
+ // - the message(s) copied into the destination folder
+ // - the original message(s) left in the source folder
+ // - the offline move operation all queued up for when we go back online
+ // When we do go back online, the offline move op will be performed and
+ // the source message(s) will be deleted. For real.
+ // Would be nice to have some marker to hide or grey out messages which are
+ // in this state of impending doom... but it's a pretty obscure corner case
+ // and we've already got quite enough of those.
+ //
+ // BUT... CopyMessagesOffline() is also used when online (ha!), *if* we're
+ // copying between folders on the same nsIMsgIncomingServer, in order to
+ // support undo. In that case we _do_ want to go ahead with the delete now.
+
+ bool sameServer;
+ rv = IsOnSameServer(srcFolder, this, &sameServer);
+
+ if (NS_SUCCEEDED(rv) && sameServer && isMove &&
+ (deleteToTrash || deleteImmediately)) {
+ DeleteStoreMessages(keysToDelete, srcFolder);
+ srcFolder->EnableNotifications(nsIMsgFolder::allMessageCountNotifications,
+ false);
+ sourceMailDB->DeleteMessages(keysToDelete, nullptr);
+ srcFolder->EnableNotifications(nsIMsgFolder::allMessageCountNotifications,
+ true);
+ }
+ nsCOMPtr<nsISupports> srcSupport = do_QueryInterface(srcFolder);
+ OnCopyCompleted(srcSupport, rv);
+
+ if (isMove) {
+ srcFolder->NotifyFolderEvent(NS_SUCCEEDED(rv) ? kDeleteOrMoveMsgCompleted
+ : kDeleteOrMoveMsgFailed);
+ }
+ return rv;
+}
+
+void nsImapMailFolder::SetPendingAttributes(
+ const nsTArray<RefPtr<nsIMsgDBHdr>>& messages, bool aIsMove,
+ bool aSetOffline) {
+ GetDatabase();
+ if (!mDatabase) return;
+
+ uint32_t supportedUserFlags;
+ GetSupportedUserFlags(&supportedUserFlags);
+
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> prefBranch(
+ do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ nsCString dontPreserve;
+
+ // These preferences exist so that extensions can control which properties
+ // are preserved in the database when a message is moved or copied. All
+ // properties are preserved except those listed in these preferences
+ if (aIsMove)
+ prefBranch->GetCharPref("mailnews.database.summary.dontPreserveOnMove",
+ dontPreserve);
+ else
+ prefBranch->GetCharPref("mailnews.database.summary.dontPreserveOnCopy",
+ dontPreserve);
+
+ // We'll add spaces at beginning and end so we can search for space-name-space
+ nsCString dontPreserveEx(" "_ns);
+ dontPreserveEx.Append(dontPreserve);
+ dontPreserveEx.Append(' ');
+
+ // these properties are set as integers below, so don't set them again
+ // in the iteration through the properties
+ dontPreserveEx.AppendLiteral(
+ "offlineMsgSize msgOffset flags priority pseudoHdr ");
+
+ // these fields are either copied separately when the server does not support
+ // custom IMAP flags, or managed directly through the flags
+ dontPreserveEx.AppendLiteral("keywords label ");
+
+ // check if any msg hdr has special flags or properties set
+ // that we need to set on the dest hdr
+ for (auto msgDBHdr : messages) {
+ if (!(supportedUserFlags & kImapMsgSupportUserFlag)) {
+ nsCString keywords;
+ msgDBHdr->GetStringProperty("keywords", keywords);
+ if (!keywords.IsEmpty())
+ mDatabase->SetAttributeOnPendingHdr(msgDBHdr, "keywords",
+ keywords.get());
+ }
+
+ nsTArray<nsCString> properties;
+ nsresult rv = msgDBHdr->GetProperties(properties);
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ nsCString sourceString;
+ for (auto property : properties) {
+ nsAutoCString propertyEx(" "_ns);
+ propertyEx.Append(property);
+ propertyEx.Append(' ');
+ if (dontPreserveEx.Find(propertyEx) != kNotFound) continue;
+
+ nsCString sourceString;
+ msgDBHdr->GetStringProperty(property.get(), sourceString);
+ mDatabase->SetAttributeOnPendingHdr(msgDBHdr, property.get(),
+ sourceString.get());
+ }
+
+ // Carry over HasRe flag.
+ uint32_t flags;
+ uint32_t storeFlags = 0;
+ msgDBHdr->GetFlags(&flags);
+ if (flags & nsMsgMessageFlags::HasRe) {
+ storeFlags = nsMsgMessageFlags::HasRe;
+ mDatabase->SetUint32AttributeOnPendingHdr(msgDBHdr, "flags", storeFlags);
+ }
+
+ uint32_t messageSize;
+ uint64_t messageOffset;
+ nsCString storeToken;
+ msgDBHdr->GetMessageOffset(&messageOffset);
+ msgDBHdr->GetOfflineMessageSize(&messageSize);
+ msgDBHdr->GetStringProperty("storeToken", storeToken);
+ if (messageSize) {
+ mDatabase->SetUint32AttributeOnPendingHdr(msgDBHdr, "offlineMsgSize",
+ messageSize);
+ mDatabase->SetUint64AttributeOnPendingHdr(msgDBHdr, "msgOffset",
+ messageOffset);
+ // Not always setting "flags" attribute to nsMsgMessageFlags::Offline
+ // here because it can cause missing parts (inline or attachments)
+ // when messages are moved or copied manually or by filter action.
+ if (aSetOffline)
+ mDatabase->SetUint32AttributeOnPendingHdr(
+ msgDBHdr, "flags", storeFlags | nsMsgMessageFlags::Offline);
+ mDatabase->SetAttributeOnPendingHdr(msgDBHdr, "storeToken",
+ storeToken.get());
+ }
+ nsMsgPriorityValue priority;
+ msgDBHdr->GetPriority(&priority);
+ if (priority != 0) {
+ nsAutoCString priorityStr;
+ priorityStr.AppendInt(priority);
+ mDatabase->SetAttributeOnPendingHdr(msgDBHdr, "priority",
+ priorityStr.get());
+ }
+ }
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::CopyMessages(
+ nsIMsgFolder* srcFolder, nsTArray<RefPtr<nsIMsgDBHdr>> const& messages,
+ bool isMove, nsIMsgWindow* msgWindow, nsIMsgCopyServiceListener* listener,
+ bool isFolder, // isFolder for future use when we do cross-server folder
+ // move/copy
+ bool allowUndo) {
+ UpdateTimestamps(allowUndo);
+
+ nsresult rv;
+ nsCOMPtr<nsISupports> srcSupport = do_QueryInterface(srcFolder);
+
+ bool sameServer;
+ rv = IsOnSameServer(srcFolder, this, &sameServer);
+ if (NS_FAILED(rv)) goto done;
+
+ // in theory, if allowUndo is true, then this is a user initiated
+ // action, and we should do it pseudo-offline. If it's not
+ // user initiated (e.g., mail filters firing), then allowUndo is
+ // false, and we should just do the action.
+ if (!WeAreOffline() && sameServer && allowUndo) {
+ // complete the copy operation as in offline mode
+ rv = CopyMessagesOffline(srcFolder, messages, isMove, msgWindow, listener);
+
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "error offline copy");
+ // We'll warn if this fails, but we should still try to play back
+ // offline ops, because it's possible the copy got far enough to
+ // create the offline ops.
+
+ // We make sure that the source folder is an imap folder by limiting
+ // pseudo-offline operations to the same imap server. If we extend the code
+ // to cover non imap folders in the future (i.e. imap folder->local folder),
+ // then the following downcast will cause either a crash or compiler error.
+ // Do not forget to change it accordingly.
+ nsImapMailFolder* srcImapFolder = static_cast<nsImapMailFolder*>(srcFolder);
+
+ // if there is no pending request, create a new one, and set the timer.
+ // Otherwise use the existing one to reset the timer. it is callback
+ // function's responsibility to delete the new request object
+ if (!srcImapFolder->m_pendingPlaybackReq) {
+ srcImapFolder->m_pendingPlaybackReq =
+ new nsPlaybackRequest(srcImapFolder, msgWindow);
+ }
+
+ // Create and start a new playback one-shot timer. If there is already a
+ // timer created that has not timed out, cancel it.
+ if (srcImapFolder->m_playbackTimer)
+ srcImapFolder->m_playbackTimer->Cancel();
+ rv = NS_NewTimerWithFuncCallback(
+ getter_AddRefs(srcImapFolder->m_playbackTimer), PlaybackTimerCallback,
+ (void*)srcImapFolder->m_pendingPlaybackReq,
+ PLAYBACK_TIMER_INTERVAL_IN_MS, nsITimer::TYPE_ONE_SHOT,
+ "nsImapMailFolder::PlaybackTimerCallback", nullptr);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Could not start m_playbackTimer timer");
+ }
+ return rv;
+ } else {
+ // sort the message array by key
+
+ nsTArray<nsMsgKey> keyArray(messages.Length());
+ for (nsIMsgDBHdr* aMessage : messages) {
+ if (!aMessage) {
+ continue;
+ }
+ nsMsgKey key;
+ aMessage->GetMessageKey(&key);
+ keyArray.AppendElement(key);
+ }
+ keyArray.Sort();
+
+ nsTArray<RefPtr<nsIMsgDBHdr>> sortedMsgs;
+ rv = MessagesInKeyOrder(keyArray, srcFolder, sortedMsgs);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (WeAreOffline())
+ return CopyMessagesOffline(srcFolder, sortedMsgs, isMove, msgWindow,
+ listener);
+
+ nsCOMPtr<nsIImapService> imapService =
+ do_GetService("@mozilla.org/messenger/imapservice;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // 3rd parameter: Do not set offline flag.
+ SetPendingAttributes(sortedMsgs, isMove, false);
+
+ // if the folders aren't on the same server, do a stream base copy
+ if (!sameServer) {
+ rv = CopyMessagesWithStream(srcFolder, sortedMsgs, isMove, true,
+ msgWindow, listener, allowUndo);
+ goto done;
+ }
+
+ nsAutoCString messageIds;
+ rv = AllocateUidStringFromKeys(keyArray, messageIds);
+ if (NS_FAILED(rv)) goto done;
+
+ nsCOMPtr<nsIUrlListener> urlListener;
+ rv =
+ QueryInterface(NS_GET_IID(nsIUrlListener), getter_AddRefs(urlListener));
+ rv = InitCopyState(srcSupport, sortedMsgs, isMove, true, false, 0,
+ EmptyCString(), listener, msgWindow, allowUndo);
+ if (NS_FAILED(rv)) goto done;
+
+ m_copyState->m_curIndex = m_copyState->m_messages.Length();
+
+ if (isMove)
+ srcFolder->EnableNotifications(
+ allMessageCountNotifications,
+ false); // disable message count notification
+
+ nsCOMPtr<nsIURI> resultUrl;
+ nsCOMPtr<nsISupports> copySupport = do_QueryInterface(m_copyState);
+ rv = imapService->OnlineMessageCopy(
+ srcFolder, messageIds, this, true, isMove, urlListener,
+ getter_AddRefs(resultUrl), copySupport, msgWindow);
+ if (NS_SUCCEEDED(rv) && m_copyState->m_allowUndo) {
+ RefPtr<nsImapMoveCopyMsgTxn> undoMsgTxn = new nsImapMoveCopyMsgTxn;
+ if (!undoMsgTxn ||
+ NS_FAILED(undoMsgTxn->Init(srcFolder, &keyArray, messageIds.get(),
+ this, true, isMove)))
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ if (isMove) {
+ if (mFlags & nsMsgFolderFlags::Trash)
+ undoMsgTxn->SetTransactionType(nsIMessenger::eDeleteMsg);
+ else
+ undoMsgTxn->SetTransactionType(nsIMessenger::eMoveMsg);
+ } else
+ undoMsgTxn->SetTransactionType(nsIMessenger::eCopyMsg);
+ m_copyState->m_undoMsgTxn = undoMsgTxn;
+ }
+
+ } // endif
+
+done:
+ if (NS_FAILED(rv)) {
+ (void)OnCopyCompleted(srcSupport, rv);
+ if (isMove) {
+ srcFolder->EnableNotifications(
+ allMessageCountNotifications,
+ true); // enable message count notification
+ NotifyFolderEvent(kDeleteOrMoveMsgFailed);
+ }
+ }
+ return rv;
+}
+
+// This is used when copying an imap or local/pop3 folder to an imap server.
+// It does not allow completely moving an imap or local/pop3 folder to an imap
+// server since only the messages can be moved between servers.
+class nsImapFolderCopyState final : public nsIUrlListener,
+ public nsIMsgCopyServiceListener {
+ public:
+ nsImapFolderCopyState(nsIMsgFolder* destParent, nsIMsgFolder* srcFolder,
+ bool isMoveMessages, nsIMsgWindow* msgWindow,
+ nsIMsgCopyServiceListener* listener);
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIURLLISTENER
+ NS_DECL_NSIMSGCOPYSERVICELISTENER
+
+ nsresult StartNextCopy();
+ nsresult AdvanceToNextFolder(nsresult aStatus);
+
+ protected:
+ ~nsImapFolderCopyState();
+ RefPtr<nsImapMailFolder> m_newDestFolder;
+ nsCOMPtr<nsISupports> m_origSrcFolder;
+ nsCOMPtr<nsIMsgFolder> m_curDestParent;
+ nsCOMPtr<nsIMsgFolder> m_curSrcFolder;
+ bool m_isMoveMessages;
+ nsCOMPtr<nsIMsgCopyServiceListener> m_copySrvcListener;
+ nsCOMPtr<nsIMsgWindow> m_msgWindow;
+ int32_t m_childIndex;
+ nsCOMArray<nsIMsgFolder> m_srcChildFolders;
+ nsCOMArray<nsIMsgFolder> m_destParents;
+};
+
+NS_IMPL_ISUPPORTS(nsImapFolderCopyState, nsIUrlListener,
+ nsIMsgCopyServiceListener)
+
+nsImapFolderCopyState::nsImapFolderCopyState(
+ nsIMsgFolder* destParent, nsIMsgFolder* srcFolder, bool isMoveMessages,
+ nsIMsgWindow* msgWindow, nsIMsgCopyServiceListener* listener) {
+ m_origSrcFolder = do_QueryInterface(srcFolder);
+ m_curDestParent = destParent;
+ m_curSrcFolder = srcFolder;
+ m_isMoveMessages = isMoveMessages;
+ m_msgWindow = msgWindow;
+ m_copySrvcListener = listener;
+ m_childIndex = -1;
+ // NOTE: The nsImapMailFolder doesn't keep a reference to us, so we're
+ // relying on our use as a listener by nsImapService and nsMsgCopyService
+ // to keep our refcount from zeroing!
+ // Might be safer to add a kungfudeathgrip on ourselves for the duration
+ // of the operation? Would need to make sure we catch all error conditions.
+}
+
+nsImapFolderCopyState::~nsImapFolderCopyState() {}
+
+nsresult nsImapFolderCopyState::StartNextCopy() {
+ nsresult rv;
+ // Create the destination folder (our OnStopRunningUrl() will be called
+ // when done).
+ nsCOMPtr<nsIImapService> imapService =
+ do_GetService("@mozilla.org/messenger/imapservice;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsString folderName;
+ m_curSrcFolder->GetName(folderName);
+ return imapService->EnsureFolderExists(m_curDestParent, folderName,
+ m_msgWindow, this);
+}
+
+nsresult nsImapFolderCopyState::AdvanceToNextFolder(nsresult aStatus) {
+ nsresult rv = NS_OK;
+ m_childIndex++;
+ if (m_childIndex >= m_srcChildFolders.Count()) {
+ if (m_newDestFolder)
+ m_newDestFolder->OnCopyCompleted(m_origSrcFolder, aStatus);
+ } else {
+ m_curDestParent = m_destParents[m_childIndex];
+ m_curSrcFolder = m_srcChildFolders[m_childIndex];
+ rv = StartNextCopy();
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsImapFolderCopyState::OnStartRunningUrl(nsIURI* aUrl) {
+ NS_ASSERTION(aUrl, "sanity check - need to be be running non-null url");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapFolderCopyState::OnStopRunningUrl(nsIURI* aUrl, nsresult aExitCode) {
+ if (NS_FAILED(aExitCode)) {
+ if (m_copySrvcListener) m_copySrvcListener->OnStopCopy(aExitCode);
+ return aExitCode; // or NS_OK???
+ }
+ nsresult rv = NS_OK;
+ if (aUrl) {
+ nsCOMPtr<nsIImapUrl> imapUrl = do_QueryInterface(aUrl);
+ if (imapUrl) {
+ nsImapAction imapAction = nsIImapUrl::nsImapTest;
+ imapUrl->GetImapAction(&imapAction);
+
+ switch (imapAction) {
+ case nsIImapUrl::nsImapEnsureExistsFolder: {
+ // Our EnsureFolderExists() call has completed successfully,
+ // so our dest folder is ready.
+ nsCOMPtr<nsIMsgFolder> newMsgFolder;
+ nsString folderName;
+ nsCString utfLeafName;
+ m_curSrcFolder->GetName(folderName);
+ bool utf8AcceptEnabled;
+ nsCOMPtr<nsIMsgImapMailFolder> imapFolder =
+ do_QueryInterface(m_curDestParent);
+ rv = imapFolder->GetShouldUseUtf8FolderName(&utf8AcceptEnabled);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (utf8AcceptEnabled) {
+ CopyUTF16toUTF8(folderName, utfLeafName);
+ } else {
+ CopyUTF16toMUTF7(folderName, utfLeafName);
+ }
+ // Create the nsIMsgFolder object which represents the folder on
+ // the IMAP server.
+ rv = m_curDestParent->FindSubFolder(utfLeafName,
+ getter_AddRefs(newMsgFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+ // Save the first new folder so we can send a notification to the
+ // copy service when this whole process is done.
+ if (!m_newDestFolder)
+ m_newDestFolder =
+ static_cast<nsImapMailFolder*>(newMsgFolder.get());
+
+ // Check if the source folder has children. If it does, list them
+ // into m_srcChildFolders, and set m_destParents for the
+ // corresponding indexes to the newly created folder.
+ nsTArray<RefPtr<nsIMsgFolder>> subFolders;
+ rv = m_curSrcFolder->GetSubFolders(subFolders);
+ NS_ENSURE_SUCCESS(rv, rv);
+ uint32_t childIndex = 0;
+ for (nsIMsgFolder* folder : subFolders) {
+ m_srcChildFolders.InsertElementAt(m_childIndex + childIndex + 1,
+ folder);
+ m_destParents.InsertElementAt(m_childIndex + childIndex + 1,
+ newMsgFolder);
+ ++childIndex;
+ }
+
+ // Now kick off a copy (or move) of messages to the new folder.
+ nsCOMPtr<nsIMsgEnumerator> enumerator;
+ rv = m_curSrcFolder->GetMessages(getter_AddRefs(enumerator));
+ nsTArray<RefPtr<nsIMsgDBHdr>> msgArray;
+ bool hasMore = false;
+
+ if (enumerator) rv = enumerator->HasMoreElements(&hasMore);
+
+ // Early-out for empty folder.
+ if (!hasMore) return AdvanceToNextFolder(NS_OK);
+
+ while (NS_SUCCEEDED(rv) && hasMore) {
+ nsCOMPtr<nsIMsgDBHdr> hdr;
+ rv = enumerator->GetNext(getter_AddRefs(hdr));
+ NS_ENSURE_SUCCESS(rv, rv);
+ msgArray.AppendElement(hdr);
+ rv = enumerator->HasMoreElements(&hasMore);
+ }
+
+ nsCOMPtr<nsIMsgCopyService> copyService =
+ do_GetService("@mozilla.org/messenger/messagecopyservice;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = copyService->CopyMessages(m_curSrcFolder, msgArray, newMsgFolder,
+ m_isMoveMessages, this, m_msgWindow,
+ false /* allowUndo */);
+ } break;
+ }
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapFolderCopyState::OnStartCopy() { return NS_OK; }
+
+/* void OnProgress (in uint32_t aProgress, in uint32_t aProgressMax); */
+NS_IMETHODIMP nsImapFolderCopyState::OnProgress(uint32_t aProgress,
+ uint32_t aProgressMax) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/* void SetMessageKey (in nsMsgKey aKey); */
+NS_IMETHODIMP nsImapFolderCopyState::SetMessageKey(nsMsgKey aKey) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/* [noscript] void GetMessageId (in nsCString aMessageId); */
+NS_IMETHODIMP nsImapFolderCopyState::GetMessageId(nsACString& messageId) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/* void OnStopCopy (in nsresult aStatus); */
+NS_IMETHODIMP nsImapFolderCopyState::OnStopCopy(nsresult aStatus) {
+ if (NS_SUCCEEDED(aStatus)) return AdvanceToNextFolder(aStatus);
+ if (m_copySrvcListener) {
+ (void)m_copySrvcListener->OnStopCopy(aStatus);
+ m_copySrvcListener = nullptr;
+ }
+
+ return NS_OK;
+}
+
+// "this" is the destination (parent) imap folder that srcFolder is copied to.
+// srcFolder may be another imap or a local/pop3 folder.
+NS_IMETHODIMP
+nsImapMailFolder::CopyFolder(nsIMsgFolder* srcFolder, bool isMoveFolder,
+ nsIMsgWindow* msgWindow,
+ nsIMsgCopyServiceListener* listener) {
+ NS_ENSURE_ARG_POINTER(srcFolder);
+ nsresult rv;
+ bool sameServer;
+ rv = IsOnSameServer(this, srcFolder, &sameServer);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (sameServer && isMoveFolder) {
+ // Do a pure folder move within the same IMAP account/server, where
+ // "pure" means the folder AND messages are copied to the destination and
+ // then both are removed from source account.
+ uint32_t folderFlags = 0;
+ if (srcFolder) srcFolder->GetFlags(&folderFlags);
+
+ // if our source folder is a virtual folder
+ if (folderFlags & nsMsgFolderFlags::Virtual) {
+ nsCOMPtr<nsIMsgFolder> newMsgFolder;
+ nsString folderName;
+ srcFolder->GetName(folderName);
+
+ nsAutoString safeFolderName(folderName);
+ NS_MsgHashIfNecessary(safeFolderName);
+
+ srcFolder->ForceDBClosed();
+
+ nsCOMPtr<nsIFile> oldPathFile;
+ rv = srcFolder->GetFilePath(getter_AddRefs(oldPathFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> summaryFile;
+ GetSummaryFileLocation(oldPathFile, getter_AddRefs(summaryFile));
+
+ nsCOMPtr<nsIFile> newPathFile;
+ rv = GetFilePath(getter_AddRefs(newPathFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool isDirectory = false;
+ newPathFile->IsDirectory(&isDirectory);
+ if (!isDirectory) {
+ AddDirectorySeparator(newPathFile);
+ rv = newPathFile->Create(nsIFile::DIRECTORY_TYPE, 0700);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ rv = CheckIfFolderExists(folderName, this, msgWindow);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = summaryFile->CopyTo(newPathFile, EmptyString());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = AddSubfolder(safeFolderName, getter_AddRefs(newMsgFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ newMsgFolder->SetPrettyName(folderName);
+
+ uint32_t flags;
+ srcFolder->GetFlags(&flags);
+ newMsgFolder->SetFlags(flags);
+
+ NotifyFolderAdded(newMsgFolder);
+
+ // now remove the old folder
+ nsCOMPtr<nsIMsgFolder> msgParent;
+ srcFolder->GetParent(getter_AddRefs(msgParent));
+ srcFolder->SetParent(nullptr);
+ if (msgParent) {
+ // The files have already been moved, so delete storage false.
+ msgParent->PropagateDelete(srcFolder, false);
+ oldPathFile->Remove(false); // berkeley mailbox
+ srcFolder->DeleteStorage();
+
+ nsCOMPtr<nsIFile> parentPathFile;
+ rv = msgParent->GetFilePath(getter_AddRefs(parentPathFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ AddDirectorySeparator(parentPathFile);
+ nsCOMPtr<nsIDirectoryEnumerator> children;
+ parentPathFile->GetDirectoryEntries(getter_AddRefs(children));
+ bool more;
+ // checks if the directory is empty or not
+ if (children && NS_SUCCEEDED(children->HasMoreElements(&more)) && !more)
+ parentPathFile->Remove(true);
+ }
+ } else // non-virtual folder
+ {
+ nsCOMPtr<nsIImapService> imapService =
+ do_GetService("@mozilla.org/messenger/imapservice;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsISupports> srcSupport = do_QueryInterface(srcFolder);
+ bool match = false;
+ bool confirmed = false;
+ if (mFlags & nsMsgFolderFlags::Trash) {
+ rv = srcFolder->MatchOrChangeFilterDestination(nullptr, false, &match);
+ if (match) {
+ srcFolder->ConfirmFolderDeletionForFilter(msgWindow, &confirmed);
+ // should we return an error to copy service?
+ // or send a notification?
+ if (!confirmed) return NS_OK;
+ }
+ }
+ rv = InitCopyState(srcSupport, {}, false, false, false, 0, EmptyCString(),
+ listener, msgWindow, false);
+ if (NS_FAILED(rv)) return OnCopyCompleted(srcSupport, rv);
+
+ rv = imapService->MoveFolder(srcFolder, this, this, msgWindow);
+ }
+ } else {
+ // !sameServer OR it's a copy. Unit tests expect a successful folder
+ // copy within the same IMAP server even though the UI forbids copy and
+ // only allows moves inside the same server. folderCopier, set below,
+ // handles the folder copy within an IMAP server (needed by unit tests) and
+ // the folder move or copy from another account or server into an IMAP
+ // account/server. The folder move from another account is "impure" since
+ // just the messages are moved and the source folder remains in place.
+ RefPtr<nsImapFolderCopyState> folderCopier = new nsImapFolderCopyState(
+ this, srcFolder,
+ isMoveFolder, // Always copy folders; if true only move the messages
+ msgWindow, listener);
+ // NOTE: the copystate object must hold itself in existence until complete,
+ // as we're not keeping hold of it here.
+ rv = folderCopier->StartNextCopy();
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::CopyFileMessage(nsIFile* file, nsIMsgDBHdr* msgToReplace,
+ bool isDraftOrTemplate, uint32_t aNewMsgFlags,
+ const nsACString& aNewMsgKeywords,
+ nsIMsgWindow* msgWindow,
+ nsIMsgCopyServiceListener* listener) {
+ nsresult rv = NS_ERROR_NULL_POINTER;
+ nsMsgKey key = nsMsgKey_None;
+ nsAutoCString messageId;
+ nsTArray<RefPtr<nsIMsgDBHdr>> messages;
+
+ nsCOMPtr<nsIImapService> imapService =
+ do_GetService("@mozilla.org/messenger/imapservice;1", &rv);
+ if (NS_FAILED(rv)) return OnCopyCompleted(file, rv);
+
+ if (msgToReplace) {
+ rv = msgToReplace->GetMessageKey(&key);
+ if (NS_SUCCEEDED(rv)) {
+ messageId.AppendInt((int32_t)key);
+ // We have an existing message to replace because the user has deleted or
+ // detached one or more attachments. So tell SetPendingAttributes() to
+ // not set several pending offline items (offset, message size, etc.) for
+ // the message to be replaced by setting message size temporarily to zero.
+ // The original message is not actually "replaced" but is imap deleted
+ // and a new message with the same body but with some deleted or detached
+ // attachments is imap appended from the file to the folder.
+ uint32_t saveMsgSize;
+ msgToReplace->GetOfflineMessageSize(&saveMsgSize);
+ msgToReplace->SetOfflineMessageSize(0);
+ SetPendingAttributes({msgToReplace}, false, false);
+ msgToReplace->SetOfflineMessageSize(saveMsgSize);
+ messages.AppendElement(msgToReplace);
+ }
+ }
+
+ bool isMove = (msgToReplace ? true : false);
+ rv = InitCopyState(file, messages, isMove, isDraftOrTemplate, false,
+ aNewMsgFlags, aNewMsgKeywords, listener, msgWindow, false);
+ if (NS_FAILED(rv)) return OnCopyCompleted(file, rv);
+
+ m_copyState->m_streamCopy = true;
+ rv = imapService->AppendMessageFromFile(file, this, messageId, true,
+ isDraftOrTemplate, this, m_copyState,
+ msgWindow);
+ if (NS_FAILED(rv)) return OnCopyCompleted(file, rv);
+
+ return rv;
+}
+
+nsresult nsImapMailFolder::CopyStreamMessage(
+ nsIMsgDBHdr* message,
+ nsIMsgFolder* dstFolder, // should be this
+ nsIMsgWindow* aMsgWindow, bool isMove) {
+ NS_ENSURE_ARG_POINTER(message);
+ if (!m_copyState)
+ MOZ_LOG(IMAP, mozilla::LogLevel::Info,
+ ("CopyStreamMessage failed with null m_copyState"));
+ NS_ENSURE_TRUE(m_copyState, NS_ERROR_NULL_POINTER);
+ nsresult rv;
+ nsCOMPtr<nsICopyMessageStreamListener> copyStreamListener = do_CreateInstance(
+ "@mozilla.org/messenger/copymessagestreamlistener;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsICopyMessageListener> copyListener(
+ do_QueryInterface(dstFolder, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgFolder> srcFolder(
+ do_QueryInterface(m_copyState->m_srcSupport, &rv));
+ if (NS_FAILED(rv))
+ MOZ_LOG(IMAP, mozilla::LogLevel::Info,
+ ("CopyStreaMessage failed with null m_copyState->m_srcSupport"));
+ if (NS_FAILED(rv)) return rv;
+ rv = copyStreamListener->Init(copyListener);
+ if (NS_FAILED(rv))
+ MOZ_LOG(IMAP, mozilla::LogLevel::Info,
+ ("CopyStreaMessage failed in copyStreamListener->Init"));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCString uri;
+ srcFolder->GetUriForMsg(message, uri);
+
+ if (!m_copyState->m_msgService)
+ rv = GetMessageServiceFromURI(uri,
+ getter_AddRefs(m_copyState->m_msgService));
+
+ if (NS_SUCCEEDED(rv) && m_copyState->m_msgService) {
+ nsCOMPtr<nsIStreamListener> streamListener(
+ do_QueryInterface(copyStreamListener, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // put up status message here, if copying more than one message.
+ if (m_copyState->m_messages.Length() > 1) {
+ nsString dstFolderName, progressText;
+ GetName(dstFolderName);
+ nsAutoString curMsgString;
+ nsAutoString totalMsgString;
+ totalMsgString.AppendInt((int32_t)m_copyState->m_messages.Length());
+ curMsgString.AppendInt(m_copyState->m_curIndex + 1);
+
+ AutoTArray<nsString, 3> formatStrings = {curMsgString, totalMsgString,
+ dstFolderName};
+
+ nsCOMPtr<nsIStringBundle> bundle;
+ rv = IMAPGetStringBundle(getter_AddRefs(bundle));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = bundle->FormatStringFromName("imapCopyingMessageOf2", formatStrings,
+ progressText);
+ nsCOMPtr<nsIMsgStatusFeedback> statusFeedback;
+ if (m_copyState->m_msgWindow)
+ m_copyState->m_msgWindow->GetStatusFeedback(
+ getter_AddRefs(statusFeedback));
+ if (statusFeedback) {
+ statusFeedback->ShowStatusString(progressText);
+ int32_t percent;
+ percent = (100 * m_copyState->m_curIndex) /
+ (int32_t)m_copyState->m_messages.Length();
+ statusFeedback->ShowProgress(percent);
+ }
+ }
+ rv = m_copyState->m_msgService->CopyMessage(
+ uri, streamListener, isMove && !m_copyState->m_isCrossServerOp, nullptr,
+ aMsgWindow);
+ if (NS_FAILED(rv))
+ MOZ_LOG(IMAP, mozilla::LogLevel::Info,
+ ("CopyMessage failed: uri %s", uri.get()));
+ }
+ return rv;
+}
+
+nsImapMailCopyState::nsImapMailCopyState()
+ : m_isMove(false),
+ m_selectedState(false),
+ m_isCrossServerOp(false),
+ m_curIndex(0),
+ m_streamCopy(false),
+ m_dataBuffer(nullptr),
+ m_dataBufferSize(0),
+ m_leftOver(0),
+ m_allowUndo(false),
+ m_eatLF(false),
+ m_newMsgFlags(0),
+ m_appendUID(nsMsgKey_None) {}
+
+nsImapMailCopyState::~nsImapMailCopyState() {
+ PR_Free(m_dataBuffer);
+ if (m_tmpFile) m_tmpFile->Remove(false);
+}
+
+NS_IMPL_ISUPPORTS(nsImapMailCopyState, nsImapMailCopyState)
+
+nsresult nsImapMailFolder::InitCopyState(
+ nsISupports* srcSupport, nsTArray<RefPtr<nsIMsgDBHdr>> const& messages,
+ bool isMove, bool selectedState, bool acrossServers, uint32_t newMsgFlags,
+ const nsACString& newMsgKeywords, nsIMsgCopyServiceListener* listener,
+ nsIMsgWindow* msgWindow, bool allowUndo) {
+ NS_ENSURE_ARG_POINTER(srcSupport);
+
+ NS_ENSURE_TRUE(!m_copyState, NS_ERROR_FAILURE);
+
+ m_copyState = new nsImapMailCopyState();
+
+ m_copyState->m_isCrossServerOp = acrossServers;
+ m_copyState->m_srcSupport = srcSupport;
+
+ m_copyState->m_messages = messages.Clone();
+ if (!m_copyState->m_isCrossServerOp) {
+ uint32_t numUnread = 0;
+ for (nsIMsgDBHdr* message : m_copyState->m_messages) {
+ // if the message is not there, then assume what the caller tells us to.
+ bool isRead = false;
+ uint32_t flags;
+ if (message) {
+ message->GetFlags(&flags);
+ isRead = flags & nsMsgMessageFlags::Read;
+ }
+ if (!isRead) numUnread++;
+ }
+ m_copyState->m_unreadCount = numUnread;
+ } else {
+ nsIMsgDBHdr* message = m_copyState->m_messages[m_copyState->m_curIndex];
+ // if the key is not there, then assume what the caller tells us to.
+ bool isRead = false;
+ uint32_t flags;
+ if (message) {
+ message->GetFlags(&flags);
+ isRead = flags & nsMsgMessageFlags::Read;
+ }
+ m_copyState->m_unreadCount = (isRead) ? 0 : 1;
+ }
+
+ m_copyState->m_isMove = isMove;
+ m_copyState->m_newMsgFlags = newMsgFlags;
+ m_copyState->m_newMsgKeywords = newMsgKeywords;
+ m_copyState->m_allowUndo = allowUndo;
+ m_copyState->m_selectedState = selectedState;
+ m_copyState->m_msgWindow = msgWindow;
+ if (listener) m_copyState->m_listener = listener;
+ return NS_OK;
+}
+
+nsresult nsImapMailFolder::CopyFileToOfflineStore(nsIFile* srcFile,
+ nsMsgKey msgKey) {
+ nsresult rv = GetDatabase();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool storeOffline = (mFlags & nsMsgFolderFlags::Offline) && !WeAreOffline();
+
+ if (msgKey == nsMsgKey_None) {
+ // To support send filters, we need to store the message in the database
+ // when it is copied to the FCC folder. In that case, we know the UID of the
+ // message and therefore have the correct msgKey. In other cases, where
+ // we don't need the offline message copied, don't add to db.
+ if (!storeOffline) return NS_OK;
+
+ mDatabase->GetNextFakeOfflineMsgKey(&msgKey);
+ }
+
+ nsCOMPtr<nsIMsgDBHdr> fakeHdr;
+ rv = mDatabase->CreateNewHdr(msgKey, getter_AddRefs(fakeHdr));
+ NS_ENSURE_SUCCESS(rv, rv);
+ fakeHdr->SetUint32Property("pseudoHdr", 1);
+
+ // Should we add this to the offline store?
+ nsCOMPtr<nsIOutputStream> offlineStore;
+ if (storeOffline) {
+ rv = GetOfflineStoreOutputStream(fakeHdr, getter_AddRefs(offlineStore));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // We set an offline kMoveResult because in any case we want to update this
+ // msgHdr with one downloaded from the server, with possible additional
+ // headers added.
+ nsCOMPtr<nsIMsgOfflineImapOperation> op;
+ nsCOMPtr<nsIMsgOfflineOpsDatabase> opsDb = do_QueryInterface(mDatabase, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = opsDb->GetOfflineOpForKey(msgKey, true, getter_AddRefs(op));
+ if (NS_SUCCEEDED(rv) && op) {
+ nsCString destFolderUri;
+ GetURI(destFolderUri);
+ op->SetOperation(nsIMsgOfflineImapOperation::kMoveResult);
+ op->SetDestinationFolderURI(destFolderUri);
+ SetFlag(nsMsgFolderFlags::OfflineEvents);
+ }
+
+ nsCOMPtr<nsIInputStream> inputStream;
+ nsCOMPtr<nsIMsgParseMailMsgState> msgParser =
+ do_CreateInstance("@mozilla.org/messenger/messagestateparser;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ msgParser->SetMailDB(mDatabase);
+
+ rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream), srcFile);
+ if (NS_SUCCEEDED(rv) && inputStream) {
+ // Now, parse the temp file to (optionally) copy to
+ // the offline store for the cur folder.
+ RefPtr<nsMsgLineStreamBuffer> inputStreamBuffer =
+ new nsMsgLineStreamBuffer(FILE_IO_BUFFER_SIZE, true, false);
+ int64_t fileSize;
+ srcFile->GetFileSize(&fileSize);
+ uint32_t bytesWritten;
+ rv = NS_OK;
+ msgParser->SetState(nsIMsgParseMailMsgState::ParseHeadersState);
+ msgParser->SetNewMsgHdr(fakeHdr);
+ bool needMoreData = false;
+ char* newLine = nullptr;
+ uint32_t numBytesInLine = 0;
+ if (offlineStore) {
+ const char* envelope = "From " CRLF;
+ offlineStore->Write(envelope, strlen(envelope), &bytesWritten);
+ fileSize += bytesWritten;
+ }
+ do {
+ newLine = inputStreamBuffer->ReadNextLine(inputStream, numBytesInLine,
+ needMoreData);
+ if (newLine) {
+ msgParser->ParseAFolderLine(newLine, numBytesInLine);
+ if (offlineStore)
+ rv = offlineStore->Write(newLine, numBytesInLine, &bytesWritten);
+
+ free(newLine);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ } while (newLine);
+
+ msgParser->FinishHeader();
+ uint32_t resultFlags;
+ if (offlineStore)
+ fakeHdr->OrFlags(nsMsgMessageFlags::Offline | nsMsgMessageFlags::Read,
+ &resultFlags);
+ else
+ fakeHdr->OrFlags(nsMsgMessageFlags::Read, &resultFlags);
+ if (offlineStore) fakeHdr->SetOfflineMessageSize(fileSize);
+ mDatabase->AddNewHdrToDB(fakeHdr, true /* notify */);
+
+ // Call FinishNewMessage before setting pending attributes, as in
+ // maildir it copies from tmp to cur and may change the storeToken
+ // to get a unique filename.
+ if (offlineStore) {
+ nsCOMPtr<nsIMsgPluggableStore> msgStore;
+ GetMsgStore(getter_AddRefs(msgStore));
+ if (msgStore) msgStore->FinishNewMessage(offlineStore, fakeHdr);
+ }
+
+ // We are copying from a file to offline store so set offline flag.
+ SetPendingAttributes({&*fakeHdr}, false, true);
+
+ // Gloda needs this notification to index the fake message.
+ nsCOMPtr<nsIMsgFolderNotificationService> notifier(
+ do_GetService("@mozilla.org/messenger/msgnotificationservice;1"));
+ if (notifier) notifier->NotifyMsgsClassified({&*fakeHdr}, false, false);
+ inputStream->Close();
+ inputStream = nullptr;
+ }
+ if (offlineStore) offlineStore->Close();
+ return rv;
+}
+
+nsresult nsImapMailFolder::OnCopyCompleted(nsISupports* srcSupport,
+ nsresult rv) {
+ // if it's a file, and the copy succeeded, then fcc the offline
+ // store, and add a kMoveResult offline op.
+ if (NS_SUCCEEDED(rv) && m_copyState) {
+ nsCOMPtr<nsIFile> srcFile(do_QueryInterface(srcSupport));
+ if (srcFile)
+ (void)CopyFileToOfflineStore(srcFile, m_copyState->m_appendUID);
+ }
+ m_copyState = nullptr;
+ nsresult result;
+ nsCOMPtr<nsIMsgCopyService> copyService =
+ do_GetService("@mozilla.org/messenger/messagecopyservice;1", &result);
+ NS_ENSURE_SUCCESS(result, result);
+ return copyService->NotifyCompletion(srcSupport, this, rv);
+}
+
+nsresult nsImapMailFolder::CreateBaseMessageURI(const nsACString& aURI) {
+ return nsCreateImapBaseMessageURI(aURI, mBaseMessageURI);
+}
+
+NS_IMETHODIMP nsImapMailFolder::GetFolderURL(nsACString& aFolderURL) {
+ nsCOMPtr<nsIMsgFolder> rootFolder;
+ nsresult rv = GetRootFolder(getter_AddRefs(rootFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rootFolder->GetURI(aFolderURL);
+ if (rootFolder == this) return NS_OK;
+
+ NS_ASSERTION(mURI.Length() > aFolderURL.Length(),
+ "Should match with a folder name!");
+ nsCString escapedName;
+ MsgEscapeString(Substring(mURI, aFolderURL.Length()),
+ nsINetUtil::ESCAPE_URL_PATH, escapedName);
+ if (escapedName.IsEmpty()) return NS_ERROR_OUT_OF_MEMORY;
+ aFolderURL.Append(escapedName);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailFolder::GetFolderNeedsSubscribing(bool* bVal) {
+ NS_ENSURE_ARG_POINTER(bVal);
+ *bVal = m_folderNeedsSubscribing;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailFolder::SetFolderNeedsSubscribing(bool bVal) {
+ m_folderNeedsSubscribing = bVal;
+ return NS_OK;
+}
+
+nsMsgIMAPFolderACL* nsImapMailFolder::GetFolderACL() {
+ if (!m_folderACL) m_folderACL = new nsMsgIMAPFolderACL(this);
+ return m_folderACL;
+}
+
+nsresult nsImapMailFolder::CreateACLRightsStringForFolder(
+ nsAString& rightsString) {
+ GetFolderACL(); // lazy create
+ NS_ENSURE_TRUE(m_folderACL, NS_ERROR_NULL_POINTER);
+ return m_folderACL->CreateACLRightsString(rightsString);
+}
+
+NS_IMETHODIMP nsImapMailFolder::GetFolderNeedsACLListed(bool* bVal) {
+ NS_ENSURE_ARG_POINTER(bVal);
+ bool dontNeedACLListed = !m_folderNeedsACLListed;
+ // if we haven't acl listed, and it's not a no select folder or the inbox,
+ // then we'll list the acl if it's not a namespace.
+ if (m_folderNeedsACLListed &&
+ !(mFlags & (nsMsgFolderFlags::ImapNoselect | nsMsgFolderFlags::Inbox)))
+ GetIsNamespace(&dontNeedACLListed);
+ *bVal = !dontNeedACLListed;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailFolder::SetFolderNeedsACLListed(bool bVal) {
+ m_folderNeedsACLListed = bVal;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailFolder::GetIsNamespace(bool* aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+ nsresult rv = NS_OK;
+ if (!m_namespace) {
+#ifdef DEBUG_bienvenu
+ // Make sure this isn't causing us to open the database
+ NS_ASSERTION(m_hierarchyDelimiter != kOnlineHierarchySeparatorUnknown,
+ "hierarchy delimiter not set");
+#endif
+
+ nsCString onlineName, serverKey;
+ GetServerKey(serverKey);
+ GetOnlineName(onlineName);
+ char hierarchyDelimiter;
+ GetHierarchyDelimiter(&hierarchyDelimiter);
+
+ nsCOMPtr<nsIImapHostSessionList> hostSession =
+ do_GetService(kCImapHostSessionList, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ m_namespace = nsImapNamespaceList::GetNamespaceForFolder(
+ serverKey.get(), onlineName.get(), hierarchyDelimiter);
+ if (m_namespace == nullptr) {
+ if (mFlags & nsMsgFolderFlags::ImapOtherUser)
+ rv = hostSession->GetDefaultNamespaceOfTypeForHost(
+ serverKey.get(), kOtherUsersNamespace, m_namespace);
+ else if (mFlags & nsMsgFolderFlags::ImapPublic)
+ rv = hostSession->GetDefaultNamespaceOfTypeForHost(
+ serverKey.get(), kPublicNamespace, m_namespace);
+ else
+ rv = hostSession->GetDefaultNamespaceOfTypeForHost(
+ serverKey.get(), kPersonalNamespace, m_namespace);
+ }
+ NS_ASSERTION(m_namespace, "failed to get namespace");
+ if (m_namespace) {
+ nsImapNamespaceList::SuggestHierarchySeparatorForNamespace(
+ m_namespace, hierarchyDelimiter);
+ m_folderIsNamespace = nsImapNamespaceList::GetFolderIsNamespace(
+ serverKey.get(), onlineName.get(), hierarchyDelimiter, m_namespace);
+ }
+ }
+ *aResult = m_folderIsNamespace;
+ return rv;
+}
+
+NS_IMETHODIMP nsImapMailFolder::SetIsNamespace(bool isNamespace) {
+ m_folderIsNamespace = isNamespace;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailFolder::ResetNamespaceReferences() {
+ nsCString serverKey;
+ nsCString onlineName;
+ GetServerKey(serverKey);
+ GetOnlineName(onlineName);
+ char hierarchyDelimiter;
+ GetHierarchyDelimiter(&hierarchyDelimiter);
+ m_namespace = nsImapNamespaceList::GetNamespaceForFolder(
+ serverKey.get(), onlineName.get(), hierarchyDelimiter);
+ m_folderIsNamespace = m_namespace ? nsImapNamespaceList::GetFolderIsNamespace(
+ serverKey.get(), onlineName.get(),
+ hierarchyDelimiter, m_namespace)
+ : false;
+
+ nsTArray<RefPtr<nsIMsgFolder>> subFolders;
+ nsresult rv = GetSubFolders(subFolders);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (nsIMsgFolder* f : subFolders) {
+ nsCOMPtr<nsIMsgImapMailFolder> imapFolder(do_QueryInterface(f, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = imapFolder->ResetNamespaceReferences();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailFolder::FindOnlineSubFolder(
+ const nsACString& targetOnlineName, nsIMsgImapMailFolder** aResultFolder) {
+ *aResultFolder = nullptr;
+ nsresult rv = NS_OK;
+
+ nsCString onlineName;
+ GetOnlineName(onlineName);
+
+ if (onlineName.Equals(targetOnlineName)) {
+ return QueryInterface(NS_GET_IID(nsIMsgImapMailFolder),
+ (void**)aResultFolder);
+ }
+
+ nsTArray<RefPtr<nsIMsgFolder>> subFolders;
+ rv = GetSubFolders(subFolders);
+ NS_ENSURE_SUCCESS(rv, rv);
+ for (nsIMsgFolder* f : subFolders) {
+ nsCOMPtr<nsIMsgImapMailFolder> imapFolder(do_QueryInterface(f, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = imapFolder->FindOnlineSubFolder(targetOnlineName, aResultFolder);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (*aResultFolder) {
+ return NS_OK; // Found it!
+ }
+ }
+ return NS_OK; // Not found.
+}
+
+NS_IMETHODIMP nsImapMailFolder::GetFolderNeedsAdded(bool* bVal) {
+ NS_ENSURE_ARG_POINTER(bVal);
+ *bVal = m_folderNeedsAdded;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailFolder::SetFolderNeedsAdded(bool bVal) {
+ m_folderNeedsAdded = bVal;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailFolder::GetFolderQuotaCommandIssued(bool* aCmdIssued) {
+ NS_ENSURE_ARG_POINTER(aCmdIssued);
+ *aCmdIssued = m_folderQuotaCommandIssued;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailFolder::SetFolderQuotaCommandIssued(bool aCmdIssued) {
+ m_folderQuotaCommandIssued = aCmdIssued;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailFolder::SetFolderQuotaData(
+ uint32_t aAction, const nsACString& aFolderQuotaRoot,
+ uint64_t aFolderQuotaUsage, uint64_t aFolderQuotaLimit) {
+ switch (aAction) {
+ case kInvalidateQuota:
+ // Reset to initialize evaluation of a new quotaroot imap response. This
+ // clears any previous array data and marks the quota data for this folder
+ // invalid.
+ m_folderQuotaDataIsValid = false;
+ m_folderQuota.Clear();
+ break;
+ case kStoreQuota:
+ // Store folder's quota data to an array. This will occur zero or more
+ // times for a folder.
+ m_folderQuota.AppendElement(new nsMsgQuota(
+ aFolderQuotaRoot, aFolderQuotaUsage, aFolderQuotaLimit));
+ break;
+ case kValidateQuota:
+ // GETQUOTAROOT command was successful and OK response has occurred. This
+ // indicates that all the untagged QUOTA responses have occurred so mark
+ // as valid.
+ m_folderQuotaDataIsValid = true;
+ break;
+ default:
+ // Called with undefined aAction parameter.
+ NS_ASSERTION(false, "undefined action");
+ }
+ return NS_OK;
+}
+
+// Provide the quota array for status bar notification.
+NS_IMETHODIMP nsImapMailFolder::GetQuota(
+ nsTArray<RefPtr<nsIMsgQuota>>& aArray) {
+ if (m_folderQuotaDataIsValid) {
+ aArray = m_folderQuota.Clone();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailFolder::PerformExpand(nsIMsgWindow* aMsgWindow) {
+ nsresult rv;
+ bool usingSubscription = false;
+ nsCOMPtr<nsIImapIncomingServer> imapServer;
+ rv = GetImapIncomingServer(getter_AddRefs(imapServer));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = imapServer->GetUsingSubscription(&usingSubscription);
+ if (NS_SUCCEEDED(rv) && !usingSubscription) {
+ nsCOMPtr<nsIImapService> imapService =
+ do_GetService("@mozilla.org/messenger/imapservice;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = imapService->DiscoverChildren(this, this, m_onlineFolderName);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapMailFolder::RenameClient(nsIMsgWindow* msgWindow,
+ nsIMsgFolder* msgFolder,
+ const nsACString& oldName,
+ const nsACString& newName) {
+ nsresult rv;
+ nsCOMPtr<nsIFile> pathFile;
+ rv = GetFilePath(getter_AddRefs(pathFile));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIMsgImapMailFolder> oldImapFolder =
+ do_QueryInterface(msgFolder, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ char hierarchyDelimiter = '/';
+ oldImapFolder->GetHierarchyDelimiter(&hierarchyDelimiter);
+ int32_t boxflags = 0;
+ oldImapFolder->GetBoxFlags(&boxflags);
+
+ nsAutoString newLeafName;
+ NS_ConvertUTF8toUTF16 newNameString(newName);
+ NS_ENSURE_SUCCESS(rv, rv);
+ newLeafName = newNameString;
+ nsAutoString folderNameStr;
+ int32_t folderStart = newLeafName.RFindChar(
+ '/'); // internal use of hierarchyDelimiter is always '/'
+ if (folderStart > 0) {
+ newLeafName = Substring(newNameString, folderStart + 1);
+ CreateDirectoryForFolder(
+ getter_AddRefs(pathFile)); // needed when we move a folder to a folder
+ // with no subfolders.
+ }
+
+ // if we get here, it's really a leaf, and "this" is the parent.
+ folderNameStr = newLeafName;
+
+ // Create an empty database for this mail folder, set its name from the user
+ nsCOMPtr<nsIMsgDatabase> mailDBFactory;
+ nsCOMPtr<nsIMsgFolder> child;
+ nsCOMPtr<nsIMsgImapMailFolder> imapFolder;
+
+ nsCOMPtr<nsIMsgDBService> msgDBService =
+ do_GetService("@mozilla.org/msgDatabase/msgDBService;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgDatabase> unusedDB;
+ nsCOMPtr<nsIFile> dbFile;
+
+ // warning, path will be changed
+ rv = CreateFileForDB(folderNameStr, pathFile, getter_AddRefs(dbFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Use openMailDBFromFile() and not OpenFolderDB() here, since we don't use
+ // the DB.
+ rv = msgDBService->OpenMailDBFromFile(dbFile, nullptr, true, true,
+ getter_AddRefs(unusedDB));
+ if (NS_SUCCEEDED(rv) && unusedDB) {
+ // need to set the folder name
+ nsCOMPtr<nsIDBFolderInfo> folderInfo;
+ rv = unusedDB->GetDBFolderInfo(getter_AddRefs(folderInfo));
+
+ // Now let's create the actual new folder
+ rv = AddSubfolderWithPath(folderNameStr, dbFile, getter_AddRefs(child));
+ if (!child || NS_FAILED(rv)) return rv;
+ nsAutoString unicodeName;
+ rv = CopyFolderNameToUTF16(NS_ConvertUTF16toUTF8(folderNameStr),
+ unicodeName);
+ if (NS_SUCCEEDED(rv)) child->SetPrettyName(unicodeName);
+ imapFolder = do_QueryInterface(child);
+ if (imapFolder) {
+ nsAutoCString onlineName(m_onlineFolderName);
+
+ if (!onlineName.IsEmpty()) onlineName.Append(hierarchyDelimiter);
+ onlineName.Append(NS_ConvertUTF16toUTF8(folderNameStr));
+ imapFolder->SetVerifiedAsOnlineFolder(true);
+ imapFolder->SetOnlineName(onlineName);
+ imapFolder->SetHierarchyDelimiter(hierarchyDelimiter);
+ imapFolder->SetBoxFlags(boxflags);
+ // 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) {
+ nsAutoString unicodeOnlineName;
+ CopyUTF8toUTF16(onlineName, unicodeOnlineName);
+ folderInfo->SetMailboxName(unicodeOnlineName);
+ }
+ bool changed = false;
+ msgFolder->MatchOrChangeFilterDestination(
+ child, false /*caseInsensitive*/, &changed);
+ if (changed) msgFolder->AlertFilterChanged(msgWindow);
+ }
+ unusedDB->SetSummaryValid(true);
+ unusedDB->Commit(nsMsgDBCommitType::kLargeCommit);
+ unusedDB->Close(true);
+ child->RenameSubFolders(msgWindow, msgFolder);
+ nsCOMPtr<nsIMsgFolder> msgParent;
+ msgFolder->GetParent(getter_AddRefs(msgParent));
+ msgFolder->SetParent(nullptr);
+ // Reset online status now that the folder is renamed.
+ nsCOMPtr<nsIMsgImapMailFolder> oldImapFolder = do_QueryInterface(msgFolder);
+ if (oldImapFolder) oldImapFolder->SetVerifiedAsOnlineFolder(false);
+ nsCOMPtr<nsIMsgFolderNotificationService> notifier(
+ do_GetService("@mozilla.org/messenger/msgnotificationservice;1"));
+ if (notifier) notifier->NotifyFolderRenamed(msgFolder, child);
+
+ // Do not propagate the deletion until after we have (synchronously)
+ // notified all listeners about the rename. This allows them to access
+ // properties on the source folder without experiencing failures.
+ if (msgParent) msgParent->PropagateDelete(msgFolder, true);
+ NotifyFolderAdded(child);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapMailFolder::RenameSubFolders(nsIMsgWindow* msgWindow,
+ nsIMsgFolder* oldFolder) {
+ m_initialized = true;
+ nsTArray<RefPtr<nsIMsgFolder>> subFolders;
+ nsresult rv = oldFolder->GetSubFolders(subFolders);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (nsIMsgFolder* msgFolder : subFolders) {
+ nsCOMPtr<nsIMsgImapMailFolder> folder(do_QueryInterface(msgFolder, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ char hierarchyDelimiter = '/';
+ folder->GetHierarchyDelimiter(&hierarchyDelimiter);
+
+ int32_t boxflags;
+ folder->GetBoxFlags(&boxflags);
+
+ bool verified;
+ folder->GetVerifiedAsOnlineFolder(&verified);
+
+ nsCOMPtr<nsIFile> oldPathFile;
+ rv = msgFolder->GetFilePath(getter_AddRefs(oldPathFile));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIFile> newParentPathFile;
+ rv = GetFilePath(getter_AddRefs(newParentPathFile));
+ if (NS_FAILED(rv)) return rv;
+
+ rv = AddDirectorySeparator(newParentPathFile);
+ nsAutoCString oldLeafName;
+ oldPathFile->GetNativeLeafName(oldLeafName);
+ newParentPathFile->AppendNative(oldLeafName);
+
+ nsCOMPtr<nsIFile> newPathFile =
+ do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ newPathFile->InitWithFile(newParentPathFile);
+
+ nsCOMPtr<nsIFile> dbFilePath = newPathFile;
+
+ nsCOMPtr<nsIMsgFolder> child;
+
+ nsString folderName;
+ rv = msgFolder->GetName(folderName);
+ if (folderName.IsEmpty() || NS_FAILED(rv)) return rv;
+
+ nsCString utfLeafName;
+ bool utf8AcceptEnabled;
+ nsCOMPtr<nsIMsgImapMailFolder> imapFolder = do_QueryInterface(msgFolder);
+ rv = imapFolder->GetShouldUseUtf8FolderName(&utf8AcceptEnabled);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (utf8AcceptEnabled) {
+ CopyUTF16toUTF8(folderName, utfLeafName);
+ } else {
+ CopyUTF16toMUTF7(folderName, utfLeafName);
+ }
+
+ // XXX : Fix this non-sense by fixing AddSubfolderWithPath
+ nsAutoString unicodeLeafName;
+ CopyUTF8toUTF16(utfLeafName, unicodeLeafName);
+
+ rv = AddSubfolderWithPath(unicodeLeafName, dbFilePath,
+ getter_AddRefs(child));
+ if (!child || NS_FAILED(rv)) return rv;
+
+ child->SetName(folderName);
+ imapFolder = do_QueryInterface(child);
+ nsCString onlineName;
+ GetOnlineName(onlineName);
+ nsAutoCString onlineCName(onlineName);
+ onlineCName.Append(hierarchyDelimiter);
+ onlineCName.Append(utfLeafName);
+ if (imapFolder) {
+ imapFolder->SetVerifiedAsOnlineFolder(verified);
+ imapFolder->SetOnlineName(onlineCName);
+ imapFolder->SetHierarchyDelimiter(hierarchyDelimiter);
+ imapFolder->SetBoxFlags(boxflags);
+
+ bool changed = false;
+ msgFolder->MatchOrChangeFilterDestination(
+ child, false /*caseInsensitive*/, &changed);
+ if (changed) msgFolder->AlertFilterChanged(msgWindow);
+ child->RenameSubFolders(msgWindow, msgFolder);
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapMailFolder::IsCommandEnabled(const nsACString& command,
+ bool* result) {
+ NS_ENSURE_ARG_POINTER(result);
+ *result = !(WeAreOffline() && (command.EqualsLiteral("cmd_renameFolder") ||
+ command.EqualsLiteral("cmd_compactFolder") ||
+ command.EqualsLiteral("button_compact") ||
+ command.EqualsLiteral("cmd_delete") ||
+ command.EqualsLiteral("button_delete")));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::GetCanFileMessages(bool* aCanFileMessages) {
+ nsresult rv;
+ *aCanFileMessages = true;
+
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = GetServer(getter_AddRefs(server));
+ if (NS_SUCCEEDED(rv) && server)
+ rv = server->GetCanFileMessagesOnServer(aCanFileMessages);
+
+ if (*aCanFileMessages)
+ rv = nsMsgDBFolder::GetCanFileMessages(aCanFileMessages);
+
+ if (*aCanFileMessages) {
+ bool noSelect;
+ GetFlag(nsMsgFolderFlags::ImapNoselect, &noSelect);
+ *aCanFileMessages =
+ (noSelect) ? false : GetFolderACL()->GetCanIInsertInFolder();
+ return NS_OK;
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::GetCanDeleteMessages(bool* aCanDeleteMessages) {
+ NS_ENSURE_ARG_POINTER(aCanDeleteMessages);
+ *aCanDeleteMessages = GetFolderACL()->GetCanIDeleteInFolder();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::GetPerformingBiff(bool* aPerformingBiff) {
+ NS_ENSURE_ARG_POINTER(aPerformingBiff);
+ *aPerformingBiff = m_performingBiff;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::SetPerformingBiff(bool aPerformingBiff) {
+ m_performingBiff = aPerformingBiff;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::SetFilterList(nsIMsgFilterList* aMsgFilterList) {
+ m_filterList = aMsgFilterList;
+ return nsMsgDBFolder::SetFilterList(aMsgFilterList);
+}
+
+nsresult nsImapMailFolder::GetMoveCoalescer() {
+ if (!m_moveCoalescer)
+ m_moveCoalescer = new nsImapMoveCoalescer(this, nullptr /* msgWindow */);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::StoreCustomKeywords(nsIMsgWindow* aMsgWindow,
+ const nsACString& aFlagsToAdd,
+ const nsACString& aFlagsToSubtract,
+ const nsTArray<nsMsgKey>& aKeysToStore,
+ nsIURI** _retval) {
+ if (aKeysToStore.IsEmpty()) return NS_OK;
+ nsresult rv = NS_OK;
+ if (WeAreOffline()) {
+ GetDatabase();
+ if (!mDatabase) return NS_ERROR_UNEXPECTED;
+ nsCOMPtr<nsIMsgOfflineOpsDatabase> opsDb =
+ do_QueryInterface(mDatabase, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ for (auto key : aKeysToStore) {
+ nsCOMPtr<nsIMsgOfflineImapOperation> op;
+ nsresult rv2 = opsDb->GetOfflineOpForKey(key, true, getter_AddRefs(op));
+ if (NS_FAILED(rv2)) rv = rv2;
+ SetFlag(nsMsgFolderFlags::OfflineEvents);
+ if (NS_SUCCEEDED(rv2) && op) {
+ if (!aFlagsToAdd.IsEmpty())
+ op->AddKeywordToAdd(PromiseFlatCString(aFlagsToAdd).get());
+ if (!aFlagsToSubtract.IsEmpty())
+ op->AddKeywordToRemove(PromiseFlatCString(aFlagsToSubtract).get());
+ }
+ }
+ mDatabase->Commit(nsMsgDBCommitType::kLargeCommit); // flush offline ops
+ return rv;
+ }
+
+ nsCOMPtr<nsIImapService> imapService(
+ do_GetService("@mozilla.org/messenger/imapservice;1", &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsAutoCString msgIds;
+ AllocateUidStringFromKeys(aKeysToStore, msgIds);
+ nsCOMPtr<nsIURI> retUri;
+ rv = imapService->StoreCustomKeywords(this, aMsgWindow, aFlagsToAdd,
+ aFlagsToSubtract, msgIds,
+ getter_AddRefs(retUri));
+ if (_retval) {
+ retUri.forget(_retval);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapMailFolder::NotifyIfNewMail() {
+ return PerformBiffNotifications();
+}
+
+bool nsImapMailFolder::ShowPreviewText() {
+ bool showPreviewText = false;
+ nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID));
+ if (prefBranch)
+ prefBranch->GetBoolPref("mail.biff.alert.show_preview", &showPreviewText);
+ return showPreviewText;
+}
+
+nsresult nsImapMailFolder::PlaybackCoalescedOperations() {
+ if (m_moveCoalescer) {
+ nsTArray<nsMsgKey>* junkKeysToClassify = m_moveCoalescer->GetKeyBucket(0);
+ if (junkKeysToClassify && !junkKeysToClassify->IsEmpty())
+ StoreCustomKeywords(m_moveCoalescer->GetMsgWindow(), "Junk"_ns,
+ EmptyCString(), *junkKeysToClassify, nullptr);
+ junkKeysToClassify->Clear();
+ nsTArray<nsMsgKey>* nonJunkKeysToClassify =
+ m_moveCoalescer->GetKeyBucket(1);
+ if (nonJunkKeysToClassify && !nonJunkKeysToClassify->IsEmpty())
+ StoreCustomKeywords(m_moveCoalescer->GetMsgWindow(), "NonJunk"_ns,
+ EmptyCString(), *nonJunkKeysToClassify, nullptr);
+ nonJunkKeysToClassify->Clear();
+ return m_moveCoalescer->PlaybackMoves(ShowPreviewText());
+ }
+ return NS_OK; // must not be any coalesced operations
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::SetJunkScoreForMessages(
+ const nsTArray<RefPtr<nsIMsgDBHdr>>& aMessages,
+ const nsACString& aJunkScore) {
+ nsresult rv = nsMsgDBFolder::SetJunkScoreForMessages(aMessages, aJunkScore);
+ if (NS_SUCCEEDED(rv)) {
+ nsAutoCString messageIds;
+ nsTArray<nsMsgKey> keys;
+ nsresult rv = BuildIdsAndKeyArray(aMessages, messageIds, keys);
+ NS_ENSURE_SUCCESS(rv, rv);
+ StoreCustomKeywords(
+ nullptr, aJunkScore.EqualsLiteral("0") ? "NonJunk"_ns : "Junk"_ns,
+ aJunkScore.EqualsLiteral("0") ? "Junk"_ns : "NonJunk"_ns, keys,
+ nullptr);
+ if (mDatabase) mDatabase->Commit(nsMsgDBCommitType::kLargeCommit);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::OnMessageClassified(const nsACString& aMsgURI,
+ nsMsgJunkStatus aClassification,
+ uint32_t aJunkPercent) {
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ nsresult rv = GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!aMsgURI.IsEmpty()) // not end of batch
+ {
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ rv = GetMsgDBHdrFromURI(aMsgURI, getter_AddRefs(msgHdr));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsMsgKey msgKey;
+ rv = msgHdr->GetMessageKey(&msgKey);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // check if this message needs junk classification
+
+ uint32_t processingFlags;
+ GetProcessingFlags(msgKey, &processingFlags);
+
+ if (processingFlags & nsMsgProcessingFlags::ClassifyJunk) {
+ nsMsgDBFolder::OnMessageClassified(aMsgURI, aClassification,
+ aJunkPercent);
+
+ GetMoveCoalescer();
+ if (m_moveCoalescer) {
+ nsTArray<nsMsgKey>* keysToClassify = m_moveCoalescer->GetKeyBucket(
+ (aClassification == nsIJunkMailPlugin::JUNK) ? 0 : 1);
+ NS_ASSERTION(keysToClassify, "error getting key bucket");
+ if (keysToClassify) keysToClassify->AppendElement(msgKey);
+ }
+ if (aClassification == nsIJunkMailPlugin::JUNK) {
+ nsCOMPtr<nsISpamSettings> spamSettings;
+ rv = server->GetSpamSettings(getter_AddRefs(spamSettings));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool markAsReadOnSpam;
+ (void)spamSettings->GetMarkAsReadOnSpam(&markAsReadOnSpam);
+ if (markAsReadOnSpam) {
+ m_junkMessagesToMarkAsRead.AppendElement(msgHdr);
+ }
+
+ bool willMoveMessage = false;
+
+ // don't do the move when we are opening up
+ // the junk mail folder or the trash folder
+ // or when manually classifying messages in those folders
+ if (!(mFlags & nsMsgFolderFlags::Junk ||
+ mFlags & nsMsgFolderFlags::Trash)) {
+ bool moveOnSpam;
+ (void)spamSettings->GetMoveOnSpam(&moveOnSpam);
+ if (moveOnSpam) {
+ nsCString spamFolderURI;
+ rv = spamSettings->GetSpamFolderURI(spamFolderURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!spamFolderURI.IsEmpty()) {
+ rv = FindFolder(spamFolderURI, getter_AddRefs(mSpamFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (mSpamFolder) {
+ rv = mSpamFolder->SetFlag(nsMsgFolderFlags::Junk);
+ NS_ENSURE_SUCCESS(rv, rv);
+ mSpamKeysToMove.AppendElement(msgKey);
+ willMoveMessage = true;
+ } else {
+ // XXX TODO
+ // JUNK MAIL RELATED
+ // the listener should do
+ // rv = folder->SetFlag(nsMsgFolderFlags::Junk);
+ // NS_ENSURE_SUCCESS(rv,rv);
+ // if (NS_SUCCEEDED(GetMoveCoalescer())) {
+ // m_moveCoalescer->AddMove(folder, msgKey);
+ // willMoveMessage = true;
+ // }
+ rv = GetOrCreateJunkFolder(spamFolderURI,
+ nullptr /* aListener */);
+ NS_ASSERTION(NS_SUCCEEDED(rv), "GetOrCreateJunkFolder failed");
+ }
+ }
+ }
+ }
+ rv = spamSettings->LogJunkHit(msgHdr, willMoveMessage);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+ }
+
+ else // end of batch
+ {
+ // Parent will apply post bayes filters.
+ nsMsgDBFolder::OnMessageClassified(EmptyCString(),
+ nsIJunkMailPlugin::UNCLASSIFIED, 0);
+
+ if (!m_junkMessagesToMarkAsRead.IsEmpty()) {
+ rv = MarkMessagesRead(m_junkMessagesToMarkAsRead, true);
+ NS_ENSURE_SUCCESS(rv, rv);
+ m_junkMessagesToMarkAsRead.Clear();
+ }
+ if (!mSpamKeysToMove.IsEmpty()) {
+ GetMoveCoalescer();
+ for (uint32_t keyIndex = 0; keyIndex < mSpamKeysToMove.Length();
+ keyIndex++) {
+ // If an upstream filter moved this message, don't move it here.
+ nsMsgKey msgKey = mSpamKeysToMove.ElementAt(keyIndex);
+ nsMsgProcessingFlagType processingFlags;
+ GetProcessingFlags(msgKey, &processingFlags);
+ if (!(processingFlags & nsMsgProcessingFlags::FilterToMove)) {
+ if (m_moveCoalescer && mSpamFolder)
+ m_moveCoalescer->AddMove(mSpamFolder, msgKey);
+ } else {
+ // We don't need the FilterToMove flag anymore.
+ AndProcessingFlags(msgKey, ~nsMsgProcessingFlags::FilterToMove);
+ }
+ }
+ mSpamKeysToMove.Clear();
+ }
+
+ // Let's not hold onto the spam folder reference longer than necessary.
+ mSpamFolder = nullptr;
+
+ bool pendingMoves = m_moveCoalescer && m_moveCoalescer->HasPendingMoves();
+ PlaybackCoalescedOperations();
+ // If we are performing biff for this folder, tell the server object
+ if ((!pendingMoves || !ShowPreviewText()) && m_performingBiff) {
+ // we don't need to adjust the num new messages in this folder because
+ // the playback moves code already did that.
+ (void)PerformBiffNotifications();
+ server->SetPerformingBiff(false);
+ m_performingBiff = false;
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsImapMailFolder::GetShouldDownloadAllHeaders(bool* aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = false;
+ // for just the inbox, we check if the filter list has arbitrary headers.
+ // for all folders, check if we have a spam plugin that requires all headers
+ if (mFlags & nsMsgFolderFlags::Inbox) {
+ nsCOMPtr<nsIMsgFilterList> filterList;
+ nsresult rv = GetFilterList(nullptr, getter_AddRefs(filterList));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = filterList->GetShouldDownloadAllHeaders(aResult);
+ if (*aResult) return rv;
+ }
+ nsCOMPtr<nsIMsgFilterPlugin> filterPlugin;
+ nsCOMPtr<nsIMsgIncomingServer> server;
+
+ if (NS_SUCCEEDED(GetServer(getter_AddRefs(server))))
+ server->GetSpamFilterPlugin(getter_AddRefs(filterPlugin));
+
+ return (filterPlugin) ? filterPlugin->GetShouldDownloadAllHeaders(aResult)
+ : NS_OK;
+}
+
+void nsImapMailFolder::GetTrashFolderName(nsAString& aFolderName) {
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ nsCOMPtr<nsIImapIncomingServer> imapServer;
+ nsresult rv;
+ rv = GetServer(getter_AddRefs(server));
+ if (NS_FAILED(rv)) return;
+ imapServer = do_QueryInterface(server, &rv);
+ if (NS_FAILED(rv)) return;
+ imapServer->GetTrashFolderName(aFolderName);
+ return;
+}
+NS_IMETHODIMP nsImapMailFolder::FetchMsgPreviewText(
+ nsTArray<nsMsgKey> const& aKeysToFetch, nsIUrlListener* aUrlListener,
+ bool* aAsyncResults) {
+ NS_ENSURE_ARG_POINTER(aAsyncResults);
+
+ nsTArray<nsMsgKey> keysToFetchFromServer;
+
+ *aAsyncResults = false;
+ nsresult rv = NS_OK;
+
+ nsCOMPtr<nsIMsgMessageService> msgService =
+ do_GetService("@mozilla.org/messenger/messageservice;1?type=imap", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (uint32_t i = 0; i < aKeysToFetch.Length(); i++) {
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ nsCString prevBody;
+ rv = GetMessageHeader(aKeysToFetch[i], getter_AddRefs(msgHdr));
+ NS_ENSURE_SUCCESS(rv, rv);
+ // ignore messages that already have a preview body.
+ msgHdr->GetStringProperty("preview", prevBody);
+ if (!prevBody.IsEmpty()) continue;
+
+ /* check if message is in memory cache or offline store. */
+ nsCOMPtr<nsIURI> url;
+ nsCOMPtr<nsIInputStream> inputStream;
+ nsCString messageUri;
+ rv = GetUriForMsg(msgHdr, messageUri);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = msgService->GetUrlForUri(messageUri, nullptr, getter_AddRefs(url));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Lets look in the offline store.
+ uint32_t msgFlags;
+ msgHdr->GetFlags(&msgFlags);
+ if (msgFlags & nsMsgMessageFlags::Offline) {
+ rv = GetLocalMsgStream(msgHdr, getter_AddRefs(inputStream));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = GetMsgPreviewTextFromStream(msgHdr, inputStream);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ nsMsgKey msgKey;
+ msgHdr->GetMessageKey(&msgKey);
+ keysToFetchFromServer.AppendElement(msgKey);
+ }
+ }
+ if (!keysToFetchFromServer.IsEmpty()) {
+ uint32_t msgCount = keysToFetchFromServer.Length();
+ nsAutoCString messageIds;
+ AllocateImapUidString(keysToFetchFromServer.Elements(), msgCount, nullptr,
+ messageIds);
+ nsCOMPtr<nsIImapService> imapService =
+ do_GetService("@mozilla.org/messenger/imapservice;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIURI> outUri;
+ rv = imapService->GetBodyStart(this, aUrlListener, messageIds, 2048,
+ getter_AddRefs(outUri));
+ *aAsyncResults = true; // the preview text will be available async...
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailFolder::AddKeywordsToMessages(
+ const nsTArray<RefPtr<nsIMsgDBHdr>>& aMessages,
+ const nsACString& aKeywords) {
+ nsresult rv = nsMsgDBFolder::AddKeywordsToMessages(aMessages, aKeywords);
+ if (NS_SUCCEEDED(rv)) {
+ nsAutoCString messageIds;
+ nsTArray<nsMsgKey> keys;
+ rv = BuildIdsAndKeyArray(aMessages, messageIds, keys);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = StoreCustomKeywords(nullptr, aKeywords, EmptyCString(), keys, nullptr);
+ if (mDatabase) mDatabase->Commit(nsMsgDBCommitType::kLargeCommit);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapMailFolder::RemoveKeywordsFromMessages(
+ const nsTArray<RefPtr<nsIMsgDBHdr>>& aMessages,
+ const nsACString& aKeywords) {
+ nsresult rv = nsMsgDBFolder::RemoveKeywordsFromMessages(aMessages, aKeywords);
+ if (NS_SUCCEEDED(rv)) {
+ nsAutoCString messageIds;
+ nsTArray<nsMsgKey> keys;
+ nsresult rv = BuildIdsAndKeyArray(aMessages, messageIds, keys);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = StoreCustomKeywords(nullptr, EmptyCString(), aKeywords, keys, nullptr);
+ if (mDatabase) mDatabase->Commit(nsMsgDBCommitType::kLargeCommit);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImapMailFolder::GetCustomIdentity(nsIMsgIdentity** aIdentity) {
+ NS_ENSURE_ARG_POINTER(aIdentity);
+ if (mFlags & nsMsgFolderFlags::ImapOtherUser) {
+ nsresult rv;
+ bool delegateOtherUsersFolders = false;
+ nsCOMPtr<nsIPrefBranch> prefBranch(
+ do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ prefBranch->GetBoolPref("mail.imap.delegateOtherUsersFolders",
+ &delegateOtherUsersFolders);
+ // if we're automatically delegating other user's folders, we need to
+ // cons up an e-mail address for the other user. We do that by
+ // taking the other user's name and the current user's domain name,
+ // assuming they'll be the same. So, <otherUsersName>@<ourDomain>
+ if (delegateOtherUsersFolders) {
+ nsCOMPtr<nsIMsgIncomingServer> server = do_QueryReferent(mServer, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIMsgAccountManager> accountManager =
+ do_GetService("@mozilla.org/messenger/account-manager;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIMsgIdentity> ourIdentity;
+ nsCOMPtr<nsIMsgIdentity> retIdentity;
+ nsCOMPtr<nsIMsgAccount> account;
+ nsCString foldersUserName;
+ nsCString ourEmailAddress;
+
+ accountManager->FindAccountForServer(server, getter_AddRefs(account));
+ NS_ENSURE_SUCCESS(rv, rv);
+ account->GetDefaultIdentity(getter_AddRefs(ourIdentity));
+ NS_ENSURE_SUCCESS(rv, rv);
+ ourIdentity->GetEmail(ourEmailAddress);
+ int32_t atPos = ourEmailAddress.FindChar('@');
+ if (atPos != kNotFound) {
+ nsCString otherUsersEmailAddress;
+ GetFolderOwnerUserName(otherUsersEmailAddress);
+ otherUsersEmailAddress.Append(
+ Substring(ourEmailAddress, atPos, ourEmailAddress.Length()));
+ nsTArray<RefPtr<nsIMsgIdentity>> identities;
+ rv = accountManager->GetIdentitiesForServer(server, identities);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (auto identity : identities) {
+ if (!identity) continue;
+ nsCString identityEmail;
+ identity->GetEmail(identityEmail);
+ if (identityEmail.Equals(otherUsersEmailAddress)) {
+ retIdentity = identity;
+ break;
+ }
+ }
+ if (!retIdentity) {
+ // create the identity
+ rv = accountManager->CreateIdentity(getter_AddRefs(retIdentity));
+ NS_ENSURE_SUCCESS(rv, rv);
+ retIdentity->SetEmail(otherUsersEmailAddress);
+ nsCOMPtr<nsIMsgAccount> account;
+ accountManager->FindAccountForServer(server, getter_AddRefs(account));
+ NS_ENSURE_SUCCESS(rv, rv);
+ account->AddIdentity(retIdentity);
+ }
+ }
+ if (retIdentity) {
+ retIdentity.forget(aIdentity);
+ return NS_OK;
+ }
+ }
+ }
+ return nsMsgDBFolder::GetCustomIdentity(aIdentity);
+}
+
+NS_IMETHODIMP nsImapMailFolder::ChangePendingTotal(int32_t aDelta) {
+ ChangeNumPendingTotalMessages(aDelta);
+ if (aDelta > 0) NotifyHasPendingMsgs();
+ return NS_OK;
+}
+
+void nsImapMailFolder::NotifyHasPendingMsgs() {
+ InitAutoSyncState();
+ nsresult rv;
+ nsCOMPtr<nsIAutoSyncManager> autoSyncMgr =
+ do_GetService(NS_AUTOSYNCMANAGER_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv)) autoSyncMgr->OnFolderHasPendingMsgs(m_autoSyncStateObj);
+}
+
+/* void changePendingUnread (in long aDelta); */
+NS_IMETHODIMP nsImapMailFolder::ChangePendingUnread(int32_t aDelta) {
+ ChangeNumPendingUnread(aDelta);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailFolder::GetServerRecent(int32_t* aServerRecent) {
+ NS_ENSURE_ARG_POINTER(aServerRecent);
+ *aServerRecent = m_numServerRecentMessages;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailFolder::GetServerTotal(int32_t* aServerTotal) {
+ NS_ENSURE_ARG_POINTER(aServerTotal);
+ *aServerTotal = m_numServerTotalMessages;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailFolder::GetServerUnseen(int32_t* aServerUnseen) {
+ NS_ENSURE_ARG_POINTER(aServerUnseen);
+ *aServerUnseen = m_numServerUnseenMessages;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailFolder::GetServerNextUID(int32_t* aNextUID) {
+ NS_ENSURE_ARG_POINTER(aNextUID);
+ *aNextUID = m_nextUID;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailFolder::GetAutoSyncStateObj(
+ nsIAutoSyncState** autoSyncStateObj) {
+ NS_ENSURE_ARG_POINTER(autoSyncStateObj);
+
+ // create auto-sync state object lazily
+ InitAutoSyncState();
+
+ NS_IF_ADDREF(*autoSyncStateObj = m_autoSyncStateObj);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailFolder::InitiateAutoSync(nsIUrlListener* aUrlListener) {
+ nsCString folderName;
+ GetURI(folderName);
+ MOZ_LOG(gAutoSyncLog, mozilla::LogLevel::Debug,
+ ("%s: Updating folder: %s", __func__, folderName.get()));
+
+ // HACK: if UpdateFolder finds out that it can't open
+ // the folder, it doesn't set the url listener and returns
+ // no error. In this case, we return success from this call
+ // but the caller never gets a notification on its url listener.
+ bool canOpenThisFolder = true;
+ GetCanOpenFolder(&canOpenThisFolder);
+
+ if (!canOpenThisFolder) {
+ MOZ_LOG(gAutoSyncLog, mozilla::LogLevel::Debug,
+ ("%s: Cannot update folder: %s", __func__, folderName.get()));
+ return NS_ERROR_FAILURE;
+ }
+
+ // create auto-sync state object lazily
+ InitAutoSyncState();
+
+ // make sure we get the counts from the folder cache.
+ ReadDBFolderInfo(false);
+
+ nsresult rv = m_autoSyncStateObj->ManageStorageSpace();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t syncState;
+ m_autoSyncStateObj->GetState(&syncState);
+ if (syncState == nsAutoSyncState::stUpdateNeeded)
+ return m_autoSyncStateObj->UpdateFolder();
+
+ // We only want to init the autosyncStateObj server counts the first time
+ // we update, and update it when the STATUS call finishes. This deals with
+ // the case where biff is doing a STATUS on a non-inbox folder, which
+ // can make autosync think the counts aren't changing.
+ PRTime lastUpdateTime;
+ m_autoSyncStateObj->GetLastUpdateTime(&lastUpdateTime);
+ if (!lastUpdateTime)
+ m_autoSyncStateObj->SetServerCounts(m_numServerTotalMessages,
+ m_numServerRecentMessages,
+ m_numServerUnseenMessages, m_nextUID);
+ // Issue a STATUS command and see if any counts changed.
+ m_autoSyncStateObj->SetState(nsAutoSyncState::stStatusIssued);
+ // The OnStopRunningUrl method of the autosync state obj
+ // will check if the counts or next uid have changed,
+ // and if so, will issue an UpdateFolder().
+ rv = UpdateStatus(m_autoSyncStateObj, nullptr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // record the last update time
+ m_autoSyncStateObj->SetLastUpdateTime(PR_Now());
+
+ return NS_OK;
+}
+
+/* static */
+void nsImapMailFolder::PlaybackTimerCallback(nsITimer* aTimer, void* aClosure) {
+ nsPlaybackRequest* request = static_cast<nsPlaybackRequest*>(aClosure);
+
+ NS_ASSERTION(request->SrcFolder->m_pendingPlaybackReq == request,
+ "wrong playback request pointer");
+
+ RefPtr<nsImapOfflineSync> offlineSync = new nsImapOfflineSync();
+ offlineSync->Init(request->MsgWindow, nullptr, request->SrcFolder, true);
+ if (offlineSync) {
+ mozilla::DebugOnly<nsresult> rv = offlineSync->ProcessNextOperation();
+ NS_ASSERTION(NS_SUCCEEDED(rv), "pseudo-offline playback is not successful");
+ }
+
+ // release request struct and timer
+ request->SrcFolder->m_pendingPlaybackReq = nullptr;
+ request->SrcFolder->m_playbackTimer = nullptr; // Just to flag timed out
+ delete request;
+}
+
+void nsImapMailFolder::InitAutoSyncState() {
+ if (!m_autoSyncStateObj) m_autoSyncStateObj = new nsAutoSyncState(this);
+}
+
+NS_IMETHODIMP nsImapMailFolder::HasMsgOffline(nsMsgKey msgKey, bool* _retval) {
+ NS_ENSURE_ARG_POINTER(_retval);
+ *_retval = false;
+ nsCOMPtr<nsIMsgFolder> msgFolder;
+ nsresult rv = GetOfflineMsgFolder(msgKey, getter_AddRefs(msgFolder));
+ if (NS_SUCCEEDED(rv) && msgFolder) *_retval = true;
+ return NS_OK;
+}
+
+nsresult nsImapMailFolder::GetOfflineMsgFolder(nsMsgKey msgKey,
+ nsIMsgFolder** aMsgFolder) {
+ // Check if we have the message in the current folder.
+ NS_ENSURE_ARG_POINTER(aMsgFolder);
+ nsCOMPtr<nsIMsgFolder> subMsgFolder;
+ nsresult rv = GetDatabase();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgDBHdr> hdr;
+ rv = mDatabase->GetMsgHdrForKey(msgKey, getter_AddRefs(hdr));
+ if (NS_FAILED(rv)) return rv;
+
+ if (hdr) {
+ uint32_t msgFlags = 0;
+ hdr->GetFlags(&msgFlags);
+ // Check if we already have this message body offline
+ if ((msgFlags & nsMsgMessageFlags::Offline)) {
+ NS_IF_ADDREF(*aMsgFolder = this);
+ return NS_OK;
+ }
+ }
+
+ if (!*aMsgFolder) {
+ // Checking the existence of message in other folders in case of GMail
+ // Server
+ bool isGMail;
+ nsCOMPtr<nsIImapIncomingServer> imapServer;
+ rv = GetImapIncomingServer(getter_AddRefs(imapServer));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = imapServer->GetIsGMailServer(&isGMail);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (isGMail) {
+ nsCString labels;
+ nsTArray<nsCString> labelNames;
+ hdr->GetStringProperty("X-GM-LABELS", labels);
+ ParseString(labels, ' ', labelNames);
+ nsCOMPtr<nsIMsgFolder> rootFolder;
+ nsCOMPtr<nsIMsgImapMailFolder> subFolder;
+ for (uint32_t i = 0; i < labelNames.Length(); i++) {
+ rv = GetRootFolder(getter_AddRefs(rootFolder));
+ if (NS_SUCCEEDED(rv) && (rootFolder)) {
+ nsCOMPtr<nsIMsgImapMailFolder> imapRootFolder =
+ do_QueryInterface(rootFolder);
+ if (labelNames[i].EqualsLiteral("\"\\\\Draft\""))
+ rv = rootFolder->GetFolderWithFlags(nsMsgFolderFlags::Drafts,
+ getter_AddRefs(subMsgFolder));
+ if (labelNames[i].EqualsLiteral("\"\\\\Inbox\""))
+ rv = rootFolder->GetFolderWithFlags(nsMsgFolderFlags::Inbox,
+ getter_AddRefs(subMsgFolder));
+ if (labelNames[i].EqualsLiteral("\"\\\\All Mail\""))
+ rv = rootFolder->GetFolderWithFlags(nsMsgFolderFlags::Archive,
+ getter_AddRefs(subMsgFolder));
+ if (labelNames[i].EqualsLiteral("\"\\\\Trash\""))
+ rv = rootFolder->GetFolderWithFlags(nsMsgFolderFlags::Trash,
+ getter_AddRefs(subMsgFolder));
+ if (labelNames[i].EqualsLiteral("\"\\\\Spam\""))
+ rv = rootFolder->GetFolderWithFlags(nsMsgFolderFlags::Junk,
+ getter_AddRefs(subMsgFolder));
+ if (labelNames[i].EqualsLiteral("\"\\\\Sent\""))
+ rv = rootFolder->GetFolderWithFlags(nsMsgFolderFlags::SentMail,
+ getter_AddRefs(subMsgFolder));
+ if (FindInReadable("[Imap]/"_ns, labelNames[i],
+ nsCaseInsensitiveCStringComparator)) {
+ labelNames[i].ReplaceSubstring("[Imap]/", "");
+ imapRootFolder->FindOnlineSubFolder(labelNames[i],
+ getter_AddRefs(subFolder));
+ subMsgFolder = do_QueryInterface(subFolder);
+ }
+ if (!subMsgFolder) {
+ imapRootFolder->FindOnlineSubFolder(labelNames[i],
+ getter_AddRefs(subFolder));
+ subMsgFolder = do_QueryInterface(subFolder);
+ }
+ if (subMsgFolder) {
+ nsCOMPtr<nsIMsgDatabase> db;
+ subMsgFolder->GetMsgDatabase(getter_AddRefs(db));
+ if (db) {
+ nsCOMPtr<nsIMsgDBHdr> retHdr;
+ nsCString gmMsgID;
+ hdr->GetStringProperty("X-GM-MSGID", gmMsgID);
+ rv = db->GetMsgHdrForGMMsgID(gmMsgID.get(),
+ getter_AddRefs(retHdr));
+ if (NS_FAILED(rv)) return rv;
+ if (retHdr) {
+ uint32_t gmFlags = 0;
+ retHdr->GetFlags(&gmFlags);
+ if ((gmFlags & nsMsgMessageFlags::Offline)) {
+ subMsgFolder.forget(aMsgFolder);
+ // Focus on first positive result.
+ return NS_OK;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ return NS_OK;
+}
+
+nsresult nsImapMailFolder::GetOfflineFileStream(nsMsgKey msgKey,
+ uint64_t* offset,
+ uint32_t* size,
+ nsIInputStream** aFileStream) {
+ NS_ENSURE_ARG(aFileStream);
+ nsCOMPtr<nsIMsgFolder> offlineFolder;
+ nsresult rv = GetOfflineMsgFolder(msgKey, getter_AddRefs(offlineFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!offlineFolder) return NS_ERROR_FAILURE;
+
+ rv = GetDatabase();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (offlineFolder == this) {
+ return nsMsgDBFolder::GetOfflineFileStream(msgKey, offset, size,
+ aFileStream);
+ }
+
+ // The message we want is stored in a different folder (hackery for gmail).
+ nsCOMPtr<nsIMsgDBHdr> hdr;
+ rv = mDatabase->GetMsgHdrForKey(msgKey, getter_AddRefs(hdr));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCString gmMsgID;
+ hdr->GetStringProperty("X-GM-MSGID", gmMsgID);
+ nsCOMPtr<nsIMsgDatabase> db;
+ offlineFolder->GetMsgDatabase(getter_AddRefs(db));
+ rv = db->GetMsgHdrForGMMsgID(gmMsgID.get(), getter_AddRefs(hdr));
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!hdr) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsMsgKey newMsgKey;
+ hdr->GetMessageKey(&newMsgKey);
+
+ // We _know_ it's a nsImapMailFolder.
+ nsImapMailFolder* other = static_cast<nsImapMailFolder*>(offlineFolder.get());
+ return other->GetOfflineFileStream(newMsgKey, offset, size, aFileStream);
+}
+
+NS_IMETHODIMP nsImapMailFolder::GetLocalMsgStream(nsIMsgDBHdr* hdr,
+ nsIInputStream** stream) {
+ nsMsgKey key;
+ hdr->GetMessageKey(&key);
+
+ uint64_t offset = 0;
+ uint32_t size = 0;
+ nsCOMPtr<nsIInputStream> rawStream;
+ nsresult rv =
+ GetOfflineFileStream(key, &offset, &size, getter_AddRefs(rawStream));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<SlicedInputStream> slicedStream =
+ new SlicedInputStream(rawStream.forget(), offset, uint64_t(size));
+ slicedStream.forget(stream);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailFolder::GetIncomingServerType(nsACString& serverType) {
+ serverType.AssignLiteral("imap");
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImapMailFolder::GetShouldUseUtf8FolderName(bool* aUseUTF8) {
+ *aUseUTF8 = false;
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ nsresult rv = GetServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIImapIncomingServer> imapServer = do_QueryInterface(server, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ imapServer->GetUtf8AcceptEnabled(aUseUTF8);
+ return NS_OK;
+}
+
+void nsImapMailFolder::DeleteStoreMessages(
+ const nsTArray<RefPtr<nsIMsgDBHdr>>& aMessages) {
+ // Delete messages for pluggable stores that do not support compaction.
+ nsCOMPtr<nsIMsgPluggableStore> offlineStore;
+ (void)GetMsgStore(getter_AddRefs(offlineStore));
+
+ if (offlineStore) {
+ bool supportsCompaction;
+ offlineStore->GetSupportsCompaction(&supportsCompaction);
+ if (!supportsCompaction) offlineStore->DeleteMessages(aMessages);
+ }
+}
+
+void nsImapMailFolder::DeleteStoreMessages(
+ const nsTArray<nsMsgKey>& aMessages) {
+ DeleteStoreMessages(aMessages, this);
+}
+
+void nsImapMailFolder::DeleteStoreMessages(const nsTArray<nsMsgKey>& aMessages,
+ nsIMsgFolder* aFolder) {
+ // Delete messages for pluggable stores that do not support compaction.
+ NS_ASSERTION(aFolder, "Missing Source Folder");
+ nsCOMPtr<nsIMsgPluggableStore> offlineStore;
+ (void)aFolder->GetMsgStore(getter_AddRefs(offlineStore));
+ if (offlineStore) {
+ bool supportsCompaction;
+ offlineStore->GetSupportsCompaction(&supportsCompaction);
+ if (!supportsCompaction) {
+ nsCOMPtr<nsIMsgDatabase> db;
+ aFolder->GetMsgDatabase(getter_AddRefs(db));
+ nsresult rv = NS_ERROR_FAILURE;
+ nsTArray<RefPtr<nsIMsgDBHdr>> messages;
+ if (db) rv = MsgGetHeadersFromKeys(db, aMessages, messages);
+ if (NS_SUCCEEDED(rv))
+ offlineStore->DeleteMessages(messages);
+ else
+ NS_WARNING("Failed to get database");
+ }
+ }
+}