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/local/src/nsLocalMailFolder.cpp | |
parent | Initial commit. (diff) | |
download | thunderbird-upstream.tar.xz thunderbird-upstream.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/local/src/nsLocalMailFolder.cpp')
-rw-r--r-- | comm/mailnews/local/src/nsLocalMailFolder.cpp | 3468 |
1 files changed, 3468 insertions, 0 deletions
diff --git a/comm/mailnews/local/src/nsLocalMailFolder.cpp b/comm/mailnews/local/src/nsLocalMailFolder.cpp new file mode 100644 index 0000000000..2602c40046 --- /dev/null +++ b/comm/mailnews/local/src/nsLocalMailFolder.cpp @@ -0,0 +1,3468 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIPrefService.h" +#include "nsIPrefBranch.h" +#include "prlog.h" + +#include "msgCore.h" // precompiled header... +#include "nsLocalMailFolder.h" +#include "nsMsgLocalFolderHdrs.h" +#include "nsMsgFolderFlags.h" +#include "nsMsgMessageFlags.h" +#include "prprf.h" +#include "prmem.h" +#include "nsITransactionManager.h" +#include "nsParseMailbox.h" +#include "nsIMsgAccountManager.h" +#include "nsIMsgWindow.h" +#include "nsCOMPtr.h" +#include "nsMsgUtils.h" +#include "nsLocalUtils.h" +#include "nsIPop3IncomingServer.h" +#include "nsILocalMailIncomingServer.h" +#include "nsIMsgIncomingServer.h" +#include "nsString.h" +#include "nsIMsgFolderCacheElement.h" +#include "nsUnicharUtils.h" +#include "nsICopyMessageStreamListener.h" +#include "nsIMsgCopyService.h" +#include "nsMsgTxn.h" +#include "nsIMessenger.h" +#include "nsNativeCharsetUtils.h" +#include "nsIDocShell.h" +#include "nsIPrompt.h" +#include "nsIPop3URL.h" +#include "nsIMsgMailSession.h" +#include "nsIMsgFolderCompactor.h" +#include "nsNetCID.h" +#include "nsISpamSettings.h" +#include "nsNativeCharsetUtils.h" +#include "nsMailHeaders.h" +#include "nsCOMArray.h" +#include "nsIRssIncomingServer.h" +#include "nsNetUtil.h" +#include "nsIMsgFolderNotificationService.h" +#include "nsReadLine.h" +#include "nsIStringEnumerator.h" +#include "nsIURIMutator.h" +#include "mozilla/Components.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/SlicedInputStream.h" + +////////////////////////////////////////////////////////////////////////////// +// nsLocal +///////////////////////////////////////////////////////////////////////////// + +nsLocalMailCopyState::nsLocalMailCopyState() + : m_flags(0), + m_lastProgressTime(PR_IntervalToMilliseconds(PR_IntervalNow())), + m_curDstKey(nsMsgKey_None), + m_curCopyIndex(0), + m_totalMsgCount(0), + m_dataBufferSize(0), + m_leftOver(0), + m_isMove(false), + m_dummyEnvelopeNeeded(false), + m_fromLineSeen(false), + m_writeFailed(false), + m_notifyFolderLoaded(false) {} + +nsLocalMailCopyState::~nsLocalMailCopyState() { + PR_Free(m_dataBuffer); + if (m_fileStream) m_fileStream->Close(); + if (m_messageService) { + nsCOMPtr<nsIMsgFolder> srcFolder = do_QueryInterface(m_srcSupport); + if (srcFolder && m_message) { + nsCString uri; + srcFolder->GetUriForMsg(m_message, uri); + } + } +} + +nsLocalFolderScanState::nsLocalFolderScanState() : m_uidl(nullptr) {} + +nsLocalFolderScanState::~nsLocalFolderScanState() {} + +/////////////////////////////////////////////////////////////////////////////// +// nsMsgLocalMailFolder interface +/////////////////////////////////////////////////////////////////////////////// + +nsMsgLocalMailFolder::nsMsgLocalMailFolder(void) + : mCopyState(nullptr), + mHaveReadNameFromDB(false), + mInitialized(false), + mCheckForNewMessagesAfterParsing(false), + m_parsingFolder(false), + mDownloadState(DOWNLOAD_STATE_NONE) {} + +nsMsgLocalMailFolder::~nsMsgLocalMailFolder(void) {} + +NS_IMPL_ISUPPORTS_INHERITED(nsMsgLocalMailFolder, nsMsgDBFolder, + nsICopyMessageListener, nsIMsgLocalMailFolder) + +//////////////////////////////////////////////////////////////////////////////// + +nsresult nsMsgLocalMailFolder::CreateChildFromURI(const nsACString& uri, + nsIMsgFolder** folder) { + nsMsgLocalMailFolder* newFolder = new nsMsgLocalMailFolder; + if (!newFolder) return NS_ERROR_OUT_OF_MEMORY; + + NS_ADDREF(*folder = newFolder); + newFolder->Init(uri); + return NS_OK; +} + +NS_IMETHODIMP nsMsgLocalMailFolder::CreateLocalSubfolder( + const nsAString& aFolderName, nsIMsgFolder** aChild) { + NS_ENSURE_ARG_POINTER(aChild); + nsresult rv = CreateSubfolderInternal(aFolderName, nullptr, aChild); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMsgFolderNotificationService> notifier( + do_GetService("@mozilla.org/messenger/msgnotificationservice;1")); + if (notifier) notifier->NotifyFolderAdded(*aChild); + + return NS_OK; +} + +NS_IMETHODIMP nsMsgLocalMailFolder::GetManyHeadersToDownload(bool* retval) { + bool isLocked; + // if the folder is locked, we're probably reparsing - let's build the + // view when we've finished reparsing. + GetLocked(&isLocked); + if (isLocked) { + *retval = true; + return NS_OK; + } + + return nsMsgDBFolder::GetManyHeadersToDownload(retval); +} + +// run the url to parse the mailbox +NS_IMETHODIMP nsMsgLocalMailFolder::ParseFolder(nsIMsgWindow* aMsgWindow, + nsIUrlListener* aListener) { + nsCOMPtr<nsIMsgPluggableStore> msgStore; + nsresult rv = GetMsgStore(getter_AddRefs(msgStore)); + NS_ENSURE_SUCCESS(rv, rv); + if (aListener != this) mReparseListener = aListener; + // if parsing is synchronous, we need to set m_parsingFolder to + // true before starting. And we need to open the db before + // setting m_parsingFolder to true. + // OpenDatabase(); + rv = msgStore->RebuildIndex(this, mDatabase, aMsgWindow, this); + if (NS_SUCCEEDED(rv)) m_parsingFolder = true; + + return rv; +} + +// this won't force a reparse of the folder if the db is invalid. +NS_IMETHODIMP +nsMsgLocalMailFolder::GetMsgDatabase(nsIMsgDatabase** aMsgDatabase) { + return GetDatabaseWOReparse(aMsgDatabase); +} + +NS_IMETHODIMP +nsMsgLocalMailFolder::GetSubFolders(nsTArray<RefPtr<nsIMsgFolder>>& folders) { + if (!mInitialized) { + nsCOMPtr<nsIMsgIncomingServer> server; + nsresult rv = GetServer(getter_AddRefs(server)); + NS_ENSURE_SUCCESS(rv, NS_MSG_INVALID_OR_MISSING_SERVER); + nsCOMPtr<nsIMsgPluggableStore> msgStore; + // need to set this flag here to avoid infinite recursion + mInitialized = true; + rv = server->GetMsgStore(getter_AddRefs(msgStore)); + NS_ENSURE_SUCCESS(rv, rv); + // This should add all existing folders as sub-folders of this folder. + rv = msgStore->DiscoverSubFolders(this, true); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIFile> path; + rv = GetFilePath(getter_AddRefs(path)); + if (NS_FAILED(rv)) return rv; + + bool directory; + path->IsDirectory(&directory); + if (directory) { + SetFlag(nsMsgFolderFlags::Mail | nsMsgFolderFlags::Elided | + nsMsgFolderFlags::Directory); + + bool isServer; + GetIsServer(&isServer); + if (isServer) { + nsCOMPtr<nsIMsgIncomingServer> server; + rv = GetServer(getter_AddRefs(server)); + NS_ENSURE_SUCCESS(rv, NS_MSG_INVALID_OR_MISSING_SERVER); + + nsCOMPtr<nsILocalMailIncomingServer> localMailServer; + localMailServer = do_QueryInterface(server, &rv); + NS_ENSURE_SUCCESS(rv, NS_MSG_INVALID_OR_MISSING_SERVER); + + // first create the folders on disk (as empty files) + rv = localMailServer->CreateDefaultMailboxes(); + if (NS_FAILED(rv) && rv != NS_MSG_FOLDER_EXISTS) return rv; + + // must happen after CreateSubFolders, or the folders won't exist. + rv = localMailServer->SetFlagsOnDefaultMailboxes(); + if (NS_FAILED(rv)) return rv; + } + } + UpdateSummaryTotals(false); + } + + return nsMsgDBFolder::GetSubFolders(folders); +} + +nsresult nsMsgLocalMailFolder::GetDatabase() { + nsCOMPtr<nsIMsgDatabase> msgDB; + return GetDatabaseWOReparse(getter_AddRefs(msgDB)); +} + +// we treat failure as null db returned +NS_IMETHODIMP nsMsgLocalMailFolder::GetDatabaseWOReparse( + nsIMsgDatabase** aDatabase) { + NS_ENSURE_ARG(aDatabase); + if (m_parsingFolder) return NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE; + + nsresult rv = NS_OK; + if (!mDatabase) { + rv = OpenDatabase(); + if (mDatabase) { + mDatabase->AddListener(this); + UpdateNewMessages(); + } + } + NS_IF_ADDREF(*aDatabase = mDatabase); + if (mDatabase) mDatabase->SetLastUseTime(PR_Now()); + return rv; +} + +// Makes sure the database is open and exists. If the database is out of date, +// then this call will return NS_ERROR_NOT_INITIALIZED and run an async url +// to reparse the folder. The passed in url listener will get called when the +// url is done. +NS_IMETHODIMP nsMsgLocalMailFolder::GetDatabaseWithReparse( + nsIUrlListener* aReparseUrlListener, nsIMsgWindow* aMsgWindow, + nsIMsgDatabase** aMsgDatabase) { + nsresult rv = NS_OK; + // if we're already reparsing, just remember the listener so we can notify it + // when we've finished. + if (m_parsingFolder) { + NS_ASSERTION(!mReparseListener, "can't have an existing listener"); + mReparseListener = aReparseUrlListener; + return NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE; + } + + if (!mDatabase) { + nsCOMPtr<nsIFile> pathFile; + rv = GetFilePath(getter_AddRefs(pathFile)); + if (NS_FAILED(rv)) return rv; + + bool exists; + rv = pathFile->Exists(&exists); + NS_ENSURE_SUCCESS(rv, rv); + if (!exists) + return NS_ERROR_NULL_POINTER; // mDatabase will be null at this point. + + nsCOMPtr<nsIMsgDBService> msgDBService = + do_GetService("@mozilla.org/msgDatabase/msgDBService;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsresult folderOpen = + msgDBService->OpenFolderDB(this, true, getter_AddRefs(mDatabase)); + if (folderOpen == NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE) { + nsCOMPtr<nsIDBFolderInfo> dbFolderInfo; + nsCOMPtr<nsIDBFolderInfo> transferInfo; + if (mDatabase) { + mDatabase->GetDBFolderInfo(getter_AddRefs(dbFolderInfo)); + if (dbFolderInfo) { + dbFolderInfo->SetNumMessages(0); + dbFolderInfo->SetNumUnreadMessages(0); + dbFolderInfo->GetTransferInfo(getter_AddRefs(transferInfo)); + } + dbFolderInfo = nullptr; + + // A backup message database might have been created earlier, for + // example if the user requested a reindex. We'll use the earlier one if + // we can, otherwise we'll try to backup at this point. + if (NS_FAILED(OpenBackupMsgDatabase())) { + CloseAndBackupFolderDB(EmptyCString()); + if (NS_FAILED(OpenBackupMsgDatabase()) && mBackupDatabase) { + mBackupDatabase->RemoveListener(this); + mBackupDatabase = nullptr; + } + } else + mDatabase->ForceClosed(); + + mDatabase = nullptr; + } + nsCOMPtr<nsIFile> summaryFile; + rv = GetSummaryFileLocation(pathFile, getter_AddRefs(summaryFile)); + NS_ENSURE_SUCCESS(rv, rv); + // Remove summary file. + summaryFile->Remove(false); + + // if it's out of date then reopen with upgrade. + rv = msgDBService->CreateNewDB(this, getter_AddRefs(mDatabase)); + NS_ENSURE_SUCCESS(rv, rv); + + if (transferInfo && mDatabase) { + SetDBTransferInfo(transferInfo); + mDatabase->SetSummaryValid(false); + } + } else if (folderOpen == NS_MSG_ERROR_FOLDER_SUMMARY_MISSING) { + rv = msgDBService->CreateNewDB(this, getter_AddRefs(mDatabase)); + } + + if (mDatabase) { + if (mAddListener) mDatabase->AddListener(this); + + // if we have to regenerate the folder, run the parser url. + if (folderOpen == NS_MSG_ERROR_FOLDER_SUMMARY_MISSING || + folderOpen == NS_MSG_ERROR_FOLDER_SUMMARY_OUT_OF_DATE) { + if (NS_FAILED(rv = ParseFolder(aMsgWindow, aReparseUrlListener))) { + if (rv == NS_MSG_FOLDER_BUSY) { + // we need to null out the db so that parsing gets kicked off again. + mDatabase->RemoveListener(this); + mDatabase = nullptr; + ThrowAlertMsg("parsingFolderFailed", aMsgWindow); + } + return rv; + } + + return NS_ERROR_NOT_INITIALIZED; + } + + // We have a valid database so lets extract necessary info. + UpdateSummaryTotals(true); + } + } + NS_IF_ADDREF(*aMsgDatabase = mDatabase); + return rv; +} + +NS_IMETHODIMP +nsMsgLocalMailFolder::UpdateFolder(nsIMsgWindow* aWindow) { + (void)RefreshSizeOnDisk(); + nsresult rv; + + if (!PromptForMasterPasswordIfNecessary()) return NS_ERROR_FAILURE; + + // If we don't currently have a database, get it. Otherwise, the folder has + // been updated (presumably this changes when we download headers when opening + // inbox). If it's updated, send NotifyFolderLoaded. + if (!mDatabase) { + // return of NS_ERROR_NOT_INITIALIZED means running parsing URL + // We don't need the return value, and assigning it to mDatabase which + // is already set internally leaks. + nsCOMPtr<nsIMsgDatabase> returnedDb; + rv = GetDatabaseWithReparse(this, aWindow, getter_AddRefs(returnedDb)); + if (NS_SUCCEEDED(rv)) NotifyFolderEvent(kFolderLoaded); + } else { + bool valid; + rv = mDatabase->GetSummaryValid(&valid); + // don't notify folder loaded or try compaction if db isn't valid + // (we're probably reparsing or copying msgs to it) + if (NS_SUCCEEDED(rv) && valid) + NotifyFolderEvent(kFolderLoaded); + else if (mCopyState) + mCopyState->m_notifyFolderLoaded = + true; // defer folder loaded notification + else if (!m_parsingFolder) // if the db was already open, it's probably OK + // to load it if not parsing + NotifyFolderEvent(kFolderLoaded); + } + bool filtersRun; + bool hasNewMessages; + GetHasNewMessages(&hasNewMessages); + if (mDatabase) ApplyRetentionSettings(); + // if we have new messages, try the filter plugins. + if (NS_SUCCEEDED(rv) && hasNewMessages) + (void)CallFilterPlugins(aWindow, &filtersRun); + // Callers should rely on folder loaded event to ensure completion of loading. + // So we'll return NS_OK even if parsing is still in progress + if (rv == NS_ERROR_NOT_INITIALIZED) rv = NS_OK; + return rv; +} + +NS_IMETHODIMP nsMsgLocalMailFolder::GetFolderURL(nsACString& aUrl) { + nsresult rv; + nsCOMPtr<nsIFile> path; + rv = GetFilePath(getter_AddRefs(path)); + if (NS_FAILED(rv)) return rv; + + rv = NS_GetURLSpecFromFile(path, aUrl); + NS_ENSURE_SUCCESS(rv, rv); + + aUrl.Replace(0, strlen("file:"), "mailbox:"); + + return NS_OK; +} + +NS_IMETHODIMP nsMsgLocalMailFolder::CreateStorageIfMissing( + nsIUrlListener* aUrlListener) { + nsresult rv = NS_OK; + nsCOMPtr<nsIMsgFolder> msgParent; + GetParent(getter_AddRefs(msgParent)); + + // parent is probably not set because *this* was probably created by rdf + // and not by folder discovery. So, we have to compute the parent. + if (!msgParent) { + nsAutoCString folderName(mURI); + nsAutoCString uri; + int32_t leafPos = folderName.RFindChar('/'); + nsAutoCString parentName(folderName); + if (leafPos > 0) { + // If there is a hierarchy, there is a parent. + // Don't strip off slash if it's the first character + parentName.SetLength(leafPos); + rv = GetOrCreateFolder(parentName, getter_AddRefs(msgParent)); + NS_ENSURE_SUCCESS(rv, rv); + } + } + + if (msgParent) { + nsString folderName; + GetName(folderName); + rv = msgParent->CreateSubfolder(folderName, nullptr); + // by definition, this is OK. + if (rv == NS_MSG_FOLDER_EXISTS) return NS_OK; + } + + return rv; +} + +NS_IMETHODIMP +nsMsgLocalMailFolder::CreateSubfolder(const nsAString& folderName, + nsIMsgWindow* msgWindow) { + nsCOMPtr<nsIMsgFolder> newFolder; + nsresult rv = + CreateSubfolderInternal(folderName, msgWindow, getter_AddRefs(newFolder)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMsgFolderNotificationService> notifier( + do_GetService("@mozilla.org/messenger/msgnotificationservice;1")); + if (notifier) notifier->NotifyFolderAdded(newFolder); + + return NS_OK; +} + +nsresult nsMsgLocalMailFolder::CreateSubfolderInternal( + const nsAString& folderName, nsIMsgWindow* msgWindow, + nsIMsgFolder** aNewFolder) { + nsresult rv = CheckIfFolderExists(folderName, this, msgWindow); + // No need for an assertion: we already throw an alert. + if (NS_FAILED(rv)) return rv; + nsCOMPtr<nsIMsgPluggableStore> msgStore; + rv = GetMsgStore(getter_AddRefs(msgStore)); + NS_ENSURE_SUCCESS(rv, rv); + rv = msgStore->CreateFolder(this, folderName, aNewFolder); + if (rv == NS_MSG_ERROR_INVALID_FOLDER_NAME) { + ThrowAlertMsg("folderCreationFailed", msgWindow); + } else if (rv == NS_MSG_FOLDER_EXISTS) { + ThrowAlertMsg("folderExists", msgWindow); + } + + if (NS_SUCCEEDED(rv)) { + // we need to notify explicitly the flag change because it failed when we + // did AddSubfolder + (*aNewFolder)->OnFlagChange(mFlags); + (*aNewFolder) + ->SetPrettyName( + folderName); // because empty trash will create a new trash folder + NotifyFolderAdded(*aNewFolder); + } + + return rv; +} + +NS_IMETHODIMP nsMsgLocalMailFolder::CompactAll(nsIUrlListener* aListener, + nsIMsgWindow* aMsgWindow) { + nsresult rv = NS_OK; + nsCOMPtr<nsIMsgFolder> rootFolder; + rv = GetRootFolder(getter_AddRefs(rootFolder)); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr<nsIMsgPluggableStore> msgStore; + rv = GetMsgStore(getter_AddRefs(msgStore)); + NS_ENSURE_SUCCESS(rv, rv); + bool storeSupportsCompaction; + msgStore->GetSupportsCompaction(&storeSupportsCompaction); + nsTArray<RefPtr<nsIMsgFolder>> folderArray; + if (storeSupportsCompaction) { + nsTArray<RefPtr<nsIMsgFolder>> allDescendants; + rv = rootFolder->GetDescendants(allDescendants); + NS_ENSURE_SUCCESS(rv, rv); + int64_t expungedBytes = 0; + for (auto folder : allDescendants) { + expungedBytes = 0; + if (folder) rv = folder->GetExpungedBytes(&expungedBytes); + + NS_ENSURE_SUCCESS(rv, rv); + + if (expungedBytes > 0) folderArray.AppendElement(folder); + } + } + + if (folderArray.IsEmpty()) { + // Nothing to do - early out. + if (aListener) { + aListener->OnStopRunningUrl(nullptr, NS_OK); + } + return NS_OK; + } + + nsCOMPtr<nsIMsgFolderCompactor> folderCompactor = + do_CreateInstance("@mozilla.org/messenger/foldercompactor;1", &rv); + return folderCompactor->CompactFolders(folderArray, aListener, aMsgWindow); +} + +NS_IMETHODIMP nsMsgLocalMailFolder::Compact(nsIUrlListener* aListener, + nsIMsgWindow* aMsgWindow) { + nsCOMPtr<nsIMsgPluggableStore> msgStore; + nsresult rv = GetMsgStore(getter_AddRefs(msgStore)); + NS_ENSURE_SUCCESS(rv, rv); + int64_t expungedBytes = 0; + GetExpungedBytes(&expungedBytes); + bool supportsCompaction; + msgStore->GetSupportsCompaction(&supportsCompaction); + if (!supportsCompaction || expungedBytes == 0) { + // Nothing to do. Early out. + if (aListener) { + aListener->OnStopRunningUrl(nullptr, NS_OK); + } + return NS_OK; + } + + nsCOMPtr<nsIMsgFolderCompactor> folderCompactor = + do_CreateInstance("@mozilla.org/messenger/foldercompactor;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + return folderCompactor->CompactFolders({this}, aListener, aMsgWindow); +} + +NS_IMETHODIMP nsMsgLocalMailFolder::EmptyTrash(nsIUrlListener* aListener) { + nsresult rv; + nsCOMPtr<nsIMsgFolder> trashFolder; + rv = GetTrashFolder(getter_AddRefs(trashFolder)); + if (NS_SUCCEEDED(rv)) { + uint32_t flags; + trashFolder->GetFlags(&flags); + int32_t totalMessages = 0; + rv = trashFolder->GetTotalMessages(true, &totalMessages); + if (totalMessages <= 0) { + // Any folders to deal with? + nsTArray<RefPtr<nsIMsgFolder>> subFolders; + rv = trashFolder->GetSubFolders(subFolders); + NS_ENSURE_SUCCESS(rv, rv); + if (subFolders.IsEmpty()) { + return NS_OK; + } + } + nsCOMPtr<nsIMsgFolder> parentFolder; + rv = trashFolder->GetParent(getter_AddRefs(parentFolder)); + if (NS_SUCCEEDED(rv) && parentFolder) { + nsCOMPtr<nsIDBFolderInfo> transferInfo; + trashFolder->GetDBTransferInfo(getter_AddRefs(transferInfo)); + trashFolder->SetParent(nullptr); + parentFolder->PropagateDelete(trashFolder, true); + parentFolder->CreateSubfolder(u"Trash"_ns, nullptr); + nsCOMPtr<nsIMsgFolder> newTrashFolder; + rv = GetTrashFolder(getter_AddRefs(newTrashFolder)); + if (NS_SUCCEEDED(rv) && newTrashFolder) { + nsCOMPtr<nsIMsgLocalMailFolder> localTrash = + do_QueryInterface(newTrashFolder); + if (transferInfo) newTrashFolder->SetDBTransferInfo(transferInfo); + if (localTrash) localTrash->RefreshSizeOnDisk(); + // update the summary totals so the front end will + // show the right thing for the new trash folder + // see bug #161999 + nsCOMPtr<nsIDBFolderInfo> dbFolderInfo; + nsCOMPtr<nsIMsgDatabase> db; + newTrashFolder->GetDBFolderInfoAndDB(getter_AddRefs(dbFolderInfo), + getter_AddRefs(db)); + if (dbFolderInfo) { + dbFolderInfo->SetNumUnreadMessages(0); + dbFolderInfo->SetNumMessages(0); + } + newTrashFolder->UpdateSummaryTotals(true); + } + } + } + return rv; +} + +nsresult nsMsgLocalMailFolder::IsChildOfTrash(bool* result) { + NS_ENSURE_ARG_POINTER(result); + uint32_t parentFlags = 0; + *result = false; + bool isServer; + nsresult rv = GetIsServer(&isServer); + if (NS_FAILED(rv) || isServer) return NS_OK; + + rv = GetFlags(&parentFlags); // this is the parent folder + if (parentFlags & nsMsgFolderFlags::Trash) { + *result = true; + return rv; + } + + nsCOMPtr<nsIMsgFolder> parentFolder; + nsCOMPtr<nsIMsgFolder> thisFolder; + rv = QueryInterface(NS_GET_IID(nsIMsgFolder), getter_AddRefs(thisFolder)); + + while (!isServer) { + thisFolder->GetParent(getter_AddRefs(parentFolder)); + if (!parentFolder) return NS_OK; + + rv = parentFolder->GetIsServer(&isServer); + if (NS_FAILED(rv) || isServer) return NS_OK; + + rv = parentFolder->GetFlags(&parentFlags); + if (NS_FAILED(rv)) return NS_OK; + + if (parentFlags & nsMsgFolderFlags::Trash) { + *result = true; + return rv; + } + + thisFolder = parentFolder; + } + return rv; +} + +NS_IMETHODIMP nsMsgLocalMailFolder::DeleteSelf(nsIMsgWindow* msgWindow) { + nsresult rv; + bool isChildOfTrash; + IsChildOfTrash(&isChildOfTrash); + + uint32_t folderFlags = 0; + GetFlags(&folderFlags); + // when deleting from trash, or virtual folder, just delete it. + if (isChildOfTrash || folderFlags & nsMsgFolderFlags::Virtual) + return nsMsgDBFolder::DeleteSelf(msgWindow); + + nsCOMPtr<nsIMsgFolder> trashFolder; + rv = GetTrashFolder(getter_AddRefs(trashFolder)); + if (NS_SUCCEEDED(rv)) { + nsCOMPtr<nsIMsgCopyService> copyService( + do_GetService("@mozilla.org/messenger/messagecopyservice;1", &rv)); + NS_ENSURE_SUCCESS(rv, rv); + rv = copyService->CopyFolder(this, trashFolder, true, nullptr, msgWindow); + } + return rv; +} + +nsresult nsMsgLocalMailFolder::ConfirmFolderDeletion(nsIMsgWindow* aMsgWindow, + nsIMsgFolder* aFolder, + bool* aResult) { + NS_ENSURE_ARG(aResult); + NS_ENSURE_ARG(aMsgWindow); + NS_ENSURE_ARG(aFolder); + nsCOMPtr<nsIDocShell> docShell; + aMsgWindow->GetRootDocShell(getter_AddRefs(docShell)); + if (docShell) { + bool confirmDeletion = true; + nsresult rv; + nsCOMPtr<nsIPrefBranch> pPrefBranch( + do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + pPrefBranch->GetBoolPref("mailnews.confirm.moveFoldersToTrash", + &confirmDeletion); + if (confirmDeletion) { + nsCOMPtr<nsIStringBundleService> bundleService = + mozilla::components::StringBundle::Service(); + NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED); + nsCOMPtr<nsIStringBundle> bundle; + rv = bundleService->CreateBundle( + "chrome://messenger/locale/localMsgs.properties", + getter_AddRefs(bundle)); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoString folderName; + rv = aFolder->GetName(folderName); + NS_ENSURE_SUCCESS(rv, rv); + AutoTArray<nsString, 1> formatStrings = {folderName}; + + nsAutoString deleteFolderDialogTitle; + rv = bundle->GetStringFromName("pop3DeleteFolderDialogTitle", + deleteFolderDialogTitle); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoString deleteFolderButtonLabel; + rv = bundle->GetStringFromName("pop3DeleteFolderButtonLabel", + deleteFolderButtonLabel); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoString confirmationStr; + rv = bundle->FormatStringFromName("pop3MoveFolderToTrash", formatStrings, + confirmationStr); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIPrompt> dialog(do_GetInterface(docShell)); + if (dialog) { + int32_t buttonPressed = 0; + // Default the dialog to "cancel". + const uint32_t buttonFlags = + (nsIPrompt::BUTTON_TITLE_IS_STRING * nsIPrompt::BUTTON_POS_0) + + (nsIPrompt::BUTTON_TITLE_CANCEL * nsIPrompt::BUTTON_POS_1); + bool dummyValue = false; + rv = dialog->ConfirmEx(deleteFolderDialogTitle.get(), + confirmationStr.get(), buttonFlags, + deleteFolderButtonLabel.get(), nullptr, nullptr, + nullptr, &dummyValue, &buttonPressed); + NS_ENSURE_SUCCESS(rv, rv); + *aResult = !buttonPressed; // "ok" is in position 0 + } + } else + *aResult = true; + } + return NS_OK; +} + +NS_IMETHODIMP nsMsgLocalMailFolder::Rename(const nsAString& aNewName, + nsIMsgWindow* msgWindow) { + // Renaming to the same name is easy + if (mName.Equals(aNewName)) return NS_OK; + + nsCOMPtr<nsIMsgFolder> parentFolder; + nsresult rv = GetParent(getter_AddRefs(parentFolder)); + if (!parentFolder) return NS_ERROR_NULL_POINTER; + + rv = CheckIfFolderExists(aNewName, parentFolder, msgWindow); + if (NS_FAILED(rv)) return rv; + + nsCOMPtr<nsIMsgPluggableStore> msgStore; + nsCOMPtr<nsIMsgFolder> newFolder; + rv = GetMsgStore(getter_AddRefs(msgStore)); + NS_ENSURE_SUCCESS(rv, rv); + rv = msgStore->RenameFolder(this, aNewName, getter_AddRefs(newFolder)); + if (NS_FAILED(rv)) { + if (msgWindow) + (void)ThrowAlertMsg( + (rv == NS_MSG_FOLDER_EXISTS) ? "folderExists" : "folderRenameFailed", + msgWindow); + return rv; + } + + int32_t count = mSubFolders.Count(); + if (newFolder) { + // Because we just renamed the db, w/o setting the pretty name in it, + // we need to force the pretty name to be correct. + // SetPrettyName won't write the name to the db if it doesn't think the + // name has changed. This hack forces the pretty name to get set in the db. + // We could set the new pretty name on the db before renaming the .msf file, + // but if the rename failed, it would be out of sync. + newFolder->SetPrettyName(EmptyString()); + newFolder->SetPrettyName(aNewName); + bool changed = false; + MatchOrChangeFilterDestination(newFolder, true /*case-insensitive*/, + &changed); + if (changed) AlertFilterChanged(msgWindow); + + if (count > 0) newFolder->RenameSubFolders(msgWindow, this); + + // Discover the subfolders inside this folder (this is recursive) + nsTArray<RefPtr<nsIMsgFolder>> dummy; + newFolder->GetSubFolders(dummy); + + // the newFolder should have the same flags + newFolder->SetFlags(mFlags); + if (parentFolder) { + SetParent(nullptr); + parentFolder->PropagateDelete(this, false); + parentFolder->NotifyFolderAdded(newFolder); + } + // Forget our path, since this folder object renamed itself. + SetFilePath(nullptr); + newFolder->NotifyFolderEvent(kRenameCompleted); + + nsCOMPtr<nsIMsgFolderNotificationService> notifier( + do_GetService("@mozilla.org/messenger/msgnotificationservice;1")); + if (notifier) notifier->NotifyFolderRenamed(this, newFolder); + } + return rv; +} + +NS_IMETHODIMP nsMsgLocalMailFolder::RenameSubFolders(nsIMsgWindow* msgWindow, + nsIMsgFolder* oldFolder) { + nsresult rv = NS_OK; + mInitialized = true; + + uint32_t flags; + oldFolder->GetFlags(&flags); + SetFlags(flags); + + nsTArray<RefPtr<nsIMsgFolder>> subFolders; + rv = oldFolder->GetSubFolders(subFolders); + NS_ENSURE_SUCCESS(rv, rv); + + for (nsIMsgFolder* msgFolder : subFolders) { + nsString folderName; + rv = msgFolder->GetName(folderName); + nsCOMPtr<nsIMsgFolder> newFolder; + AddSubfolder(folderName, getter_AddRefs(newFolder)); + if (newFolder) { + newFolder->SetPrettyName(folderName); + bool changed = false; + msgFolder->MatchOrChangeFilterDestination( + newFolder, true /*case-insensitive*/, &changed); + if (changed) msgFolder->AlertFilterChanged(msgWindow); + newFolder->RenameSubFolders(msgWindow, msgFolder); + } + } + return NS_OK; +} + +NS_IMETHODIMP nsMsgLocalMailFolder::GetPrettyName(nsAString& prettyName) { + return nsMsgDBFolder::GetPrettyName(prettyName); +} + +NS_IMETHODIMP nsMsgLocalMailFolder::SetPrettyName(const nsAString& aName) { + nsresult rv = nsMsgDBFolder::SetPrettyName(aName); + NS_ENSURE_SUCCESS(rv, rv); + nsCString folderName; + rv = GetStringProperty("folderName", folderName); + NS_ConvertUTF16toUTF8 utf8FolderName(mName); + return NS_FAILED(rv) || !folderName.Equals(utf8FolderName) + ? SetStringProperty("folderName", utf8FolderName) + : rv; +} + +NS_IMETHODIMP nsMsgLocalMailFolder::GetName(nsAString& aName) { + ReadDBFolderInfo(false); + return nsMsgDBFolder::GetName(aName); +} + +nsresult nsMsgLocalMailFolder::OpenDatabase() { + nsresult rv; + nsCOMPtr<nsIMsgDBService> msgDBService = + do_GetService("@mozilla.org/msgDatabase/msgDBService;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIFile> file; + rv = GetFilePath(getter_AddRefs(file)); + + rv = msgDBService->OpenFolderDB(this, true, getter_AddRefs(mDatabase)); + if (rv == NS_MSG_ERROR_FOLDER_SUMMARY_MISSING) { + // check if we're a real folder by looking at the parent folder. + nsCOMPtr<nsIMsgFolder> parent; + GetParent(getter_AddRefs(parent)); + if (parent) { + // This little dance creates an empty .msf file and then checks + // if the db is valid - this works if the folder is empty, which + // we don't have a direct way of checking. + nsCOMPtr<nsIMsgDatabase> db; + rv = msgDBService->CreateNewDB(this, getter_AddRefs(db)); + if (db) { + UpdateSummaryTotals(true); + db->Close(true); + mDatabase = nullptr; + db = nullptr; + rv = msgDBService->OpenFolderDB(this, false, getter_AddRefs(mDatabase)); + if (NS_FAILED(rv)) mDatabase = nullptr; + } + } + } else if (NS_FAILED(rv)) + mDatabase = nullptr; + + return rv; +} + +NS_IMETHODIMP +nsMsgLocalMailFolder::GetDBFolderInfoAndDB(nsIDBFolderInfo** folderInfo, + nsIMsgDatabase** db) { + if (!db || !folderInfo || !mPath || mIsServer) + return NS_ERROR_NULL_POINTER; // ducarroz: should we use + // NS_ERROR_INVALID_ARG? + + nsresult rv; + if (mDatabase) + rv = NS_OK; + else { + rv = OpenDatabase(); + + if (mAddListener && mDatabase) mDatabase->AddListener(this); + } + + NS_IF_ADDREF(*db = mDatabase); + if (NS_SUCCEEDED(rv) && *db) rv = (*db)->GetDBFolderInfo(folderInfo); + return rv; +} + +NS_IMETHODIMP nsMsgLocalMailFolder::ReadFromFolderCacheElem( + nsIMsgFolderCacheElement* element) { + NS_ENSURE_ARG_POINTER(element); + nsresult rv = nsMsgDBFolder::ReadFromFolderCacheElem(element); + NS_ENSURE_SUCCESS(rv, rv); + nsCString utf8Name; + rv = element->GetCachedString("folderName", utf8Name); + NS_ENSURE_SUCCESS(rv, rv); + CopyUTF8toUTF16(utf8Name, mName); + return rv; +} + +NS_IMETHODIMP nsMsgLocalMailFolder::WriteToFolderCacheElem( + nsIMsgFolderCacheElement* element) { + NS_ENSURE_ARG_POINTER(element); + nsMsgDBFolder::WriteToFolderCacheElem(element); + return element->SetCachedString("folderName", NS_ConvertUTF16toUTF8(mName)); +} + +NS_IMETHODIMP nsMsgLocalMailFolder::GetDeletable(bool* deletable) { + NS_ENSURE_ARG_POINTER(deletable); + + bool isServer; + GetIsServer(&isServer); + *deletable = !(isServer || (mFlags & nsMsgFolderFlags::SpecialUse)); + return NS_OK; +} + +NS_IMETHODIMP nsMsgLocalMailFolder::RefreshSizeOnDisk() { + int64_t oldFolderSize = mFolderSize; + // we set this to unknown to force it to get recalculated from disk + mFolderSize = kSizeUnknown; + if (NS_SUCCEEDED(GetSizeOnDisk(&mFolderSize))) + NotifyIntPropertyChanged(kFolderSize, oldFolderSize, mFolderSize); + return NS_OK; +} + +NS_IMETHODIMP nsMsgLocalMailFolder::GetSizeOnDisk(int64_t* aSize) { + NS_ENSURE_ARG_POINTER(aSize); + + bool isServer = false; + nsresult rv = GetIsServer(&isServer); + // If this is the rootFolder, return 0 as a safe value. + if (NS_FAILED(rv) || isServer) mFolderSize = 0; + + if (mFolderSize == kSizeUnknown) { + nsCOMPtr<nsIFile> file; + rv = GetFilePath(getter_AddRefs(file)); + NS_ENSURE_SUCCESS(rv, rv); + // Use a temporary variable so that we keep mFolderSize on kSizeUnknown + // if GetFileSize() fails. + int64_t folderSize; + rv = file->GetFileSize(&folderSize); + NS_ENSURE_SUCCESS(rv, rv); + + mFolderSize = folderSize; + } + *aSize = mFolderSize; + return NS_OK; +} + +nsresult nsMsgLocalMailFolder::GetTrashFolder(nsIMsgFolder** result) { + NS_ENSURE_ARG_POINTER(result); + nsresult rv; + nsCOMPtr<nsIMsgFolder> rootFolder; + rv = GetRootFolder(getter_AddRefs(rootFolder)); + if (NS_SUCCEEDED(rv)) { + rootFolder->GetFolderWithFlags(nsMsgFolderFlags::Trash, result); + if (!*result) rv = NS_ERROR_FAILURE; + } + return rv; +} + +NS_IMETHODIMP +nsMsgLocalMailFolder::DeleteMessages( + nsTArray<RefPtr<nsIMsgDBHdr>> const& msgHeaders, nsIMsgWindow* msgWindow, + bool deleteStorage, bool isMove, nsIMsgCopyServiceListener* listener, + bool allowUndo) { + nsresult rv; + + // shift delete case - (delete to trash is handled in EndMove) + // this is also the case when applying retention settings. + if (deleteStorage && !isMove) { + nsTArray<RefPtr<nsIMsgDBHdr>> hdrsToDelete; + for (auto msgHdr : msgHeaders) { + uint32_t attachmentDetached = 0; + msgHdr->GetUint32Property("attachmentDetached", &attachmentDetached); + if (!attachmentDetached) { + hdrsToDelete.AppendElement(msgHdr); + } + } + MarkMsgsOnPop3Server(hdrsToDelete, POP3_DELETE); + } + + bool isTrashFolder = mFlags & nsMsgFolderFlags::Trash; + + // notify on delete from trash and shift-delete + if (!isMove && (deleteStorage || isTrashFolder)) { + nsCOMPtr<nsIMsgFolderNotificationService> notifier( + do_GetService("@mozilla.org/messenger/msgnotificationservice;1")); + if (notifier) { + if (listener) { + listener->OnStartCopy(); + listener->OnStopCopy(NS_OK); + } + notifier->NotifyMsgsDeleted(msgHeaders); + } + } + + if (!deleteStorage && !isTrashFolder) { + // We're moving the messages to trash folder. Start by kicking off a copy. + nsCOMPtr<nsIMsgFolder> trashFolder; + rv = GetTrashFolder(getter_AddRefs(trashFolder)); + if (NS_SUCCEEDED(rv)) { + nsCOMPtr<nsIMsgCopyService> copyService = + do_GetService("@mozilla.org/messenger/messagecopyservice;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + // When the copy completes, DeleteMessages() will be called again to + // perform the actual delete. + return copyService->CopyMessages(this, msgHeaders, trashFolder, true, + listener, msgWindow, allowUndo); + } + } else { + // Performing an _actual_ delete. There are two ways we got here: + // 1) We're deleting messages without moving to trash. + // 2) We're in the second phase of a Move (to trash or elsewhere). The + // copy succeeded, and now we need to delete the source messages. + nsCOMPtr<nsIMsgDatabase> msgDB; + rv = GetDatabaseWOReparse(getter_AddRefs(msgDB)); + if (NS_SUCCEEDED(rv)) { + if (deleteStorage && isMove && GetDeleteFromServerOnMove()) + MarkMsgsOnPop3Server(msgHeaders, POP3_DELETE); + + nsCOMPtr<nsISupports> msgSupport; + rv = EnableNotifications(allMessageCountNotifications, false); + if (NS_SUCCEEDED(rv)) { + // First, delete the actual messages in the store. + nsCOMPtr<nsIMsgPluggableStore> msgStore; + rv = GetMsgStore(getter_AddRefs(msgStore)); + if (NS_SUCCEEDED(rv)) { + // Second, remove the message entries from the DB. + rv = msgStore->DeleteMessages(msgHeaders); + for (auto hdr : msgHeaders) { + rv = msgDB->DeleteHeader(hdr, nullptr, false, true); + } + } + } else if (rv == NS_MSG_FOLDER_BUSY) { + ThrowAlertMsg("deletingMsgsFailed", msgWindow); + } + + // Let everyone know the operation has finished. + NotifyFolderEvent(NS_SUCCEEDED(rv) ? kDeleteOrMoveMsgCompleted + : kDeleteOrMoveMsgFailed); + // NOTE: This reenabling also forces immediate recount + notification. + EnableNotifications(allMessageCountNotifications, true); + if (msgWindow) { + AutoCompact(msgWindow); + } + } + } + + if (msgWindow && !isMove && (deleteStorage || isTrashFolder)) { + // Clear undo and redo stack. + nsCOMPtr<nsITransactionManager> txnMgr; + msgWindow->GetTransactionManager(getter_AddRefs(txnMgr)); + if (txnMgr) { + txnMgr->Clear(); + } + } + return rv; +} + +NS_IMETHODIMP +nsMsgLocalMailFolder::AddMessageDispositionState( + nsIMsgDBHdr* aMessage, nsMsgDispositionState aDispositionFlag) { + nsMsgMessageFlagType msgFlag = 0; + switch (aDispositionFlag) { + case nsIMsgFolder::nsMsgDispositionState_Replied: + msgFlag = nsMsgMessageFlags::Replied; + break; + case nsIMsgFolder::nsMsgDispositionState_Forwarded: + msgFlag = nsMsgMessageFlags::Forwarded; + break; + case nsIMsgFolder::nsMsgDispositionState_Redirected: + msgFlag = nsMsgMessageFlags::Redirected; + break; + default: + return NS_ERROR_UNEXPECTED; + } + + nsresult rv = + nsMsgDBFolder::AddMessageDispositionState(aMessage, aDispositionFlag); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMsgPluggableStore> msgStore; + rv = GetMsgStore(getter_AddRefs(msgStore)); + NS_ENSURE_SUCCESS(rv, rv); + return msgStore->ChangeFlags({aMessage}, msgFlag, true); +} + +NS_IMETHODIMP +nsMsgLocalMailFolder::MarkMessagesRead( + const nsTArray<RefPtr<nsIMsgDBHdr>>& aMessages, bool aMarkRead) { + nsresult rv = nsMsgDBFolder::MarkMessagesRead(aMessages, aMarkRead); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr<nsIMsgPluggableStore> msgStore; + rv = GetMsgStore(getter_AddRefs(msgStore)); + NS_ENSURE_SUCCESS(rv, rv); + return msgStore->ChangeFlags(aMessages, nsMsgMessageFlags::Read, aMarkRead); +} + +NS_IMETHODIMP +nsMsgLocalMailFolder::MarkMessagesFlagged( + const nsTArray<RefPtr<nsIMsgDBHdr>>& aMessages, bool aMarkFlagged) { + nsresult rv = nsMsgDBFolder::MarkMessagesFlagged(aMessages, aMarkFlagged); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr<nsIMsgPluggableStore> msgStore; + rv = GetMsgStore(getter_AddRefs(msgStore)); + NS_ENSURE_SUCCESS(rv, rv); + return msgStore->ChangeFlags(aMessages, nsMsgMessageFlags::Marked, + aMarkFlagged); +} + +NS_IMETHODIMP +nsMsgLocalMailFolder::MarkAllMessagesRead(nsIMsgWindow* aMsgWindow) { + nsresult rv = GetDatabase(); + NS_ENSURE_SUCCESS(rv, rv); + + nsTArray<nsMsgKey> thoseMarked; + EnableNotifications(allMessageCountNotifications, false); + rv = mDatabase->MarkAllRead(thoseMarked); + EnableNotifications(allMessageCountNotifications, true); + NS_ENSURE_SUCCESS(rv, rv); + + if (thoseMarked.IsEmpty()) { + return NS_OK; + } + + nsTArray<RefPtr<nsIMsgDBHdr>> messages; + rv = MsgGetHeadersFromKeys(mDatabase, thoseMarked, messages); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMsgPluggableStore> msgStore; + rv = GetMsgStore(getter_AddRefs(msgStore)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = msgStore->ChangeFlags(messages, nsMsgMessageFlags::Read, true); + NS_ENSURE_SUCCESS(rv, rv); + + mDatabase->Commit(nsMsgDBCommitType::kLargeCommit); + + // Setup a undo-state + if (aMsgWindow) + rv = AddMarkAllReadUndoAction(aMsgWindow, thoseMarked.Elements(), + thoseMarked.Length()); + + return rv; +} + +NS_IMETHODIMP nsMsgLocalMailFolder::MarkThreadRead(nsIMsgThread* thread) { + nsresult rv = GetDatabase(); + NS_ENSURE_SUCCESS(rv, rv); + + nsTArray<nsMsgKey> thoseMarked; + rv = mDatabase->MarkThreadRead(thread, nullptr, thoseMarked); + NS_ENSURE_SUCCESS(rv, rv); + if (thoseMarked.IsEmpty()) { + return NS_OK; + } + + nsTArray<RefPtr<nsIMsgDBHdr>> messages; + rv = MsgGetHeadersFromKeys(mDatabase, thoseMarked, messages); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMsgPluggableStore> msgStore; + rv = GetMsgStore(getter_AddRefs(msgStore)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = msgStore->ChangeFlags(messages, nsMsgMessageFlags::Read, true); + NS_ENSURE_SUCCESS(rv, rv); + + mDatabase->Commit(nsMsgDBCommitType::kLargeCommit); + + return rv; +} + +nsresult nsMsgLocalMailFolder::InitCopyState( + nsISupports* aSupport, nsTArray<RefPtr<nsIMsgDBHdr>> const& messages, + bool isMove, nsIMsgCopyServiceListener* listener, nsIMsgWindow* msgWindow, + bool isFolder, bool allowUndo) { + nsCOMPtr<nsIFile> path; + + NS_ASSERTION(!mCopyState, "already copying a msg into this folder"); + if (mCopyState) return NS_ERROR_FAILURE; // already has a copy in progress + + // get mDatabase set, so we can use it to add new hdrs to this db. + // calling GetDatabase will set mDatabase - we use the comptr + // here to avoid doubling the refcnt on mDatabase. We don't care if this + // fails - we just want to give it a chance. It will definitely fail in + // nsLocalMailFolder::EndCopy because we will have written data to the folder + // and changed its size. + nsCOMPtr<nsIMsgDatabase> msgDB; + GetDatabaseWOReparse(getter_AddRefs(msgDB)); + bool isLocked; + + GetLocked(&isLocked); + if (isLocked) return NS_MSG_FOLDER_BUSY; + + AcquireSemaphore(static_cast<nsIMsgLocalMailFolder*>(this)); + + mCopyState = new nsLocalMailCopyState(); + NS_ENSURE_TRUE(mCopyState, NS_ERROR_OUT_OF_MEMORY); + + mCopyState->m_dataBuffer = (char*)PR_CALLOC(COPY_BUFFER_SIZE + 1); + NS_ENSURE_TRUE(mCopyState->m_dataBuffer, NS_ERROR_OUT_OF_MEMORY); + + mCopyState->m_dataBufferSize = COPY_BUFFER_SIZE; + mCopyState->m_destDB = msgDB; + + mCopyState->m_srcSupport = aSupport; + mCopyState->m_messages = messages.Clone(); + mCopyState->m_curCopyIndex = 0; + mCopyState->m_isMove = isMove; + mCopyState->m_isFolder = isFolder; + mCopyState->m_allowUndo = allowUndo; + mCopyState->m_msgWindow = msgWindow; + mCopyState->m_totalMsgCount = messages.Length(); + if (listener) mCopyState->m_listener = listener; + mCopyState->m_copyingMultipleMessages = false; + mCopyState->m_wholeMsgInStream = false; + + return NS_OK; +} + +NS_IMETHODIMP nsMsgLocalMailFolder::OnAnnouncerGoingAway( + nsIDBChangeAnnouncer* instigator) { + if (mCopyState) mCopyState->m_destDB = nullptr; + return nsMsgDBFolder::OnAnnouncerGoingAway(instigator); +} + +NS_IMETHODIMP +nsMsgLocalMailFolder::OnCopyCompleted(nsISupports* srcSupport, + bool moveCopySucceeded) { + if (mCopyState && mCopyState->m_notifyFolderLoaded) + NotifyFolderEvent(kFolderLoaded); + + (void)RefreshSizeOnDisk(); + // we are the destination folder for a move/copy + bool haveSemaphore; + nsresult rv = + TestSemaphore(static_cast<nsIMsgLocalMailFolder*>(this), &haveSemaphore); + if (NS_SUCCEEDED(rv) && haveSemaphore) + ReleaseSemaphore(static_cast<nsIMsgLocalMailFolder*>(this)); + + if (mCopyState && !mCopyState->m_newMsgKeywords.IsEmpty() && + mCopyState->m_newHdr) { + AddKeywordsToMessages({&*mCopyState->m_newHdr}, + mCopyState->m_newMsgKeywords); + } + if (moveCopySucceeded && mDatabase) { + mDatabase->SetSummaryValid(true); + (void)CloseDBIfFolderNotOpen(false); + } + + delete mCopyState; + mCopyState = nullptr; + nsCOMPtr<nsIMsgCopyService> copyService = + do_GetService("@mozilla.org/messenger/messagecopyservice;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + return copyService->NotifyCompletion( + srcSupport, this, moveCopySucceeded ? NS_OK : NS_ERROR_FAILURE); +} + +bool nsMsgLocalMailFolder::CheckIfSpaceForCopy(nsIMsgWindow* msgWindow, + nsIMsgFolder* srcFolder, + nsISupports* srcSupports, + bool isMove, + int64_t totalMsgSize) { + bool spaceNotAvailable = true; + nsresult rv = + WarnIfLocalFileTooBig(msgWindow, totalMsgSize, &spaceNotAvailable); + if (NS_FAILED(rv) || spaceNotAvailable) { + if (isMove && srcFolder) + srcFolder->NotifyFolderEvent(kDeleteOrMoveMsgFailed); + OnCopyCompleted(srcSupports, false); + return false; + } + return true; +} + +NS_IMETHODIMP +nsMsgLocalMailFolder::CopyMessages(nsIMsgFolder* srcFolder, + nsTArray<RefPtr<nsIMsgDBHdr>> const& srcHdrs, + bool isMove, nsIMsgWindow* msgWindow, + nsIMsgCopyServiceListener* listener, + bool isFolder, bool allowUndo) { + nsCOMPtr<nsISupports> srcSupport = do_QueryInterface(srcFolder); + bool isServer; + nsresult rv = GetIsServer(&isServer); + if (NS_SUCCEEDED(rv) && isServer) { + NS_ERROR("Destination is the root folder. Cannot move/copy here"); + if (isMove) srcFolder->NotifyFolderEvent(kDeleteOrMoveMsgFailed); + return OnCopyCompleted(srcSupport, false); + } + + UpdateTimestamps(allowUndo); + nsCString protocolType; + rv = srcFolder->GetURI(protocolType); + protocolType.SetLength(protocolType.FindChar(':')); + + // If we're offline and the source folder is imap or news, to do the + // copy the message bodies MUST reside in offline storage. + bool needOfflineBodies = + (WeAreOffline() && (protocolType.LowerCaseEqualsLiteral("imap") || + protocolType.LowerCaseEqualsLiteral("news"))); + int64_t totalMsgSize = 0; + bool allMsgsHaveOfflineStore = true; + for (auto message : srcHdrs) { + nsMsgKey key; + uint32_t msgSize; + message->GetMessageSize(&msgSize); + + /* 200 is a per-message overhead to account for any extra data added + to the message. + */ + totalMsgSize += msgSize + 200; + + // Check if each source folder message has offline storage regardless + // of whether we're online or offline. + message->GetMessageKey(&key); + bool hasMsgOffline = false; + srcFolder->HasMsgOffline(key, &hasMsgOffline); + allMsgsHaveOfflineStore = allMsgsHaveOfflineStore && hasMsgOffline; + + // If we're offline and not all messages are in offline storage, the copy + // or move can't occur and a notification for the user to download the + // messages is posted. + if (needOfflineBodies && !hasMsgOffline) { + if (isMove) srcFolder->NotifyFolderEvent(kDeleteOrMoveMsgFailed); + ThrowAlertMsg("cantMoveMsgWOBodyOffline", msgWindow); + return OnCopyCompleted(srcSupport, false); + } + } + + if (!CheckIfSpaceForCopy(msgWindow, srcFolder, srcSupport, isMove, + totalMsgSize)) + return NS_OK; + + NS_ENSURE_SUCCESS(rv, rv); + bool storeDidCopy = false; + nsCOMPtr<nsIMsgPluggableStore> msgStore; + rv = GetMsgStore(getter_AddRefs(msgStore)); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr<nsITransaction> undoTxn; + nsTArray<RefPtr<nsIMsgDBHdr>> dstHdrs; + rv = msgStore->CopyMessages(isMove, srcHdrs, this, dstHdrs, + getter_AddRefs(undoTxn), &storeDidCopy); + if (storeDidCopy) { + NS_ASSERTION(undoTxn, "if store does copy, it needs to add undo action"); + if (msgWindow && undoTxn) { + nsCOMPtr<nsITransactionManager> txnMgr; + msgWindow->GetTransactionManager(getter_AddRefs(txnMgr)); + if (txnMgr) txnMgr->DoTransaction(undoTxn); + } + if (isMove) { + srcFolder->NotifyFolderEvent(NS_SUCCEEDED(rv) ? kDeleteOrMoveMsgCompleted + : kDeleteOrMoveMsgFailed); + } + + if (NS_SUCCEEDED(rv)) { + // If the store did the copy, like maildir, we need to mark messages on + // the server. Otherwise that's done in EndMove(). + nsCOMPtr<nsIMsgLocalMailFolder> localDstFolder; + QueryInterface(NS_GET_IID(nsIMsgLocalMailFolder), + getter_AddRefs(localDstFolder)); + if (localDstFolder) { + // If we are the trash and a local msg is being moved to us, mark the + // source for delete from server, if so configured. + if (mFlags & nsMsgFolderFlags::Trash) { + // If we're deleting on all moves, we'll mark this message for + // deletion when we call DeleteMessages on the source folder. So don't + // mark it for deletion here, in that case. + if (!GetDeleteFromServerOnMove()) { + localDstFolder->MarkMsgsOnPop3Server(dstHdrs, POP3_DELETE); + } + } + } + } + + OnCopyCompleted(srcSupport, NS_SUCCEEDED(rv)); + return rv; + } + // If the store doesn't do the copy, we'll stream the source messages into + // the target folder, using getMsgInputStream and getNewMsgOutputStream. + + // don't update the counts in the dest folder until it is all over + EnableNotifications(allMessageCountNotifications, false); + + // sort the message array by key + nsTArray<nsMsgKey> keyArray(srcHdrs.Length()); + nsTArray<RefPtr<nsIMsgDBHdr>> sortedMsgs(srcHdrs.Length()); + for (nsIMsgDBHdr* aMessage : srcHdrs) { + nsMsgKey key; + aMessage->GetMessageKey(&key); + keyArray.AppendElement(key); + } + keyArray.Sort(); + rv = MessagesInKeyOrder(keyArray, srcFolder, sortedMsgs); + NS_ENSURE_SUCCESS(rv, rv); + + rv = InitCopyState(srcSupport, sortedMsgs, isMove, listener, msgWindow, + isFolder, allowUndo); + + if (NS_FAILED(rv)) { + ThrowAlertMsg("operationFailedFolderBusy", msgWindow); + (void)OnCopyCompleted(srcSupport, false); + return rv; + } + + if (!protocolType.LowerCaseEqualsLiteral("mailbox")) { + // Copying from a non-mbox source, so we will be synthesising + // a "From " line and "X-Mozilla-*" headers before copying the message + // proper. + mCopyState->m_dummyEnvelopeNeeded = true; + nsParseMailMessageState* parseMsgState = new nsParseMailMessageState(); + if (parseMsgState) { + nsCOMPtr<nsIMsgDatabase> msgDb; + mCopyState->m_parseMsgState = parseMsgState; + GetDatabaseWOReparse(getter_AddRefs(msgDb)); + if (msgDb) parseMsgState->SetMailDB(msgDb); + } + } + + // undo stuff + if (allowUndo) // no undo for folder move/copy or or move/copy from search + // window + { + RefPtr<nsLocalMoveCopyMsgTxn> msgTxn = new nsLocalMoveCopyMsgTxn; + if (msgTxn && NS_SUCCEEDED(msgTxn->Init(srcFolder, this, isMove))) { + msgTxn->SetMsgWindow(msgWindow); + if (isMove) { + if (mFlags & nsMsgFolderFlags::Trash) + msgTxn->SetTransactionType(nsIMessenger::eDeleteMsg); + else + msgTxn->SetTransactionType(nsIMessenger::eMoveMsg); + } else + msgTxn->SetTransactionType(nsIMessenger::eCopyMsg); + msgTxn.swap(mCopyState->m_undoMsgTxn); + } + } + + if (srcHdrs.Length() > 1 && + ((protocolType.LowerCaseEqualsLiteral("imap") && + !allMsgsHaveOfflineStore) || + protocolType.LowerCaseEqualsLiteral("mailbox"))) { + // For an imap source folder with more than one message to be copied that + // are not all in offline storage, this fetches all the messages from the + // imap server to do the copy. When source folder is "mailbox", this is not + // a concern since source messages are in local storage. + mCopyState->m_copyingMultipleMessages = true; + rv = CopyMessagesTo(keyArray, msgWindow, isMove); + if (NS_FAILED(rv)) { + NS_ERROR("copy message failed"); + (void)OnCopyCompleted(srcSupport, false); + } + } else { + // This obtains the source messages from local/offline storage to do the + // copy. Note: CopyMessageTo() actually handles one or more messages. + nsIMsgDBHdr* msgSupport = mCopyState->m_messages[0]; + if (msgSupport) { + rv = CopyMessageTo(msgSupport, msgWindow, isMove); + if (NS_FAILED(rv)) { + NS_ASSERTION(false, "copy message failed"); + (void)OnCopyCompleted(srcSupport, false); + } + } + } + // if this failed immediately, need to turn back on notifications and inform + // FE. + if (NS_FAILED(rv)) { + if (isMove) srcFolder->NotifyFolderEvent(kDeleteOrMoveMsgFailed); + EnableNotifications(allMessageCountNotifications, true); + } + return rv; +} + +// for srcFolder that are on different server than the dstFolder. +// "this" is the parent of the new dest folder. +nsresult nsMsgLocalMailFolder::CopyFolderAcrossServer( + nsIMsgFolder* srcFolder, nsIMsgWindow* msgWindow, + nsIMsgCopyServiceListener* listener, bool moveMsgs) { + mInitialized = true; + + nsString folderName; + srcFolder->GetName(folderName); + + nsCOMPtr<nsIMsgFolder> newMsgFolder; + nsresult rv = CreateSubfolderInternal(folderName, msgWindow, + getter_AddRefs(newMsgFolder)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMsgEnumerator> messages; + rv = srcFolder->GetMessages(getter_AddRefs(messages)); + NS_ENSURE_SUCCESS(rv, rv); + + nsTArray<RefPtr<nsIMsgDBHdr>> msgArray; + bool hasMoreElements = false; + + if (messages) rv = messages->HasMoreElements(&hasMoreElements); + + while (NS_SUCCEEDED(rv) && hasMoreElements) { + nsCOMPtr<nsIMsgDBHdr> msg; + rv = messages->GetNext(getter_AddRefs(msg)); + NS_ENSURE_SUCCESS(rv, rv); + + msgArray.AppendElement(msg); + rv = messages->HasMoreElements(&hasMoreElements); + NS_ENSURE_SUCCESS(rv, rv); + } + + if (msgArray.Length() > 0) // if only srcFolder has messages.. + // Allow move of copied messages but keep source folder in place. + newMsgFolder->CopyMessages(srcFolder, msgArray, moveMsgs, msgWindow, + listener, true /* is folder*/, + false /* allowUndo */); + else { + nsCOMPtr<nsIMsgLocalMailFolder> localFolder = + do_QueryInterface(newMsgFolder); + if (localFolder) { + // normally these would get called from ::EndCopy when the last message + // was finished copying. But since there are no messages, we have to call + // them explicitly. + nsCOMPtr<nsISupports> srcSupports = do_QueryInterface(srcFolder); + localFolder->CopyAllSubFolders(srcFolder, msgWindow, listener, moveMsgs); + return localFolder->OnCopyCompleted(srcSupports, true); + } + } + return NS_OK; // otherwise the front-end will say Exception::CopyFolder +} + +nsresult // copy the sub folders +nsMsgLocalMailFolder::CopyAllSubFolders(nsIMsgFolder* srcFolder, + nsIMsgWindow* msgWindow, + nsIMsgCopyServiceListener* listener, + bool isMove) { + nsTArray<RefPtr<nsIMsgFolder>> subFolders; + nsresult rv = srcFolder->GetSubFolders(subFolders); + NS_ENSURE_SUCCESS(rv, rv); + + for (nsIMsgFolder* folder : subFolders) { + CopyFolderAcrossServer(folder, msgWindow, listener, isMove); + } + return NS_OK; +} + +// "this" is the destination (parent) folder that srcFolder is copied to. +NS_IMETHODIMP +nsMsgLocalMailFolder::CopyFolder(nsIMsgFolder* srcFolder, bool isMoveFolder, + nsIMsgWindow* msgWindow, + nsIMsgCopyServiceListener* listener) { + NS_ENSURE_ARG_POINTER(srcFolder); + nsresult rv; + bool sameServer; + rv = IsOnSameServer(this, srcFolder, &sameServer); + NS_ENSURE_SUCCESS(rv, rv); + if (sameServer && isMoveFolder) { + // Do a pure folder move within the same Local Folder account/server. where + // "pure" means the folder AND messages are copied to the Local Folders + // destination and then both are removed from source account. + rv = CopyFolderLocal(srcFolder, isMoveFolder, msgWindow, listener); + } else { + // !sameServer OR it's a copy. Unit tests expect a successful folder + // copy within Local Folders account/server even though the UI forbids copy + // and only allows moves inside the same account. CopyFolderAcrossServer(), + // called below, handles the folder copy within Local Folders (needed by + // unit tests) and it handles the folder move or copy from another account + // or server into Local Folders. The move from another account is "impure" + // since just the messages are moved but the source folder remains in place. + rv = CopyFolderAcrossServer(srcFolder, msgWindow, listener, isMoveFolder); + } + return rv; +} + +NS_IMETHODIMP +nsMsgLocalMailFolder::CopyFolderLocal(nsIMsgFolder* srcFolder, + bool isMoveFolder, + nsIMsgWindow* msgWindow, + nsIMsgCopyServiceListener* aListener) { + mInitialized = true; + bool isChildOfTrash; + nsresult rv = IsChildOfTrash(&isChildOfTrash); + if (NS_SUCCEEDED(rv) && isChildOfTrash) { + // do it just for the parent folder (isMoveFolder is true for parent only) + // if we are deleting/moving a folder tree don't confirm for rss folders. + if (isMoveFolder) { + // if there's a msgWindow, confirm the deletion + if (msgWindow) { + bool okToDelete = false; + ConfirmFolderDeletion(msgWindow, srcFolder, &okToDelete); + if (!okToDelete) return NS_MSG_ERROR_COPY_FOLDER_ABORTED; + } + // if we are moving a favorite folder to trash, we should clear the + // favorites flag so it gets removed from the view. + srcFolder->ClearFlag(nsMsgFolderFlags::Favorite); + } + + bool match = false; + srcFolder->MatchOrChangeFilterDestination(nullptr, false, &match); + if (match && msgWindow) { + bool confirmed = false; + srcFolder->ConfirmFolderDeletionForFilter(msgWindow, &confirmed); + if (!confirmed) return NS_MSG_ERROR_COPY_FOLDER_ABORTED; + } + } + + nsAutoString newFolderName; + nsAutoString folderName; + rv = srcFolder->GetName(folderName); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (!isMoveFolder) { + rv = CheckIfFolderExists(folderName, this, msgWindow); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } else { + // If folder name already exists in destination, generate a new unique name. + bool containsChild = true; + uint32_t i; + for (i = 1; containsChild; i++) { + newFolderName.Assign(folderName); + if (i > 1) { + // This could be localizable but Toolkit is fine without it, see + // mozilla/toolkit/content/contentAreaUtils.js::uniqueFile() + newFolderName.Append('('); + newFolderName.AppendInt(i); + newFolderName.Append(')'); + } + rv = ContainsChildNamed(newFolderName, &containsChild); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + + // 'i' is one more than the number of iterations done + // and the number tacked onto the name of the folder. + if (i > 2 && !isChildOfTrash) { + // Folder name already exists, ask if rename is OK. + // If moving to Trash, don't ask and do it. + if (!ConfirmAutoFolderRename(msgWindow, folderName, newFolderName)) + return NS_MSG_ERROR_COPY_FOLDER_ABORTED; + } + } + + nsCOMPtr<nsIMsgPluggableStore> msgStore; + rv = GetMsgStore(getter_AddRefs(msgStore)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return msgStore->CopyFolder(srcFolder, this, isMoveFolder, msgWindow, + aListener, newFolderName); +} + +NS_IMETHODIMP +nsMsgLocalMailFolder::CopyFileMessage(nsIFile* aFile, nsIMsgDBHdr* msgToReplace, + bool isDraftOrTemplate, + uint32_t newMsgFlags, + const nsACString& aNewMsgKeywords, + nsIMsgWindow* msgWindow, + nsIMsgCopyServiceListener* listener) { + NS_ENSURE_ARG_POINTER(aFile); + nsresult rv = NS_ERROR_NULL_POINTER; + nsParseMailMessageState* parseMsgState = nullptr; + int64_t fileSize = 0; + + nsCOMPtr<nsISupports> fileSupport(aFile); + + aFile->GetFileSize(&fileSize); + if (!CheckIfSpaceForCopy(msgWindow, nullptr, fileSupport, false, fileSize)) + return NS_OK; + + nsTArray<RefPtr<nsIMsgDBHdr>> messages; + if (msgToReplace) messages.AppendElement(msgToReplace); + + rv = InitCopyState(fileSupport, messages, msgToReplace ? true : false, + listener, msgWindow, false, false); + if (NS_SUCCEEDED(rv)) { + if (mCopyState) { + mCopyState->m_newMsgKeywords = aNewMsgKeywords; + mCopyState->m_flags = newMsgFlags; + } + + parseMsgState = new nsParseMailMessageState(); + NS_ENSURE_TRUE(parseMsgState, NS_ERROR_OUT_OF_MEMORY); + nsCOMPtr<nsIMsgDatabase> msgDb; + mCopyState->m_parseMsgState = parseMsgState; + GetDatabaseWOReparse(getter_AddRefs(msgDb)); + if (msgDb) parseMsgState->SetMailDB(msgDb); + + nsCOMPtr<nsIInputStream> inputStream; + rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream), aFile); + + // All or none for adding a message file to the store + if (NS_SUCCEEDED(rv) && fileSize > PR_INT32_MAX) + rv = NS_ERROR_ILLEGAL_VALUE; // may need error code for max msg size + + if (NS_SUCCEEDED(rv) && inputStream) { + char buffer[5]; + uint32_t readCount; + rv = inputStream->Read(buffer, 5, &readCount); + if (NS_SUCCEEDED(rv)) { + if (strncmp(buffer, "From ", 5)) + mCopyState->m_dummyEnvelopeNeeded = true; + nsCOMPtr<nsISeekableStream> seekableStream = + do_QueryInterface(inputStream, &rv); + if (NS_SUCCEEDED(rv)) + seekableStream->Seek(nsISeekableStream::NS_SEEK_SET, 0); + } + } + + mCopyState->m_wholeMsgInStream = true; + if (NS_SUCCEEDED(rv)) rv = BeginCopy(); + + if (NS_SUCCEEDED(rv)) rv = CopyData(inputStream, (int32_t)fileSize); + + if (NS_SUCCEEDED(rv)) rv = EndCopy(true); + + // mDatabase should have been initialized above - if we got msgDb + // If we were going to delete, here is where we would do it. But because + // existing code already supports doing those deletes, we are just going + // to end the copy. + if (NS_SUCCEEDED(rv) && msgToReplace && mDatabase) + rv = OnCopyCompleted(fileSupport, true); + + if (inputStream) inputStream->Close(); + } + + if (NS_FAILED(rv)) (void)OnCopyCompleted(fileSupport, false); + + return rv; +} + +NS_IMETHODIMP nsMsgLocalMailFolder::GetNewMessages(nsIMsgWindow* aWindow, + nsIUrlListener* aListener) { + nsCOMPtr<nsIMsgIncomingServer> server; + nsresult rv = GetServer(getter_AddRefs(server)); + NS_ENSURE_SUCCESS(rv, NS_MSG_INVALID_OR_MISSING_SERVER); + + nsCOMPtr<nsILocalMailIncomingServer> localMailServer = + do_QueryInterface(server, &rv); + NS_ENSURE_SUCCESS(rv, NS_MSG_INVALID_OR_MISSING_SERVER); + + // XXX todo, move all this into nsILocalMailIncomingServer's GetNewMail + // so that we don't have to have RSS foo here. + nsCOMPtr<nsIRssIncomingServer> rssServer = do_QueryInterface(server, &rv); + mozilla::Unused << rssServer; + if (NS_SUCCEEDED(rv)) { + nsCOMPtr<nsIURI> resultURI; + return localMailServer->GetNewMail(aWindow, aListener, this, + getter_AddRefs(resultURI)); + } + + nsCOMPtr<nsIMsgFolder> inbox; + nsCOMPtr<nsIMsgFolder> rootFolder; + rv = server->GetRootMsgFolder(getter_AddRefs(rootFolder)); + if (NS_SUCCEEDED(rv) && rootFolder) { + rootFolder->GetFolderWithFlags(nsMsgFolderFlags::Inbox, + getter_AddRefs(inbox)); + } + nsCOMPtr<nsIMsgLocalMailFolder> localInbox = do_QueryInterface(inbox, &rv); + if (NS_SUCCEEDED(rv)) { + bool valid = false; + nsCOMPtr<nsIMsgDatabase> db; + // this will kick off a reparse if the db is out of date. + rv = localInbox->GetDatabaseWithReparse(nullptr, aWindow, + getter_AddRefs(db)); + if (NS_SUCCEEDED(rv)) { + nsCOMPtr<nsIURI> resultURI; + db->GetSummaryValid(&valid); + rv = valid ? localMailServer->GetNewMail(aWindow, aListener, inbox, + getter_AddRefs(resultURI)) + : localInbox->SetCheckForNewMessagesAfterParsing(true); + } + } + return rv; +} + +nsresult nsMsgLocalMailFolder::WriteStartOfNewMessage() { + // If moving, delete the message in source folder that was just copied. + // It will have index one less than the current index. + // But only do this if source folder is imap. + // Could be optimized (DeleteMessages() operate on non-array)? + nsresult rv; + uint32_t idx = mCopyState->m_curCopyIndex; + if (mCopyState->m_isMove && idx) { + nsCOMPtr<nsIMsgFolder> srcFolder = + do_QueryInterface(mCopyState->m_srcSupport, &rv); + if (NS_SUCCEEDED(rv) && srcFolder) { + // Delete source messages as we go only if they come from + // an imap folder. + nsCString protocolType; + if (NS_SUCCEEDED(srcFolder->GetURI(protocolType))) { + if (StringHead(protocolType, 5).LowerCaseEqualsLiteral("imap:")) { + // Create "array" of one message header to delete + idx--; + if (idx < mCopyState->m_messages.Length()) { + // Above check avoids a possible MOZ_CRASH after error recovery. + RefPtr<nsIMsgDBHdr> msg = mCopyState->m_messages[idx]; + srcFolder->DeleteMessages({msg}, mCopyState->m_msgWindow, true, + true, nullptr, mCopyState->m_allowUndo); + } + } + } + } + } + + // CopyFileMessage() and CopyMessages() from servers other than pop3 + if (mCopyState->m_parseMsgState) { + // Make sure the parser knows where the "From " separator is. + // A hack for Bug 1734847. + // If we were using nsMsgMailboxParser, that would handle it automatically. + // But we're using the base class (nsParseMailMessageState) which doesn't. + mCopyState->m_parseMsgState->m_envelope_pos = + mCopyState->m_parseMsgState->m_position; + + if (mCopyState->m_parseMsgState->m_newMsgHdr) { + mCopyState->m_parseMsgState->m_newMsgHdr->GetMessageKey( + &mCopyState->m_curDstKey); + } + mCopyState->m_parseMsgState->SetState( + nsIMsgParseMailMsgState::ParseHeadersState); + } + if (mCopyState->m_dummyEnvelopeNeeded) { + nsCString result; + nsAutoCString nowStr; + MsgGenerateNowStr(nowStr); + result.AppendLiteral("From - "); + result.Append(nowStr); + result.Append(MSG_LINEBREAK); + + uint32_t bytesWritten; + mCopyState->m_fileStream->Write(result.get(), result.Length(), + &bytesWritten); + if (mCopyState->m_parseMsgState) { + mCopyState->m_parseMsgState->ParseAFolderLine(result.get(), + result.Length()); + // Make sure the parser knows where the header block begins. + // Another hack for Bug 1734847. + mCopyState->m_parseMsgState->m_headerstartpos = + mCopyState->m_parseMsgState->m_position; + } + + // *** jt - hard code status line for now; come back later + char statusStrBuf[50]; + if (mCopyState->m_curCopyIndex < mCopyState->m_messages.Length()) { + uint32_t dbFlags = 0; + mCopyState->m_messages[mCopyState->m_curCopyIndex]->GetFlags(&dbFlags); + + // write out x-mozilla-status, but make sure we don't write out + // nsMsgMessageFlags::Offline + PR_snprintf( + statusStrBuf, sizeof(statusStrBuf), + X_MOZILLA_STATUS_FORMAT MSG_LINEBREAK, + dbFlags & + ~(nsMsgMessageFlags::RuntimeOnly | nsMsgMessageFlags::Offline) & + 0x0000FFFF); + } else + strcpy(statusStrBuf, "X-Mozilla-Status: 0001" MSG_LINEBREAK); + mCopyState->m_fileStream->Write(statusStrBuf, strlen(statusStrBuf), + &bytesWritten); + if (mCopyState->m_parseMsgState) + mCopyState->m_parseMsgState->ParseAFolderLine(statusStrBuf, + strlen(statusStrBuf)); + result = "X-Mozilla-Status2: 00000000" MSG_LINEBREAK; + mCopyState->m_fileStream->Write(result.get(), result.Length(), + &bytesWritten); + if (mCopyState->m_parseMsgState) + mCopyState->m_parseMsgState->ParseAFolderLine(result.get(), + result.Length()); + result = X_MOZILLA_KEYWORDS; + mCopyState->m_fileStream->Write(result.get(), result.Length(), + &bytesWritten); + if (mCopyState->m_parseMsgState) + mCopyState->m_parseMsgState->ParseAFolderLine(result.get(), + result.Length()); + mCopyState->m_fromLineSeen = true; + } else + mCopyState->m_fromLineSeen = false; + + mCopyState->m_curCopyIndex++; + return NS_OK; +} + +nsresult nsMsgLocalMailFolder::InitCopyMsgHdrAndFileStream() { + nsresult rv = GetMsgStore(getter_AddRefs(mCopyState->m_msgStore)); + NS_ENSURE_SUCCESS(rv, rv); + rv = mCopyState->m_msgStore->GetNewMsgOutputStream( + this, getter_AddRefs(mCopyState->m_newHdr), + getter_AddRefs(mCopyState->m_fileStream)); + NS_ENSURE_SUCCESS(rv, rv); + if (mCopyState->m_parseMsgState) + mCopyState->m_parseMsgState->SetNewMsgHdr(mCopyState->m_newHdr); + return rv; +} + +// nsICopyMessageListener +NS_IMETHODIMP nsMsgLocalMailFolder::BeginCopy() { + if (!mCopyState) return NS_ERROR_NULL_POINTER; + + if (!mCopyState->m_copyingMultipleMessages) { + nsresult rv = InitCopyMsgHdrAndFileStream(); + NS_ENSURE_SUCCESS(rv, rv); + } + // The output stream may or may not be set already, depending upon all kinds + // of inscrutable conditions. This needs cleaning up (see Bug 1731177). + if (!mCopyState->m_fileStream) { + return NS_OK; + } + + int32_t messageIndex = (mCopyState->m_copyingMultipleMessages) + ? mCopyState->m_curCopyIndex - 1 + : mCopyState->m_curCopyIndex; + NS_ASSERTION(!mCopyState->m_copyingMultipleMessages || messageIndex >= 0, + "messageIndex invalid"); + // by the time we get here, m_curCopyIndex is 1 relative because + // WriteStartOfNewMessage increments it + if (messageIndex < (int32_t)mCopyState->m_messages.Length()) { + mCopyState->m_message = mCopyState->m_messages[messageIndex]; + } else { + mCopyState->m_message = nullptr; + } + // The flags of the source message can get changed when it is deleted, so + // save them here. + if (mCopyState->m_message) + mCopyState->m_message->GetFlags(&(mCopyState->m_flags)); + DisplayMoveCopyStatusMsg(); + if (mCopyState->m_listener) + mCopyState->m_listener->OnProgress(mCopyState->m_curCopyIndex, + mCopyState->m_totalMsgCount); + // if we're copying more than one message, StartMessage will handle this. + return !mCopyState->m_copyingMultipleMessages ? WriteStartOfNewMessage() + : NS_OK; +} + +NS_IMETHODIMP nsMsgLocalMailFolder::CopyData(nsIInputStream* aIStream, + int32_t aLength) { + // check to make sure we have control of the write. + bool haveSemaphore; + nsresult rv = NS_OK; + + rv = TestSemaphore(static_cast<nsIMsgLocalMailFolder*>(this), &haveSemaphore); + if (NS_FAILED(rv)) return rv; + if (!haveSemaphore) return NS_MSG_FOLDER_BUSY; + + if (!mCopyState) return NS_ERROR_OUT_OF_MEMORY; + + uint32_t readCount; + // allocate one extra byte for '\0' at the end and another extra byte at the + // front to insert a '>' if we have a "From" line + // allocate 2 more for crlf that may be needed for those without crlf at end + // of file + if (aLength + mCopyState->m_leftOver + 4 > mCopyState->m_dataBufferSize) { + char* newBuffer = (char*)PR_REALLOC(mCopyState->m_dataBuffer, + aLength + mCopyState->m_leftOver + 4); + if (!newBuffer) return NS_ERROR_OUT_OF_MEMORY; + + mCopyState->m_dataBuffer = newBuffer; + mCopyState->m_dataBufferSize = aLength + mCopyState->m_leftOver + 3; + } + + rv = aIStream->Read(mCopyState->m_dataBuffer + mCopyState->m_leftOver + 1, + aLength, &readCount); + NS_ENSURE_SUCCESS(rv, rv); + mCopyState->m_leftOver += readCount; + mCopyState->m_dataBuffer[mCopyState->m_leftOver + 1] = '\0'; + char* start = mCopyState->m_dataBuffer + 1; + char* endBuffer = mCopyState->m_dataBuffer + mCopyState->m_leftOver + 1; + + uint32_t lineLength; + uint32_t bytesWritten; + + while (1) { + char* end = PL_strnpbrk(start, "\r\n", endBuffer - start); + if (!end) { + mCopyState->m_leftOver -= (start - mCopyState->m_dataBuffer - 1); + // In CopyFileMessage, a complete message is being copied in a single + // call to CopyData, and if it does not have a LINEBREAK at the EOF, + // then end will be null after reading the last line, and we need + // to append the LINEBREAK to the buffer to enable transfer of the last + // line. + if (mCopyState->m_wholeMsgInStream) { + end = start + mCopyState->m_leftOver; + memcpy(end, MSG_LINEBREAK "\0", MSG_LINEBREAK_LEN + 1); + } else { + memmove(mCopyState->m_dataBuffer + 1, start, mCopyState->m_leftOver); + break; + } + } + + // need to set the linebreak_len each time + uint32_t linebreak_len = 1; // assume CR or LF + if (*end == '\r' && *(end + 1) == '\n') linebreak_len = 2; // CRLF + + if (!mCopyState->m_fromLineSeen) { + mCopyState->m_fromLineSeen = true; + NS_ASSERTION(strncmp(start, "From ", 5) == 0, + "Fatal ... bad message format\n"); + } else if (strncmp(start, "From ", 5) == 0) { + // if we're at the beginning of the buffer, we've reserved a byte to + // insert a '>'. If we're in the middle, we're overwriting the previous + // line ending, but we've already written it to m_fileStream, so it's OK. + *--start = '>'; + } + + if (!mCopyState->m_fileStream) { + ThrowAlertMsg("copyMsgWriteFailed", mCopyState->m_msgWindow); + mCopyState->m_writeFailed = true; + return NS_ERROR_UNEXPECTED; + } + + lineLength = end - start + linebreak_len; + rv = mCopyState->m_fileStream->Write(start, lineLength, &bytesWritten); + if (bytesWritten != lineLength || NS_FAILED(rv)) { + ThrowAlertMsg("copyMsgWriteFailed", mCopyState->m_msgWindow); + mCopyState->m_writeFailed = true; + return NS_MSG_ERROR_WRITING_MAIL_FOLDER; + } + + if (mCopyState->m_parseMsgState) + mCopyState->m_parseMsgState->ParseAFolderLine(start, lineLength); + + start = end + linebreak_len; + if (start >= endBuffer) { + mCopyState->m_leftOver = 0; + break; + } + } + return rv; +} + +void nsMsgLocalMailFolder::CopyPropertiesToMsgHdr(nsIMsgDBHdr* destHdr, + nsIMsgDBHdr* srcHdr, + bool aIsMove) { + nsresult rv; + nsCOMPtr<nsIPrefBranch> prefBranch( + do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS_VOID(rv); + + nsCString dontPreserve; + + // These preferences exist so that extensions can control which properties + // are preserved in the database when a message is moved or copied. All + // properties are preserved except those listed in these preferences + if (aIsMove) + prefBranch->GetCharPref("mailnews.database.summary.dontPreserveOnMove", + dontPreserve); + else + prefBranch->GetCharPref("mailnews.database.summary.dontPreserveOnCopy", + dontPreserve); + + CopyHdrPropertiesWithSkipList(destHdr, srcHdr, dontPreserve); +} + +void nsMsgLocalMailFolder::CopyHdrPropertiesWithSkipList( + nsIMsgDBHdr* destHdr, nsIMsgDBHdr* srcHdr, const nsCString& skipList) { + nsTArray<nsCString> properties; + nsresult rv = srcHdr->GetProperties(properties); + NS_ENSURE_SUCCESS_VOID(rv); + + // We'll add spaces at beginning and end so we can search for space-name-space + nsCString dontPreserveEx(" "_ns); + dontPreserveEx.Append(skipList); + dontPreserveEx.Append(' '); + + nsCString sourceString; + for (auto property : properties) { + nsAutoCString propertyEx(" "_ns); + propertyEx.Append(property); + propertyEx.Append(' '); + if (dontPreserveEx.Find(propertyEx) != -1) // -1 is not found + continue; + + srcHdr->GetStringProperty(property.get(), sourceString); + destHdr->SetStringProperty(property.get(), sourceString); + } +} + +MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHODIMP +nsMsgLocalMailFolder::EndCopy(bool aCopySucceeded) { + if (!mCopyState) return NS_OK; + + // we are the destination folder for a move/copy + nsresult rv = aCopySucceeded ? NS_OK : NS_ERROR_FAILURE; + + if (!aCopySucceeded || mCopyState->m_writeFailed) { + if (mCopyState->m_fileStream) { + if (mCopyState->m_curDstKey != nsMsgKey_None) + mCopyState->m_msgStore->DiscardNewMessage(mCopyState->m_fileStream, + mCopyState->m_newHdr); + mCopyState->m_fileStream->Close(); + } + + if (!mCopyState->m_isMove) { + // passing true because the messages that have been successfully + // copied have their corresponding hdrs in place. The message that has + // failed has been truncated so the msf file and berkeley mailbox + // are in sync. + (void)OnCopyCompleted(mCopyState->m_srcSupport, true); + // enable the dest folder + EnableNotifications(allMessageCountNotifications, true); + } + return NS_OK; + } + + bool multipleCopiesFinished = + (mCopyState->m_curCopyIndex >= mCopyState->m_totalMsgCount); + + RefPtr<nsLocalMoveCopyMsgTxn> localUndoTxn = mCopyState->m_undoMsgTxn; + + NS_ASSERTION(mCopyState->m_leftOver == 0, + "whoops, something wrong with previous copy"); + mCopyState->m_leftOver = 0; // reset to 0. + // need to reset this in case we're move/copying multiple msgs. + mCopyState->m_fromLineSeen = false; + + // flush the copied message. We need a close at the end to get the + // file size and time updated correctly. + // + // These filestream closes are handled inconsistently in the code. In some + // cases, this is done in EndMessage, while in others it is done here in + // EndCopy. When we do the close in EndMessage, we'll set + // mCopyState->m_fileStream to null since it is no longer needed, and detect + // here the null stream so we know that we don't have to close it here. + // + // Similarly, m_parseMsgState->GetNewMsgHdr() returns a null hdr if the hdr + // has already been processed by EndMessage so it is not doubly added here. + + if (mCopyState->m_fileStream) { + rv = FinishNewLocalMessage(mCopyState->m_fileStream, mCopyState->m_newHdr, + mCopyState->m_msgStore, + mCopyState->m_parseMsgState); + if (NS_SUCCEEDED(rv) && mCopyState->m_newHdr) + mCopyState->m_newHdr->GetMessageKey(&mCopyState->m_curDstKey); + if (multipleCopiesFinished) + mCopyState->m_fileStream->Close(); + else + mCopyState->m_fileStream->Flush(); + } + // Copy the header to the new database + if (mCopyState->m_message) { + // CopyMessages() goes here, and CopyFileMessages() with metadata to save; + nsCOMPtr<nsIMsgDBHdr> newHdr; + if (!mCopyState->m_parseMsgState) { + if (mCopyState->m_destDB) { + if (mCopyState->m_newHdr) { + newHdr = mCopyState->m_newHdr; + CopyHdrPropertiesWithSkipList(newHdr, mCopyState->m_message, + "storeToken msgOffset"_ns); + // We need to copy more than just what UpdateNewMsgHdr does. In fact, + // I think we want to copy almost every property other than + // storeToken and msgOffset. + mCopyState->m_destDB->AddNewHdrToDB(newHdr, true); + } else { + rv = mCopyState->m_destDB->CopyHdrFromExistingHdr( + mCopyState->m_curDstKey, mCopyState->m_message, true, + getter_AddRefs(newHdr)); + } + uint32_t newHdrFlags; + if (newHdr) { + // turn off offline flag - it's not valid for local mail folders. + newHdr->AndFlags(~nsMsgMessageFlags::Offline, &newHdrFlags); + mCopyState->m_destMessages.AppendElement(newHdr); + } + } + // we can do undo with the dest folder db, see bug #198909 + // else + // mCopyState->m_undoMsgTxn = nullptr; // null out the transaction + // // because we can't undo w/o + // // the msg db + } + + // if we plan on allowing undo, (if we have a mCopyState->m_parseMsgState or + // not) we need to save the source and dest keys on the undo txn. see bug + // #179856 for details + bool isImap; + if (NS_SUCCEEDED(rv) && localUndoTxn) { + localUndoTxn->GetSrcIsImap(&isImap); + if (!isImap || !mCopyState->m_copyingMultipleMessages) { + nsMsgKey aKey; + mCopyState->m_message->GetMessageKey(&aKey); + localUndoTxn->AddSrcKey(aKey); + localUndoTxn->AddDstKey(mCopyState->m_curDstKey); + } + } + } + nsCOMPtr<nsIMsgDBHdr> newHdr; + // CopyFileMessage() and CopyMessages() from servers other than mailbox + if (mCopyState->m_parseMsgState) { + nsCOMPtr<nsIMsgDatabase> msgDb; + mCopyState->m_parseMsgState->FinishHeader(); + GetDatabaseWOReparse(getter_AddRefs(msgDb)); + if (msgDb) { + nsresult result = + mCopyState->m_parseMsgState->GetNewMsgHdr(getter_AddRefs(newHdr)); + // we need to copy newHdr because mCopyState will get cleared + // in OnCopyCompleted, but we need OnCopyCompleted to know about + // the newHdr, via mCopyState. And we send a notification about newHdr + // after OnCopyCompleted. + mCopyState->m_newHdr = newHdr; + if (NS_SUCCEEDED(result) && newHdr) { + // Copy message metadata. + uint32_t newFlags; + newHdr->GetFlags(&newFlags); + if (mCopyState->m_message) { + // Propagate the new flag on an imap to local folder filter action + // Flags may get changed when deleting the original source message in + // IMAP. We have a copy of the original flags, but parseMsgState has + // already tried to decide what those flags should be. Who to believe? + // Let's deal here with the flags that might get changed, Read and + // New, and trust upstream code for everything else. However, + // we need to carry over HasRe since the subject is copied over + // from the original. + uint32_t carryOver = nsMsgMessageFlags::New | + nsMsgMessageFlags::Read | + nsMsgMessageFlags::HasRe; + newHdr->SetFlags((newFlags & ~carryOver) | + ((mCopyState->m_flags) & carryOver)); + + // Copy other message properties. + CopyPropertiesToMsgHdr(newHdr, mCopyState->m_message, + mCopyState->m_isMove); + } else { + // Carry over some of the enforced flags, but do not clear any of the + // already set flags (for example nsMsgMessageFlags::Queued or + // nsMsgMessageFlags::MDNReportSent). + uint32_t carryOver = nsMsgMessageFlags::New | + nsMsgMessageFlags::Read | + nsMsgMessageFlags::Marked; + newHdr->SetFlags((newFlags & ~carryOver) | + ((mCopyState->m_flags) & carryOver)); + } + msgDb->AddNewHdrToDB(newHdr, true); + if (localUndoTxn) { + // ** jt - recording the message size for possible undo use; the + // message size is different for pop3 and imap4 messages + uint32_t msgSize; + newHdr->GetMessageSize(&msgSize); + localUndoTxn->AddDstMsgSize(msgSize); + } + + mCopyState->m_destMessages.AppendElement(newHdr); + } + // msgDb->SetSummaryValid(true); + // msgDb->Commit(nsMsgDBCommitType::kLargeCommit); + } else + mCopyState->m_undoMsgTxn = nullptr; // null out the transaction because + // we can't undo w/o the msg db + + mCopyState->m_parseMsgState->Clear(); + if (mCopyState->m_listener) // CopyFileMessage() only + mCopyState->m_listener->SetMessageKey(mCopyState->m_curDstKey); + } + + if (!multipleCopiesFinished && !mCopyState->m_copyingMultipleMessages) { + // CopyMessages() goes here; CopyFileMessage() never gets in here because + // curCopyIndex will always be less than the mCopyState->m_totalMsgCount + nsIMsgDBHdr* aSupport = mCopyState->m_messages[mCopyState->m_curCopyIndex]; + rv = CopyMessageTo(aSupport, mCopyState->m_msgWindow, mCopyState->m_isMove); + } else { + // If we have some headers, then there is a source, so notify + // itemMoveCopyCompleted. If we don't have any headers already, (eg save as + // draft, send) then notify itemAdded. This notification is done after the + // messages are deleted, so that saving a new draft of a message works + // correctly -- first an itemDeleted is sent for the old draft, then an + // itemAdded for the new draft. + uint32_t numHdrs = mCopyState->m_messages.Length(); + + if (multipleCopiesFinished && numHdrs && !mCopyState->m_isFolder) { + // we need to send this notification before we delete the source messages, + // because deleting the source messages clears out the src msg db hdr. + nsCOMPtr<nsIMsgFolderNotificationService> notifier( + do_GetService("@mozilla.org/messenger/msgnotificationservice;1")); + if (notifier) { + notifier->NotifyMsgsMoveCopyCompleted(mCopyState->m_isMove, + mCopyState->m_messages, this, + mCopyState->m_destMessages); + } + } + + // Now allow folder or nested folders move of their msgs from Local Folders. + // The original source folder(s) remain, just the msgs are moved (after + // copy they are deleted). + if (multipleCopiesFinished) { + nsCOMPtr<nsIMsgFolder> srcFolder; + srcFolder = do_QueryInterface(mCopyState->m_srcSupport); + if (mCopyState->m_isFolder) { + // Copy or move all subfolders then notify completion + CopyAllSubFolders(srcFolder, nullptr, nullptr, mCopyState->m_isMove); + } + + // If this is done on move of selected messages between "mailbox" folders, + // the source messages are never deleted. So do this only on msg copy. + if (!mCopyState->m_isMove) { + if (mCopyState->m_msgWindow && mCopyState->m_undoMsgTxn) { + nsCOMPtr<nsITransactionManager> txnMgr; + mCopyState->m_msgWindow->GetTransactionManager( + getter_AddRefs(txnMgr)); + if (txnMgr) { + RefPtr<nsLocalMoveCopyMsgTxn> txn = mCopyState->m_undoMsgTxn; + txnMgr->DoTransaction(txn); + } + } + + // enable the dest folder + EnableNotifications(allMessageCountNotifications, true); + if (srcFolder && !mCopyState->m_isFolder) { + // I'm not too sure of the proper location of this event. It seems to + // need to be after the EnableNotifications, or the folder counts can + // be incorrect during the kDeleteOrMoveMsgCompleted call. + srcFolder->NotifyFolderEvent(kDeleteOrMoveMsgCompleted); + } + (void)OnCopyCompleted(mCopyState->m_srcSupport, true); + } + } + // Send the itemAdded notification in case we didn't send the + // itemMoveCopyCompleted notification earlier. Posting news messages + // involves this, yet doesn't have the newHdr initialized, so don't send any + // notifications in that case. + if (!numHdrs && newHdr) { + nsCOMPtr<nsIMsgFolderNotificationService> notifier( + do_GetService("@mozilla.org/messenger/msgnotificationservice;1")); + if (notifier) { + notifier->NotifyMsgAdded(newHdr); + // We do not appear to trigger classification in this case, so let's + // paper over the abyss by just sending the classification notification. + notifier->NotifyMsgsClassified({&*newHdr}, false, false); + // (We do not add the NotReportedClassified processing flag since we + // just reported it!) + } + } + } + return rv; +} + +static bool gGotGlobalPrefs; +static bool gDeleteFromServerOnMove; + +bool nsMsgLocalMailFolder::GetDeleteFromServerOnMove() { + if (!gGotGlobalPrefs) { + nsCOMPtr<nsIPrefBranch> pPrefBranch( + do_GetService(NS_PREFSERVICE_CONTRACTID)); + if (pPrefBranch) { + pPrefBranch->GetBoolPref("mail.pop3.deleteFromServerOnMove", + &gDeleteFromServerOnMove); + gGotGlobalPrefs = true; + } + } + return gDeleteFromServerOnMove; +} + +MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHODIMP +nsMsgLocalMailFolder::EndMove(bool moveSucceeded) { + nsresult rv; + if (!mCopyState) return NS_OK; + + if (!moveSucceeded || mCopyState->m_writeFailed) { + // Notify that a completion finished. + nsCOMPtr<nsIMsgFolder> srcFolder = + do_QueryInterface(mCopyState->m_srcSupport, &rv); + NS_ENSURE_SUCCESS(rv, rv); + srcFolder->NotifyFolderEvent(kDeleteOrMoveMsgFailed); + + /* passing true because the messages that have been successfully copied have + their corresponding hdrs in place. The message that has failed has been + truncated so the msf file and berkeley mailbox are in sync*/ + + (void)OnCopyCompleted(mCopyState->m_srcSupport, true); + // enable the dest folder + EnableNotifications(allMessageCountNotifications, true); + return NS_OK; + } + + if (mCopyState && mCopyState->m_curCopyIndex >= mCopyState->m_totalMsgCount) { + // Notify that a completion finished. + nsCOMPtr<nsIMsgFolder> srcFolder = + do_QueryInterface(mCopyState->m_srcSupport, &rv); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr<nsIMsgLocalMailFolder> localSrcFolder = + do_QueryInterface(srcFolder); + if (localSrcFolder) { + // if we are the trash and a local msg is being moved to us, mark the + // source for delete from server, if so configured. + if (mFlags & nsMsgFolderFlags::Trash) { + // if we're deleting on all moves, we'll mark this message for deletion + // when we call DeleteMessages on the source folder. So don't mark it + // for deletion here, in that case. + if (!GetDeleteFromServerOnMove()) { + localSrcFolder->MarkMsgsOnPop3Server(mCopyState->m_messages, + POP3_DELETE); + } + } + } + // lets delete these all at once - much faster that way + rv = srcFolder->DeleteMessages(mCopyState->m_messages, + mCopyState->m_msgWindow, true, true, nullptr, + mCopyState->m_allowUndo); + AutoCompact(mCopyState->m_msgWindow); + + // enable the dest folder + EnableNotifications(allMessageCountNotifications, true); + // I'm not too sure of the proper location of this event. It seems to need + // to be after the EnableNotifications, or the folder counts can be + // incorrect during the kDeleteOrMoveMsgCompleted call. + srcFolder->NotifyFolderEvent(NS_SUCCEEDED(rv) ? kDeleteOrMoveMsgCompleted + : kDeleteOrMoveMsgFailed); + + if (NS_SUCCEEDED(rv) && mCopyState->m_msgWindow && + mCopyState->m_undoMsgTxn) { + nsCOMPtr<nsITransactionManager> txnMgr; + mCopyState->m_msgWindow->GetTransactionManager(getter_AddRefs(txnMgr)); + if (txnMgr) { + RefPtr<nsLocalMoveCopyMsgTxn> txn = mCopyState->m_undoMsgTxn; + txnMgr->DoTransaction(txn); + } + } + (void)OnCopyCompleted( + mCopyState->m_srcSupport, + NS_SUCCEEDED(rv) + ? true + : false); // clear the copy state so that the next message from a + // different folder can be move + } + + return NS_OK; +} + +// this is the beginning of the next message copied +NS_IMETHODIMP nsMsgLocalMailFolder::StartMessage() { + // We get crashes that we don't understand (bug 284876), so stupidly prevent + // that. + NS_ENSURE_ARG_POINTER(mCopyState); + nsresult rv = InitCopyMsgHdrAndFileStream(); + NS_ENSURE_SUCCESS(rv, rv); + return WriteStartOfNewMessage(); +} + +// just finished the current message. +NS_IMETHODIMP nsMsgLocalMailFolder::EndMessage(nsMsgKey key) { + NS_ENSURE_ARG_POINTER(mCopyState); + + RefPtr<nsLocalMoveCopyMsgTxn> localUndoTxn = mCopyState->m_undoMsgTxn; + nsCOMPtr<nsIMsgWindow> msgWindow; + nsresult rv; + + if (localUndoTxn) { + localUndoTxn->GetMsgWindow(getter_AddRefs(msgWindow)); + localUndoTxn->AddSrcKey(key); + localUndoTxn->AddDstKey(mCopyState->m_curDstKey); + } + + // I think this is always true for online to offline copy + mCopyState->m_dummyEnvelopeNeeded = true; + if (mCopyState->m_fileStream) { + rv = FinishNewLocalMessage(mCopyState->m_fileStream, mCopyState->m_newHdr, + mCopyState->m_msgStore, + mCopyState->m_parseMsgState); + mCopyState->m_fileStream->Close(); + mCopyState->m_fileStream = nullptr; // all done with the file stream + } + + // CopyFileMessage() and CopyMessages() from servers other than mailbox + if (mCopyState->m_parseMsgState) { + nsCOMPtr<nsIMsgDatabase> msgDb; + nsCOMPtr<nsIMsgDBHdr> newHdr; + + mCopyState->m_parseMsgState->FinishHeader(); + + rv = mCopyState->m_parseMsgState->GetNewMsgHdr(getter_AddRefs(newHdr)); + if (NS_SUCCEEDED(rv) && newHdr) { + nsCOMPtr<nsIMsgFolder> srcFolder = + do_QueryInterface(mCopyState->m_srcSupport, &rv); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr<nsIMsgDatabase> srcDB; + srcFolder->GetMsgDatabase(getter_AddRefs(srcDB)); + if (srcDB) { + nsCOMPtr<nsIMsgDBHdr> srcMsgHdr; + srcDB->GetMsgHdrForKey(key, getter_AddRefs(srcMsgHdr)); + if (srcMsgHdr) + CopyPropertiesToMsgHdr(newHdr, srcMsgHdr, mCopyState->m_isMove); + } + rv = GetDatabaseWOReparse(getter_AddRefs(msgDb)); + if (NS_SUCCEEDED(rv) && msgDb) { + msgDb->AddNewHdrToDB(newHdr, true); + if (localUndoTxn) { + // ** jt - recording the message size for possible undo use; the + // message size is different for pop3 and imap4 messages + uint32_t msgSize; + newHdr->GetMessageSize(&msgSize); + localUndoTxn->AddDstMsgSize(msgSize); + } + } else + mCopyState->m_undoMsgTxn = nullptr; // null out the transaction because + // we can't undo w/o the msg db + } + mCopyState->m_parseMsgState->Clear(); + + if (mCopyState->m_listener) // CopyFileMessage() only + mCopyState->m_listener->SetMessageKey(mCopyState->m_curDstKey); + } + + if (mCopyState->m_fileStream) mCopyState->m_fileStream->Flush(); + return NS_OK; +} + +nsresult nsMsgLocalMailFolder::CopyMessagesTo(nsTArray<nsMsgKey>& keyArray, + nsIMsgWindow* aMsgWindow, + bool isMove) { + if (!mCopyState) return NS_ERROR_OUT_OF_MEMORY; + + nsresult rv; + + nsCOMPtr<nsICopyMessageStreamListener> copyStreamListener = do_CreateInstance( + "@mozilla.org/messenger/copymessagestreamlistener;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMsgFolder> srcFolder( + do_QueryInterface(mCopyState->m_srcSupport, &rv)); + NS_ENSURE_SUCCESS(rv, NS_ERROR_NO_INTERFACE); + + rv = copyStreamListener->Init(this); + NS_ENSURE_SUCCESS(rv, rv); + + if (!mCopyState->m_messageService) { + nsCString uri; + srcFolder->GetURI(uri); + rv = GetMessageServiceFromURI(uri, + getter_AddRefs(mCopyState->m_messageService)); + } + + if (NS_SUCCEEDED(rv) && mCopyState->m_messageService) { + nsCOMPtr<nsIStreamListener> streamListener( + do_QueryInterface(copyStreamListener, &rv)); + NS_ENSURE_SUCCESS(rv, NS_ERROR_NO_INTERFACE); + + mCopyState->m_curCopyIndex = 0; + // we need to kick off the first message - subsequent messages + // are kicked off by nsMailboxProtocol when it finishes a message + // before starting the next message. Only do this if the source folder + // is a local folder, however. IMAP will handle calling StartMessage for + // each message that gets downloaded, and news doesn't go through here + // because news only downloads one message at a time, and this routine + // is for multiple message copy. + nsCOMPtr<nsIMsgLocalMailFolder> srcLocalFolder = + do_QueryInterface(srcFolder); + if (srcLocalFolder) { + StartMessage(); + } + nsCOMPtr<nsIURI> dummyNull; + rv = mCopyState->m_messageService->CopyMessages( + keyArray, srcFolder, streamListener, isMove, nullptr, aMsgWindow, + getter_AddRefs(dummyNull)); + } + return rv; +} + +nsresult nsMsgLocalMailFolder::CopyMessageTo(nsISupports* message, + nsIMsgWindow* aMsgWindow, + bool isMove) { + if (!mCopyState) return NS_ERROR_OUT_OF_MEMORY; + + nsresult rv; + nsCOMPtr<nsIMsgDBHdr> msgHdr(do_QueryInterface(message, &rv)); + NS_ENSURE_SUCCESS(rv, NS_ERROR_NO_INTERFACE); + + mCopyState->m_message = msgHdr; + + nsCOMPtr<nsIMsgFolder> srcFolder( + do_QueryInterface(mCopyState->m_srcSupport, &rv)); + NS_ENSURE_SUCCESS(rv, NS_ERROR_NO_INTERFACE); + nsCString uri; + srcFolder->GetUriForMsg(msgHdr, uri); + + nsCOMPtr<nsICopyMessageStreamListener> copyStreamListener = do_CreateInstance( + "@mozilla.org/messenger/copymessagestreamlistener;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = copyStreamListener->Init(this); + if (NS_FAILED(rv)) return rv; + + if (!mCopyState->m_messageService) + rv = GetMessageServiceFromURI(uri, + getter_AddRefs(mCopyState->m_messageService)); + + if (NS_SUCCEEDED(rv) && mCopyState->m_messageService) { + nsCOMPtr<nsIStreamListener> streamListener( + do_QueryInterface(copyStreamListener, &rv)); + NS_ENSURE_SUCCESS(rv, NS_ERROR_NO_INTERFACE); + rv = mCopyState->m_messageService->CopyMessage(uri, streamListener, isMove, + nullptr, aMsgWindow); + } + return rv; +} + +// A message is being deleted from a POP3 mail file, so check and see if we have +// the message being deleted in the server. If so, then we need to remove the +// message from the server as well. We have saved the UIDL of the message in the +// popstate.dat file and we must match this uidl, so read the message headers +// and see if we have it, then mark the message for deletion from the server. +// The next time we look at mail the message will be deleted from the server. + +NS_IMETHODIMP +nsMsgLocalMailFolder::MarkMsgsOnPop3Server( + const nsTArray<RefPtr<nsIMsgDBHdr>>& aMessages, int32_t aMark) { + nsLocalFolderScanState folderScanState; + nsCOMPtr<nsIPop3IncomingServer> curFolderPop3MailServer; + nsCOMArray<nsIPop3IncomingServer> + pop3Servers; // servers with msgs deleted... + + nsCOMPtr<nsIMsgIncomingServer> incomingServer; + nsresult rv = GetServer(getter_AddRefs(incomingServer)); + NS_ENSURE_SUCCESS(rv, NS_MSG_INVALID_OR_MISSING_SERVER); + + nsCOMPtr<nsIMsgAccountManager> accountManager = + do_GetService("@mozilla.org/messenger/account-manager;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + // I wonder if we should run through the pop3 accounts and see if any of them + // have leave on server set. If not, we could short-circuit some of this. + + curFolderPop3MailServer = do_QueryInterface(incomingServer, &rv); + rv = GetFolderScanState(&folderScanState); + NS_ENSURE_SUCCESS(rv, rv); + + // Filter delete requests are always honored, others are subject + // to the deleteMailLeftOnServer preference. + int32_t mark; + mark = (aMark == POP3_FORCE_DEL) ? POP3_DELETE : aMark; + + for (auto msgDBHdr : aMessages) { + uint32_t flags = 0; + if (msgDBHdr) { + msgDBHdr->GetFlags(&flags); + nsCOMPtr<nsIPop3IncomingServer> msgPop3Server = curFolderPop3MailServer; + bool leaveOnServer = false; + bool deleteMailLeftOnServer = false; + // set up defaults, in case there's no x-mozilla-account header + if (curFolderPop3MailServer) { + curFolderPop3MailServer->GetDeleteMailLeftOnServer( + &deleteMailLeftOnServer); + curFolderPop3MailServer->GetLeaveMessagesOnServer(&leaveOnServer); + } + + rv = GetUidlFromFolder(&folderScanState, msgDBHdr); + if (!NS_SUCCEEDED(rv)) continue; + + if (folderScanState.m_uidl) { + nsCOMPtr<nsIMsgAccount> account; + rv = accountManager->GetAccount(folderScanState.m_accountKey, + getter_AddRefs(account)); + if (NS_SUCCEEDED(rv) && account) { + account->GetIncomingServer(getter_AddRefs(incomingServer)); + nsCOMPtr<nsIPop3IncomingServer> curMsgPop3MailServer = + do_QueryInterface(incomingServer); + if (curMsgPop3MailServer) { + msgPop3Server = curMsgPop3MailServer; + msgPop3Server->GetDeleteMailLeftOnServer(&deleteMailLeftOnServer); + msgPop3Server->GetLeaveMessagesOnServer(&leaveOnServer); + } + } + } + // ignore this header if not partial and leaveOnServer not set... + // or if we can't find the pop3 server. + if (!msgPop3Server || + (!(flags & nsMsgMessageFlags::Partial) && !leaveOnServer)) + continue; + // if marking deleted, ignore header if we're not deleting from + // server when deleting locally. + if (aMark == POP3_DELETE && leaveOnServer && !deleteMailLeftOnServer) + continue; + if (folderScanState.m_uidl) { + msgPop3Server->AddUidlToMark(folderScanState.m_uidl, mark); + // remember this pop server in list of servers with msgs deleted + if (pop3Servers.IndexOfObject(msgPop3Server) == -1) + pop3Servers.AppendObject(msgPop3Server); + } + } + } + if (folderScanState.m_inputStream) folderScanState.m_inputStream->Close(); + // need to do this for all pop3 mail servers that had messages deleted. + uint32_t serverCount = pop3Servers.Count(); + for (uint32_t index = 0; index < serverCount; index++) + pop3Servers[index]->MarkMessages(); + + return rv; +} + +NS_IMETHODIMP nsMsgLocalMailFolder::DeleteDownloadMsg(nsIMsgDBHdr* aMsgHdr) { + uint32_t numMsgs; + char* newMsgId; + + // This method is only invoked through DownloadMessagesForOffline() + if (mDownloadState != DOWNLOAD_STATE_NONE) { + // We only remember the first key, no matter how many + // messages were originally selected. + if (mDownloadState == DOWNLOAD_STATE_INITED) { + aMsgHdr->GetMessageKey(&mDownloadSelectKey); + mDownloadState = DOWNLOAD_STATE_GOTMSG; + } + + aMsgHdr->GetMessageId(&newMsgId); + + // Walk through all the selected headers, looking for a matching + // Message-ID. + numMsgs = mDownloadMessages.Length(); + for (uint32_t i = 0; i < numMsgs; i++) { + nsresult rv; + nsCOMPtr<nsIMsgDBHdr> msgDBHdr = mDownloadMessages[i]; + char* oldMsgId = nullptr; + msgDBHdr->GetMessageId(&oldMsgId); + + // Delete the first match and remove it from the array + if (!PL_strcmp(newMsgId, oldMsgId)) { + rv = GetDatabase(); + if (!mDatabase) return rv; + + UpdateNewMsgHdr(msgDBHdr, aMsgHdr); + + mDatabase->DeleteHeader(msgDBHdr, nullptr, false, true); + mDownloadMessages.RemoveElementAt(i); + break; + } + } + } + + return NS_OK; +} + +NS_IMETHODIMP nsMsgLocalMailFolder::DownloadMessagesForOffline( + nsTArray<RefPtr<nsIMsgDBHdr>> const& aMessages, nsIMsgWindow* aWindow) { + if (mDownloadState != DOWNLOAD_STATE_NONE) + return NS_ERROR_FAILURE; // already has a download in progress + + // We're starting a download... + mDownloadState = DOWNLOAD_STATE_INITED; + + MarkMsgsOnPop3Server(aMessages, POP3_FETCH_BODY); + + // Pull out all the PARTIAL messages into a new array + nsresult rv; + for (nsIMsgDBHdr* hdr : aMessages) { + uint32_t flags = 0; + hdr->GetFlags(&flags); + if (flags & nsMsgMessageFlags::Partial) { + mDownloadMessages.AppendElement(hdr); + } + } + mDownloadWindow = aWindow; + + nsCOMPtr<nsIMsgIncomingServer> server; + rv = GetServer(getter_AddRefs(server)); + NS_ENSURE_SUCCESS(rv, NS_MSG_INVALID_OR_MISSING_SERVER); + + nsCOMPtr<nsILocalMailIncomingServer> localMailServer = + do_QueryInterface(server, &rv); + NS_ENSURE_SUCCESS(rv, NS_MSG_INVALID_OR_MISSING_SERVER); + nsCOMPtr<nsIURI> resultURI; + return localMailServer->GetNewMail(aWindow, this, this, + getter_AddRefs(resultURI)); +} + +NS_IMETHODIMP nsMsgLocalMailFolder::HasMsgOffline(nsMsgKey msgKey, + bool* result) { + NS_ENSURE_ARG(result); + *result = false; + GetDatabase(); + if (!mDatabase) return NS_ERROR_FAILURE; + + nsresult rv; + nsCOMPtr<nsIMsgDBHdr> hdr; + rv = mDatabase->GetMsgHdrForKey(msgKey, getter_AddRefs(hdr)); + if (NS_FAILED(rv)) return rv; + + if (hdr) { + uint32_t flags = 0; + hdr->GetFlags(&flags); + // Would be nice to check nsMsgMessageFlags::Offline... but local + // folders don't set it. + // Don't want partial messages. + if (!(flags & nsMsgMessageFlags::Partial)) { + *result = true; + } + } + return NS_OK; +} + +NS_IMETHODIMP nsMsgLocalMailFolder::GetLocalMsgStream(nsIMsgDBHdr* hdr, + nsIInputStream** stream) { + uint64_t offset = 0; + hdr->GetMessageOffset(&offset); + // It's a local folder - .messageSize holds the size. + uint32_t size = 0; + hdr->GetMessageSize(&size); + nsCOMPtr<nsIInputStream> fileStream; + nsresult rv = GetMsgInputStream(hdr, getter_AddRefs(fileStream)); + NS_ENSURE_SUCCESS(rv, rv); + + RefPtr<mozilla::SlicedInputStream> slicedStream = + new mozilla::SlicedInputStream(fileStream.forget(), offset, + uint64_t(size)); + slicedStream.forget(stream); + return NS_OK; +} + +NS_IMETHODIMP nsMsgLocalMailFolder::NotifyDelete() { + NotifyFolderEvent(kDeleteOrMoveMsgCompleted); + return NS_OK; +} + +// TODO: once we move certain code into the IncomingServer (search for TODO) +// this method will go away. +// sometimes this gets called when we don't have the server yet, so +// that's why we're not calling GetServer() +NS_IMETHODIMP +nsMsgLocalMailFolder::GetIncomingServerType(nsACString& aServerType) { + nsresult rv; + if (mType.IsEmpty()) { + nsCOMPtr<nsIURL> url; + rv = NS_MutateURI(NS_STANDARDURLMUTATOR_CONTRACTID) + .SetSpec(mURI) + .Finalize(url); + if (NS_FAILED(rv)) return rv; + + nsCOMPtr<nsIMsgAccountManager> accountManager = + do_GetService("@mozilla.org/messenger/account-manager;1", &rv); + if (NS_FAILED(rv)) return rv; + + nsCOMPtr<nsIMsgIncomingServer> server; + // try "none" first + rv = NS_MutateURI(url).SetScheme("none"_ns).Finalize(url); + NS_ENSURE_SUCCESS(rv, rv); + rv = accountManager->FindServerByURI(url, getter_AddRefs(server)); + if (NS_SUCCEEDED(rv) && server) + mType.AssignLiteral("none"); + else { + // next try "pop3" + rv = NS_MutateURI(url).SetScheme("pop3"_ns).Finalize(url); + NS_ENSURE_SUCCESS(rv, rv); + rv = accountManager->FindServerByURI(url, getter_AddRefs(server)); + if (NS_SUCCEEDED(rv) && server) + mType.AssignLiteral("pop3"); + else { + // next try "rss" + rv = NS_MutateURI(url).SetScheme("rss"_ns).Finalize(url); + NS_ENSURE_SUCCESS(rv, rv); + rv = accountManager->FindServerByURI(url, getter_AddRefs(server)); + if (NS_SUCCEEDED(rv) && server) + mType.AssignLiteral("rss"); + else { + } + } + } + } + aServerType = mType; + return NS_OK; +} + +nsresult nsMsgLocalMailFolder::CreateBaseMessageURI(const nsACString& aURI) { + return nsCreateLocalBaseMessageURI(aURI, mBaseMessageURI); +} + +NS_IMETHODIMP +nsMsgLocalMailFolder::OnStartRunningUrl(nsIURI* aUrl) { + nsresult rv; + nsCOMPtr<nsIPop3URL> popurl = do_QueryInterface(aUrl, &rv); + if (NS_SUCCEEDED(rv)) { + nsAutoCString aSpec; + rv = aUrl->GetSpec(aSpec); + NS_ENSURE_SUCCESS(rv, rv); + if (strstr(aSpec.get(), "uidl=")) { + nsCOMPtr<nsIPop3Sink> popsink; + rv = popurl->GetPop3Sink(getter_AddRefs(popsink)); + if (NS_SUCCEEDED(rv)) { + popsink->SetBaseMessageUri(mBaseMessageURI); + nsCString messageuri; + popurl->GetMessageUri(messageuri); + popsink->SetOrigMessageUri(messageuri); + } + } + } + return nsMsgDBFolder::OnStartRunningUrl(aUrl); +} + +NS_IMETHODIMP +nsMsgLocalMailFolder::OnStopRunningUrl(nsIURI* aUrl, nsresult aExitCode) { + // If we just finished a DownloadMessages call, reset... + if (mDownloadState != DOWNLOAD_STATE_NONE) { + mDownloadState = DOWNLOAD_STATE_NONE; + mDownloadMessages.Clear(); + mDownloadWindow = nullptr; + return nsMsgDBFolder::OnStopRunningUrl(aUrl, aExitCode); + } + + nsresult rv; + if (NS_SUCCEEDED(aExitCode)) { + nsCOMPtr<nsIMsgMailSession> mailSession = + do_GetService("@mozilla.org/messenger/services/session;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr<nsIMsgWindow> msgWindow; + rv = mailSession->GetTopmostMsgWindow(getter_AddRefs(msgWindow)); + nsAutoCString aSpec; + if (aUrl) { + rv = aUrl->GetSpec(aSpec); + NS_ENSURE_SUCCESS(rv, rv); + } + + if (mFlags & nsMsgFolderFlags::Inbox) { + if (mDatabase && mCheckForNewMessagesAfterParsing) { + bool valid = + false; // GetSummaryValid may return without setting valid. + mDatabase->GetSummaryValid(&valid); + if (valid && msgWindow) rv = GetNewMessages(msgWindow, nullptr); + mCheckForNewMessagesAfterParsing = false; + } + } + } + + if (m_parsingFolder) { + // Clear this before calling OnStopRunningUrl, in case the url listener + // tries to get the database. + m_parsingFolder = false; + + // TODO: Updating the size should be pushed down into the msg store backend + // so that the size is recalculated as part of parsing the folder data + // (important for maildir), once GetSizeOnDisk is pushed into the msgStores + // (bug 1032360). + (void)RefreshSizeOnDisk(); + + // Update the summary totals so the front end will + // show the right thing. + UpdateSummaryTotals(true); + + if (mReparseListener) { + nsCOMPtr<nsIUrlListener> saveReparseListener = mReparseListener; + mReparseListener = nullptr; + saveReparseListener->OnStopRunningUrl(aUrl, aExitCode); + } + } + if (mFlags & nsMsgFolderFlags::Inbox) { + // if we are the inbox and running pop url + nsCOMPtr<nsIPop3URL> popurl = do_QueryInterface(aUrl, &rv); + mozilla::Unused << popurl; + if (NS_SUCCEEDED(rv)) { + nsCOMPtr<nsIMsgIncomingServer> server; + GetServer(getter_AddRefs(server)); + // this is the deferred to account, in the global inbox case + if (server) server->SetPerformingBiff(false); // biff is over + } + } + return nsMsgDBFolder::OnStopRunningUrl(aUrl, aExitCode); +} + +nsresult nsMsgLocalMailFolder::DisplayMoveCopyStatusMsg() { + nsresult rv = NS_OK; + if (mCopyState) { + if (!mCopyState->m_statusFeedback) { + // get msgWindow from undo txn + nsCOMPtr<nsIMsgWindow> msgWindow; + if (mCopyState->m_undoMsgTxn) + mCopyState->m_undoMsgTxn->GetMsgWindow(getter_AddRefs(msgWindow)); + if (!msgWindow) return NS_OK; // not a fatal error. + + msgWindow->GetStatusFeedback( + getter_AddRefs(mCopyState->m_statusFeedback)); + } + + if (!mCopyState->m_stringBundle) { + nsCOMPtr<nsIStringBundleService> bundleService = + mozilla::components::StringBundle::Service(); + NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED); + rv = bundleService->CreateBundle( + "chrome://messenger/locale/localMsgs.properties", + getter_AddRefs(mCopyState->m_stringBundle)); + NS_ENSURE_SUCCESS(rv, rv); + } + if (mCopyState->m_statusFeedback && mCopyState->m_stringBundle) { + nsString folderName; + GetName(folderName); + nsAutoString numMsgSoFarString; + numMsgSoFarString.AppendInt((mCopyState->m_copyingMultipleMessages) + ? mCopyState->m_curCopyIndex + : 1); + + nsAutoString totalMessagesString; + totalMessagesString.AppendInt(mCopyState->m_totalMsgCount); + nsString finalString; + AutoTArray<nsString, 3> stringArray = {numMsgSoFarString, + totalMessagesString, folderName}; + rv = mCopyState->m_stringBundle->FormatStringFromName( + (mCopyState->m_isMove) ? "movingMessagesStatus" + : "copyingMessagesStatus", + stringArray, finalString); + int64_t nowMS = PR_IntervalToMilliseconds(PR_IntervalNow()); + + // only update status/progress every half second + if (nowMS - mCopyState->m_lastProgressTime < 500 && + mCopyState->m_curCopyIndex < mCopyState->m_totalMsgCount) + return NS_OK; + + mCopyState->m_lastProgressTime = nowMS; + mCopyState->m_statusFeedback->ShowStatusString(finalString); + mCopyState->m_statusFeedback->ShowProgress( + mCopyState->m_curCopyIndex * 100 / mCopyState->m_totalMsgCount); + } + } + return rv; +} + +NS_IMETHODIMP +nsMsgLocalMailFolder::SetFlagsOnDefaultMailboxes(uint32_t flags) { + if (flags & nsMsgFolderFlags::Inbox) + setSubfolderFlag(u"Inbox"_ns, nsMsgFolderFlags::Inbox); + + if (flags & nsMsgFolderFlags::SentMail) + setSubfolderFlag(u"Sent"_ns, nsMsgFolderFlags::SentMail); + + if (flags & nsMsgFolderFlags::Drafts) + setSubfolderFlag(u"Drafts"_ns, nsMsgFolderFlags::Drafts); + + if (flags & nsMsgFolderFlags::Templates) + setSubfolderFlag(u"Templates"_ns, nsMsgFolderFlags::Templates); + + if (flags & nsMsgFolderFlags::Trash) + setSubfolderFlag(u"Trash"_ns, nsMsgFolderFlags::Trash); + + if (flags & nsMsgFolderFlags::Queue) + setSubfolderFlag(u"Unsent Messages"_ns, nsMsgFolderFlags::Queue); + + if (flags & nsMsgFolderFlags::Junk) + setSubfolderFlag(u"Junk"_ns, nsMsgFolderFlags::Junk); + + if (flags & nsMsgFolderFlags::Archive) + setSubfolderFlag(u"Archives"_ns, nsMsgFolderFlags::Archive); + + return NS_OK; +} + +nsresult nsMsgLocalMailFolder::setSubfolderFlag(const nsAString& aFolderName, + uint32_t flags) { + // FindSubFolder() expects the folder name to be escaped + // see bug #192043 + nsAutoCString escapedFolderName; + nsresult rv = NS_MsgEscapeEncodeURLPath(aFolderName, escapedFolderName); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr<nsIMsgFolder> msgFolder; + rv = FindSubFolder(escapedFolderName, getter_AddRefs(msgFolder)); + NS_ENSURE_SUCCESS(rv, rv); + + // we only want to do this if the folder *really* exists, + // so check if it has a parent. Otherwise, we'll create the + // .msf file when we don't want to. + nsCOMPtr<nsIMsgFolder> parent; + msgFolder->GetParent(getter_AddRefs(parent)); + if (!parent) return NS_ERROR_FAILURE; + + rv = msgFolder->SetFlag(flags); + NS_ENSURE_SUCCESS(rv, rv); + return msgFolder->SetPrettyName(aFolderName); +} + +NS_IMETHODIMP +nsMsgLocalMailFolder::GetCheckForNewMessagesAfterParsing( + bool* aCheckForNewMessagesAfterParsing) { + NS_ENSURE_ARG_POINTER(aCheckForNewMessagesAfterParsing); + *aCheckForNewMessagesAfterParsing = mCheckForNewMessagesAfterParsing; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgLocalMailFolder::SetCheckForNewMessagesAfterParsing( + bool aCheckForNewMessagesAfterParsing) { + mCheckForNewMessagesAfterParsing = aCheckForNewMessagesAfterParsing; + return NS_OK; +} + +NS_IMETHODIMP +nsMsgLocalMailFolder::NotifyCompactCompleted() { + mExpungedBytes = 0; + m_newMsgs.Clear(); // if compacted, m_newMsgs probably aren't valid. + // if compacted, processing flags probably also aren't valid. + ClearProcessingFlags(); + (void)RefreshSizeOnDisk(); + (void)CloseDBIfFolderNotOpen(false); + NotifyFolderEvent(kCompactCompleted); + return NS_OK; +} + +NS_IMETHODIMP nsMsgLocalMailFolder::Shutdown(bool shutdownChildren) { + mInitialized = false; + return nsMsgDBFolder::Shutdown(shutdownChildren); +} + +NS_IMETHODIMP +nsMsgLocalMailFolder::OnMessageClassified(const nsACString& aMsgURI, + nsMsgJunkStatus aClassification, + uint32_t aJunkPercent) + +{ + nsCOMPtr<nsIMsgIncomingServer> server; + nsresult rv = GetServer(getter_AddRefs(server)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsISpamSettings> spamSettings; + rv = server->GetSpamSettings(getter_AddRefs(spamSettings)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCString spamFolderURI; + rv = spamSettings->GetSpamFolderURI(spamFolderURI); + NS_ENSURE_SUCCESS(rv, rv); + + if (!aMsgURI.IsEmpty()) // not end of batch + { + nsCOMPtr<nsIMsgDBHdr> msgHdr; + rv = GetMsgDBHdrFromURI(aMsgURI, getter_AddRefs(msgHdr)); + NS_ENSURE_SUCCESS(rv, rv); + + nsMsgKey msgKey; + rv = msgHdr->GetMessageKey(&msgKey); + NS_ENSURE_SUCCESS(rv, rv); + + // check if this message needs junk classification + uint32_t processingFlags; + GetProcessingFlags(msgKey, &processingFlags); + + if (processingFlags & nsMsgProcessingFlags::ClassifyJunk) { + nsMsgDBFolder::OnMessageClassified(aMsgURI, aClassification, + aJunkPercent); + + if (aClassification == nsIJunkMailPlugin::JUNK) { + bool willMoveMessage = false; + + // don't do the move when we are opening up + // the junk mail folder or the trash folder + // or when manually classifying messages in those folders + if (!(mFlags & nsMsgFolderFlags::Junk || + mFlags & nsMsgFolderFlags::Trash)) { + bool moveOnSpam = false; + rv = spamSettings->GetMoveOnSpam(&moveOnSpam); + NS_ENSURE_SUCCESS(rv, rv); + if (moveOnSpam) { + nsCOMPtr<nsIMsgFolder> folder; + rv = FindFolder(spamFolderURI, getter_AddRefs(folder)); + NS_ENSURE_SUCCESS(rv, rv); + if (folder) { + rv = folder->SetFlag(nsMsgFolderFlags::Junk); + NS_ENSURE_SUCCESS(rv, rv); + mSpamKeysToMove.AppendElement(msgKey); + willMoveMessage = true; + } else { + // XXX TODO + // JUNK MAIL RELATED + // the listener should do + // rv = folder->SetFlag(nsMsgFolderFlags::Junk); + // NS_ENSURE_SUCCESS(rv,rv); + // mSpamKeysToMove.AppendElement(msgKey); + // willMoveMessage = true; + rv = + GetOrCreateJunkFolder(spamFolderURI, nullptr /* aListener */); + NS_ASSERTION(NS_SUCCEEDED(rv), "GetOrCreateJunkFolder failed"); + } + } + } + rv = spamSettings->LogJunkHit(msgHdr, willMoveMessage); + NS_ENSURE_SUCCESS(rv, rv); + } + } + } + + else // end of batch + { + // Parent will apply post bayes filters. + nsMsgDBFolder::OnMessageClassified(EmptyCString(), + nsIJunkMailPlugin::UNCLASSIFIED, 0); + nsTArray<RefPtr<nsIMsgDBHdr>> messages; + if (!mSpamKeysToMove.IsEmpty()) { + nsCOMPtr<nsIMsgFolder> folder; + if (!spamFolderURI.IsEmpty()) { + rv = FindFolder(spamFolderURI, getter_AddRefs(folder)); + NS_ENSURE_SUCCESS(rv, rv); + } + for (uint32_t keyIndex = 0; keyIndex < mSpamKeysToMove.Length(); + keyIndex++) { + // If an upstream filter moved this message, don't move it here. + nsMsgKey msgKey = mSpamKeysToMove.ElementAt(keyIndex); + nsMsgProcessingFlagType processingFlags; + GetProcessingFlags(msgKey, &processingFlags); + if (folder && !(processingFlags & nsMsgProcessingFlags::FilterToMove)) { + nsCOMPtr<nsIMsgDBHdr> mailHdr; + rv = GetMessageHeader(msgKey, getter_AddRefs(mailHdr)); + if (NS_SUCCEEDED(rv) && mailHdr) messages.AppendElement(mailHdr); + } else { + // We don't need the processing flag any more. + AndProcessingFlags(msgKey, ~nsMsgProcessingFlags::FilterToMove); + } + } + + if (folder) { + nsCOMPtr<nsIMsgCopyService> copySvc = + do_GetService("@mozilla.org/messenger/messagecopyservice;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = copySvc->CopyMessages( + this, messages, folder, true, + /*nsIMsgCopyServiceListener* listener*/ nullptr, nullptr, + false /*allowUndo*/); + NS_ASSERTION(NS_SUCCEEDED(rv), "CopyMessages failed"); + if (NS_FAILED(rv)) { + nsAutoCString logMsg( + "failed to copy junk messages to junk folder rv = "); + logMsg.AppendInt(static_cast<uint32_t>(rv), 16); + spamSettings->LogJunkString(logMsg.get()); + } + } + } + int32_t numNewMessages; + GetNumNewMessages(false, &numNewMessages); + SetNumNewMessages(numNewMessages - messages.Length()); + mSpamKeysToMove.Clear(); + // check if this is the inbox first... + if (mFlags & nsMsgFolderFlags::Inbox) PerformBiffNotifications(); + } + return NS_OK; +} + +NS_IMETHODIMP +nsMsgLocalMailFolder::GetFolderScanState(nsLocalFolderScanState* aState) { + NS_ENSURE_ARG_POINTER(aState); + + nsresult rv = GetMsgStore(getter_AddRefs(aState->m_msgStore)); + NS_ENSURE_SUCCESS(rv, rv); + aState->m_uidl = nullptr; + return rv; +} + +NS_IMETHODIMP +nsMsgLocalMailFolder::GetUidlFromFolder(nsLocalFolderScanState* aState, + nsIMsgDBHdr* aMsgDBHdr) { + bool more = false; + uint32_t size = 0, len = 0; + const char* accountKey = nullptr; + nsresult rv = + GetMsgInputStream(aMsgDBHdr, getter_AddRefs(aState->m_inputStream)); + NS_ENSURE_SUCCESS(rv, rv); + + mozilla::UniquePtr<nsLineBuffer<char>> lineBuffer(new nsLineBuffer<char>); + + aState->m_uidl = nullptr; + + aMsgDBHdr->GetMessageSize(&len); + while (len > 0) { + rv = NS_ReadLine(aState->m_inputStream.get(), lineBuffer.get(), + aState->m_header, &more); + if (NS_SUCCEEDED(rv)) { + size = aState->m_header.Length(); + if (!size) break; + // this isn't quite right - need to account for line endings + len -= size; + // account key header will always be before X_UIDL header + if (!accountKey) { + accountKey = + strstr(aState->m_header.get(), HEADER_X_MOZILLA_ACCOUNT_KEY); + if (accountKey) { + accountKey += strlen(HEADER_X_MOZILLA_ACCOUNT_KEY) + 2; + aState->m_accountKey = accountKey; + } + } else { + aState->m_uidl = strstr(aState->m_header.get(), X_UIDL); + if (aState->m_uidl) { + aState->m_uidl += X_UIDL_LEN + 2; // skip UIDL: header + break; + } + } + } + } + aState->m_inputStream->Close(); + aState->m_inputStream = nullptr; + return rv; +} + +/** + * Adds a message to the end of the folder, parsing it as it goes, and + * applying filters, if applicable. + */ +NS_IMETHODIMP +nsMsgLocalMailFolder::AddMessage(const char* aMessage, nsIMsgDBHdr** aHdr) { + NS_ENSURE_ARG_POINTER(aHdr); + AutoTArray<nsCString, 1> aMessages = {nsDependentCString(aMessage)}; + nsTArray<RefPtr<nsIMsgDBHdr>> hdrs; + nsresult rv = AddMessageBatch(aMessages, hdrs); + NS_ENSURE_SUCCESS(rv, rv); + NS_ADDREF(*aHdr = hdrs[0]); + return rv; +} + +NS_IMETHODIMP +nsMsgLocalMailFolder::AddMessageBatch( + const nsTArray<nsCString>& aMessages, + nsTArray<RefPtr<nsIMsgDBHdr>>& aHdrArray) { + aHdrArray.ClearAndRetainStorage(); + aHdrArray.SetCapacity(aMessages.Length()); + + nsCOMPtr<nsIMsgIncomingServer> server; + nsresult rv = GetServer(getter_AddRefs(server)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMsgPluggableStore> msgStore; + nsCOMPtr<nsIOutputStream> outFileStream; + nsCOMPtr<nsIMsgDBHdr> newHdr; + + rv = server->GetMsgStore(getter_AddRefs(msgStore)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMsgFolder> rootFolder; + rv = GetRootFolder(getter_AddRefs(rootFolder)); + NS_ENSURE_SUCCESS(rv, rv); + + bool isLocked; + + GetLocked(&isLocked); + if (isLocked) return NS_MSG_FOLDER_BUSY; + + AcquireSemaphore(static_cast<nsIMsgLocalMailFolder*>(this)); + + if (NS_SUCCEEDED(rv)) { + NS_ENSURE_SUCCESS(rv, rv); + for (uint32_t i = 0; i < aMessages.Length(); i++) { + RefPtr<nsParseNewMailState> newMailParser = new nsParseNewMailState; + NS_ENSURE_TRUE(newMailParser, NS_ERROR_OUT_OF_MEMORY); + if (!mGettingNewMessages) newMailParser->DisableFilters(); + rv = msgStore->GetNewMsgOutputStream(this, getter_AddRefs(newHdr), + getter_AddRefs(outFileStream)); + NS_ENSURE_SUCCESS(rv, rv); + + // Get a msgWindow. Proceed without one, but filter actions to imap + // folders will silently fail if not signed in and no window for a prompt. + nsCOMPtr<nsIMsgWindow> msgWindow; + nsCOMPtr<nsIMsgMailSession> mailSession = + do_GetService("@mozilla.org/messenger/services/session;1", &rv); + if (NS_SUCCEEDED(rv)) + mailSession->GetTopmostMsgWindow(getter_AddRefs(msgWindow)); + + rv = newMailParser->Init(rootFolder, this, msgWindow, newHdr, + outFileStream); + + uint32_t bytesWritten; + uint32_t messageLen = aMessages[i].Length(); + outFileStream->Write(aMessages[i].get(), messageLen, &bytesWritten); + rv = newMailParser->BufferInput(aMessages[i].get(), messageLen); + NS_ENSURE_SUCCESS(rv, rv); + rv = newMailParser->Flush(); + NS_ENSURE_SUCCESS(rv, rv); + + FinishNewLocalMessage(outFileStream, newHdr, msgStore, newMailParser); + outFileStream->Close(); + outFileStream = nullptr; + newMailParser->OnStopRequest(nullptr, NS_OK); + newMailParser->EndMsgDownload(); + aHdrArray.AppendElement(newHdr); + } + } + ReleaseSemaphore(static_cast<nsIMsgLocalMailFolder*>(this)); + return rv; +} + +nsresult nsMsgLocalMailFolder::FinishNewLocalMessage( + nsIOutputStream* aOutputStream, nsIMsgDBHdr* aNewHdr, + nsIMsgPluggableStore* aMsgStore, nsParseMailMessageState* aParseMsgState) { + uint32_t bytesWritten; + aOutputStream->Write(MSG_LINEBREAK, MSG_LINEBREAK_LEN, &bytesWritten); + if (aParseMsgState) + aParseMsgState->ParseAFolderLine(MSG_LINEBREAK, MSG_LINEBREAK_LEN); + return aMsgStore->FinishNewMessage(aOutputStream, aNewHdr); +} + +NS_IMETHODIMP +nsMsgLocalMailFolder::WarnIfLocalFileTooBig(nsIMsgWindow* aWindow, + int64_t aSpaceRequested, + bool* aTooBig) { + NS_ENSURE_ARG_POINTER(aTooBig); + + *aTooBig = true; + nsCOMPtr<nsIMsgPluggableStore> msgStore; + nsresult rv = GetMsgStore(getter_AddRefs(msgStore)); + NS_ENSURE_SUCCESS(rv, rv); + bool spaceAvailable = false; + // check if we have a reasonable amount of space left + rv = msgStore->HasSpaceAvailable(this, aSpaceRequested, &spaceAvailable); + if (NS_SUCCEEDED(rv) && spaceAvailable) { + *aTooBig = false; + } else if (rv == NS_ERROR_FILE_TOO_BIG) { + ThrowAlertMsg("mailboxTooLarge", aWindow); + } else { + ThrowAlertMsg("outOfDiskSpace", aWindow); + } + return NS_OK; +} + +NS_IMETHODIMP nsMsgLocalMailFolder::FetchMsgPreviewText( + nsTArray<nsMsgKey> const& aKeysToFetch, nsIUrlListener* aUrlListener, + bool* aAsyncResults) { + NS_ENSURE_ARG_POINTER(aAsyncResults); + + *aAsyncResults = false; + nsCOMPtr<nsIInputStream> inputStream; + + for (uint32_t i = 0; i < aKeysToFetch.Length(); i++) { + nsCOMPtr<nsIMsgDBHdr> msgHdr; + nsCString prevBody; + nsresult rv = GetMessageHeader(aKeysToFetch[i], getter_AddRefs(msgHdr)); + NS_ENSURE_SUCCESS(rv, rv); + // ignore messages that already have a preview body. + msgHdr->GetStringProperty("preview", prevBody); + if (!prevBody.IsEmpty()) continue; + + rv = GetMsgInputStream(msgHdr, getter_AddRefs(inputStream)); + NS_ENSURE_SUCCESS(rv, rv); + rv = GetMsgPreviewTextFromStream(msgHdr, inputStream); + NS_ENSURE_SUCCESS(rv, rv); + } + return NS_OK; +} + +NS_IMETHODIMP nsMsgLocalMailFolder::AddKeywordsToMessages( + const nsTArray<RefPtr<nsIMsgDBHdr>>& aMessages, + const nsACString& aKeywords) { + nsresult rv = nsMsgDBFolder::AddKeywordsToMessages(aMessages, aKeywords); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr<nsIMsgPluggableStore> msgStore; + rv = GetMsgStore(getter_AddRefs(msgStore)); + NS_ENSURE_SUCCESS(rv, rv); + return msgStore->ChangeKeywords(aMessages, aKeywords, true /* add */); +} + +NS_IMETHODIMP nsMsgLocalMailFolder::RemoveKeywordsFromMessages( + const nsTArray<RefPtr<nsIMsgDBHdr>>& aMessages, + const nsACString& aKeywords) { + nsresult rv = nsMsgDBFolder::RemoveKeywordsFromMessages(aMessages, aKeywords); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr<nsIMsgPluggableStore> msgStore; + rv = GetMsgStore(getter_AddRefs(msgStore)); + NS_ENSURE_SUCCESS(rv, rv); + return msgStore->ChangeKeywords(aMessages, aKeywords, false /* remove */); +} + +NS_IMETHODIMP nsMsgLocalMailFolder::UpdateNewMsgHdr(nsIMsgDBHdr* aOldHdr, + nsIMsgDBHdr* aNewHdr) { + NS_ENSURE_ARG_POINTER(aOldHdr); + NS_ENSURE_ARG_POINTER(aNewHdr); + // Preserve any properties set on the message. + CopyPropertiesToMsgHdr(aNewHdr, aOldHdr, true); + + // Preserve keywords manually, since they are set as don't preserve. + nsCString keywordString; + aOldHdr->GetStringProperty("keywords", keywordString); + aNewHdr->SetStringProperty("keywords", keywordString); + + // If the junk score was set by the plugin, remove junkscore to force a new + // junk analysis, this time using the body. + nsCString junkScoreOrigin; + aOldHdr->GetStringProperty("junkscoreorigin", junkScoreOrigin); + if (junkScoreOrigin.EqualsLiteral("plugin")) + aNewHdr->SetStringProperty("junkscore", ""_ns); + + return NS_OK; +} |