diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
commit | 6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch) | |
tree | a68f146d7fa01f0134297619fbe7e33db084e0aa /comm/mailnews/base/src/nsMsgAccountManager.cpp | |
parent | Initial commit. (diff) | |
download | thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.tar.xz thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.zip |
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'comm/mailnews/base/src/nsMsgAccountManager.cpp')
-rw-r--r-- | comm/mailnews/base/src/nsMsgAccountManager.cpp | 3546 |
1 files changed, 3546 insertions, 0 deletions
diff --git a/comm/mailnews/base/src/nsMsgAccountManager.cpp b/comm/mailnews/base/src/nsMsgAccountManager.cpp new file mode 100644 index 0000000000..5352486cb4 --- /dev/null +++ b/comm/mailnews/base/src/nsMsgAccountManager.cpp @@ -0,0 +1,3546 @@ +/* -*- 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/. */ + +/** + * The account manager service - manages all accounts, servers, and identities + */ + +#include "nsCOMPtr.h" +#include "nsISupports.h" +#include "nsIThread.h" +#include "nscore.h" +#include "mozilla/Assertions.h" +#include "mozilla/Likely.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/RefCountType.h" +#include "mozilla/RefPtr.h" +#include "nsIComponentManager.h" +#include "nsIServiceManager.h" +#include "nsMsgAccountManager.h" +#include "prmem.h" +#include "prcmon.h" +#include "prthread.h" +#include "plstr.h" +#include "nsString.h" +#include "nsMemory.h" +#include "nsUnicharUtils.h" +#include "nscore.h" +#include "prprf.h" +#include "nsIMsgFolderCache.h" +#include "nsMsgFolderCache.h" +#include "nsMsgUtils.h" +#include "nsMsgDBFolder.h" +#include "nsIFile.h" +#include "nsIURL.h" +#include "nsNetCID.h" +#include "nsIPrefService.h" +#include "nsIPrefBranch.h" +#include "nsISmtpService.h" +#include "nsIMsgBiffManager.h" +#include "nsIMsgPurgeService.h" +#include "nsIObserverService.h" +#include "nsINoIncomingServer.h" +#include "nsIMsgMailSession.h" +#include "nsIDirectoryService.h" +#include "nsAppDirectoryServiceDefs.h" +#include "nsMailDirServiceDefs.h" +#include "nsMsgFolderFlags.h" +#include "nsIMsgFolderNotificationService.h" +#include "nsIImapIncomingServer.h" +#include "nsIImapUrl.h" +#include "nsICategoryManager.h" +#include "nsISupportsPrimitives.h" +#include "nsIMsgFilterService.h" +#include "nsIMsgFilter.h" +#include "nsIMsgSearchSession.h" +#include "nsIMsgSearchTerm.h" +#include "nsIDBFolderInfo.h" +#include "nsIMsgHdr.h" +#include "nsILineInputStream.h" +#include "nsThreadUtils.h" +#include "nsNetUtil.h" +#include "nsIStringBundle.h" +#include "nsMsgMessageFlags.h" +#include "nsIMsgFilterList.h" +#include "nsDirectoryServiceUtils.h" +#include "mozilla/Components.h" +#include "mozilla/Services.h" +#include "nsIFileStreams.h" +#include "nsIOutputStream.h" +#include "nsISafeOutputStream.h" +#include "nsXULAppAPI.h" +#include "nsICacheStorageService.h" +#include "UrlListener.h" +#include "nsIIDNService.h" + +#define PREF_MAIL_ACCOUNTMANAGER_ACCOUNTS "mail.accountmanager.accounts" +#define PREF_MAIL_ACCOUNTMANAGER_DEFAULTACCOUNT \ + "mail.accountmanager.defaultaccount" +#define PREF_MAIL_ACCOUNTMANAGER_LOCALFOLDERSSERVER \ + "mail.accountmanager.localfoldersserver" +#define PREF_MAIL_SERVER_PREFIX "mail.server." +#define ACCOUNT_PREFIX "account" +#define SERVER_PREFIX "server" +#define ID_PREFIX "id" +#define ABOUT_TO_GO_OFFLINE_TOPIC "network:offline-about-to-go-offline" +#define ACCOUNT_DELIMITER ',' +#define APPEND_ACCOUNTS_VERSION_PREF_NAME "append_preconfig_accounts.version" +#define MAILNEWS_ROOT_PREF "mailnews." +#define PREF_MAIL_ACCOUNTMANAGER_APPEND_ACCOUNTS \ + "mail.accountmanager.appendaccounts" + +#define NS_MSGACCOUNT_CID \ + { \ + 0x68b25510, 0xe641, 0x11d2, { \ + 0xb7, 0xfc, 0x0, 0x80, 0x5f, 0x5, 0xff, 0xa5 \ + } \ + } +static NS_DEFINE_CID(kMsgAccountCID, NS_MSGACCOUNT_CID); + +#define SEARCH_FOLDER_FLAG "searchFolderFlag" +#define SEARCH_FOLDER_FLAG_LEN (sizeof(SEARCH_FOLDER_FLAG) - 1) + +const char* kSearchFolderUriProp = "searchFolderUri"; + +bool nsMsgAccountManager::m_haveShutdown = false; +bool nsMsgAccountManager::m_shutdownInProgress = false; + +NS_IMPL_ISUPPORTS(nsMsgAccountManager, nsIMsgAccountManager, nsIObserver, + nsISupportsWeakReference, nsIFolderListener) + +nsMsgAccountManager::nsMsgAccountManager() + : m_accountsLoaded(false), + m_emptyTrashInProgress(false), + m_cleanupInboxInProgress(false), + m_userAuthenticated(false), + m_loadingVirtualFolders(false), + m_virtualFoldersLoaded(false), + m_lastFindServerPort(0) {} + +nsMsgAccountManager::~nsMsgAccountManager() { + if (!m_haveShutdown) { + Shutdown(); + // Don't remove from Observer service in Shutdown because Shutdown also gets + // called from xpcom shutdown observer. And we don't want to remove from + // the service in that case. + nsCOMPtr<nsIObserverService> observerService = + mozilla::services::GetObserverService(); + if (observerService) { + observerService->RemoveObserver(this, "search-folders-changed"); + observerService->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID); + observerService->RemoveObserver(this, "quit-application-granted"); + observerService->RemoveObserver(this, ABOUT_TO_GO_OFFLINE_TOPIC); + observerService->RemoveObserver(this, "sleep_notification"); + } + } +} + +nsresult nsMsgAccountManager::Init() { + if (!XRE_IsParentProcess()) { + return NS_ERROR_NOT_AVAILABLE; + } + + nsresult rv; + + m_prefs = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIObserverService> observerService = + mozilla::services::GetObserverService(); + if (observerService) { + observerService->AddObserver(this, "search-folders-changed", true); + observerService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, true); + observerService->AddObserver(this, "quit-application-granted", true); + observerService->AddObserver(this, ABOUT_TO_GO_OFFLINE_TOPIC, true); + observerService->AddObserver(this, "profile-before-change", true); + observerService->AddObserver(this, "sleep_notification", true); + } + + // Make sure PSM gets initialized before any accounts use certificates. + net_EnsurePSMInit(); + + return NS_OK; +} + +nsresult nsMsgAccountManager::Shutdown() { + if (m_haveShutdown) // do not shutdown twice + return NS_OK; + + nsresult rv; + + SaveVirtualFolders(); + + if (m_dbService) { + nsTObserverArray<RefPtr<VirtualFolderChangeListener>>::ForwardIterator iter( + m_virtualFolderListeners); + RefPtr<VirtualFolderChangeListener> listener; + + while (iter.HasMore()) { + listener = iter.GetNext(); + m_dbService->UnregisterPendingListener(listener); + } + + m_dbService = nullptr; + } + m_virtualFolders.Clear(); + if (m_msgFolderCache) WriteToFolderCache(m_msgFolderCache); + (void)ShutdownServers(); + (void)UnloadAccounts(); + + // shutdown removes nsIIncomingServer listener from biff manager, so do it + // after accounts have been unloaded + nsCOMPtr<nsIMsgBiffManager> biffService = + do_GetService("@mozilla.org/messenger/biffManager;1", &rv); + if (NS_SUCCEEDED(rv) && biffService) biffService->Shutdown(); + + // shutdown removes nsIIncomingServer listener from purge service, so do it + // after accounts have been unloaded + nsCOMPtr<nsIMsgPurgeService> purgeService = + do_GetService("@mozilla.org/messenger/purgeService;1", &rv); + if (NS_SUCCEEDED(rv) && purgeService) purgeService->Shutdown(); + + if (m_msgFolderCache) { + // The DTOR is meant to do the flushing, but observed behaviour is + // that it doesn't always get called. So flush explicitly. + m_msgFolderCache->Flush(); + m_msgFolderCache = nullptr; + } + + m_haveShutdown = true; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgAccountManager::GetShutdownInProgress(bool* _retval) { + NS_ENSURE_ARG_POINTER(_retval); + *_retval = m_shutdownInProgress; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgAccountManager::GetUserNeedsToAuthenticate(bool* aRetval) { + NS_ENSURE_ARG_POINTER(aRetval); + if (!m_userAuthenticated) + return m_prefs->GetBoolPref("mail.password_protect_local_cache", aRetval); + *aRetval = !m_userAuthenticated; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgAccountManager::SetUserNeedsToAuthenticate(bool aUserNeedsToAuthenticate) { + m_userAuthenticated = !aUserNeedsToAuthenticate; + return NS_OK; +} + +NS_IMETHODIMP nsMsgAccountManager::Observe(nsISupports* aSubject, + const char* aTopic, + const char16_t* someData) { + if (!strcmp(aTopic, "search-folders-changed")) { + nsCOMPtr<nsIMsgFolder> virtualFolder = do_QueryInterface(aSubject); + nsCOMPtr<nsIMsgDatabase> db; + nsCOMPtr<nsIDBFolderInfo> dbFolderInfo; + virtualFolder->GetDBFolderInfoAndDB(getter_AddRefs(dbFolderInfo), + getter_AddRefs(db)); + nsCString srchFolderUris; + dbFolderInfo->GetCharProperty(kSearchFolderUriProp, srchFolderUris); + AddVFListenersForVF(virtualFolder, srchFolderUris); + return NS_OK; + } + if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) { + Shutdown(); + return NS_OK; + } + if (!strcmp(aTopic, "quit-application-granted")) { + // CleanupOnExit will set m_shutdownInProgress to true. + CleanupOnExit(); + return NS_OK; + } + if (!strcmp(aTopic, ABOUT_TO_GO_OFFLINE_TOPIC)) { + nsAutoString dataString(u"offline"_ns); + if (someData) { + nsAutoString someDataString(someData); + if (dataString.Equals(someDataString)) CloseCachedConnections(); + } + return NS_OK; + } + if (!strcmp(aTopic, "sleep_notification")) return CloseCachedConnections(); + + if (!strcmp(aTopic, "profile-before-change")) { + Shutdown(); + return NS_OK; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsMsgAccountManager::GetUniqueAccountKey(nsACString& aResult) { + int32_t lastKey = 0; + nsresult rv; + nsCOMPtr<nsIPrefService> prefservice( + do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + if (NS_SUCCEEDED(rv)) { + nsCOMPtr<nsIPrefBranch> prefBranch; + prefservice->GetBranch("", getter_AddRefs(prefBranch)); + + rv = prefBranch->GetIntPref("mail.account.lastKey", &lastKey); + if (NS_FAILED(rv) || lastKey == 0) { + // If lastKey pref does not contain a valid value, loop over existing + // pref names mail.account.* . + nsCOMPtr<nsIPrefBranch> prefBranchAccount; + rv = prefservice->GetBranch("mail.account.", + getter_AddRefs(prefBranchAccount)); + if (NS_SUCCEEDED(rv)) { + nsTArray<nsCString> prefList; + rv = prefBranchAccount->GetChildList("", prefList); + if (NS_SUCCEEDED(rv)) { + // Pref names are of the format accountX. + // Find the maximum value of 'X' used so far. + for (auto& prefName : prefList) { + if (StringBeginsWith(prefName, nsLiteralCString(ACCOUNT_PREFIX))) { + int32_t dotPos = prefName.FindChar('.'); + if (dotPos != kNotFound) { + nsCString keyString(Substring(prefName, strlen(ACCOUNT_PREFIX), + dotPos - strlen(ACCOUNT_PREFIX))); + int32_t thisKey = keyString.ToInteger(&rv); + if (NS_SUCCEEDED(rv)) lastKey = std::max(lastKey, thisKey); + } + } + } + } + } + } + + // Use next available key and store the value in the pref. + aResult.Assign(ACCOUNT_PREFIX); + aResult.AppendInt(++lastKey); + rv = prefBranch->SetIntPref("mail.account.lastKey", lastKey); + } else { + // If pref service is not working, try to find a free accountX key + // by checking which keys exist. + int32_t i = 1; + nsCOMPtr<nsIMsgAccount> account; + + do { + aResult = ACCOUNT_PREFIX; + aResult.AppendInt(i++); + GetAccount(aResult, getter_AddRefs(account)); + } while (account); + } + return NS_OK; +} + +NS_IMETHODIMP +nsMsgAccountManager::GetUniqueServerKey(nsACString& aResult) { + nsAutoCString prefResult; + bool usePrefsScan = true; + nsresult rv; + nsCOMPtr<nsIPrefService> prefService( + do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + if (NS_FAILED(rv)) usePrefsScan = false; + + // Loop over existing pref names mail.server.server(lastKey).type + nsCOMPtr<nsIPrefBranch> prefBranchServer; + if (prefService) { + rv = prefService->GetBranch(PREF_MAIL_SERVER_PREFIX, + getter_AddRefs(prefBranchServer)); + if (NS_FAILED(rv)) usePrefsScan = false; + } + + if (usePrefsScan) { + nsAutoCString type; + nsAutoCString typeKey; + for (uint32_t lastKey = 1;; lastKey++) { + aResult.AssignLiteral(SERVER_PREFIX); + aResult.AppendInt(lastKey); + typeKey.Assign(aResult); + typeKey.AppendLiteral(".type"); + prefBranchServer->GetCharPref(typeKey.get(), type); + if (type.IsEmpty()) // a server slot with no type is considered empty + return NS_OK; + } + } else { + // If pref service fails, try to find a free serverX key + // by checking which keys exist. + nsAutoCString internalResult; + nsCOMPtr<nsIMsgIncomingServer> server; + uint32_t i = 1; + do { + aResult.AssignLiteral(SERVER_PREFIX); + aResult.AppendInt(i++); + m_incomingServers.Get(aResult, getter_AddRefs(server)); + } while (server); + return NS_OK; + } +} + +nsresult nsMsgAccountManager::CreateIdentity(nsIMsgIdentity** _retval) { + NS_ENSURE_ARG_POINTER(_retval); + nsresult rv; + nsAutoCString key; + nsCOMPtr<nsIMsgIdentity> identity; + int32_t i = 1; + do { + key.AssignLiteral(ID_PREFIX); + key.AppendInt(i++); + m_identities.Get(key, getter_AddRefs(identity)); + } while (identity); + + rv = createKeyedIdentity(key, _retval); + return rv; +} + +NS_IMETHODIMP +nsMsgAccountManager::GetIdentity(const nsACString& key, + nsIMsgIdentity** _retval) { + NS_ENSURE_ARG_POINTER(_retval); + nsresult rv = NS_OK; + *_retval = nullptr; + + if (!key.IsEmpty()) { + nsCOMPtr<nsIMsgIdentity> identity; + m_identities.Get(key, getter_AddRefs(identity)); + if (identity) + identity.forget(_retval); + else // identity doesn't exist. create it. + rv = createKeyedIdentity(key, _retval); + } + + return rv; +} + +/* + * the shared identity-creation code + * create an identity and add it to the accountmanager's list. + */ +nsresult nsMsgAccountManager::createKeyedIdentity(const nsACString& key, + nsIMsgIdentity** aIdentity) { + nsresult rv; + nsCOMPtr<nsIMsgIdentity> identity = + do_CreateInstance("@mozilla.org/messenger/identity;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + identity->SetKey(key); + m_identities.InsertOrUpdate(key, identity); + identity.forget(aIdentity); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgAccountManager::CreateIncomingServer(const nsACString& username, + const nsACString& hostname, + const nsACString& type, + nsIMsgIncomingServer** _retval) { + NS_ENSURE_ARG_POINTER(_retval); + + nsresult rv = LoadAccounts(); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString key; + GetUniqueServerKey(key); + rv = createKeyedServer(key, username, hostname, type, _retval); + if (*_retval) { + nsCString defaultStore; + m_prefs->GetCharPref("mail.serverDefaultStoreContractID", defaultStore); + (*_retval)->SetCharValue("storeContractID", defaultStore); + + // From when we first create the account until we have created some folders, + // we can change the store type. + (*_retval)->SetBoolValue("canChangeStoreType", true); + } + return rv; +} + +NS_IMETHODIMP +nsMsgAccountManager::GetIncomingServer(const nsACString& key, + nsIMsgIncomingServer** _retval) { + NS_ENSURE_ARG_POINTER(_retval); + nsresult rv; + + if (m_incomingServers.Get(key, _retval)) return NS_OK; + + // server doesn't exist, so create it + // this is really horrible because we are doing our own prefname munging + // instead of leaving it up to the incoming server. + // this should be fixed somehow so that we can create the incoming server + // and then read from the incoming server's attributes + + // in order to create the right kind of server, we have to look + // at the pref for this server to get the username, hostname, and type + nsAutoCString serverPrefPrefix(PREF_MAIL_SERVER_PREFIX); + serverPrefPrefix.Append(key); + + nsCString serverType; + nsAutoCString serverPref(serverPrefPrefix); + serverPref.AppendLiteral(".type"); + rv = m_prefs->GetCharPref(serverPref.get(), serverType); + NS_ENSURE_SUCCESS(rv, NS_ERROR_NOT_INITIALIZED); + + // + // .userName + serverPref = serverPrefPrefix; + serverPref.AppendLiteral(".userName"); + nsCString username; + rv = m_prefs->GetCharPref(serverPref.get(), username); + + // .hostname + serverPref = serverPrefPrefix; + serverPref.AppendLiteral(".hostname"); + nsCString hostname; + rv = m_prefs->GetCharPref(serverPref.get(), hostname); + NS_ENSURE_SUCCESS(rv, NS_ERROR_NOT_INITIALIZED); + + return createKeyedServer(key, username, hostname, serverType, _retval); +} + +NS_IMETHODIMP +nsMsgAccountManager::RemoveIncomingServer(nsIMsgIncomingServer* aServer, + bool aRemoveFiles) { + NS_ENSURE_ARG_POINTER(aServer); + + nsCString serverKey; + nsresult rv = aServer->GetKey(serverKey); + NS_ENSURE_SUCCESS(rv, rv); + + // close cached connections and forget session password + LogoutOfServer(aServer); + + // invalidate the FindServer() cache if we are removing the cached server + if (m_lastFindServerResult == aServer) + SetLastServerFound(nullptr, EmptyCString(), EmptyCString(), 0, + EmptyCString()); + + m_incomingServers.Remove(serverKey); + + nsCOMPtr<nsIMsgFolder> rootFolder; + rv = aServer->GetRootFolder(getter_AddRefs(rootFolder)); + NS_ENSURE_SUCCESS(rv, rv); + + nsTArray<RefPtr<nsIMsgFolder>> allDescendants; + rv = rootFolder->GetDescendants(allDescendants); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMsgFolderNotificationService> notifier = + do_GetService("@mozilla.org/messenger/msgnotificationservice;1"); + nsCOMPtr<nsIFolderListener> mailSession = + do_GetService("@mozilla.org/messenger/services/session;1"); + + for (auto folder : allDescendants) { + folder->ForceDBClosed(); + if (notifier) notifier->NotifyFolderDeleted(folder); + if (mailSession) { + nsCOMPtr<nsIMsgFolder> parentFolder; + folder->GetParent(getter_AddRefs(parentFolder)); + mailSession->OnFolderRemoved(parentFolder, folder); + } + } + if (notifier) notifier->NotifyFolderDeleted(rootFolder); + if (mailSession) mailSession->OnFolderRemoved(nullptr, rootFolder); + + removeListenersFromFolder(rootFolder); + NotifyServerUnloaded(aServer); + if (aRemoveFiles) { + rv = aServer->RemoveFiles(); + NS_ENSURE_SUCCESS(rv, rv); + } + + nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); + if (obs) { + obs->NotifyObservers(aServer, "message-server-removed", + NS_ConvertUTF8toUTF16(serverKey).get()); + } + + // now clear out the server once and for all. + // watch out! could be scary + aServer->ClearAllValues(); + rootFolder->Shutdown(true); + return rv; +} + +/* + * create a server when you know the key and the type + */ +nsresult nsMsgAccountManager::createKeyedServer( + const nsACString& key, const nsACString& username, + const nsACString& hostname, const nsACString& type, + nsIMsgIncomingServer** aServer) { + nsresult rv; + *aServer = nullptr; + + // construct the contractid + nsAutoCString serverContractID("@mozilla.org/messenger/server;1?type="); + serverContractID += type; + + // finally, create the server + // (This will fail if type is from an extension that has been removed) + nsCOMPtr<nsIMsgIncomingServer> server = + do_CreateInstance(serverContractID.get(), &rv); + NS_ENSURE_SUCCESS(rv, NS_ERROR_NOT_AVAILABLE); + + int32_t port; + nsCOMPtr<nsIMsgIncomingServer> existingServer; + server->SetKey(key); + server->SetType(type); + server->SetUsername(username); + server->SetHostName(hostname); + server->GetPort(&port); + FindServer(username, hostname, type, port, getter_AddRefs(existingServer)); + // don't allow duplicate servers. + if (existingServer) return NS_ERROR_FAILURE; + + m_incomingServers.InsertOrUpdate(key, server); + + // now add all listeners that are supposed to be + // waiting on root folders + nsCOMPtr<nsIMsgFolder> rootFolder; + rv = server->GetRootFolder(getter_AddRefs(rootFolder)); + NS_ENSURE_SUCCESS(rv, rv); + + nsTObserverArray<nsCOMPtr<nsIFolderListener>>::ForwardIterator iter( + mFolderListeners); + while (iter.HasMore()) { + rootFolder->AddFolderListener(iter.GetNext()); + } + + server.forget(aServer); + return NS_OK; +} + +void nsMsgAccountManager::removeListenersFromFolder(nsIMsgFolder* aFolder) { + nsTObserverArray<nsCOMPtr<nsIFolderListener>>::ForwardIterator iter( + mFolderListeners); + while (iter.HasMore()) { + aFolder->RemoveFolderListener(iter.GetNext()); + } +} + +NS_IMETHODIMP +nsMsgAccountManager::RemoveAccount(nsIMsgAccount* aAccount, + bool aRemoveFiles = false) { + NS_ENSURE_ARG_POINTER(aAccount); + // Hold account in scope while we tidy up potentially-shared identities. + nsresult rv = LoadAccounts(); + NS_ENSURE_SUCCESS(rv, rv); + + if (!m_accounts.RemoveElement(aAccount)) { + return NS_ERROR_INVALID_ARG; + } + + rv = OutputAccountsPref(); + // If we couldn't write out the pref, restore the account. + if (NS_FAILED(rv)) { + m_accounts.AppendElement(aAccount); + return rv; + } + + // If it's the default, choose a new default account. + if (m_defaultAccount == aAccount) AutosetDefaultAccount(); + + // XXX - need to figure out if this is the last time this server is + // being used, and only send notification then. + // (and only remove from hashtable then too!) + nsCOMPtr<nsIMsgIncomingServer> server; + rv = aAccount->GetIncomingServer(getter_AddRefs(server)); + if (NS_SUCCEEDED(rv) && server) RemoveIncomingServer(server, aRemoveFiles); + + nsTArray<RefPtr<nsIMsgIdentity>> identities; + rv = aAccount->GetIdentities(identities); + if (NS_SUCCEEDED(rv)) { + for (auto identity : identities) { + bool identityStillUsed = false; + // for each identity, see if any remaining account still uses it, + // and if not, clear it. + // Note that we are also searching here accounts with missing servers from + // unloaded extension types. + for (auto account : m_accounts) { + nsTArray<RefPtr<nsIMsgIdentity>> existingIdentities; + account->GetIdentities(existingIdentities); + auto pos = existingIdentities.IndexOf(identity); + if (pos != existingIdentities.NoIndex) { + identityStillUsed = true; + break; + } + } + // clear out all identity information if no other account uses it. + if (!identityStillUsed) identity->ClearAllValues(); + } + } + + nsCString accountKey; + aAccount->GetKey(accountKey); + + // It is not a critical problem if this fails as the account was already + // removed from the list of accounts so should not ever be referenced. + // Just print it out for debugging. + rv = aAccount->ClearAllValues(); + NS_ASSERTION(NS_SUCCEEDED(rv), "removing of account prefs failed"); + + nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); + if (obs) { + obs->NotifyObservers(nullptr, "message-account-removed", + NS_ConvertUTF8toUTF16(accountKey).get()); + } + return NS_OK; +} + +nsresult nsMsgAccountManager::OutputAccountsPref() { + nsCString accountKey; + mAccountKeyList.Truncate(); + + for (uint32_t index = 0; index < m_accounts.Length(); index++) { + m_accounts[index]->GetKey(accountKey); + if (index) mAccountKeyList.Append(ACCOUNT_DELIMITER); + mAccountKeyList.Append(accountKey); + } + return m_prefs->SetCharPref(PREF_MAIL_ACCOUNTMANAGER_ACCOUNTS, + mAccountKeyList); +} + +/** + * Get the default account. If no default account, return null. + */ +NS_IMETHODIMP +nsMsgAccountManager::GetDefaultAccount(nsIMsgAccount** aDefaultAccount) { + NS_ENSURE_ARG_POINTER(aDefaultAccount); + + nsresult rv = LoadAccounts(); + NS_ENSURE_SUCCESS(rv, rv); + + if (!m_defaultAccount) { + nsCString defaultKey; + rv = m_prefs->GetCharPref(PREF_MAIL_ACCOUNTMANAGER_DEFAULTACCOUNT, + defaultKey); + if (NS_SUCCEEDED(rv)) { + rv = GetAccount(defaultKey, getter_AddRefs(m_defaultAccount)); + if (NS_SUCCEEDED(rv) && m_defaultAccount) { + bool canBeDefault = false; + rv = CheckDefaultAccount(m_defaultAccount, canBeDefault); + if (NS_FAILED(rv) || !canBeDefault) m_defaultAccount = nullptr; + } + } + } + + NS_IF_ADDREF(*aDefaultAccount = m_defaultAccount); + return NS_OK; +} + +/** + * Check if the given account can be default. + */ +nsresult nsMsgAccountManager::CheckDefaultAccount(nsIMsgAccount* aAccount, + bool& aCanBeDefault) { + aCanBeDefault = false; + nsCOMPtr<nsIMsgIncomingServer> server; + // Server could be null if created by an unloaded extension. + nsresult rv = aAccount->GetIncomingServer(getter_AddRefs(server)); + NS_ENSURE_SUCCESS(rv, rv); + if (server) { + // Check if server can be default. + rv = server->GetCanBeDefaultServer(&aCanBeDefault); + } + return rv; +} + +/** + * Pick the first account that can be default and make it the default. + */ +nsresult nsMsgAccountManager::AutosetDefaultAccount() { + for (nsIMsgAccount* account : m_accounts) { + bool canBeDefault = false; + nsresult rv = CheckDefaultAccount(account, canBeDefault); + if (NS_SUCCEEDED(rv) && canBeDefault) { + return SetDefaultAccount(account); + } + } + + // No accounts can be the default. Clear it. + if (m_defaultAccount) { + nsCOMPtr<nsIMsgAccount> oldAccount = m_defaultAccount; + m_defaultAccount = nullptr; + (void)setDefaultAccountPref(nullptr); + (void)notifyDefaultServerChange(oldAccount, nullptr); + } + return NS_OK; +} + +NS_IMETHODIMP +nsMsgAccountManager::SetDefaultAccount(nsIMsgAccount* aDefaultAccount) { + if (!aDefaultAccount) return NS_ERROR_INVALID_ARG; + + if (m_defaultAccount != aDefaultAccount) { + bool canBeDefault = false; + nsresult rv = CheckDefaultAccount(aDefaultAccount, canBeDefault); + if (NS_FAILED(rv) || !canBeDefault) { + // Report failure if we were explicitly asked to use an unusable server. + return NS_ERROR_INVALID_ARG; + } + nsCOMPtr<nsIMsgAccount> oldAccount = m_defaultAccount; + m_defaultAccount = aDefaultAccount; + (void)setDefaultAccountPref(aDefaultAccount); + (void)notifyDefaultServerChange(oldAccount, aDefaultAccount); + } + return NS_OK; +} + +// fire notifications +nsresult nsMsgAccountManager::notifyDefaultServerChange( + nsIMsgAccount* aOldAccount, nsIMsgAccount* aNewAccount) { + nsresult rv; + + nsCOMPtr<nsIMsgIncomingServer> server; + nsCOMPtr<nsIMsgFolder> rootFolder; + + // first tell old server it's no longer the default + if (aOldAccount) { + rv = aOldAccount->GetIncomingServer(getter_AddRefs(server)); + if (NS_SUCCEEDED(rv) && server) { + rv = server->GetRootFolder(getter_AddRefs(rootFolder)); + if (NS_SUCCEEDED(rv) && rootFolder) + rootFolder->NotifyBoolPropertyChanged(kDefaultServer, true, false); + } + } + + // now tell new server it is. + if (aNewAccount) { + rv = aNewAccount->GetIncomingServer(getter_AddRefs(server)); + if (NS_SUCCEEDED(rv) && server) { + rv = server->GetRootFolder(getter_AddRefs(rootFolder)); + if (NS_SUCCEEDED(rv) && rootFolder) + rootFolder->NotifyBoolPropertyChanged(kDefaultServer, false, true); + } + } + + // only notify if the user goes and changes default account + if (aOldAccount && aNewAccount) { + nsCOMPtr<nsIObserverService> observerService = + mozilla::services::GetObserverService(); + + if (observerService) + observerService->NotifyObservers(nullptr, "mailDefaultAccountChanged", + nullptr); + } + + return NS_OK; +} + +nsresult nsMsgAccountManager::setDefaultAccountPref( + nsIMsgAccount* aDefaultAccount) { + nsresult rv; + + if (aDefaultAccount) { + nsCString key; + rv = aDefaultAccount->GetKey(key); + NS_ENSURE_SUCCESS(rv, rv); + + rv = m_prefs->SetCharPref(PREF_MAIL_ACCOUNTMANAGER_DEFAULTACCOUNT, key); + NS_ENSURE_SUCCESS(rv, rv); + } else + m_prefs->ClearUserPref(PREF_MAIL_ACCOUNTMANAGER_DEFAULTACCOUNT); + + return NS_OK; +} + +void nsMsgAccountManager::LogoutOfServer(nsIMsgIncomingServer* aServer) { + if (!aServer) return; + mozilla::DebugOnly<nsresult> rv = aServer->Shutdown(); + NS_ASSERTION(NS_SUCCEEDED(rv), "Shutdown of server failed"); + rv = aServer->ForgetSessionPassword(false); + NS_ASSERTION(NS_SUCCEEDED(rv), + "failed to remove the password associated with server"); +} + +NS_IMETHODIMP nsMsgAccountManager::GetFolderCache( + nsIMsgFolderCache** aFolderCache) { + NS_ENSURE_ARG_POINTER(aFolderCache); + + if (m_msgFolderCache) { + NS_IF_ADDREF(*aFolderCache = m_msgFolderCache); + return NS_OK; + } + + // Create the foldercache. + nsCOMPtr<nsIFile> cacheFile; + nsresult rv = NS_GetSpecialDirectory(NS_APP_MESSENGER_FOLDER_CACHE_50_FILE, + getter_AddRefs(cacheFile)); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr<nsIFile> legacyFile; + rv = NS_GetSpecialDirectory(NS_APP_MESSENGER_LEGACY_FOLDER_CACHE_50_FILE, + getter_AddRefs(legacyFile)); + NS_ENSURE_SUCCESS(rv, rv); + m_msgFolderCache = new nsMsgFolderCache(); + m_msgFolderCache->Init(cacheFile, legacyFile); + NS_IF_ADDREF(*aFolderCache = m_msgFolderCache); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgAccountManager::GetAccounts(nsTArray<RefPtr<nsIMsgAccount>>& accounts) { + nsresult rv = LoadAccounts(); + NS_ENSURE_SUCCESS(rv, rv); + + accounts.Clear(); + accounts.SetCapacity(m_accounts.Length()); + for (auto existingAccount : m_accounts) { + nsCOMPtr<nsIMsgIncomingServer> server; + existingAccount->GetIncomingServer(getter_AddRefs(server)); + if (!server) continue; + if (server) { + bool hidden = false; + server->GetHidden(&hidden); + if (hidden) continue; + } + accounts.AppendElement(existingAccount); + } + return NS_OK; +} + +NS_IMETHODIMP +nsMsgAccountManager::GetAllIdentities( + nsTArray<RefPtr<nsIMsgIdentity>>& result) { + nsresult rv = LoadAccounts(); + NS_ENSURE_SUCCESS(rv, rv); + + result.Clear(); + + for (auto account : m_accounts) { + nsTArray<RefPtr<nsIMsgIdentity>> identities; + rv = account->GetIdentities(identities); + if (NS_FAILED(rv)) continue; + + for (auto identity : identities) { + // Have we already got this identity? + nsAutoCString key; + rv = identity->GetKey(key); + if (NS_FAILED(rv)) continue; + + bool found = false; + for (auto thisIdentity : result) { + nsAutoCString thisKey; + rv = thisIdentity->GetKey(thisKey); + if (NS_FAILED(rv)) continue; + + if (key == thisKey) { + found = true; + break; + } + } + + if (!found) result.AppendElement(identity); + } + } + return rv; +} + +NS_IMETHODIMP +nsMsgAccountManager::GetAllServers( + nsTArray<RefPtr<nsIMsgIncomingServer>>& servers) { + servers.Clear(); + nsresult rv = LoadAccounts(); + NS_ENSURE_SUCCESS(rv, rv); + + for (auto iter = m_incomingServers.Iter(); !iter.Done(); iter.Next()) { + nsCOMPtr<nsIMsgIncomingServer>& server = iter.Data(); + if (!server) continue; + + bool hidden = false; + server->GetHidden(&hidden); + if (hidden) continue; + + nsCString type; + if (NS_FAILED(server->GetType(type))) { + NS_WARNING("server->GetType() failed"); + continue; + } + + if (!type.EqualsLiteral("im")) { + servers.AppendElement(server); + } + } + return NS_OK; +} + +nsresult nsMsgAccountManager::LoadAccounts() { + nsresult rv; + + // for now safeguard multiple calls to this function + if (m_accountsLoaded) return NS_OK; + + // If we have code trying to do things after we've unloaded accounts, + // ignore it. + if (m_shutdownInProgress || m_haveShutdown) return NS_ERROR_FAILURE; + + // Make sure correct modules are loaded before creating any server. + nsCOMPtr<nsIObserver> moduleLoader; + moduleLoader = + do_GetService("@mozilla.org/messenger/imap-module-loader;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMsgMailSession> mailSession = + do_GetService("@mozilla.org/messenger/services/session;1", &rv); + + if (NS_SUCCEEDED(rv)) + mailSession->AddFolderListener( + this, nsIFolderListener::added | nsIFolderListener::removed | + nsIFolderListener::intPropertyChanged); + + // Ensure biff service has started + nsCOMPtr<nsIMsgBiffManager> biffService = + do_GetService("@mozilla.org/messenger/biffManager;1", &rv); + + if (NS_SUCCEEDED(rv)) biffService->Init(); + + // Ensure purge service has started + nsCOMPtr<nsIMsgPurgeService> purgeService = + do_GetService("@mozilla.org/messenger/purgeService;1", &rv); + + if (NS_SUCCEEDED(rv)) purgeService->Init(); + + nsCOMPtr<nsIPrefService> prefservice( + do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + // mail.accountmanager.accounts is the main entry point for all accounts + nsCString accountList; + rv = m_prefs->GetCharPref(PREF_MAIL_ACCOUNTMANAGER_ACCOUNTS, accountList); + + /** + * Check to see if we need to add pre-configured accounts. + * Following prefs are important to note in understanding the procedure here. + * + * 1. pref("mailnews.append_preconfig_accounts.version", version number); + * This pref registers the current version in the user prefs file. A default + * value is stored in mailnews.js file. If a given vendor needs to add more + * preconfigured accounts, the default version number can be increased. + * Comparing version number from user's prefs file and the default one from + * mailnews.js, we can add new accounts and any other version level changes + * that need to be done. + * + * 2. pref("mail.accountmanager.appendaccounts", <comma sep. account list>); + * This pref contains the list of pre-configured accounts that ISP/Vendor + * wants to add to the existing accounts list. + */ + nsCOMPtr<nsIPrefBranch> defaultsPrefBranch; + rv = prefservice->GetDefaultBranch(MAILNEWS_ROOT_PREF, + getter_AddRefs(defaultsPrefBranch)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIPrefBranch> prefBranch; + rv = prefservice->GetBranch(MAILNEWS_ROOT_PREF, getter_AddRefs(prefBranch)); + NS_ENSURE_SUCCESS(rv, rv); + + int32_t appendAccountsCurrentVersion = 0; + int32_t appendAccountsDefaultVersion = 0; + rv = prefBranch->GetIntPref(APPEND_ACCOUNTS_VERSION_PREF_NAME, + &appendAccountsCurrentVersion); + NS_ENSURE_SUCCESS(rv, rv); + + rv = defaultsPrefBranch->GetIntPref(APPEND_ACCOUNTS_VERSION_PREF_NAME, + &appendAccountsDefaultVersion); + NS_ENSURE_SUCCESS(rv, rv); + + // Update the account list if needed + if ((appendAccountsCurrentVersion <= appendAccountsDefaultVersion)) { + // Get a list of pre-configured accounts + nsCString appendAccountList; + rv = m_prefs->GetCharPref(PREF_MAIL_ACCOUNTMANAGER_APPEND_ACCOUNTS, + appendAccountList); + appendAccountList.StripWhitespace(); + + // If there are pre-configured accounts, we need to add them to the + // existing list. + if (!appendAccountList.IsEmpty()) { + if (!accountList.IsEmpty()) { + // Tokenize the data and add each account + // in the user's current mailnews account list + nsTArray<nsCString> accountsArray; + ParseString(accountList, ACCOUNT_DELIMITER, accountsArray); + uint32_t i = accountsArray.Length(); + + // Append each account in the pre-configured account list + ParseString(appendAccountList, ACCOUNT_DELIMITER, accountsArray); + + // Now add each account that does not already appear in the list + for (; i < accountsArray.Length(); i++) { + if (accountsArray.IndexOf(accountsArray[i]) == i) { + accountList.Append(ACCOUNT_DELIMITER); + accountList.Append(accountsArray[i]); + } + } + } else { + accountList = appendAccountList; + } + // Increase the version number so that updates will happen as and when + // needed + rv = prefBranch->SetIntPref(APPEND_ACCOUNTS_VERSION_PREF_NAME, + appendAccountsCurrentVersion + 1); + } + } + + // It is ok to return null accounts like when we create new profile. + m_accountsLoaded = true; + m_haveShutdown = false; + + if (accountList.IsEmpty()) return NS_OK; + + /* parse accountList and run loadAccount on each string, comma-separated */ + nsCOMPtr<nsIMsgAccount> account; + // Tokenize the data and add each account + // in the user's current mailnews account list + nsTArray<nsCString> accountsArray; + ParseString(accountList, ACCOUNT_DELIMITER, accountsArray); + + // These are the duplicate accounts we found. We keep track of these + // because if any other server defers to one of these accounts, we need + // to defer to the correct account. + nsCOMArray<nsIMsgAccount> dupAccounts; + + // Now add each account that does not already appear in the list + for (uint32_t i = 0; i < accountsArray.Length(); i++) { + // if we've already seen this exact account, advance to the next account. + // After the loop, we'll notice that we don't have as many actual accounts + // as there were accounts in the pref, and rewrite the pref. + if (accountsArray.IndexOf(accountsArray[i]) != i) continue; + + // get the "server" pref to see if we already have an account with this + // server. If we do, we ignore this account. + nsAutoCString serverKeyPref("mail.account."); + serverKeyPref += accountsArray[i]; + + nsCOMPtr<nsIPrefBranch> accountPrefBranch; + rv = prefservice->GetBranch(serverKeyPref.get(), + getter_AddRefs(accountPrefBranch)); + NS_ENSURE_SUCCESS(rv, rv); + + serverKeyPref += ".server"; + nsCString serverKey; + rv = m_prefs->GetCharPref(serverKeyPref.get(), serverKey); + if (NS_FAILED(rv)) continue; + + nsCOMPtr<nsIMsgAccount> serverAccount; + findAccountByServerKey(serverKey, getter_AddRefs(serverAccount)); + // If we have an existing account with the same server, ignore this account + if (serverAccount) continue; + + if (NS_FAILED(createKeyedAccount(accountsArray[i], true, + getter_AddRefs(account))) || + !account) { + NS_WARNING("unexpected entry in account list; prefs corrupt?"); + continue; + } + + // See nsIMsgAccount.idl for a description of the secondsToLeaveUnavailable + // and timeFoundUnavailable preferences + nsAutoCString toLeavePref(PREF_MAIL_SERVER_PREFIX); + toLeavePref.Append(serverKey); + nsAutoCString unavailablePref( + toLeavePref); // this is the server-specific prefix + unavailablePref.AppendLiteral(".timeFoundUnavailable"); + toLeavePref.AppendLiteral(".secondsToLeaveUnavailable"); + int32_t secondsToLeave = 0; + int32_t timeUnavailable = 0; + + m_prefs->GetIntPref(toLeavePref.get(), &secondsToLeave); + + // force load of accounts (need to find a better way to do this) + nsTArray<RefPtr<nsIMsgIdentity>> unused; + account->GetIdentities(unused); + + rv = account->CreateServer(); + bool deleteAccount = NS_FAILED(rv); + + if (secondsToLeave) { // we need to process timeUnavailable + if (NS_SUCCEEDED(rv)) // clear the time if server is available + { + m_prefs->ClearUserPref(unavailablePref.get()); + } + // NS_ERROR_NOT_AVAILABLE signifies a server that could not be + // instantiated, presumably because of an invalid type. + else if (rv == NS_ERROR_NOT_AVAILABLE) { + m_prefs->GetIntPref(unavailablePref.get(), &timeUnavailable); + if (!timeUnavailable) { // we need to set it, this must be the first + // time unavailable + uint32_t nowSeconds; + PRTime2Seconds(PR_Now(), &nowSeconds); + m_prefs->SetIntPref(unavailablePref.get(), nowSeconds); + deleteAccount = false; + } + } + } + + // Our server is still unavailable. Have we timed out yet? + if (rv == NS_ERROR_NOT_AVAILABLE && timeUnavailable != 0) { + uint32_t nowSeconds; + PRTime2Seconds(PR_Now(), &nowSeconds); + if ((int32_t)nowSeconds < timeUnavailable + secondsToLeave) + deleteAccount = false; + } + + if (deleteAccount) { + dupAccounts.AppendObject(account); + m_accounts.RemoveElement(account); + } + } + + // Check if we removed one or more of the accounts in the pref string. + // If so, rewrite the pref string. + if (accountsArray.Length() != m_accounts.Length()) OutputAccountsPref(); + + int32_t cnt = dupAccounts.Count(); + nsCOMPtr<nsIMsgAccount> dupAccount; + + // Go through the accounts seeing if any existing server is deferred to + // an account we removed. If so, fix the deferral. Then clean up the prefs + // for the removed account. + for (int32_t i = 0; i < cnt; i++) { + dupAccount = dupAccounts[i]; + for (auto iter = m_incomingServers.Iter(); !iter.Done(); iter.Next()) { + /* + * This loop gets run for every incoming server, and is passed a + * duplicate account. It checks that the server is not deferred to the + * duplicate account. If it is, then it looks up the information for the + * duplicate account's server (username, hostName, type), and finds an + * account with a server with the same username, hostname, and type, and + * if it finds one, defers to that account instead. Generally, this will + * be a Local Folders account, since 2.0 has a bug where duplicate Local + * Folders accounts are created. + */ + nsCOMPtr<nsIMsgIncomingServer>& server = iter.Data(); + nsCString type; + server->GetType(type); + if (type.EqualsLiteral("pop3")) { + nsCString deferredToAccount; + // Get the pref directly, because the GetDeferredToAccount accessor + // attempts to fix broken deferrals, but we know more about what the + // deferred to account was. + server->GetCharValue("deferred_to_account", deferredToAccount); + if (!deferredToAccount.IsEmpty()) { + nsCString dupAccountKey; + dupAccount->GetKey(dupAccountKey); + if (deferredToAccount.Equals(dupAccountKey)) { + nsresult rv; + nsCString accountPref("mail.account."); + nsCString dupAccountServerKey; + accountPref.Append(dupAccountKey); + accountPref.AppendLiteral(".server"); + nsCOMPtr<nsIPrefService> prefservice( + do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + if (NS_FAILED(rv)) { + continue; + } + nsCOMPtr<nsIPrefBranch> prefBranch( + do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + if (NS_FAILED(rv)) { + continue; + } + rv = + prefBranch->GetCharPref(accountPref.get(), dupAccountServerKey); + if (NS_FAILED(rv)) { + continue; + } + nsCOMPtr<nsIPrefBranch> serverPrefBranch; + nsCString serverKeyPref(PREF_MAIL_SERVER_PREFIX); + serverKeyPref.Append(dupAccountServerKey); + serverKeyPref.Append('.'); + rv = prefservice->GetBranch(serverKeyPref.get(), + getter_AddRefs(serverPrefBranch)); + if (NS_FAILED(rv)) { + continue; + } + nsCString userName; + nsCString hostName; + nsCString type; + serverPrefBranch->GetCharPref("userName", userName); + serverPrefBranch->GetCharPref("hostname", hostName); + serverPrefBranch->GetCharPref("type", type); + // Find a server with the same info. + nsCOMPtr<nsIMsgAccountManager> accountManager = + do_GetService("@mozilla.org/messenger/account-manager;1", &rv); + if (NS_FAILED(rv)) { + continue; + } + nsCOMPtr<nsIMsgIncomingServer> server; + accountManager->FindServer(userName, hostName, type, 0, + getter_AddRefs(server)); + if (server) { + nsCOMPtr<nsIMsgAccount> replacement; + accountManager->FindAccountForServer(server, + getter_AddRefs(replacement)); + if (replacement) { + nsCString accountKey; + replacement->GetKey(accountKey); + if (!accountKey.IsEmpty()) + server->SetCharValue("deferred_to_account", accountKey); + } + } + } + } + } + } + + nsAutoCString accountKeyPref("mail.account."); + nsCString dupAccountKey; + dupAccount->GetKey(dupAccountKey); + if (dupAccountKey.IsEmpty()) continue; + accountKeyPref.Append(dupAccountKey); + accountKeyPref.Append('.'); + + nsCOMPtr<nsIPrefBranch> accountPrefBranch; + rv = prefservice->GetBranch(accountKeyPref.get(), + getter_AddRefs(accountPrefBranch)); + if (accountPrefBranch) { + nsTArray<nsCString> prefNames; + nsresult rv = accountPrefBranch->GetChildList("", prefNames); + NS_ENSURE_SUCCESS(rv, rv); + + for (auto& prefName : prefNames) { + accountPrefBranch->ClearUserPref(prefName.get()); + } + } + } + + // Make sure we have an account that points at the local folders server + nsCString localFoldersServerKey; + rv = m_prefs->GetCharPref(PREF_MAIL_ACCOUNTMANAGER_LOCALFOLDERSSERVER, + localFoldersServerKey); + + if (!localFoldersServerKey.IsEmpty()) { + nsCOMPtr<nsIMsgIncomingServer> server; + rv = GetIncomingServer(localFoldersServerKey, getter_AddRefs(server)); + if (server) { + nsCOMPtr<nsIMsgAccount> localFoldersAccount; + findAccountByServerKey(localFoldersServerKey, + getter_AddRefs(localFoldersAccount)); + // If we don't have an existing account pointing at the local folders + // server, we're going to add one. + if (!localFoldersAccount) { + nsCOMPtr<nsIMsgAccount> account; + (void)CreateAccount(getter_AddRefs(account)); + if (account) account->SetIncomingServer(server); + } + } + } + return NS_OK; +} + +NS_IMETHODIMP +nsMsgAccountManager::ReactivateAccounts() { + for (nsIMsgAccount* account : m_accounts) { + // This will error out if the account already has its server, or + // if this isn't the account that the extension is trying to reactivate. + if (NS_SUCCEEDED(account->CreateServer())) { + nsCOMPtr<nsIMsgIncomingServer> server; + account->GetIncomingServer(getter_AddRefs(server)); + // This triggers all of the notifications required by the UI. + account->SetIncomingServer(server); + } + } + return NS_OK; +} + +// this routine goes through all the identities and makes sure +// that the special folders for each identity have the +// correct special folder flags set, e.g, the Sent folder has +// the sent flag set. +// +// it also goes through all the spam settings for each account +// and makes sure the folder flags are set there, too +NS_IMETHODIMP +nsMsgAccountManager::SetSpecialFolders() { + nsTArray<RefPtr<nsIMsgIdentity>> identities; + GetAllIdentities(identities); + + for (auto identity : identities) { + nsresult rv; + nsCString folderUri; + nsCOMPtr<nsIMsgFolder> folder; + + identity->GetFccFolder(folderUri); + if (!folderUri.IsEmpty() && + NS_SUCCEEDED(GetOrCreateFolder(folderUri, getter_AddRefs(folder)))) { + nsCOMPtr<nsIMsgFolder> parent; + rv = folder->GetParent(getter_AddRefs(parent)); + if (NS_SUCCEEDED(rv) && parent) { + rv = folder->SetFlag(nsMsgFolderFlags::SentMail); + NS_ENSURE_SUCCESS(rv, rv); + } + } + + identity->GetDraftFolder(folderUri); + if (!folderUri.IsEmpty() && + NS_SUCCEEDED(GetOrCreateFolder(folderUri, getter_AddRefs(folder)))) { + nsCOMPtr<nsIMsgFolder> parent; + rv = folder->GetParent(getter_AddRefs(parent)); + if (NS_SUCCEEDED(rv) && parent) { + rv = folder->SetFlag(nsMsgFolderFlags::Drafts); + NS_ENSURE_SUCCESS(rv, rv); + } + } + + identity->GetArchiveFolder(folderUri); + if (!folderUri.IsEmpty() && + NS_SUCCEEDED(GetOrCreateFolder(folderUri, getter_AddRefs(folder)))) { + nsCOMPtr<nsIMsgFolder> parent; + rv = folder->GetParent(getter_AddRefs(parent)); + if (NS_SUCCEEDED(rv) && parent) { + bool archiveEnabled; + identity->GetArchiveEnabled(&archiveEnabled); + if (archiveEnabled) + rv = folder->SetFlag(nsMsgFolderFlags::Archive); + else + rv = folder->ClearFlag(nsMsgFolderFlags::Archive); + NS_ENSURE_SUCCESS(rv, rv); + } + } + + identity->GetStationeryFolder(folderUri); + if (!folderUri.IsEmpty() && + NS_SUCCEEDED(GetOrCreateFolder(folderUri, getter_AddRefs(folder)))) { + nsCOMPtr<nsIMsgFolder> parent; + rv = folder->GetParent(getter_AddRefs(parent)); + if (NS_SUCCEEDED(rv) && parent) { + folder->SetFlag(nsMsgFolderFlags::Templates); + NS_ENSURE_SUCCESS(rv, rv); + } + } + } + + // XXX todo + // get all servers + // get all spam settings for each server + // set the JUNK folder flag on the spam folders, right? + return NS_OK; +} + +NS_IMETHODIMP +nsMsgAccountManager::UnloadAccounts() { + // release the default account + m_defaultAccount = nullptr; + for (auto iter = m_incomingServers.Iter(); !iter.Done(); iter.Next()) { + nsCOMPtr<nsIMsgIncomingServer>& server = iter.Data(); + if (!server) continue; + nsresult rv; + NotifyServerUnloaded(server); + + nsCOMPtr<nsIMsgFolder> rootFolder; + rv = server->GetRootFolder(getter_AddRefs(rootFolder)); + if (NS_SUCCEEDED(rv)) { + removeListenersFromFolder(rootFolder); + + rootFolder->Shutdown(true); + } + } + + m_accounts.Clear(); // will release all elements + m_identities.Clear(); + m_incomingServers.Clear(); + mAccountKeyList.Truncate(); + SetLastServerFound(nullptr, EmptyCString(), EmptyCString(), 0, + EmptyCString()); + + if (m_accountsLoaded) { + nsCOMPtr<nsIMsgMailSession> mailSession = + do_GetService("@mozilla.org/messenger/services/session;1"); + if (mailSession) mailSession->RemoveFolderListener(this); + m_accountsLoaded = false; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsMsgAccountManager::ShutdownServers() { + for (auto iter = m_incomingServers.Iter(); !iter.Done(); iter.Next()) { + nsCOMPtr<nsIMsgIncomingServer>& server = iter.Data(); + if (server) server->Shutdown(); + } + return NS_OK; +} + +NS_IMETHODIMP +nsMsgAccountManager::CloseCachedConnections() { + for (auto iter = m_incomingServers.Iter(); !iter.Done(); iter.Next()) { + nsCOMPtr<nsIMsgIncomingServer>& server = iter.Data(); + if (server) server->CloseCachedConnections(); + } + return NS_OK; +} + +NS_IMETHODIMP +nsMsgAccountManager::CleanupOnExit() { + // This can get called multiple times, and potentially re-entrantly. + // So add some protection against that. + if (m_shutdownInProgress) return NS_OK; + + m_shutdownInProgress = true; + + nsresult rv; + // If enabled, clear cache on shutdown. This is common to all accounts. + bool clearCache = false; + m_prefs->GetBoolPref("privacy.clearOnShutdown.cache", &clearCache); + if (clearCache) { + nsCOMPtr<nsICacheStorageService> cacheStorageService = + do_GetService("@mozilla.org/netwerk/cache-storage-service;1", &rv); + if (NS_SUCCEEDED(rv)) cacheStorageService->Clear(); + } + + for (auto iter = m_incomingServers.Iter(); !iter.Done(); iter.Next()) { + nsCOMPtr<nsIMsgIncomingServer>& server = iter.Data(); + + bool emptyTrashOnExit = false; + bool cleanupInboxOnExit = false; + + if (WeAreOffline()) break; + + if (!server) continue; + + server->GetEmptyTrashOnExit(&emptyTrashOnExit); + nsCOMPtr<nsIImapIncomingServer> imapserver = do_QueryInterface(server); + if (imapserver) { + imapserver->GetCleanupInboxOnExit(&cleanupInboxOnExit); + imapserver->SetShuttingDown(true); + } + if (emptyTrashOnExit || cleanupInboxOnExit) { + nsCOMPtr<nsIMsgFolder> root; + server->GetRootFolder(getter_AddRefs(root)); + nsCString type; + server->GetType(type); + if (root) { + nsString passwd; + int32_t authMethod = 0; + bool serverRequiresPasswordForAuthentication = true; + bool isImap = type.EqualsLiteral("imap"); + if (isImap) { + server->GetServerRequiresPasswordForBiff( + &serverRequiresPasswordForAuthentication); + server->GetPassword(passwd); + server->GetAuthMethod(&authMethod); + } + if (!isImap || (isImap && (!serverRequiresPasswordForAuthentication || + !passwd.IsEmpty() || + authMethod == nsMsgAuthMethod::OAuth2))) { + nsCOMPtr<nsIMsgAccountManager> accountManager = + do_GetService("@mozilla.org/messenger/account-manager;1", &rv); + if (NS_FAILED(rv)) continue; + + if (isImap && cleanupInboxOnExit) { + // Find the inbox. + nsTArray<RefPtr<nsIMsgFolder>> subFolders; + rv = root->GetSubFolders(subFolders); + if (NS_SUCCEEDED(rv)) { + for (nsIMsgFolder* folder : subFolders) { + uint32_t flags; + folder->GetFlags(&flags); + if (flags & nsMsgFolderFlags::Inbox) { + // This is inbox, so Compact() it. There's an implied + // Expunge too, because this is IMAP. + RefPtr<UrlListener> cleanupListener = new UrlListener(); + RefPtr<nsMsgAccountManager> self = this; + // This runs when the compaction (+expunge) is complete. + cleanupListener->mStopFn = + [self](nsIURI* url, nsresult status) -> nsresult { + if (self->m_folderDoingCleanupInbox) { + PR_CEnterMonitor(self->m_folderDoingCleanupInbox); + PR_CNotifyAll(self->m_folderDoingCleanupInbox); + self->m_cleanupInboxInProgress = false; + PR_CExitMonitor(self->m_folderDoingCleanupInbox); + self->m_folderDoingCleanupInbox = nullptr; + } + return NS_OK; + }; + + rv = folder->Compact(cleanupListener, nullptr); + if (NS_SUCCEEDED(rv)) + accountManager->SetFolderDoingCleanupInbox(folder); + break; + } + } + } + } + + if (emptyTrashOnExit) { + RefPtr<UrlListener> emptyTrashListener = new UrlListener(); + RefPtr<nsMsgAccountManager> self = this; + // This runs when the trash-emptying is complete. + // (It'll be a nsIImapUrl::nsImapDeleteAllMsgs url). + emptyTrashListener->mStopFn = [self](nsIURI* url, + nsresult status) -> nsresult { + if (self->m_folderDoingEmptyTrash) { + PR_CEnterMonitor(self->m_folderDoingEmptyTrash); + PR_CNotifyAll(self->m_folderDoingEmptyTrash); + self->m_emptyTrashInProgress = false; + PR_CExitMonitor(self->m_folderDoingEmptyTrash); + self->m_folderDoingEmptyTrash = nullptr; + } + return NS_OK; + }; + + rv = root->EmptyTrash(emptyTrashListener); + if (isImap && NS_SUCCEEDED(rv)) + accountManager->SetFolderDoingEmptyTrash(root); + } + + if (isImap) { + nsCOMPtr<nsIThread> thread(do_GetCurrentThread()); + + // Pause until any possible inbox-compaction and trash-emptying + // are complete (or time out). + bool inProgress = false; + if (cleanupInboxOnExit) { + int32_t loopCount = 0; // used to break out after 5 seconds + accountManager->GetCleanupInboxInProgress(&inProgress); + while (inProgress && loopCount++ < 5000) { + accountManager->GetCleanupInboxInProgress(&inProgress); + PR_CEnterMonitor(root); + PR_CWait(root, PR_MicrosecondsToInterval(1000UL)); + PR_CExitMonitor(root); + NS_ProcessPendingEvents(thread, + PR_MicrosecondsToInterval(1000UL)); + } + } + if (emptyTrashOnExit) { + accountManager->GetEmptyTrashInProgress(&inProgress); + int32_t loopCount = 0; + while (inProgress && loopCount++ < 5000) { + accountManager->GetEmptyTrashInProgress(&inProgress); + PR_CEnterMonitor(root); + PR_CWait(root, PR_MicrosecondsToInterval(1000UL)); + PR_CExitMonitor(root); + NS_ProcessPendingEvents(thread, + PR_MicrosecondsToInterval(1000UL)); + } + } + } + } + } + } + } + + // Try to do this early on in the shutdown process before + // necko shuts itself down. + CloseCachedConnections(); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgAccountManager::WriteToFolderCache(nsIMsgFolderCache* folderCache) { + for (auto iter = m_incomingServers.Iter(); !iter.Done(); iter.Next()) { + iter.Data()->WriteToFolderCache(folderCache); + } + return NS_OK; +} + +nsresult nsMsgAccountManager::createKeyedAccount(const nsCString& key, + bool forcePositionToEnd, + nsIMsgAccount** aAccount) { + nsresult rv; + nsCOMPtr<nsIMsgAccount> account = do_CreateInstance(kMsgAccountCID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + account->SetKey(key); + + nsCString localFoldersAccountKey; + nsCString lastFolderAccountKey; + if (!forcePositionToEnd) { + nsCOMPtr<nsIMsgIncomingServer> localFoldersServer; + rv = GetLocalFoldersServer(getter_AddRefs(localFoldersServer)); + if (NS_SUCCEEDED(rv)) { + for (auto account : m_accounts) { + nsCOMPtr<nsIMsgIncomingServer> server; + rv = account->GetIncomingServer(getter_AddRefs(server)); + if (NS_SUCCEEDED(rv) && server == localFoldersServer) { + account->GetKey(localFoldersAccountKey); + break; + } + } + } + + // Extracting the account key of the last mail acoount. + for (int32_t index = m_accounts.Length() - 1; index >= 0; index--) { + nsCOMPtr<nsIMsgIncomingServer> server; + rv = m_accounts[index]->GetIncomingServer(getter_AddRefs(server)); + if (NS_SUCCEEDED(rv) && server) { + nsCString accountType; + rv = server->GetType(accountType); + if (NS_SUCCEEDED(rv) && !accountType.EqualsLiteral("im")) { + m_accounts[index]->GetKey(lastFolderAccountKey); + break; + } + } + } + } + + if (!forcePositionToEnd && !localFoldersAccountKey.IsEmpty() && + !lastFolderAccountKey.IsEmpty() && + lastFolderAccountKey == localFoldersAccountKey) { + // Insert account before Local Folders if that is the last account. + m_accounts.InsertElementAt(m_accounts.Length() - 1, account); + } else { + m_accounts.AppendElement(account); + } + + nsCString newAccountKeyList; + nsCString accountKey; + for (uint32_t index = 0; index < m_accounts.Length(); index++) { + m_accounts[index]->GetKey(accountKey); + if (index) newAccountKeyList.Append(ACCOUNT_DELIMITER); + newAccountKeyList.Append(accountKey); + } + mAccountKeyList = newAccountKeyList; + + m_prefs->SetCharPref(PREF_MAIL_ACCOUNTMANAGER_ACCOUNTS, mAccountKeyList); + account.forget(aAccount); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgAccountManager::CreateAccount(nsIMsgAccount** _retval) { + NS_ENSURE_ARG_POINTER(_retval); + + nsAutoCString key; + GetUniqueAccountKey(key); + + return createKeyedAccount(key, false, _retval); +} + +NS_IMETHODIMP +nsMsgAccountManager::GetAccount(const nsACString& aKey, + nsIMsgAccount** aAccount) { + NS_ENSURE_ARG_POINTER(aAccount); + *aAccount = nullptr; + + for (uint32_t i = 0; i < m_accounts.Length(); ++i) { + nsCOMPtr<nsIMsgAccount> account(m_accounts[i]); + nsCString key; + account->GetKey(key); + if (key.Equals(aKey)) { + account.forget(aAccount); + break; + } + } + + // If not found, create on demand. + return NS_OK; +} + +NS_IMETHODIMP +nsMsgAccountManager::FindServerIndex(nsIMsgIncomingServer* server, + int32_t* result) { + NS_ENSURE_ARG_POINTER(server); + NS_ENSURE_ARG_POINTER(result); + + nsCString key; + nsresult rv = server->GetKey(key); + NS_ENSURE_SUCCESS(rv, rv); + + // do this by account because the account list is in order + uint32_t i; + for (i = 0; i < m_accounts.Length(); ++i) { + nsCOMPtr<nsIMsgIncomingServer> server; + rv = m_accounts[i]->GetIncomingServer(getter_AddRefs(server)); + if (!server || NS_FAILED(rv)) continue; + + nsCString serverKey; + rv = server->GetKey(serverKey); + if (NS_FAILED(rv)) continue; + + // stop when found, + // index will be set to the current index + if (serverKey.Equals(key)) break; + } + + // Even if the search failed, we can return index. + // This means that all servers not in the array return an index higher + // than all "registered" servers. + *result = i; + return NS_OK; +} + +NS_IMETHODIMP nsMsgAccountManager::AddIncomingServerListener( + nsIIncomingServerListener* serverListener) { + m_incomingServerListeners.AppendObject(serverListener); + return NS_OK; +} + +NS_IMETHODIMP nsMsgAccountManager::RemoveIncomingServerListener( + nsIIncomingServerListener* serverListener) { + m_incomingServerListeners.RemoveObject(serverListener); + return NS_OK; +} + +NS_IMETHODIMP nsMsgAccountManager::NotifyServerLoaded( + nsIMsgIncomingServer* server) { + int32_t count = m_incomingServerListeners.Count(); + for (int32_t i = 0; i < count; i++) { + nsIIncomingServerListener* listener = m_incomingServerListeners[i]; + listener->OnServerLoaded(server); + } + + return NS_OK; +} + +NS_IMETHODIMP nsMsgAccountManager::NotifyServerUnloaded( + nsIMsgIncomingServer* server) { + NS_ENSURE_ARG_POINTER(server); + + int32_t count = m_incomingServerListeners.Count(); + // Clear this to cut shutdown leaks. We are always passing valid non-null + // server here. + server->SetFilterList(nullptr); + + for (int32_t i = 0; i < count; i++) { + nsIIncomingServerListener* listener = m_incomingServerListeners[i]; + listener->OnServerUnloaded(server); + } + + return NS_OK; +} + +NS_IMETHODIMP nsMsgAccountManager::NotifyServerChanged( + nsIMsgIncomingServer* server) { + int32_t count = m_incomingServerListeners.Count(); + for (int32_t i = 0; i < count; i++) { + nsIIncomingServerListener* listener = m_incomingServerListeners[i]; + listener->OnServerChanged(server); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsMsgAccountManager::FindServerByURI(nsIURI* aURI, + nsIMsgIncomingServer** aResult) { + NS_ENSURE_ARG_POINTER(aURI); + + nsresult rv = LoadAccounts(); + NS_ENSURE_SUCCESS(rv, rv); + + // Get username and hostname and port so we can get the server + nsAutoCString username; + nsAutoCString escapedUsername; + rv = aURI->GetUserPass(escapedUsername); + if (NS_SUCCEEDED(rv) && !escapedUsername.IsEmpty()) + MsgUnescapeString(escapedUsername, 0, username); + + nsAutoCString hostname; + nsAutoCString escapedHostname; + rv = aURI->GetHost(escapedHostname); + if (NS_SUCCEEDED(rv) && !escapedHostname.IsEmpty()) { + MsgUnescapeString(escapedHostname, 0, hostname); + } + + nsAutoCString type; + rv = aURI->GetScheme(type); + if (NS_SUCCEEDED(rv) && !type.IsEmpty()) { + // Remove "-message" from the scheme in case we get called with + // "imap-message", "mailbox-message", or friends. + if (StringEndsWith(type, "-message"_ns)) type.SetLength(type.Length() - 8); + // now modify type if pop or news + if (type.EqualsLiteral("pop")) type.AssignLiteral("pop3"); + // we use "nntp" in the server list so translate it here. + else if (type.EqualsLiteral("news")) + type.AssignLiteral("nntp"); + // we use "any" as the wildcard type. + else if (type.EqualsLiteral("any")) + type.Truncate(); + } + + int32_t port = 0; + // check the port of the scheme is not 'none' or blank + if (!(type.EqualsLiteral("none") || type.IsEmpty())) { + rv = aURI->GetPort(&port); + // Set the port to zero if we got a -1 (use default) + if (NS_SUCCEEDED(rv) && (port == -1)) port = 0; + } + + return findServerInternal(username, hostname, type, port, aResult); +} + +nsresult nsMsgAccountManager::findServerInternal( + const nsACString& username, const nsACString& serverHostname, + const nsACString& type, int32_t port, nsIMsgIncomingServer** aResult) { + if ((m_lastFindServerUserName.Equals(username)) && + (m_lastFindServerHostName.Equals(serverHostname)) && + (m_lastFindServerType.Equals(type)) && (m_lastFindServerPort == port) && + m_lastFindServerResult) { + NS_ADDREF(*aResult = m_lastFindServerResult); + return NS_OK; + } + + nsresult rv; + nsCString hostname; + nsCOMPtr<nsIIDNService> idnService = + do_GetService("@mozilla.org/network/idn-service;1"); + rv = idnService->Normalize(serverHostname, hostname); + NS_ENSURE_SUCCESS(rv, rv); + + for (auto iter = m_incomingServers.Iter(); !iter.Done(); iter.Next()) { + // Find matching server by user+host+type+port. + nsCOMPtr<nsIMsgIncomingServer>& server = iter.Data(); + + if (!server) continue; + + nsCString thisHostname; + rv = server->GetHostName(thisHostname); + if (NS_FAILED(rv)) continue; + + rv = idnService->Normalize(thisHostname, thisHostname); + if (NS_FAILED(rv)) continue; + + // If the hostname was a IP with trailing dot, that dot gets removed + // during URI mutation. We may well be here in findServerInternal to + // find a server from a folder URI. Remove the trailing dot so we can + // find the server. + nsCString thisHostnameNoDot(thisHostname); + if (!thisHostname.IsEmpty() && + thisHostname.CharAt(thisHostname.Length() - 1) == '.') { + thisHostnameNoDot.Cut(thisHostname.Length() - 1, 1); + } + + nsCString thisUsername; + rv = server->GetUsername(thisUsername); + if (NS_FAILED(rv)) continue; + + nsCString thisType; + rv = server->GetType(thisType); + if (NS_FAILED(rv)) continue; + + int32_t thisPort = -1; // use the default port identifier + // Don't try and get a port for the 'none' scheme + if (!thisType.EqualsLiteral("none")) { + rv = server->GetPort(&thisPort); + if (NS_FAILED(rv)) { + continue; + } + } + + // treat "" as a wild card, so if the caller passed in "" for the desired + // attribute treat it as a match + if ((type.IsEmpty() || thisType.Equals(type)) && + (hostname.IsEmpty() || + thisHostname.Equals(hostname, nsCaseInsensitiveCStringComparator) || + thisHostnameNoDot.Equals(hostname, + nsCaseInsensitiveCStringComparator)) && + (!(port != 0) || (port == thisPort)) && + (username.IsEmpty() || thisUsername.Equals(username))) { + // stop on first find; cache for next time + SetLastServerFound(server, hostname, username, port, type); + + NS_ADDREF(*aResult = server); // Was populated from member variable. + return NS_OK; + } + } + + return NS_ERROR_UNEXPECTED; +} + +// Always return NS_OK; +NS_IMETHODIMP +nsMsgAccountManager::FindServer(const nsACString& username, + const nsACString& hostname, + const nsACString& type, int32_t port, + nsIMsgIncomingServer** aResult) { + *aResult = nullptr; + findServerInternal(username, hostname, type, port, aResult); + return NS_OK; +} + +void nsMsgAccountManager::findAccountByServerKey(const nsCString& aKey, + nsIMsgAccount** aResult) { + *aResult = nullptr; + + for (uint32_t i = 0; i < m_accounts.Length(); ++i) { + nsCOMPtr<nsIMsgIncomingServer> server; + nsresult rv = m_accounts[i]->GetIncomingServer(getter_AddRefs(server)); + if (!server || NS_FAILED(rv)) continue; + + nsCString key; + rv = server->GetKey(key); + if (NS_FAILED(rv)) continue; + + // if the keys are equal, the servers are equal + if (key.Equals(aKey)) { + NS_ADDREF(*aResult = m_accounts[i]); + break; // stop on first found account + } + } +} + +NS_IMETHODIMP +nsMsgAccountManager::FindAccountForServer(nsIMsgIncomingServer* server, + nsIMsgAccount** aResult) { + NS_ENSURE_ARG_POINTER(aResult); + + if (!server) { + (*aResult) = nullptr; + return NS_OK; + } + + nsresult rv; + + nsCString key; + rv = server->GetKey(key); + NS_ENSURE_SUCCESS(rv, rv); + + findAccountByServerKey(key, aResult); + return NS_OK; +} + +NS_IMETHODIMP +nsMsgAccountManager::GetFirstIdentityForServer(nsIMsgIncomingServer* aServer, + nsIMsgIdentity** aIdentity) { + NS_ENSURE_ARG_POINTER(aServer); + NS_ENSURE_ARG_POINTER(aIdentity); + + nsTArray<RefPtr<nsIMsgIdentity>> identities; + nsresult rv = GetIdentitiesForServer(aServer, identities); + NS_ENSURE_SUCCESS(rv, rv); + + // not all servers have identities + // for example, Local Folders + if (identities.IsEmpty()) { + *aIdentity = nullptr; + } else { + NS_IF_ADDREF(*aIdentity = identities[0]); + } + return rv; +} + +NS_IMETHODIMP +nsMsgAccountManager::GetIdentitiesForServer( + nsIMsgIncomingServer* server, + nsTArray<RefPtr<nsIMsgIdentity>>& identities) { + NS_ENSURE_ARG_POINTER(server); + nsresult rv = LoadAccounts(); + NS_ENSURE_SUCCESS(rv, rv); + + identities.Clear(); + + nsAutoCString serverKey; + rv = server->GetKey(serverKey); + NS_ENSURE_SUCCESS(rv, rv); + + for (auto account : m_accounts) { + nsCOMPtr<nsIMsgIncomingServer> thisServer; + rv = account->GetIncomingServer(getter_AddRefs(thisServer)); + if (NS_FAILED(rv) || !thisServer) continue; + + nsAutoCString thisServerKey; + rv = thisServer->GetKey(thisServerKey); + if (serverKey.Equals(thisServerKey)) { + nsTArray<RefPtr<nsIMsgIdentity>> theseIdentities; + rv = account->GetIdentities(theseIdentities); + NS_ENSURE_SUCCESS(rv, rv); + identities.AppendElements(theseIdentities); + } + } + return NS_OK; +} + +NS_IMETHODIMP +nsMsgAccountManager::GetServersForIdentity( + nsIMsgIdentity* aIdentity, + nsTArray<RefPtr<nsIMsgIncomingServer>>& servers) { + NS_ENSURE_ARG_POINTER(aIdentity); + servers.Clear(); + + nsresult rv; + rv = LoadAccounts(); + NS_ENSURE_SUCCESS(rv, rv); + + for (auto account : m_accounts) { + nsTArray<RefPtr<nsIMsgIdentity>> identities; + if (NS_FAILED(account->GetIdentities(identities))) continue; + + nsCString identityKey; + aIdentity->GetKey(identityKey); + for (auto thisIdentity : identities) { + nsCString thisIdentityKey; + rv = thisIdentity->GetKey(thisIdentityKey); + + if (NS_SUCCEEDED(rv) && identityKey.Equals(thisIdentityKey)) { + nsCOMPtr<nsIMsgIncomingServer> thisServer; + rv = account->GetIncomingServer(getter_AddRefs(thisServer)); + if (thisServer && NS_SUCCEEDED(rv)) { + servers.AppendElement(thisServer); + break; + } + } + } + } + return NS_OK; +} + +NS_IMETHODIMP +nsMsgAccountManager::AddRootFolderListener(nsIFolderListener* aListener) { + NS_ENSURE_TRUE(aListener, NS_OK); + mFolderListeners.AppendElement(aListener); + for (auto iter = m_incomingServers.Iter(); !iter.Done(); iter.Next()) { + nsCOMPtr<nsIMsgFolder> rootFolder; + nsresult rv = iter.Data()->GetRootFolder(getter_AddRefs(rootFolder)); + if (NS_FAILED(rv)) { + continue; + } + rv = rootFolder->AddFolderListener(aListener); + } + return NS_OK; +} + +NS_IMETHODIMP +nsMsgAccountManager::RemoveRootFolderListener(nsIFolderListener* aListener) { + NS_ENSURE_TRUE(aListener, NS_OK); + mFolderListeners.RemoveElement(aListener); + for (auto iter = m_incomingServers.Iter(); !iter.Done(); iter.Next()) { + nsCOMPtr<nsIMsgFolder> rootFolder; + nsresult rv = iter.Data()->GetRootFolder(getter_AddRefs(rootFolder)); + if (NS_FAILED(rv)) { + continue; + } + rv = rootFolder->RemoveFolderListener(aListener); + } + + return NS_OK; +} + +NS_IMETHODIMP nsMsgAccountManager::SetLocalFoldersServer( + nsIMsgIncomingServer* aServer) { + NS_ENSURE_ARG_POINTER(aServer); + nsCString key; + nsresult rv = aServer->GetKey(key); + NS_ENSURE_SUCCESS(rv, rv); + + return m_prefs->SetCharPref(PREF_MAIL_ACCOUNTMANAGER_LOCALFOLDERSSERVER, key); +} + +NS_IMETHODIMP nsMsgAccountManager::GetLocalFoldersServer( + nsIMsgIncomingServer** aServer) { + NS_ENSURE_ARG_POINTER(aServer); + + nsCString serverKey; + + nsresult rv = m_prefs->GetCharPref( + PREF_MAIL_ACCOUNTMANAGER_LOCALFOLDERSSERVER, serverKey); + + if (NS_SUCCEEDED(rv) && !serverKey.IsEmpty()) { + rv = GetIncomingServer(serverKey, aServer); + if (NS_SUCCEEDED(rv)) return rv; + // otherwise, we're going to fall through to looking for an existing local + // folders account, because now we fail creating one if one already exists. + } + + // try ("nobody","Local Folders","none"), and work down to any "none" server. + rv = findServerInternal("nobody"_ns, "Local Folders"_ns, "none"_ns, 0, + aServer); + if (NS_FAILED(rv) || !*aServer) { + rv = findServerInternal("nobody"_ns, EmptyCString(), "none"_ns, 0, aServer); + if (NS_FAILED(rv) || !*aServer) { + rv = findServerInternal(EmptyCString(), "Local Folders"_ns, "none"_ns, 0, + aServer); + if (NS_FAILED(rv) || !*aServer) + rv = findServerInternal(EmptyCString(), EmptyCString(), "none"_ns, 0, + aServer); + } + } + + NS_ENSURE_SUCCESS(rv, rv); + if (!*aServer) return NS_ERROR_FAILURE; + + // we don't want the Smart Mailboxes server to be the local server. + bool hidden; + (*aServer)->GetHidden(&hidden); + if (hidden) return NS_ERROR_FAILURE; + + rv = SetLocalFoldersServer(*aServer); + return rv; +} + +nsresult nsMsgAccountManager::GetLocalFoldersPrettyName( + nsString& localFoldersName) { + // we don't want "nobody at Local Folders" to show up in the + // folder pane, so we set the pretty name to a localized "Local Folders" + nsCOMPtr<nsIStringBundle> bundle; + nsresult rv; + nsCOMPtr<nsIStringBundleService> sBundleService = + mozilla::components::StringBundle::Service(); + NS_ENSURE_TRUE(sBundleService, NS_ERROR_UNEXPECTED); + + rv = sBundleService->CreateBundle( + "chrome://messenger/locale/messenger.properties", getter_AddRefs(bundle)); + NS_ENSURE_SUCCESS(rv, rv); + + return bundle->GetStringFromName("localFolders", localFoldersName); +} + +NS_IMETHODIMP +nsMsgAccountManager::CreateLocalMailAccount() { + // create the server + nsCOMPtr<nsIMsgIncomingServer> server; + nsresult rv = CreateIncomingServer("nobody"_ns, "Local Folders"_ns, "none"_ns, + getter_AddRefs(server)); + NS_ENSURE_SUCCESS(rv, rv); + + nsString localFoldersName; + rv = GetLocalFoldersPrettyName(localFoldersName); + NS_ENSURE_SUCCESS(rv, rv); + server->SetPrettyName(localFoldersName); + + nsCOMPtr<nsINoIncomingServer> noServer; + noServer = do_QueryInterface(server, &rv); + if (NS_FAILED(rv)) return rv; + + // create the directory structure for old 4.x "Local Mail" + // under <profile dir>/Mail/Local Folders or + // <"mail.directory" pref>/Local Folders + nsCOMPtr<nsIFile> mailDir; + bool dirExists; + + // we want <profile>/Mail + rv = NS_GetSpecialDirectory(NS_APP_MAIL_50_DIR, getter_AddRefs(mailDir)); + if (NS_FAILED(rv)) return rv; + + rv = mailDir->Exists(&dirExists); + if (NS_SUCCEEDED(rv) && !dirExists) + rv = mailDir->Create(nsIFile::DIRECTORY_TYPE, 0775); + if (NS_FAILED(rv)) return rv; + + // set the default local path for "none" + rv = server->SetDefaultLocalPath(mailDir); + if (NS_FAILED(rv)) return rv; + + // Create an account when valid server values are established. + // This will keep the status of accounts sane by avoiding the addition of + // incomplete accounts. + nsCOMPtr<nsIMsgAccount> account; + rv = CreateAccount(getter_AddRefs(account)); + if (NS_FAILED(rv)) return rv; + + // notice, no identity for local mail + // hook the server to the account + // after we set the server's local path + // (see bug #66018) + account->SetIncomingServer(server); + + // remember this as the local folders server + return SetLocalFoldersServer(server); +} + +NS_IMETHODIMP +nsMsgAccountManager::SetFolderDoingEmptyTrash(nsIMsgFolder* folder) { + m_folderDoingEmptyTrash = folder; + m_emptyTrashInProgress = true; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgAccountManager::GetEmptyTrashInProgress(bool* bVal) { + NS_ENSURE_ARG_POINTER(bVal); + *bVal = m_emptyTrashInProgress; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgAccountManager::SetFolderDoingCleanupInbox(nsIMsgFolder* folder) { + m_folderDoingCleanupInbox = folder; + m_cleanupInboxInProgress = true; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgAccountManager::GetCleanupInboxInProgress(bool* bVal) { + NS_ENSURE_ARG_POINTER(bVal); + *bVal = m_cleanupInboxInProgress; + return NS_OK; +} + +void nsMsgAccountManager::SetLastServerFound(nsIMsgIncomingServer* server, + const nsACString& hostname, + const nsACString& username, + const int32_t port, + const nsACString& type) { + m_lastFindServerResult = server; + m_lastFindServerHostName = hostname; + m_lastFindServerUserName = username; + m_lastFindServerPort = port; + m_lastFindServerType = type; +} + +NS_IMETHODIMP +nsMsgAccountManager::SaveAccountInfo() { + nsresult rv; + nsCOMPtr<nsIPrefService> pref(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + return pref->SavePrefFile(nullptr); +} + +NS_IMETHODIMP +nsMsgAccountManager::GetChromePackageName(const nsACString& aExtensionName, + nsACString& aChromePackageName) { + nsresult rv; + nsCOMPtr<nsICategoryManager> catman = + do_GetService(NS_CATEGORYMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsISimpleEnumerator> e; + rv = catman->EnumerateCategory(MAILNEWS_ACCOUNTMANAGER_EXTENSIONS, + getter_AddRefs(e)); + if (NS_SUCCEEDED(rv) && e) { + while (true) { + nsCOMPtr<nsISupports> supports; + rv = e->GetNext(getter_AddRefs(supports)); + nsCOMPtr<nsISupportsCString> catEntry = do_QueryInterface(supports); + if (NS_FAILED(rv) || !catEntry) break; + + nsAutoCString entryString; + rv = catEntry->GetData(entryString); + if (NS_FAILED(rv)) break; + + nsCString contractidString; + rv = catman->GetCategoryEntry(MAILNEWS_ACCOUNTMANAGER_EXTENSIONS, + entryString, contractidString); + if (NS_FAILED(rv)) break; + + nsCOMPtr<nsIMsgAccountManagerExtension> extension = + do_GetService(contractidString.get(), &rv); + if (NS_FAILED(rv) || !extension) break; + + nsCString name; + rv = extension->GetName(name); + if (NS_FAILED(rv)) break; + + if (name.Equals(aExtensionName)) + return extension->GetChromePackageName(aChromePackageName); + } + } + return NS_ERROR_UNEXPECTED; +} + +class VFChangeListenerEvent : public mozilla::Runnable { + public: + VFChangeListenerEvent(VirtualFolderChangeListener* vfChangeListener, + nsIMsgFolder* virtFolder, nsIMsgDatabase* virtDB) + : mozilla::Runnable("VFChangeListenerEvent"), + mVFChangeListener(vfChangeListener), + mFolder(virtFolder), + mDB(virtDB) {} + + NS_IMETHOD Run() { + if (mVFChangeListener) mVFChangeListener->ProcessUpdateEvent(mFolder, mDB); + return NS_OK; + } + + private: + RefPtr<VirtualFolderChangeListener> mVFChangeListener; + nsCOMPtr<nsIMsgFolder> mFolder; + nsCOMPtr<nsIMsgDatabase> mDB; +}; + +NS_IMPL_ISUPPORTS(VirtualFolderChangeListener, nsIDBChangeListener) + +VirtualFolderChangeListener::VirtualFolderChangeListener() + : m_searchOnMsgStatus(false), m_batchingEvents(false) {} + +nsresult VirtualFolderChangeListener::Init() { + nsCOMPtr<nsIMsgDatabase> msgDB; + nsCOMPtr<nsIDBFolderInfo> dbFolderInfo; + + nsresult rv = m_virtualFolder->GetDBFolderInfoAndDB( + getter_AddRefs(dbFolderInfo), getter_AddRefs(msgDB)); + if (NS_SUCCEEDED(rv) && msgDB) { + nsCString searchTermString; + dbFolderInfo->GetCharProperty("searchStr", searchTermString); + nsCOMPtr<nsIMsgFilterService> filterService = + do_GetService("@mozilla.org/messenger/services/filters;1", &rv); + nsCOMPtr<nsIMsgFilterList> filterList; + rv = filterService->GetTempFilterList(m_virtualFolder, + getter_AddRefs(filterList)); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr<nsIMsgFilter> tempFilter; + filterList->CreateFilter(u"temp"_ns, getter_AddRefs(tempFilter)); + NS_ENSURE_SUCCESS(rv, rv); + filterList->ParseCondition(tempFilter, searchTermString.get()); + NS_ENSURE_SUCCESS(rv, rv); + m_searchSession = + do_CreateInstance("@mozilla.org/messenger/searchSession;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsTArray<RefPtr<nsIMsgSearchTerm>> searchTerms; + rv = tempFilter->GetSearchTerms(searchTerms); + NS_ENSURE_SUCCESS(rv, rv); + + // we add the search scope right before we match the header, + // because we don't want the search scope caching the body input + // stream, because that holds onto the mailbox file, breaking + // compaction. + + // add each search term to the search session + for (nsIMsgSearchTerm* searchTerm : searchTerms) { + nsMsgSearchAttribValue attrib; + searchTerm->GetAttrib(&attrib); + if (attrib == nsMsgSearchAttrib::MsgStatus) m_searchOnMsgStatus = true; + m_searchSession->AppendTerm(searchTerm); + } + } + return rv; +} + +/** + * nsIDBChangeListener + */ + +NS_IMETHODIMP +VirtualFolderChangeListener::OnHdrPropertyChanged( + nsIMsgDBHdr* aHdrChanged, const nsACString& property, bool aPreChange, + uint32_t* aStatus, nsIDBChangeListener* aInstigator) { + const uint32_t kMatch = 0x1; + const uint32_t kRead = 0x2; + const uint32_t kNew = 0x4; + NS_ENSURE_ARG_POINTER(aHdrChanged); + NS_ENSURE_ARG_POINTER(aStatus); + + uint32_t flags; + bool match; + nsCOMPtr<nsIMsgDatabase> msgDB; + nsresult rv = m_folderWatching->GetMsgDatabase(getter_AddRefs(msgDB)); + NS_ENSURE_SUCCESS(rv, rv); + // we don't want any early returns from this function, until we've + // called ClearScopes on the search session. + m_searchSession->AddScopeTerm(nsMsgSearchScope::offlineMail, + m_folderWatching); + rv = m_searchSession->MatchHdr(aHdrChanged, msgDB, &match); + m_searchSession->ClearScopes(); + NS_ENSURE_SUCCESS(rv, rv); + aHdrChanged->GetFlags(&flags); + + if (aPreChange) // We're looking at the old header, save status + { + *aStatus = 0; + if (match) *aStatus |= kMatch; + if (flags & nsMsgMessageFlags::Read) *aStatus |= kRead; + if (flags & nsMsgMessageFlags::New) *aStatus |= kNew; + return NS_OK; + } + + // This is the post change section where changes are detected + + bool wasMatch = *aStatus & kMatch; + if (!match && !wasMatch) // header not in virtual folder + return NS_OK; + + int32_t totalDelta = 0, unreadDelta = 0, newDelta = 0; + + if (match) { + totalDelta++; + if (!(flags & nsMsgMessageFlags::Read)) unreadDelta++; + if (flags & nsMsgMessageFlags::New) newDelta++; + } + + if (wasMatch) { + totalDelta--; + if (!(*aStatus & kRead)) unreadDelta--; + if (*aStatus & kNew) newDelta--; + } + + if (!(unreadDelta || totalDelta || newDelta)) return NS_OK; + + nsCOMPtr<nsIMsgDatabase> virtDatabase; + nsCOMPtr<nsIDBFolderInfo> dbFolderInfo; + rv = m_virtualFolder->GetDBFolderInfoAndDB(getter_AddRefs(dbFolderInfo), + getter_AddRefs(virtDatabase)); + NS_ENSURE_SUCCESS(rv, rv); + + if (unreadDelta) dbFolderInfo->ChangeNumUnreadMessages(unreadDelta); + + if (newDelta) { + int32_t numNewMessages; + m_virtualFolder->GetNumNewMessages(false, &numNewMessages); + m_virtualFolder->SetNumNewMessages(numNewMessages + newDelta); + m_virtualFolder->SetHasNewMessages(numNewMessages + newDelta > 0); + } + + if (totalDelta) { + dbFolderInfo->ChangeNumMessages(totalDelta); + nsCString searchUri; + m_virtualFolder->GetURI(searchUri); + msgDB->UpdateHdrInCache(searchUri, aHdrChanged, totalDelta == 1); + } + + PostUpdateEvent(m_virtualFolder, virtDatabase); + + return NS_OK; +} + +void VirtualFolderChangeListener::DecrementNewMsgCount() { + int32_t numNewMessages; + m_virtualFolder->GetNumNewMessages(false, &numNewMessages); + if (numNewMessages > 0) numNewMessages--; + m_virtualFolder->SetNumNewMessages(numNewMessages); + if (!numNewMessages) m_virtualFolder->SetHasNewMessages(false); +} + +NS_IMETHODIMP VirtualFolderChangeListener::OnHdrFlagsChanged( + nsIMsgDBHdr* aHdrChanged, uint32_t aOldFlags, uint32_t aNewFlags, + nsIDBChangeListener* aInstigator) { + nsCOMPtr<nsIMsgDatabase> msgDB; + + nsresult rv = m_folderWatching->GetMsgDatabase(getter_AddRefs(msgDB)); + bool oldMatch = false, newMatch = false; + // we don't want any early returns from this function, until we've + // called ClearScopes 0n the search session. + m_searchSession->AddScopeTerm(nsMsgSearchScope::offlineMail, + m_folderWatching); + rv = m_searchSession->MatchHdr(aHdrChanged, msgDB, &newMatch); + if (NS_SUCCEEDED(rv) && m_searchOnMsgStatus) { + // if status is a search criteria, check if the header matched before + // it changed, in order to determine if we need to bump the counts. + aHdrChanged->SetFlags(aOldFlags); + rv = m_searchSession->MatchHdr(aHdrChanged, msgDB, &oldMatch); + // restore new flags even on match failure. + aHdrChanged->SetFlags(aNewFlags); + } else + oldMatch = newMatch; + m_searchSession->ClearScopes(); + NS_ENSURE_SUCCESS(rv, rv); + // we don't want to change the total counts if this virtual folder is open in + // a view, because we won't remove the header from view while it's open. On + // the other hand, it's hard to fix the count when the user clicks away to + // another folder, w/o re-running the search, or setting some sort of pending + // count change. Maybe this needs to be handled in the view code...the view + // could do the same calculation and also keep track of the counts changed. + // Then, when the view was closed, if it's a virtual folder, it could update + // the counts for the db. + if (oldMatch != newMatch || + (oldMatch && (aOldFlags & nsMsgMessageFlags::Read) != + (aNewFlags & nsMsgMessageFlags::Read))) { + nsCOMPtr<nsIMsgDatabase> virtDatabase; + nsCOMPtr<nsIDBFolderInfo> dbFolderInfo; + + rv = m_virtualFolder->GetDBFolderInfoAndDB(getter_AddRefs(dbFolderInfo), + getter_AddRefs(virtDatabase)); + NS_ENSURE_SUCCESS(rv, rv); + int32_t totalDelta = 0, unreadDelta = 0; + if (oldMatch != newMatch) { + // bool isOpen = false; + // nsCOMPtr<nsIMsgMailSession> mailSession = + // do_GetService("@mozilla.org/messenger/services/session;1"); + // if (mailSession && aFolder) + // mailSession->IsFolderOpenInWindow(m_virtualFolder, &isOpen); + // we can't remove headers that no longer match - but we might add headers + // that newly match, someday. + // if (!isOpen) + totalDelta = (oldMatch) ? -1 : 1; + } + bool msgHdrIsRead; + aHdrChanged->GetIsRead(&msgHdrIsRead); + if (oldMatch == newMatch) // read flag changed state + unreadDelta = (msgHdrIsRead) ? -1 : 1; + else if (oldMatch) // else header should removed + unreadDelta = (aOldFlags & nsMsgMessageFlags::Read) ? 0 : -1; + else // header should be added + unreadDelta = (aNewFlags & nsMsgMessageFlags::Read) ? 0 : 1; + if (unreadDelta) dbFolderInfo->ChangeNumUnreadMessages(unreadDelta); + if (totalDelta) dbFolderInfo->ChangeNumMessages(totalDelta); + if (unreadDelta == -1 && aOldFlags & nsMsgMessageFlags::New) + DecrementNewMsgCount(); + + if (totalDelta) { + nsCString searchUri; + m_virtualFolder->GetURI(searchUri); + msgDB->UpdateHdrInCache(searchUri, aHdrChanged, totalDelta == 1); + } + + PostUpdateEvent(m_virtualFolder, virtDatabase); + } else if (oldMatch && (aOldFlags & nsMsgMessageFlags::New) && + !(aNewFlags & nsMsgMessageFlags::New)) + DecrementNewMsgCount(); + + return rv; +} + +NS_IMETHODIMP VirtualFolderChangeListener::OnHdrDeleted( + nsIMsgDBHdr* aHdrDeleted, nsMsgKey aParentKey, int32_t aFlags, + nsIDBChangeListener* aInstigator) { + nsCOMPtr<nsIMsgDatabase> msgDB; + + nsresult rv = m_folderWatching->GetMsgDatabase(getter_AddRefs(msgDB)); + NS_ENSURE_SUCCESS(rv, rv); + bool match = false; + m_searchSession->AddScopeTerm(nsMsgSearchScope::offlineMail, + m_folderWatching); + // Since the notifier went to the trouble of passing in the msg flags, + // we should use them when doing the match. + uint32_t msgFlags; + aHdrDeleted->GetFlags(&msgFlags); + aHdrDeleted->SetFlags(aFlags); + rv = m_searchSession->MatchHdr(aHdrDeleted, msgDB, &match); + aHdrDeleted->SetFlags(msgFlags); + m_searchSession->ClearScopes(); + if (match) { + nsCOMPtr<nsIMsgDatabase> virtDatabase; + nsCOMPtr<nsIDBFolderInfo> dbFolderInfo; + + rv = m_virtualFolder->GetDBFolderInfoAndDB(getter_AddRefs(dbFolderInfo), + getter_AddRefs(virtDatabase)); + NS_ENSURE_SUCCESS(rv, rv); + bool msgHdrIsRead; + aHdrDeleted->GetIsRead(&msgHdrIsRead); + if (!msgHdrIsRead) dbFolderInfo->ChangeNumUnreadMessages(-1); + dbFolderInfo->ChangeNumMessages(-1); + if (aFlags & nsMsgMessageFlags::New) { + int32_t numNewMessages; + m_virtualFolder->GetNumNewMessages(false, &numNewMessages); + m_virtualFolder->SetNumNewMessages(numNewMessages - 1); + if (numNewMessages == 1) m_virtualFolder->SetHasNewMessages(false); + } + + nsCString searchUri; + m_virtualFolder->GetURI(searchUri); + msgDB->UpdateHdrInCache(searchUri, aHdrDeleted, false); + + PostUpdateEvent(m_virtualFolder, virtDatabase); + } + return rv; +} + +NS_IMETHODIMP VirtualFolderChangeListener::OnHdrAdded( + nsIMsgDBHdr* aNewHdr, nsMsgKey aParentKey, int32_t aFlags, + nsIDBChangeListener* aInstigator) { + nsCOMPtr<nsIMsgDatabase> msgDB; + + nsresult rv = m_folderWatching->GetMsgDatabase(getter_AddRefs(msgDB)); + NS_ENSURE_SUCCESS(rv, rv); + bool match = false; + if (!m_searchSession) return NS_ERROR_NULL_POINTER; + + m_searchSession->AddScopeTerm(nsMsgSearchScope::offlineMail, + m_folderWatching); + rv = m_searchSession->MatchHdr(aNewHdr, msgDB, &match); + m_searchSession->ClearScopes(); + if (match) { + nsCOMPtr<nsIMsgDatabase> virtDatabase; + nsCOMPtr<nsIDBFolderInfo> dbFolderInfo; + + rv = m_virtualFolder->GetDBFolderInfoAndDB(getter_AddRefs(dbFolderInfo), + getter_AddRefs(virtDatabase)); + NS_ENSURE_SUCCESS(rv, rv); + bool msgHdrIsRead; + uint32_t msgFlags; + aNewHdr->GetIsRead(&msgHdrIsRead); + aNewHdr->GetFlags(&msgFlags); + if (!msgHdrIsRead) dbFolderInfo->ChangeNumUnreadMessages(1); + if (msgFlags & nsMsgMessageFlags::New) { + int32_t numNewMessages; + m_virtualFolder->GetNumNewMessages(false, &numNewMessages); + m_virtualFolder->SetHasNewMessages(true); + m_virtualFolder->SetNumNewMessages(numNewMessages + 1); + } + nsCString searchUri; + m_virtualFolder->GetURI(searchUri); + msgDB->UpdateHdrInCache(searchUri, aNewHdr, true); + dbFolderInfo->ChangeNumMessages(1); + PostUpdateEvent(m_virtualFolder, virtDatabase); + } + return rv; +} + +NS_IMETHODIMP VirtualFolderChangeListener::OnParentChanged( + nsMsgKey aKeyChanged, nsMsgKey oldParent, nsMsgKey newParent, + nsIDBChangeListener* aInstigator) { + return NS_OK; +} + +NS_IMETHODIMP VirtualFolderChangeListener::OnAnnouncerGoingAway( + nsIDBChangeAnnouncer* instigator) { + nsCOMPtr<nsIMsgDatabase> msgDB = do_QueryInterface(instigator); + if (msgDB) msgDB->RemoveListener(this); + return NS_OK; +} + +NS_IMETHODIMP +VirtualFolderChangeListener::OnEvent(nsIMsgDatabase* aDB, const char* aEvent) { + return NS_OK; +} + +NS_IMETHODIMP VirtualFolderChangeListener::OnReadChanged( + nsIDBChangeListener* aInstigator) { + return NS_OK; +} + +NS_IMETHODIMP VirtualFolderChangeListener::OnJunkScoreChanged( + nsIDBChangeListener* aInstigator) { + return NS_OK; +} + +nsresult VirtualFolderChangeListener::PostUpdateEvent( + nsIMsgFolder* virtualFolder, nsIMsgDatabase* virtDatabase) { + if (m_batchingEvents) return NS_OK; + m_batchingEvents = true; + nsCOMPtr<nsIRunnable> event = + new VFChangeListenerEvent(this, virtualFolder, virtDatabase); + return NS_DispatchToCurrentThread(event); +} + +void VirtualFolderChangeListener::ProcessUpdateEvent(nsIMsgFolder* virtFolder, + nsIMsgDatabase* virtDB) { + m_batchingEvents = false; + virtFolder->UpdateSummaryTotals(true); // force update from db. + virtDB->Commit(nsMsgDBCommitType::kLargeCommit); +} + +nsresult nsMsgAccountManager::GetVirtualFoldersFile(nsCOMPtr<nsIFile>& aFile) { + nsCOMPtr<nsIFile> profileDir; + nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, + getter_AddRefs(profileDir)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = profileDir->AppendNative("virtualFolders.dat"_ns); + if (NS_SUCCEEDED(rv)) aFile = profileDir; + return rv; +} + +NS_IMETHODIMP nsMsgAccountManager::LoadVirtualFolders() { + nsCOMPtr<nsIFile> file; + GetVirtualFoldersFile(file); + if (!file) return NS_ERROR_FAILURE; + + if (m_virtualFoldersLoaded) return NS_OK; + + m_loadingVirtualFolders = true; + + // Before loading virtual folders, ensure that all real folders exist. + // Some may not have been created yet, which would break virtual folders + // that depend on them. + nsTArray<RefPtr<nsIMsgIncomingServer>> allServers; + nsresult rv = GetAllServers(allServers); + NS_ENSURE_SUCCESS(rv, rv); + for (auto server : allServers) { + if (server) { + nsCOMPtr<nsIMsgFolder> rootFolder; + server->GetRootFolder(getter_AddRefs(rootFolder)); + if (rootFolder) { + nsTArray<RefPtr<nsIMsgFolder>> dummy; + rootFolder->GetSubFolders(dummy); + } + } + } + + if (!m_dbService) { + m_dbService = do_GetService("@mozilla.org/msgDatabase/msgDBService;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + } + + nsCOMPtr<nsIFileInputStream> fileStream = + do_CreateInstance(NS_LOCALFILEINPUTSTREAM_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = fileStream->Init(file, PR_RDONLY, 0664, false); + nsCOMPtr<nsILineInputStream> lineInputStream(do_QueryInterface(fileStream)); + + bool isMore = true; + nsAutoCString buffer; + int32_t version = -1; + nsCOMPtr<nsIMsgFolder> virtualFolder; + nsCOMPtr<nsIDBFolderInfo> dbFolderInfo; + + while (isMore && NS_SUCCEEDED(lineInputStream->ReadLine(buffer, &isMore))) { + if (!buffer.IsEmpty()) { + if (version == -1) { + buffer.Cut(0, 8); + nsresult irv; + version = buffer.ToInteger(&irv); + continue; + } + if (StringBeginsWith(buffer, "uri="_ns)) { + buffer.Cut(0, 4); + dbFolderInfo = nullptr; + + rv = GetOrCreateFolder(buffer, getter_AddRefs(virtualFolder)); + NS_ENSURE_SUCCESS(rv, rv); + + virtualFolder->SetFlag(nsMsgFolderFlags::Virtual); + + nsCOMPtr<nsIMsgFolder> grandParent; + nsCOMPtr<nsIMsgFolder> oldParent; + nsCOMPtr<nsIMsgFolder> parentFolder; + bool isServer; + // This loop handles creating virtual folders without an existing + // parent. + do { + // need to add the folder as a sub-folder of its parent. + int32_t lastSlash = buffer.RFindChar('/'); + if (lastSlash == kNotFound) break; + nsDependentCSubstring parentUri(buffer, 0, lastSlash); + // hold a reference so it won't get deleted before it's parented. + oldParent = parentFolder; + + rv = GetOrCreateFolder(parentUri, getter_AddRefs(parentFolder)); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoString currentFolderNameStr; + nsAutoCString currentFolderNameCStr; + MsgUnescapeString( + nsCString(Substring(buffer, lastSlash + 1, buffer.Length())), 0, + currentFolderNameCStr); + CopyUTF8toUTF16(currentFolderNameCStr, currentFolderNameStr); + nsCOMPtr<nsIMsgFolder> childFolder; + nsCOMPtr<nsIMsgDatabase> db; + // force db to get created. + // XXX TODO: is this SetParent() right? Won't it screw up if virtual + // folder is nested >2 deep? Leave for now, but revisit when getting + // rid of dangling folders (BenC). + virtualFolder->SetParent(parentFolder); + rv = virtualFolder->GetMsgDatabase(getter_AddRefs(db)); + if (rv == NS_MSG_ERROR_FOLDER_SUMMARY_MISSING) + m_dbService->CreateNewDB(virtualFolder, getter_AddRefs(db)); + if (db) + rv = db->GetDBFolderInfo(getter_AddRefs(dbFolderInfo)); + else + break; + + parentFolder->AddSubfolder(currentFolderNameStr, + getter_AddRefs(childFolder)); + if (childFolder) parentFolder->NotifyFolderAdded(childFolder); + // here we make sure if our parent is rooted - if not, we're + // going to loop and add our parent as a child of its grandparent + // and repeat until we get to the server, or a folder that + // has its parent set. + parentFolder->GetParent(getter_AddRefs(grandParent)); + parentFolder->GetIsServer(&isServer); + buffer.SetLength(lastSlash); + } while (!grandParent && !isServer); + } else if (dbFolderInfo && StringBeginsWith(buffer, "scope="_ns)) { + buffer.Cut(0, 6); + // if this is a cross folder virtual folder, we have a list of folders + // uris, and we have to add a pending listener for each of them. + if (!buffer.IsEmpty()) { + ParseAndVerifyVirtualFolderScope(buffer); + dbFolderInfo->SetCharProperty(kSearchFolderUriProp, buffer); + AddVFListenersForVF(virtualFolder, buffer); + } + } else if (dbFolderInfo && StringBeginsWith(buffer, "terms="_ns)) { + buffer.Cut(0, 6); + dbFolderInfo->SetCharProperty("searchStr", buffer); + } else if (dbFolderInfo && StringBeginsWith(buffer, "searchOnline="_ns)) { + buffer.Cut(0, 13); + dbFolderInfo->SetBooleanProperty("searchOnline", + buffer.EqualsLiteral("true")); + } else if (dbFolderInfo && + Substring(buffer, 0, SEARCH_FOLDER_FLAG_LEN + 1) + .Equals(SEARCH_FOLDER_FLAG "=")) { + buffer.Cut(0, SEARCH_FOLDER_FLAG_LEN + 1); + dbFolderInfo->SetCharProperty(SEARCH_FOLDER_FLAG, buffer); + } + } + } + + m_loadingVirtualFolders = false; + m_virtualFoldersLoaded = true; + return rv; +} + +NS_IMETHODIMP nsMsgAccountManager::SaveVirtualFolders() { + if (!m_virtualFoldersLoaded) return NS_OK; + + nsCOMPtr<nsIFile> file; + GetVirtualFoldersFile(file); + + // Open a buffered, safe output stream + nsCOMPtr<nsIOutputStream> outStream; + nsresult rv = MsgNewSafeBufferedFileOutputStream( + getter_AddRefs(outStream), file, PR_CREATE_FILE | PR_WRONLY | PR_TRUNCATE, + 0664); + NS_ENSURE_SUCCESS(rv, rv); + + WriteLineToOutputStream("version=", "1", outStream); + for (auto iter = m_incomingServers.Iter(); !iter.Done(); iter.Next()) { + nsCOMPtr<nsIMsgIncomingServer>& server = iter.Data(); + if (server) { + nsCOMPtr<nsIMsgFolder> rootFolder; + server->GetRootFolder(getter_AddRefs(rootFolder)); + if (rootFolder) { + nsTArray<RefPtr<nsIMsgFolder>> virtualFolders; + nsresult rv = rootFolder->GetFoldersWithFlags(nsMsgFolderFlags::Virtual, + virtualFolders); + if (NS_FAILED(rv)) { + continue; + } + for (auto msgFolder : virtualFolders) { + nsCOMPtr<nsIMsgDatabase> db; + nsCOMPtr<nsIDBFolderInfo> dbFolderInfo; + rv = msgFolder->GetDBFolderInfoAndDB( + getter_AddRefs(dbFolderInfo), + getter_AddRefs(db)); // force db to get created. + if (dbFolderInfo) { + nsCString srchFolderUri; + nsCString searchTerms; + nsCString regexScope; + nsCString vfFolderFlag; + bool searchOnline = false; + dbFolderInfo->GetBooleanProperty("searchOnline", false, + &searchOnline); + dbFolderInfo->GetCharProperty(kSearchFolderUriProp, srchFolderUri); + dbFolderInfo->GetCharProperty("searchStr", searchTerms); + // logically searchFolderFlag is an int, but since we want to + // write out a string, get it as a string. + dbFolderInfo->GetCharProperty(SEARCH_FOLDER_FLAG, vfFolderFlag); + nsCString uri; + msgFolder->GetURI(uri); + if (!srchFolderUri.IsEmpty() && !searchTerms.IsEmpty()) { + WriteLineToOutputStream("uri=", uri.get(), outStream); + if (!vfFolderFlag.IsEmpty()) + WriteLineToOutputStream(SEARCH_FOLDER_FLAG "=", + vfFolderFlag.get(), outStream); + WriteLineToOutputStream("scope=", srchFolderUri.get(), outStream); + WriteLineToOutputStream("terms=", searchTerms.get(), outStream); + WriteLineToOutputStream( + "searchOnline=", searchOnline ? "true" : "false", outStream); + } + } + } + } + } + } + + nsCOMPtr<nsISafeOutputStream> safeStream = do_QueryInterface(outStream, &rv); + NS_ASSERTION(safeStream, "expected a safe output stream!"); + if (safeStream) { + rv = safeStream->Finish(); + if (NS_FAILED(rv)) { + NS_WARNING("failed to save personal dictionary file! possible data loss"); + } + } + return rv; +} + +nsresult nsMsgAccountManager::WriteLineToOutputStream( + const char* prefix, const char* line, nsIOutputStream* outputStream) { + uint32_t writeCount; + outputStream->Write(prefix, strlen(prefix), &writeCount); + outputStream->Write(line, strlen(line), &writeCount); + outputStream->Write("\n", 1, &writeCount); + return NS_OK; +} + +/** + * Parse the '|' separated folder uri string into individual folders, verify + * that the folders are real. If we were to add things like wildcards, we + * could implement the expansion into real folders here. + * + * @param buffer On input, list of folder uri's, on output, verified list. + */ +void nsMsgAccountManager::ParseAndVerifyVirtualFolderScope(nsCString& buffer) { + if (buffer.Equals("*")) { + // This is a special virtual folder that searches all folders in all + // accounts. Folders are chosen by the front end at search time. + return; + } + nsCString verifiedFolders; + nsTArray<nsCString> folderUris; + ParseString(buffer, '|', folderUris); + nsCOMPtr<nsIMsgIncomingServer> server; + nsCOMPtr<nsIMsgFolder> parent; + + for (uint32_t i = 0; i < folderUris.Length(); i++) { + nsCOMPtr<nsIMsgFolder> realFolder; + nsresult rv = GetOrCreateFolder(folderUris[i], getter_AddRefs(realFolder)); + if (!NS_SUCCEEDED(rv)) { + continue; + } + realFolder->GetParent(getter_AddRefs(parent)); + if (!parent) continue; + realFolder->GetServer(getter_AddRefs(server)); + if (!server) continue; + if (!verifiedFolders.IsEmpty()) verifiedFolders.Append('|'); + verifiedFolders.Append(folderUris[i]); + } + buffer.Assign(verifiedFolders); +} + +// This conveniently works to add a single folder as well. +nsresult nsMsgAccountManager::AddVFListenersForVF( + nsIMsgFolder* virtualFolder, const nsCString& srchFolderUris) { + if (srchFolderUris.Equals("*")) { + return NS_OK; + } + + nsresult rv; + if (!m_dbService) { + m_dbService = do_GetService("@mozilla.org/msgDatabase/msgDBService;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + } + + // Avoid any possible duplicate listeners for this virtual folder. + RemoveVFListenerForVF(virtualFolder, nullptr); + + nsTArray<nsCString> folderUris; + ParseString(srchFolderUris, '|', folderUris); + + for (uint32_t i = 0; i < folderUris.Length(); i++) { + nsCOMPtr<nsIMsgFolder> realFolder; + rv = GetOrCreateFolder(folderUris[i], getter_AddRefs(realFolder)); + NS_ENSURE_SUCCESS(rv, rv); + RefPtr<VirtualFolderChangeListener> dbListener = + new VirtualFolderChangeListener(); + NS_ENSURE_TRUE(dbListener, NS_ERROR_OUT_OF_MEMORY); + dbListener->m_virtualFolder = virtualFolder; + dbListener->m_folderWatching = realFolder; + if (NS_FAILED(dbListener->Init())) { + dbListener = nullptr; + continue; + } + m_virtualFolderListeners.AppendElement(dbListener); + m_dbService->RegisterPendingListener(realFolder, dbListener); + } + if (!m_virtualFolders.Contains(virtualFolder)) { + m_virtualFolders.AppendElement(virtualFolder); + } + return NS_OK; +} + +// This is called if a folder that's part of the scope of a saved search +// has gone away. +nsresult nsMsgAccountManager::RemoveVFListenerForVF(nsIMsgFolder* virtualFolder, + nsIMsgFolder* folder) { + nsresult rv; + if (!m_dbService) { + m_dbService = do_GetService("@mozilla.org/msgDatabase/msgDBService;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + } + + nsTObserverArray<RefPtr<VirtualFolderChangeListener>>::ForwardIterator iter( + m_virtualFolderListeners); + RefPtr<VirtualFolderChangeListener> listener; + + while (iter.HasMore()) { + listener = iter.GetNext(); + if (listener->m_virtualFolder == virtualFolder && + (!folder || listener->m_folderWatching == folder)) { + m_dbService->UnregisterPendingListener(listener); + m_virtualFolderListeners.RemoveElement(listener); + if (folder) { + break; + } + } + } + return NS_OK; +} + +NS_IMETHODIMP nsMsgAccountManager::GetAllFolders( + nsTArray<RefPtr<nsIMsgFolder>>& aAllFolders) { + aAllFolders.Clear(); + nsTArray<RefPtr<nsIMsgIncomingServer>> allServers; + nsresult rv = GetAllServers(allServers); + NS_ENSURE_SUCCESS(rv, rv); + + for (auto server : allServers) { + if (server) { + nsCOMPtr<nsIMsgFolder> rootFolder; + server->GetRootFolder(getter_AddRefs(rootFolder)); + if (rootFolder) { + nsTArray<RefPtr<nsIMsgFolder>> descendents; + rootFolder->GetDescendants(descendents); + aAllFolders.AppendElements(descendents); + } + } + } + return NS_OK; +} + +NS_IMETHODIMP nsMsgAccountManager::OnFolderAdded(nsIMsgFolder* parent, + nsIMsgFolder* folder) { + if (!parent) { + // This method gets called for folders that aren't connected to anything, + // such as a junk folder that appears when an IMAP account is created. We + // don't want these added to the virtual folder. + return NS_OK; + } + + uint32_t folderFlags; + folder->GetFlags(&folderFlags); + + bool addToSmartFolders = false; + folder->IsSpecialFolder(nsMsgFolderFlags::Inbox | + nsMsgFolderFlags::Templates | + nsMsgFolderFlags::Trash | + nsMsgFolderFlags::Drafts | nsMsgFolderFlags::Junk, + false, &addToSmartFolders); + // For Sent/Archives/Trash, we treat sub-folders of those folders as + // "special", and want to add them the smart folders search scope. + // So we check if this is a sub-folder of one of those special folders + // and set the corresponding folderFlag if so. + if (!addToSmartFolders) { + bool isSpecial = false; + folder->IsSpecialFolder(nsMsgFolderFlags::SentMail, true, &isSpecial); + if (isSpecial) { + addToSmartFolders = true; + folderFlags |= nsMsgFolderFlags::SentMail; + } + folder->IsSpecialFolder(nsMsgFolderFlags::Archive, true, &isSpecial); + if (isSpecial) { + addToSmartFolders = true; + folderFlags |= nsMsgFolderFlags::Archive; + } + folder->IsSpecialFolder(nsMsgFolderFlags::Trash, true, &isSpecial); + if (isSpecial) { + addToSmartFolders = true; + folderFlags |= nsMsgFolderFlags::Trash; + } + } + nsresult rv = NS_OK; + // if this is a special folder, check if we have a saved search over + // folders with this flag, and if so, add this folder to the scope. + if (addToSmartFolders) { + // quick way to enumerate the saved searches. + for (nsCOMPtr<nsIMsgFolder> virtualFolder : m_virtualFolders) { + nsCOMPtr<nsIMsgDatabase> db; + nsCOMPtr<nsIDBFolderInfo> dbFolderInfo; + virtualFolder->GetDBFolderInfoAndDB(getter_AddRefs(dbFolderInfo), + getter_AddRefs(db)); + if (dbFolderInfo) { + uint32_t vfFolderFlag; + dbFolderInfo->GetUint32Property("searchFolderFlag", 0, &vfFolderFlag); + // found a saved search over folders w/ the same flag as the new folder. + if (vfFolderFlag & folderFlags) { + nsCString searchURI; + dbFolderInfo->GetCharProperty(kSearchFolderUriProp, searchURI); + + // "normalize" searchURI so we can search for |folderURI|. + if (!searchURI.IsEmpty()) { + searchURI.Insert('|', 0); + searchURI.Append('|'); + } + nsCString folderURI; + folder->GetURI(folderURI); + folderURI.Insert('|', 0); + folderURI.Append('|'); + + int32_t index = searchURI.Find(folderURI); + if (index == kNotFound) { + searchURI.Cut(0, 1); + folderURI.Cut(0, 1); + folderURI.SetLength(folderURI.Length() - 1); + searchURI.Append(folderURI); + dbFolderInfo->SetCharProperty(kSearchFolderUriProp, searchURI); + nsCOMPtr<nsIObserverService> obs = + mozilla::services::GetObserverService(); + obs->NotifyObservers(virtualFolder, "search-folders-changed", + nullptr); + } + + // Add sub-folders to smart folder. + nsTArray<RefPtr<nsIMsgFolder>> allDescendants; + rv = folder->GetDescendants(allDescendants); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMsgFolder> parentFolder; + for (auto subFolder : allDescendants) { + subFolder->GetParent(getter_AddRefs(parentFolder)); + OnFolderAdded(parentFolder, subFolder); + } + } + } + } + } + + // Find any virtual folders that search `parent`, and add `folder` to them. + if (!(folderFlags & nsMsgFolderFlags::Virtual)) { + nsTObserverArray<RefPtr<VirtualFolderChangeListener>>::ForwardIterator iter( + m_virtualFolderListeners); + RefPtr<VirtualFolderChangeListener> listener; + + while (iter.HasMore()) { + listener = iter.GetNext(); + if (listener->m_folderWatching == parent) { + nsCOMPtr<nsIMsgDatabase> db; + nsCOMPtr<nsIDBFolderInfo> dbFolderInfo; + listener->m_virtualFolder->GetDBFolderInfoAndDB( + getter_AddRefs(dbFolderInfo), getter_AddRefs(db)); + + uint32_t vfFolderFlag; + dbFolderInfo->GetUint32Property("searchFolderFlag", 0, &vfFolderFlag); + if (addToSmartFolders && vfFolderFlag && + !(vfFolderFlag & nsMsgFolderFlags::Trash)) { + // Don't add folders of one type to the unified folder of another + // type, unless it's the Trash unified folder. + continue; + } + nsCString searchURI; + dbFolderInfo->GetCharProperty(kSearchFolderUriProp, searchURI); + + // "normalize" searchURI so we can search for |folderURI|. + if (!searchURI.IsEmpty()) { + searchURI.Insert('|', 0); + searchURI.Append('|'); + } + nsCString folderURI; + folder->GetURI(folderURI); + + int32_t index = searchURI.Find(folderURI); + if (index == kNotFound) { + searchURI.Cut(0, 1); + searchURI.Append(folderURI); + dbFolderInfo->SetCharProperty(kSearchFolderUriProp, searchURI); + nsCOMPtr<nsIObserverService> obs = + mozilla::services::GetObserverService(); + obs->NotifyObservers(listener->m_virtualFolder, + "search-folders-changed", nullptr); + } + } + } + } + + // need to make sure this isn't happening during loading of virtualfolders.dat + if (folderFlags & nsMsgFolderFlags::Virtual && !m_loadingVirtualFolders) { + // When a new virtual folder is added, need to create a db Listener for it. + nsCOMPtr<nsIMsgDatabase> virtDatabase; + nsCOMPtr<nsIDBFolderInfo> dbFolderInfo; + rv = folder->GetDBFolderInfoAndDB(getter_AddRefs(dbFolderInfo), + getter_AddRefs(virtDatabase)); + NS_ENSURE_SUCCESS(rv, rv); + nsCString srchFolderUri; + dbFolderInfo->GetCharProperty(kSearchFolderUriProp, srchFolderUri); + AddVFListenersForVF(folder, srchFolderUri); + rv = SaveVirtualFolders(); + } + return rv; +} + +NS_IMETHODIMP nsMsgAccountManager::OnMessageAdded(nsIMsgFolder* parent, + nsIMsgDBHdr* msg) { + return NS_OK; +} + +NS_IMETHODIMP nsMsgAccountManager::OnFolderRemoved(nsIMsgFolder* parentFolder, + nsIMsgFolder* folder) { + nsresult rv = NS_OK; + uint32_t folderFlags; + folder->GetFlags(&folderFlags); + // if we removed a VF, flush VF list to disk. + if (folderFlags & nsMsgFolderFlags::Virtual) { + RemoveVFListenerForVF(folder, nullptr); + m_virtualFolders.RemoveElement(folder); + rv = SaveVirtualFolders(); + // clear flags on deleted folder if it's a virtual folder, so that creating + // a new folder with the same name doesn't cause confusion. + folder->SetFlags(0); + return rv; + } + // need to update the saved searches to check for a few things: + // 1. Folder removed was in the scope of a saved search - if so, remove the + // uri from the scope of the saved search. + // 2. If the scope is now empty, remove the saved search. + + // build a "normalized" uri that we can do a find on. + nsCString removedFolderURI; + folder->GetURI(removedFolderURI); + removedFolderURI.Insert('|', 0); + removedFolderURI.Append('|'); + + // Enumerate the saved searches. + nsTObserverArray<RefPtr<VirtualFolderChangeListener>>::ForwardIterator iter( + m_virtualFolderListeners); + RefPtr<VirtualFolderChangeListener> listener; + + while (iter.HasMore()) { + listener = iter.GetNext(); + nsCOMPtr<nsIMsgDatabase> db; + nsCOMPtr<nsIDBFolderInfo> dbFolderInfo; + nsCOMPtr<nsIMsgFolder> savedSearch = listener->m_virtualFolder; + savedSearch->GetDBFolderInfoAndDB(getter_AddRefs(dbFolderInfo), + getter_AddRefs(db)); + if (dbFolderInfo) { + nsCString searchURI; + dbFolderInfo->GetCharProperty(kSearchFolderUriProp, searchURI); + // "normalize" searchURI so we can search for |folderURI|. + searchURI.Insert('|', 0); + searchURI.Append('|'); + int32_t index = searchURI.Find(removedFolderURI); + if (index != kNotFound) { + RemoveVFListenerForVF(savedSearch, folder); + + // remove |folderURI + searchURI.Cut(index, removedFolderURI.Length() - 1); + // remove last '|' we added + searchURI.SetLength(searchURI.Length() - 1); + + uint32_t vfFolderFlag; + dbFolderInfo->GetUint32Property("searchFolderFlag", 0, &vfFolderFlag); + + // If saved search is empty now, delete it. But not if it's a smart + // folder. + if (searchURI.IsEmpty() && !vfFolderFlag) { + db = nullptr; + dbFolderInfo = nullptr; + + nsCOMPtr<nsIMsgFolder> parent; + rv = savedSearch->GetParent(getter_AddRefs(parent)); + NS_ENSURE_SUCCESS(rv, rv); + + if (!parent) continue; + parent->PropagateDelete(savedSearch, true); + } else { + if (!searchURI.IsEmpty()) { + // Remove leading '|' we added (or one after |folderURI, if first + // URI). + searchURI.Cut(0, 1); + } + dbFolderInfo->SetCharProperty(kSearchFolderUriProp, searchURI); + nsCOMPtr<nsIObserverService> obs = + mozilla::services::GetObserverService(); + obs->NotifyObservers(savedSearch, "search-folders-changed", nullptr); + } + } + } + } + + return rv; +} + +NS_IMETHODIMP nsMsgAccountManager::OnMessageRemoved(nsIMsgFolder* parent, + nsIMsgDBHdr* msg) { + return NS_OK; +} + +NS_IMETHODIMP nsMsgAccountManager::OnFolderPropertyChanged( + nsIMsgFolder* folder, const nsACString& property, + const nsACString& oldValue, const nsACString& newValue) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsMsgAccountManager::OnFolderIntPropertyChanged(nsIMsgFolder* aFolder, + const nsACString& aProperty, + int64_t oldValue, + int64_t newValue) { + if (aProperty.Equals(kFolderFlag)) { + if (newValue & nsMsgFolderFlags::Virtual) { + // This is a virtual folder, let's get out of here. + return NS_OK; + } + uint32_t smartFlagsChanged = + (oldValue ^ newValue) & + (nsMsgFolderFlags::SpecialUse & ~nsMsgFolderFlags::Queue); + if (smartFlagsChanged) { + if (smartFlagsChanged & newValue) { + // if the smart folder flag was set, calling OnFolderAdded will + // do the right thing. + nsCOMPtr<nsIMsgFolder> parent; + aFolder->GetParent(getter_AddRefs(parent)); + nsresult rv = OnFolderAdded(parent, aFolder); + NS_ENSURE_SUCCESS(rv, rv); + + // This folder has one of the smart folder flags. + // Remove it from any other smart folders it might've been included in + // because of the flags of its ancestors. + RemoveFolderFromSmartFolder( + aFolder, (nsMsgFolderFlags::SpecialUse & ~nsMsgFolderFlags::Queue) & + ~newValue); + return NS_OK; + } + RemoveFolderFromSmartFolder(aFolder, smartFlagsChanged); + + nsTArray<RefPtr<nsIMsgFolder>> allDescendants; + nsresult rv = aFolder->GetDescendants(allDescendants); + NS_ENSURE_SUCCESS(rv, rv); + for (auto subFolder : allDescendants) { + RemoveFolderFromSmartFolder(subFolder, smartFlagsChanged); + } + } + } + return NS_OK; +} + +nsresult nsMsgAccountManager::RemoveFolderFromSmartFolder( + nsIMsgFolder* aFolder, uint32_t flagsChanged) { + nsCString removedFolderURI; + aFolder->GetURI(removedFolderURI); + removedFolderURI.Insert('|', 0); + removedFolderURI.Append('|'); + uint32_t flags; + aFolder->GetFlags(&flags); + NS_ASSERTION(!(flags & flagsChanged), "smart folder flag should not be set"); + // Flag was removed. Look for smart folder based on that flag, + // and remove this folder from its scope. + for (nsCOMPtr<nsIMsgFolder> virtualFolder : m_virtualFolders) { + nsCOMPtr<nsIMsgDatabase> db; + nsCOMPtr<nsIDBFolderInfo> dbFolderInfo; + virtualFolder->GetDBFolderInfoAndDB(getter_AddRefs(dbFolderInfo), + getter_AddRefs(db)); + if (dbFolderInfo) { + uint32_t vfFolderFlag; + dbFolderInfo->GetUint32Property("searchFolderFlag", 0, &vfFolderFlag); + // found a smart folder over the removed flag + if (vfFolderFlag & flagsChanged) { + nsCString searchURI; + dbFolderInfo->GetCharProperty(kSearchFolderUriProp, searchURI); + // "normalize" searchURI so we can search for |folderURI|. + searchURI.Insert('|', 0); + searchURI.Append('|'); + int32_t index = searchURI.Find(removedFolderURI); + if (index != kNotFound) { + RemoveVFListenerForVF(virtualFolder, aFolder); + + // remove |folderURI + searchURI.Cut(index, removedFolderURI.Length() - 1); + // remove last '|' we added + searchURI.SetLength(searchURI.Length() - 1); + + // remove leading '|' we added (or one after |folderURI, if first URI) + searchURI.Cut(0, 1); + dbFolderInfo->SetCharProperty(kSearchFolderUriProp, searchURI); + nsCOMPtr<nsIObserverService> obs = + mozilla::services::GetObserverService(); + obs->NotifyObservers(virtualFolder, "search-folders-changed", + nullptr); + } + } + } + } + return NS_OK; +} + +NS_IMETHODIMP nsMsgAccountManager::OnFolderBoolPropertyChanged( + nsIMsgFolder* folder, const nsACString& property, bool oldValue, + bool newValue) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsMsgAccountManager::OnFolderUnicharPropertyChanged( + nsIMsgFolder* folder, const nsACString& property, const nsAString& oldValue, + const nsAString& newValue) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsMsgAccountManager::OnFolderPropertyFlagChanged( + nsIMsgDBHdr* msg, const nsACString& property, uint32_t oldFlag, + uint32_t newFlag) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsMsgAccountManager::OnFolderEvent(nsIMsgFolder* aFolder, + const nsACString& aEvent) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsMsgAccountManager::FolderUriForPath(nsIFile* aLocalPath, + nsACString& aMailboxUri) { + NS_ENSURE_ARG_POINTER(aLocalPath); + bool equals; + if (m_lastPathLookedUp && + NS_SUCCEEDED(aLocalPath->Equals(m_lastPathLookedUp, &equals)) && equals) { + aMailboxUri = m_lastFolderURIForPath; + return NS_OK; + } + nsTArray<RefPtr<nsIMsgFolder>> folders; + nsresult rv = GetAllFolders(folders); + NS_ENSURE_SUCCESS(rv, rv); + + for (auto folder : folders) { + nsCOMPtr<nsIFile> folderPath; + rv = folder->GetFilePath(getter_AddRefs(folderPath)); + NS_ENSURE_SUCCESS(rv, rv); + + // Check if we're equal + rv = folderPath->Equals(aLocalPath, &equals); + NS_ENSURE_SUCCESS(rv, rv); + + if (equals) { + rv = folder->GetURI(aMailboxUri); + m_lastFolderURIForPath = aMailboxUri; + aLocalPath->Clone(getter_AddRefs(m_lastPathLookedUp)); + return rv; + } + } + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsMsgAccountManager::GetSortOrder(nsIMsgIncomingServer* aServer, + int32_t* aSortOrder) { + NS_ENSURE_ARG_POINTER(aServer); + NS_ENSURE_ARG_POINTER(aSortOrder); + + // If the passed in server is the default, return its sort order as 0 + // regardless of its server sort order. + + nsCOMPtr<nsIMsgAccount> defaultAccount; + nsresult rv = GetDefaultAccount(getter_AddRefs(defaultAccount)); + if (NS_SUCCEEDED(rv) && defaultAccount) { + nsCOMPtr<nsIMsgIncomingServer> defaultServer; + rv = m_defaultAccount->GetIncomingServer(getter_AddRefs(defaultServer)); + if (NS_SUCCEEDED(rv) && (aServer == defaultServer)) { + *aSortOrder = 0; + return NS_OK; + } + // It is OK if there is no default account. + } + + // This function returns the sort order by querying the server object for its + // sort order value and then incrementing it by the position of the server in + // the accounts list. This ensures that even when several accounts have the + // same sort order value, the returned value is not the same and keeps + // their relative order in the account list when and unstable sort is run + // on the returned sort order values. + int32_t sortOrder; + int32_t serverIndex; + + rv = aServer->GetSortOrder(&sortOrder); + if (NS_SUCCEEDED(rv)) rv = FindServerIndex(aServer, &serverIndex); + + if (NS_FAILED(rv)) { + *aSortOrder = 999999999; + } else { + *aSortOrder = sortOrder + serverIndex; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsMsgAccountManager::ReorderAccounts(const nsTArray<nsCString>& newAccounts) { + nsTArray<nsCString> allNewAccounts = newAccounts.Clone(); + + // Add all hidden accounts to the list of new accounts. + nsresult rv; + for (auto account : m_accounts) { + nsCString key; + account->GetKey(key); + nsCOMPtr<nsIMsgIncomingServer> server; + rv = account->GetIncomingServer(getter_AddRefs(server)); + if (NS_SUCCEEDED(rv) && server) { + bool hidden = false; + rv = server->GetHidden(&hidden); + if (NS_SUCCEEDED(rv) && hidden && !allNewAccounts.Contains(key)) { + allNewAccounts.AppendElement(key); + } + } + } + + // Check that the new account list contains all the existing accounts, + // just in a different order. + if (allNewAccounts.Length() != m_accounts.Length()) + return NS_ERROR_INVALID_ARG; + + for (uint32_t i = 0; i < m_accounts.Length(); i++) { + nsCString accountKey; + m_accounts[i]->GetKey(accountKey); + if (!allNewAccounts.Contains(accountKey)) return NS_ERROR_INVALID_ARG; + } + + // In-place swap the elements in m_accounts to the order defined in + // newAccounts. + for (uint32_t i = 0; i < allNewAccounts.Length(); i++) { + nsCString newKey = allNewAccounts[i]; + for (uint32_t j = i; j < m_accounts.Length(); j++) { + nsCString oldKey; + m_accounts[j]->GetKey(oldKey); + if (newKey.Equals(oldKey)) { + if (i != j) std::swap(m_accounts[i], m_accounts[j]); + break; + } + } + } + + return OutputAccountsPref(); +} |