/* -*- 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 "nsArrayEnumerator.h" #include "nsLocalMailFolder.h" #include "nsMsgLocalFolderHdrs.h" #include "nsMsgFolderFlags.h" #include "nsMsgMessageFlags.h" #include "prprf.h" #include "prmem.h" #include "nsIArray.h" #include "nsIServiceManager.h" #include "nsIMailboxService.h" #include "nsParseMailbox.h" #include "nsIMsgAccountManager.h" #include "nsIMsgWindow.h" #include "nsCOMPtr.h" #include "nsIRDFService.h" #include "nsMsgDBCID.h" #include "nsMsgUtils.h" #include "nsLocalUtils.h" #include "nsIPop3IncomingServer.h" #include "nsILocalMailIncomingServer.h" #include "nsIMsgIncomingServer.h" #include "nsMsgBaseCID.h" #include "nsMsgLocalCID.h" #include "nsStringGlue.h" #include "nsIMsgFolderCacheElement.h" #include "nsUnicharUtils.h" #include "nsMsgUtils.h" #include "nsICopyMsgStreamListener.h" #include "nsIMsgCopyService.h" #include "nsMsgTxn.h" #include "nsIMessenger.h" #include "nsMsgBaseCID.h" #include "nsNativeCharsetUtils.h" #include "nsIDocShell.h" #include "nsIPrompt.h" #include "nsIInterfaceRequestor.h" #include "nsIInterfaceRequestorUtils.h" #include "nsIPop3URL.h" #include "nsIMsgMailSession.h" #include "nsIMsgFolderCompactor.h" #include "nsNetCID.h" #include "nsIMsgMailNewsUrl.h" #include "nsISpamSettings.h" #include "nsINoIncomingServer.h" #include "nsNativeCharsetUtils.h" #include "nsMailHeaders.h" #include "nsCOMArray.h" #include "nsILineInputStream.h" #include "nsIFileStreams.h" #include "nsAutoPtr.h" #include "nsIRssIncomingServer.h" #include "nsNetUtil.h" #include "nsIMsgFolderNotificationService.h" #include "nsReadLine.h" #include "nsArrayUtils.h" #include "nsIMsgTraitService.h" #include "nsIStringEnumerator.h" #include "mozilla/Services.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 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) //////////////////////////////////////////////////////////////////////////////// NS_IMETHODIMP nsMsgLocalMailFolder::Init(const char* aURI) { return nsMsgDBFolder::Init(aURI); } nsresult nsMsgLocalMailFolder::CreateChildFromURI(const nsCString &uri, nsIMsgFolder **folder) { nsMsgLocalMailFolder *newFolder = new nsMsgLocalMailFolder; if (!newFolder) return NS_ERROR_OUT_OF_MEMORY; NS_ADDREF(*folder = newFolder); newFolder->Init(uri.get()); 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 notifier( do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID)); 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 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(nsISimpleEnumerator **aResult) { if (!mInitialized) { nsCOMPtr server; nsresult rv = GetServer(getter_AddRefs(server)); NS_ENSURE_SUCCESS(rv, NS_MSG_INVALID_OR_MISSING_SERVER); nsCOMPtr 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); nsCOMPtr 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 server; rv = GetServer(getter_AddRefs(server)); NS_ENSURE_SUCCESS(rv, NS_MSG_INVALID_OR_MISSING_SERVER); nsCOMPtr 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 aResult ? NS_NewArrayEnumerator(aResult, mSubFolders) : NS_ERROR_NULL_POINTER; } nsresult nsMsgLocalMailFolder::GetDatabase() { nsCOMPtr 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 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 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 msgDBService = do_GetService(NS_MSGDB_SERVICE_CONTRACTID, &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 dbFolderInfo; nsCOMPtr 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 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) { mDatabase->RemoveListener(this); //we need to null out the db so that parsing gets kicked off again. 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 returnedDb; rv = GetDatabaseWithReparse(this, aWindow, getter_AddRefs(returnedDb)); if (NS_SUCCEEDED(rv)) NotifyFolderEvent(mFolderLoadedAtom); } 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(mFolderLoadedAtom); 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(mFolderLoadedAtom); } 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::GetMessages(nsISimpleEnumerator **result) { nsCOMPtr msgDB; nsresult rv = GetDatabaseWOReparse(getter_AddRefs(msgDB)); return NS_SUCCEEDED(rv) ? msgDB->EnumerateMessages(result) : rv; } NS_IMETHODIMP nsMsgLocalMailFolder::GetFolderURL(nsACString& aUrl) { nsresult rv; nsCOMPtr 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; nsCOMPtr 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); // get the corresponding RDF resource // RDF will create the folder resource if it doesn't already exist nsCOMPtr rdf = do_GetService("@mozilla.org/rdf/rdf-service;1", &rv); NS_ENSURE_SUCCESS(rv,rv); nsCOMPtr resource; rv = rdf->GetResource(parentName, getter_AddRefs(resource)); NS_ENSURE_SUCCESS(rv,rv); msgParent = do_QueryInterface(resource, &rv); 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 newFolder; nsresult rv = CreateSubfolderInternal(folderName, msgWindow, getter_AddRefs(newFolder)); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr notifier(do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID)); 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 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)) { nsCOMPtr child = *aNewFolder; //we need to notify explicitly the flag change because it failed when we did AddSubfolder child->OnFlagChange(mFlags); child->SetPrettyName(folderName); //because empty trash will create a new trash folder NotifyItemAdded(child); if (aNewFolder) child.swap(*aNewFolder); } return rv; } NS_IMETHODIMP nsMsgLocalMailFolder::CompactAll(nsIUrlListener *aListener, nsIMsgWindow *aMsgWindow, bool aCompactOfflineAlso) { nsresult rv = NS_OK; nsCOMPtr folderArray; nsCOMPtr rootFolder; nsCOMPtr allDescendents; rv = GetRootFolder(getter_AddRefs(rootFolder)); nsCOMPtr msgStore; GetMsgStore(getter_AddRefs(msgStore)); bool storeSupportsCompaction; msgStore->GetSupportsCompaction(&storeSupportsCompaction); if (!storeSupportsCompaction) return NotifyCompactCompleted(); if (NS_SUCCEEDED(rv) && rootFolder) { rv = rootFolder->GetDescendants(getter_AddRefs(allDescendents)); NS_ENSURE_SUCCESS(rv, rv); uint32_t cnt = 0; rv = allDescendents->GetLength(&cnt); NS_ENSURE_SUCCESS(rv, rv); folderArray = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv); int64_t expungedBytes = 0; for (uint32_t i = 0; i < cnt; i++) { nsCOMPtr folder = do_QueryElementAt(allDescendents, i, &rv); NS_ENSURE_SUCCESS(rv, rv); expungedBytes = 0; if (folder) rv = folder->GetExpungedBytes(&expungedBytes); NS_ENSURE_SUCCESS(rv, rv); if (expungedBytes > 0) rv = folderArray->AppendElement(folder, false); } rv = folderArray->GetLength(&cnt); NS_ENSURE_SUCCESS(rv,rv); if (cnt == 0) return NotifyCompactCompleted(); } nsCOMPtr folderCompactor = do_CreateInstance(NS_MSGLOCALFOLDERCOMPACTOR_CONTRACTID, &rv); NS_ENSURE_SUCCESS(rv, rv); return folderCompactor->CompactFolders(folderArray, nullptr, aListener, aMsgWindow); } NS_IMETHODIMP nsMsgLocalMailFolder::Compact(nsIUrlListener *aListener, nsIMsgWindow *aMsgWindow) { nsCOMPtr msgStore; nsresult rv = GetMsgStore(getter_AddRefs(msgStore)); NS_ENSURE_SUCCESS(rv, rv); bool supportsCompaction; msgStore->GetSupportsCompaction(&supportsCompaction); if (supportsCompaction) return msgStore->CompactFolder(this, aListener, aMsgWindow); return NS_OK; } NS_IMETHODIMP nsMsgLocalMailFolder::EmptyTrash(nsIMsgWindow *msgWindow, nsIUrlListener *aListener) { nsresult rv; nsCOMPtr 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) { nsCOMPtr enumerator; rv = trashFolder->GetSubFolders(getter_AddRefs(enumerator)); NS_ENSURE_SUCCESS(rv,rv); // Any folders to deal with? bool hasMore; rv = enumerator->HasMoreElements(&hasMore); if (NS_FAILED(rv) || !hasMore) return NS_OK; } nsCOMPtr parentFolder; rv = trashFolder->GetParent(getter_AddRefs(parentFolder)); if (NS_SUCCEEDED(rv) && parentFolder) { nsCOMPtr transferInfo; trashFolder->GetDBTransferInfo(getter_AddRefs(transferInfo)); trashFolder->SetParent(nullptr); parentFolder->PropagateDelete(trashFolder, true, msgWindow); parentFolder->CreateSubfolder(NS_LITERAL_STRING("Trash"), nullptr); nsCOMPtr newTrashFolder; rv = GetTrashFolder(getter_AddRefs(newTrashFolder)); if (NS_SUCCEEDED(rv) && newTrashFolder) { nsCOMPtr localTrash = do_QueryInterface(newTrashFolder); 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 dbFolderInfo; nsCOMPtr 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 parentFolder; nsCOMPtr thisFolder; rv = QueryInterface(NS_GET_IID(nsIMsgFolder), (void **) 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::DeleteSubFolders(nsIArray *folders, nsIMsgWindow *msgWindow) { nsresult rv; bool isChildOfTrash; IsChildOfTrash(&isChildOfTrash); // we don't allow multiple folder selection so this is ok. nsCOMPtr folder = do_QueryElementAt(folders, 0); uint32_t folderFlags = 0; if (folder) folder->GetFlags(&folderFlags); // when deleting from trash, or virtual folder, just delete it. if (isChildOfTrash || folderFlags & nsMsgFolderFlags::Virtual) return nsMsgDBFolder::DeleteSubFolders(folders, msgWindow); nsCOMPtr trashFolder; rv = GetTrashFolder(getter_AddRefs(trashFolder)); if (NS_SUCCEEDED(rv)) { if (folder) { nsCOMPtr copyService(do_GetService(NS_MSGCOPYSERVICE_CONTRACTID, &rv)); NS_ENSURE_SUCCESS(rv, rv); rv = copyService->CopyFolders(folders, 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 docShell; aMsgWindow->GetRootDocShell(getter_AddRefs(docShell)); if (docShell) { bool confirmDeletion = true; nsresult rv; nsCOMPtr pPrefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); NS_ENSURE_SUCCESS(rv, rv); pPrefBranch->GetBoolPref("mailnews.confirm.moveFoldersToTrash", &confirmDeletion); if (confirmDeletion) { nsCOMPtr bundleService = mozilla::services::GetStringBundleService(); NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED); nsCOMPtr 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); const char16_t *formatStrings[1] = { folderName.get() }; nsAutoString deleteFolderDialogTitle; rv = bundle->GetStringFromName( u"pop3DeleteFolderDialogTitle", getter_Copies(deleteFolderDialogTitle)); NS_ENSURE_SUCCESS(rv, rv); nsAutoString deleteFolderButtonLabel; rv = bundle->GetStringFromName( u"pop3DeleteFolderButtonLabel", getter_Copies(deleteFolderButtonLabel)); NS_ENSURE_SUCCESS(rv, rv); nsAutoString confirmationStr; rv = bundle->FormatStringFromName( u"pop3MoveFolderToTrash", formatStrings, 1, getter_Copies(confirmationStr)); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr 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 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 msgStore; nsCOMPtr 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 /*caseInsenstive*/, &changed); if (changed) AlertFilterChanged(msgWindow); if (count > 0) newFolder->RenameSubFolders(msgWindow, this); // Discover the subfolders inside this folder (this is recursive) nsCOMPtr dummy; newFolder->GetSubFolders(getter_AddRefs(dummy)); // the newFolder should have the same flags newFolder->SetFlags(mFlags); if (parentFolder) { SetParent(nullptr); parentFolder->PropagateDelete(this, false, msgWindow); parentFolder->NotifyItemAdded(newFolder); } SetFilePath(nullptr); // forget our path, since this folder object renamed itself nsCOMPtr folderRenameAtom = MsgGetAtom("RenameCompleted"); newFolder->NotifyFolderEvent(folderRenameAtom); nsCOMPtr notifier(do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID)); 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); nsCOMPtr enumerator; rv = oldFolder->GetSubFolders(getter_AddRefs(enumerator)); NS_ENSURE_SUCCESS(rv, rv); bool hasMore; while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMore)) && hasMore) { nsCOMPtr item; enumerator->GetNext(getter_AddRefs(item)); nsCOMPtr msgFolder(do_QueryInterface(item)); if (!msgFolder) continue; nsString folderName; rv = msgFolder->GetName(folderName); nsCOMPtr newFolder; AddSubfolder(folderName, getter_AddRefs(newFolder)); if (newFolder) { newFolder->SetPrettyName(folderName); bool changed = false; msgFolder->MatchOrChangeFilterDestination(newFolder, true /*caseInsenstive*/, &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 msgDBService = do_GetService(NS_MSGDB_SERVICE_CONTRACTID, &rv); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr 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 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 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->GetStringProperty("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->SetStringProperty("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(kFolderSizeAtom, 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 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 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(nsIArray *messages, nsIMsgWindow *msgWindow, bool deleteStorage, bool isMove, nsIMsgCopyServiceListener* listener, bool allowUndo) { NS_ENSURE_ARG_POINTER(messages); uint32_t messageCount; nsresult rv = messages->GetLength(&messageCount); NS_ENSURE_SUCCESS(rv, rv); // shift delete case - (delete to trash is handled in EndMove) // this is also the case when applying retention settings. if (deleteStorage && !isMove) { MarkMsgsOnPop3Server(messages, POP3_DELETE); } bool isTrashFolder = mFlags & nsMsgFolderFlags::Trash; // notify on delete from trash and shift-delete if (!isMove && (deleteStorage || isTrashFolder)) { nsCOMPtr notifier(do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID)); if (notifier) notifier->NotifyMsgsDeleted(messages); } if (!deleteStorage && !isTrashFolder) { nsCOMPtr trashFolder; rv = GetTrashFolder(getter_AddRefs(trashFolder)); if (NS_SUCCEEDED(rv)) { nsCOMPtr copyService = do_GetService(NS_MSGCOPYSERVICE_CONTRACTID, &rv); NS_ENSURE_SUCCESS(rv, rv); return copyService->CopyMessages(this, messages, trashFolder, true, listener, msgWindow, allowUndo); } } else { nsCOMPtr msgDB; rv = GetDatabaseWOReparse(getter_AddRefs(msgDB)); if (NS_SUCCEEDED(rv)) { if (deleteStorage && isMove && GetDeleteFromServerOnMove()) MarkMsgsOnPop3Server(messages, POP3_DELETE); nsCOMPtr msgSupport; rv = EnableNotifications(allMessageCountNotifications, false, true /*dbBatching*/); if (NS_SUCCEEDED(rv)) { nsCOMPtr msgStore; rv = GetMsgStore(getter_AddRefs(msgStore)); if (NS_SUCCEEDED(rv)) { rv = msgStore->DeleteMessages(messages); nsCOMPtr msgDBHdr; for (uint32_t i = 0; i < messageCount; ++i) { msgDBHdr = do_QueryElementAt(messages, i, &rv); rv = msgDB->DeleteHeader(msgDBHdr, nullptr, false, true); } } } else if (rv == NS_MSG_FOLDER_BUSY) ThrowAlertMsg("deletingMsgsFailed", msgWindow); // we are the source folder here for a move or shift delete //enable notifications because that will close the file stream // we've been caching, mark the db as valid, and commit it. EnableNotifications(allMessageCountNotifications, true, true /*dbBatching*/); if (!isMove) NotifyFolderEvent(NS_SUCCEEDED(rv) ? mDeleteOrMoveMsgCompletedAtom : mDeleteOrMoveMsgFailedAtom); if (msgWindow && !isMove) AutoCompact(msgWindow); } } if (msgWindow && !isMove && (deleteStorage || isTrashFolder)) { // Clear undo and redo stack. nsCOMPtr 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; default: return NS_ERROR_UNEXPECTED; } nsresult rv = nsMsgDBFolder::AddMessageDispositionState(aMessage, aDispositionFlag); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr msgStore; rv = GetMsgStore(getter_AddRefs(msgStore)); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr messages(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv)); NS_ENSURE_SUCCESS(rv, rv); messages->AppendElement(aMessage, false); return msgStore->ChangeFlags(messages, msgFlag, true); } NS_IMETHODIMP nsMsgLocalMailFolder::MarkMessagesRead(nsIArray *aMessages, bool aMarkRead) { nsresult rv = nsMsgDBFolder::MarkMessagesRead(aMessages, aMarkRead); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr msgStore; rv = GetMsgStore(getter_AddRefs(msgStore)); NS_ENSURE_SUCCESS(rv, rv); return msgStore->ChangeFlags(aMessages, nsMsgMessageFlags::Read, aMarkRead); } NS_IMETHODIMP nsMsgLocalMailFolder::MarkMessagesFlagged(nsIArray *aMessages, bool aMarkFlagged) { nsresult rv = nsMsgDBFolder::MarkMessagesFlagged(aMessages, aMarkFlagged); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr 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); nsMsgKey *thoseMarked = nullptr; uint32_t numMarked = 0; EnableNotifications(allMessageCountNotifications, false, true /*dbBatching*/); rv = mDatabase->MarkAllRead(&numMarked, &thoseMarked); EnableNotifications(allMessageCountNotifications, true, true /*dbBatching*/); if (NS_FAILED(rv) || !numMarked || !thoseMarked) return rv; do { nsCOMPtr messages; rv = MsgGetHdrsFromKeys(mDatabase, thoseMarked, numMarked, getter_AddRefs(messages)); if (NS_FAILED(rv)) break; nsCOMPtr msgStore; rv = GetMsgStore(getter_AddRefs(msgStore)); if (NS_FAILED(rv)) break; rv = msgStore->ChangeFlags(messages, nsMsgMessageFlags::Read, true); if (NS_FAILED(rv)) break; mDatabase->Commit(nsMsgDBCommitType::kLargeCommit); // Setup a undo-state if (aMsgWindow) rv = AddMarkAllReadUndoAction(aMsgWindow, thoseMarked, numMarked); } while (false); free(thoseMarked); return rv; } NS_IMETHODIMP nsMsgLocalMailFolder::MarkThreadRead(nsIMsgThread *thread) { nsresult rv = GetDatabase(); NS_ENSURE_SUCCESS(rv, rv); nsMsgKey *thoseMarked = nullptr; uint32_t numMarked = 0; rv = mDatabase->MarkThreadRead(thread, nullptr, &numMarked, &thoseMarked); if (NS_FAILED(rv) || !numMarked || !thoseMarked) return rv; do { nsCOMPtr messages; rv = MsgGetHdrsFromKeys(mDatabase, thoseMarked, numMarked, getter_AddRefs(messages)); if (NS_FAILED(rv)) break; nsCOMPtr msgStore; rv = GetMsgStore(getter_AddRefs(msgStore)); if (NS_FAILED(rv)) break; rv = msgStore->ChangeFlags(messages, nsMsgMessageFlags::Read, true); if (NS_FAILED(rv)) break; mDatabase->Commit(nsMsgDBCommitType::kLargeCommit); } while (false); free(thoseMarked); return rv; } nsresult nsMsgLocalMailFolder::InitCopyState(nsISupports* aSupport, nsIArray* messages, bool isMove, nsIMsgCopyServiceListener* listener, nsIMsgWindow *msgWindow, bool isFolder, bool allowUndo) { nsCOMPtr 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 msgDB; GetDatabaseWOReparse(getter_AddRefs(msgDB)); bool isLocked; GetLocked(&isLocked); if (isLocked) return NS_MSG_FOLDER_BUSY; AcquireSemaphore(static_cast(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; //Before we continue we should verify that there is enough diskspace. //XXX How do we do this? nsresult rv; mCopyState->m_srcSupport = do_QueryInterface(aSupport, &rv); NS_ENSURE_SUCCESS(rv, rv); mCopyState->m_messages = messages; mCopyState->m_curCopyIndex = 0; mCopyState->m_isMove = isMove; mCopyState->m_isFolder = isFolder; mCopyState->m_allowUndo = allowUndo; mCopyState->m_msgWindow = msgWindow; rv = messages->GetLength(&mCopyState->m_totalMsgCount); if (listener) mCopyState->m_listener = do_QueryInterface(listener, &rv); mCopyState->m_copyingMultipleMessages = false; mCopyState->m_wholeMsgInStream = false; // If we have source messages then we need destination messages too. if (messages) mCopyState->m_destMessages = do_CreateInstance(NS_ARRAY_CONTRACTID); return rv; } 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(mFolderLoadedAtom); (void) RefreshSizeOnDisk(); // we are the destination folder for a move/copy bool haveSemaphore; nsresult rv = TestSemaphore(static_cast(this), &haveSemaphore); if (NS_SUCCEEDED(rv) && haveSemaphore) ReleaseSemaphore(static_cast(this)); if (mCopyState && !mCopyState->m_newMsgKeywords.IsEmpty() && mCopyState->m_newHdr) { nsCOMPtr messageArray(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv)); NS_ENSURE_TRUE(messageArray, rv); messageArray->AppendElement(mCopyState->m_newHdr, false); AddKeywordsToMessages(messageArray, mCopyState->m_newMsgKeywords); } if (moveCopySucceeded && mDatabase) { mDatabase->SetSummaryValid(true); (void) CloseDBIfFolderNotOpen(); } delete mCopyState; mCopyState = nullptr; nsCOMPtr copyService = do_GetService(NS_MSGCOPYSERVICE_CONTRACTID, &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(mDeleteOrMoveMsgFailedAtom); OnCopyCompleted(srcSupports, false); return false; } return true; } NS_IMETHODIMP nsMsgLocalMailFolder::CopyMessages(nsIMsgFolder* srcFolder, nsIArray* messages, bool isMove, nsIMsgWindow *msgWindow, nsIMsgCopyServiceListener* listener, bool isFolder, bool allowUndo) { nsCOMPtr 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(mDeleteOrMoveMsgFailedAtom); return OnCopyCompleted(srcSupport, false); } UpdateTimestamps(allowUndo); nsCString protocolType; rv = srcFolder->GetURI(protocolType); protocolType.SetLength(protocolType.FindChar(':')); bool needOfflineBody = (WeAreOffline() && (MsgLowerCaseEqualsLiteral(protocolType, "imap") || MsgLowerCaseEqualsLiteral(protocolType, "news"))); int64_t totalMsgSize = 0; uint32_t numMessages = 0; messages->GetLength(&numMessages); for (uint32_t i = 0; i < numMessages; i++) { nsCOMPtr message(do_QueryElementAt(messages, i, &rv)); if (NS_SUCCEEDED(rv) && message) { 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; if (needOfflineBody) { bool hasMsgOffline = false; message->GetMessageKey(&key); srcFolder->HasMsgOffline(key, &hasMsgOffline); if (!hasMsgOffline) { if (isMove) srcFolder->NotifyFolderEvent(mDeleteOrMoveMsgFailedAtom); 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 msgStore; rv = GetMsgStore(getter_AddRefs(msgStore)); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr undoTxn; nsCOMPtr dstHdrs; rv = msgStore->CopyMessages(isMove, messages, this, listener, getter_AddRefs(dstHdrs), getter_AddRefs(undoTxn), &storeDidCopy); if (storeDidCopy) { NS_ASSERTION(undoTxn, "if store does copy, it needs to add undo action"); if (msgWindow && undoTxn) { nsCOMPtr txnMgr; msgWindow->GetTransactionManager(getter_AddRefs(txnMgr)); if (txnMgr) txnMgr->DoTransaction(undoTxn); } if (isMove) srcFolder->NotifyFolderEvent(NS_SUCCEEDED(rv) ? mDeleteOrMoveMsgCompletedAtom : mDeleteOrMoveMsgFailedAtom); 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 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); } } } 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, false /*dbBatching*/); //dest folder doesn't need db batching // sort the message array by key uint32_t numMsgs = 0; messages->GetLength(&numMsgs); nsTArray keyArray(numMsgs); if (numMsgs > 1) { for (uint32_t i = 0; i < numMsgs; i++) { nsCOMPtr aMessage = do_QueryElementAt(messages, i, &rv); if (NS_SUCCEEDED(rv) && aMessage) { nsMsgKey key; aMessage->GetMessageKey(&key); keyArray.AppendElement(key); } } keyArray.Sort(); nsCOMPtr sortedMsgs(do_CreateInstance(NS_ARRAY_CONTRACTID)); rv = MessagesInKeyOrder(keyArray, srcFolder, sortedMsgs); NS_ENSURE_SUCCESS(rv, rv); rv = InitCopyState(srcSupport, sortedMsgs, isMove, listener, msgWindow, isFolder, allowUndo); } else rv = InitCopyState(srcSupport, messages, isMove, listener, msgWindow, isFolder, allowUndo); if (NS_FAILED(rv)) { ThrowAlertMsg("operationFailedFolderBusy", msgWindow); (void) OnCopyCompleted(srcSupport, false); return rv; } if (!MsgLowerCaseEqualsLiteral(protocolType, "mailbox")) { mCopyState->m_dummyEnvelopeNeeded = true; nsParseMailMessageState* parseMsgState = new nsParseMailMessageState(); if (parseMsgState) { nsCOMPtr 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 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 (numMsgs > 1 && ((MsgLowerCaseEqualsLiteral(protocolType, "imap") && !WeAreOffline()) || MsgLowerCaseEqualsLiteral(protocolType, "mailbox"))) { mCopyState->m_copyingMultipleMessages = true; rv = CopyMessagesTo(mCopyState->m_messages, keyArray, msgWindow, this, isMove); if (NS_FAILED(rv)) { NS_ERROR("copy message failed"); (void) OnCopyCompleted(srcSupport, false); } } else { nsCOMPtr msgSupport = do_QueryElementAt(mCopyState->m_messages, 0); if (msgSupport) { rv = CopyMessageTo(msgSupport, this, 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(mDeleteOrMoveMsgFailedAtom); EnableNotifications(allMessageCountNotifications, true, false /*dbBatching*/); //dest folder doesn't need db batching } 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 ) { mInitialized = true; nsString folderName; srcFolder->GetName(folderName); nsCOMPtr newMsgFolder; nsresult rv = CreateSubfolderInternal(folderName, msgWindow, getter_AddRefs(newMsgFolder)); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr messages; rv = srcFolder->GetMessages(getter_AddRefs(messages)); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr msgArray(do_CreateInstance(NS_ARRAY_CONTRACTID)); bool hasMoreElements = false; nsCOMPtr aSupport; if (messages) rv = messages->HasMoreElements(&hasMoreElements); while (NS_SUCCEEDED(rv) && hasMoreElements) { rv = messages->GetNext(getter_AddRefs(aSupport)); NS_ENSURE_SUCCESS(rv, rv); rv = msgArray->AppendElement(aSupport, false); NS_ENSURE_SUCCESS(rv, rv); rv = messages->HasMoreElements(&hasMoreElements); } uint32_t numMsgs=0; msgArray->GetLength(&numMsgs); if (numMsgs > 0 ) //if only srcFolder has messages.. newMsgFolder->CopyMessages(srcFolder, msgArray, false, msgWindow, listener, true /* is folder*/, false /* allowUndo */); else { nsCOMPtr 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 srcSupports = do_QueryInterface(newMsgFolder); localFolder->CopyAllSubFolders(srcFolder, msgWindow, listener); 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 ) { nsCOMPtr enumerator; nsresult rv = srcFolder->GetSubFolders(getter_AddRefs(enumerator)); NS_ENSURE_SUCCESS(rv, rv); bool hasMore; while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMore)) && hasMore) { nsCOMPtr item; enumerator->GetNext(getter_AddRefs(item)); nsCOMPtr folder(do_QueryInterface(item)); if (folder) CopyFolderAcrossServer(folder, msgWindow, listener); } return rv; } NS_IMETHODIMP nsMsgLocalMailFolder::CopyFolder( nsIMsgFolder* srcFolder, bool isMoveFolder, nsIMsgWindow *msgWindow, nsIMsgCopyServiceListener* listener) { NS_ENSURE_ARG_POINTER(srcFolder); // isMoveFolder == true when "this" and srcFolder are on same server return isMoveFolder ? CopyFolderLocal(srcFolder, isMoveFolder, msgWindow, listener ) : CopyFolderAcrossServer(srcFolder, msgWindow, listener ); } 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; rv = 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 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 fileSupport(do_QueryInterface(aFile, &rv)); aFile->GetFileSize(&fileSize); if (!CheckIfSpaceForCopy(msgWindow, nullptr, fileSupport, false, fileSize)) return NS_OK; nsCOMPtr messages(do_CreateInstance(NS_ARRAY_CONTRACTID)); if (msgToReplace) messages->AppendElement(msgToReplace, false); rv = InitCopyState(fileSupport, messages, msgToReplace ? true : false, listener, msgWindow, false, false); if (NS_SUCCEEDED(rv)) { if (mCopyState) mCopyState->m_newMsgKeywords = aNewMsgKeywords; parseMsgState = new nsParseMailMessageState(); NS_ENSURE_TRUE(parseMsgState, NS_ERROR_OUT_OF_MEMORY); nsCOMPtr msgDb; mCopyState->m_parseMsgState = parseMsgState; GetDatabaseWOReparse(getter_AddRefs(msgDb)); if (msgDb) parseMsgState->SetMailDB(msgDb); nsCOMPtr 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 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(nullptr); 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; } nsresult nsMsgLocalMailFolder::DeleteMessage(nsISupports *message, nsIMsgWindow *msgWindow, bool deleteStorage, bool commit) { nsresult rv = NS_OK; if (deleteStorage) { nsCOMPtr msgDBHdr(do_QueryInterface(message, &rv)); if (NS_SUCCEEDED(rv)) { GetDatabase(); if (mDatabase) rv = mDatabase->DeleteHeader(msgDBHdr, nullptr, commit, true); } } return rv; } NS_IMETHODIMP nsMsgLocalMailFolder::GetNewMessages(nsIMsgWindow *aWindow, nsIUrlListener *aListener) { nsCOMPtr server; nsresult rv = GetServer(getter_AddRefs(server)); NS_ENSURE_SUCCESS(rv, NS_MSG_INVALID_OR_MISSING_SERVER); nsCOMPtr 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 rssServer = do_QueryInterface(server, &rv); if (NS_SUCCEEDED(rv)) return localMailServer->GetNewMail(aWindow, aListener, this, nullptr); nsCOMPtr inbox; nsCOMPtr rootFolder; rv = server->GetRootMsgFolder(getter_AddRefs(rootFolder)); if (NS_SUCCEEDED(rv) && rootFolder) { rootFolder->GetFolderWithFlags(nsMsgFolderFlags::Inbox, getter_AddRefs(inbox)); } nsCOMPtr localInbox = do_QueryInterface(inbox, &rv); if (NS_SUCCEEDED(rv)) { bool valid = false; nsCOMPtr 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)) { db->GetSummaryValid(&valid); rv = valid ? localMailServer->GetNewMail(aWindow, aListener, inbox, nullptr) : localInbox->SetCheckForNewMessagesAfterParsing(true); } } return rv; } nsresult nsMsgLocalMailFolder::WriteStartOfNewMessage() { nsCOMPtr seekableStream = do_QueryInterface(mCopyState->m_fileStream); int64_t filePos; seekableStream->Tell(&filePos); // CopyFileMessage() and CopyMessages() from servers other than pop3 if (mCopyState->m_parseMsgState) { if (mCopyState->m_parseMsgState->m_newMsgHdr) mCopyState->m_parseMsgState->m_newMsgHdr->GetMessageKey(&mCopyState->m_curDstKey); mCopyState->m_parseMsgState->SetEnvelopePos(filePos); 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); // *** jt - hard code status line for now; come back later nsresult rv; nsCOMPtr curSourceMessage = do_QueryElementAt(mCopyState->m_messages, mCopyState->m_curCopyIndex, &rv); char statusStrBuf[50]; if (curSourceMessage) { uint32_t dbFlags = 0; curSourceMessage->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); uint32_t bytesWritten; mCopyState->m_fileStream->Write(result.get(), result.Length(), &bytesWritten); if (mCopyState->m_parseMsgState) mCopyState->m_parseMsgState->ParseAFolderLine( result.get(), result.Length()); 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); bool reusable; rv = mCopyState->m_msgStore-> GetNewMsgOutputStream(this, getter_AddRefs(mCopyState->m_newHdr), &reusable, 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(nsIMsgDBHdr *message) { if (!mCopyState) return NS_ERROR_NULL_POINTER; nsresult rv; if (!mCopyState->m_copyingMultipleMessages) { rv = InitCopyMsgHdrAndFileStream(); NS_ENSURE_SUCCESS(rv, rv); } nsCOMPtr seekableStream = do_QueryInterface(mCopyState->m_fileStream, &rv); // XXX ToDo: When copying multiple messages from a non-offline-enabled IMAP // server, this fails. (The copy succeeds because the file stream is created // subsequently in StartMessage) We should not be warning on an expected error. // Perhaps there are unexpected consequences of returning early? NS_ENSURE_SUCCESS(rv, rv); seekableStream->Seek(nsISeekableStream::NS_SEEK_END, 0); 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 mCopyState->m_messages->QueryElementAt(messageIndex, NS_GET_IID(nsIMsgDBHdr), (void **)getter_AddRefs(mCopyState->m_message)); // 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() : rv; } 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(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; } nsCOMPtr seekableStream = do_QueryInterface(mCopyState->m_fileStream, &rv); NS_ENSURE_SUCCESS(rv, rv); seekableStream->Seek(nsISeekableStream::NS_SEEK_END, 0); 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 = '>'; } 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 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", getter_Copies(dontPreserve)); else prefBranch->GetCharPref("mailnews.database.summary.dontPreserveOnCopy", getter_Copies(dontPreserve)); CopyHdrPropertiesWithSkipList(destHdr, srcHdr, dontPreserve); } void nsMsgLocalMailFolder::CopyHdrPropertiesWithSkipList(nsIMsgDBHdr *destHdr, nsIMsgDBHdr *srcHdr, const nsCString &skipList) { nsCOMPtr propertyEnumerator; nsresult rv = srcHdr->GetPropertyEnumerator(getter_AddRefs(propertyEnumerator)); NS_ENSURE_SUCCESS_VOID(rv); // We'll add spaces at beginning and end so we can search for space-name-space nsCString dontPreserveEx(NS_LITERAL_CSTRING(" ")); dontPreserveEx.Append(skipList); dontPreserveEx.AppendLiteral(" "); nsAutoCString property; nsCString sourceString; bool hasMore; while (NS_SUCCEEDED(propertyEnumerator->HasMore(&hasMore)) && hasMore) { propertyEnumerator->GetNext(property); nsAutoCString propertyEx(NS_LITERAL_CSTRING(" ")); propertyEx.Append(property); propertyEx.AppendLiteral(" "); if (dontPreserveEx.Find(propertyEx) != -1) // -1 is not found continue; srcHdr->GetStringProperty(property.get(), getter_Copies(sourceString)); destHdr->SetStringProperty(property.get(), sourceString.get()); } nsMsgLabelValue label = 0; srcHdr->GetLabel(&label); destHdr->SetLabel(label); } 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, false /*dbBatching*/); //dest folder doesn't need db batching } return NS_OK; } bool multipleCopiesFinished = (mCopyState->m_curCopyIndex >= mCopyState->m_totalMsgCount); RefPtr 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. nsCOMPtr seekableStream(do_QueryInterface(mCopyState->m_fileStream)); if (seekableStream) { seekableStream->Seek(nsISeekableStream::NS_SEEK_END, 0); 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 newHdr; if (!mCopyState->m_parseMsgState) { if (mCopyState->m_destDB) { if (mCopyState->m_newHdr) { newHdr = mCopyState->m_newHdr; CopyHdrPropertiesWithSkipList(newHdr, mCopyState->m_message, NS_LITERAL_CSTRING("storeToken msgOffset")); // UpdateNewMsgHdr(mCopyState->m_message, newHdr); // 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, false); } } // 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; uint32_t statusOffset; mCopyState->m_message->GetMessageKey(&aKey); mCopyState->m_message->GetStatusOffset(&statusOffset); localUndoTxn->AddSrcKey(aKey); localUndoTxn->AddSrcStatusOffset(statusOffset); localUndoTxn->AddDstKey(mCopyState->m_curDstKey); } } } nsCOMPtr newHdr; // CopyFileMessage() and CopyMessages() from servers other than mailbox if (mCopyState->m_parseMsgState) { nsCOMPtr 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. 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 only deal here with the flags that might get changed, Read // and New, and trust upstream code for everything else. uint32_t readAndNew = nsMsgMessageFlags::New | nsMsgMessageFlags::Read; uint32_t newFlags; newHdr->GetFlags(&newFlags); newHdr->SetFlags( (newFlags & ~readAndNew) | ((mCopyState->m_flags) & readAndNew)); // Copy other message properties. CopyPropertiesToMsgHdr(newHdr, mCopyState->m_message, mCopyState->m_isMove); } 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, false); } // 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 nsCOMPtr aSupport = do_QueryElementAt(mCopyState->m_messages, mCopyState->m_curCopyIndex); rv = CopyMessageTo(aSupport, this, 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->GetLength(&numHdrs); 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 notifier(do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID)); if (notifier) notifier->NotifyMsgsMoveCopyCompleted(mCopyState->m_isMove, mCopyState->m_messages, this, mCopyState->m_destMessages); } if (!mCopyState->m_isMove) { if (multipleCopiesFinished) { nsCOMPtr srcFolder; srcFolder = do_QueryInterface(mCopyState->m_srcSupport); if (mCopyState->m_isFolder) CopyAllSubFolders(srcFolder, nullptr, nullptr); //Copy all subfolders then notify completion if (mCopyState->m_msgWindow && mCopyState->m_undoMsgTxn) { nsCOMPtr txnMgr; mCopyState->m_msgWindow->GetTransactionManager(getter_AddRefs(txnMgr)); if (txnMgr) txnMgr->DoTransaction(mCopyState->m_undoMsgTxn); } // enable the dest folder EnableNotifications(allMessageCountNotifications, true, false /*dbBatching*/); //dest folder doesn't need db batching 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 mDeleteOrMoveMsgCompletedAtom call. srcFolder->NotifyFolderEvent(mDeleteOrMoveMsgCompletedAtom); } (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 notifier(do_GetService(NS_MSGNOTIFICATIONSERVICE_CONTRACTID)); 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. nsCOMPtr oneHeaderArray = do_CreateInstance(NS_ARRAY_CONTRACTID); oneHeaderArray->AppendElement(newHdr, false); notifier->NotifyMsgsClassified(oneHeaderArray, 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 pPrefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID)); if (pPrefBranch) { pPrefBranch->GetBoolPref("mail.pop3.deleteFromServerOnMove", &gDeleteFromServerOnMove); gGotGlobalPrefs = true; } } return gDeleteFromServerOnMove; } NS_IMETHODIMP nsMsgLocalMailFolder::EndMove(bool moveSucceeded) { nsresult rv; if (!mCopyState) return NS_OK; if (!moveSucceeded || mCopyState->m_writeFailed) { //Notify that a completion finished. nsCOMPtr srcFolder = do_QueryInterface(mCopyState->m_srcSupport, &rv); NS_ENSURE_SUCCESS(rv, rv); srcFolder->NotifyFolderEvent(mDeleteOrMoveMsgFailedAtom); /* passing true because the messages that have been successfully copied have their corressponding 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, false /*dbBatching*/ ); //dest folder doesn't need db batching return NS_OK; } if (mCopyState && mCopyState->m_curCopyIndex >= mCopyState->m_totalMsgCount) { //Notify that a completion finished. nsCOMPtr srcFolder = do_QueryInterface(mCopyState->m_srcSupport, &rv); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr 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, false /*dbBatching*/); //dest folder doesn't need db batching // 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 mDeleteOrMoveMsgCompletedAtom call. srcFolder->NotifyFolderEvent(NS_SUCCEEDED(rv) ? mDeleteOrMoveMsgCompletedAtom : mDeleteOrMoveMsgFailedAtom); if (NS_SUCCEEDED(rv) && mCopyState->m_msgWindow && mCopyState->m_undoMsgTxn) { nsCOMPtr txnMgr; mCopyState->m_msgWindow->GetTransactionManager(getter_AddRefs(txnMgr)); if (txnMgr) txnMgr->DoTransaction(mCopyState->m_undoMsgTxn); } (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 localUndoTxn = mCopyState->m_undoMsgTxn; nsCOMPtr 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; nsCOMPtr seekableStream = do_QueryInterface(mCopyState->m_fileStream, &rv); NS_ENSURE_SUCCESS(rv, rv); seekableStream->Seek(nsISeekableStream::NS_SEEK_END, 0); 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 msgDb; nsCOMPtr newHdr; mCopyState->m_parseMsgState->FinishHeader(); rv = mCopyState->m_parseMsgState->GetNewMsgHdr(getter_AddRefs(newHdr)); if (NS_SUCCEEDED(rv) && newHdr) { nsCOMPtr srcFolder = do_QueryInterface(mCopyState->m_srcSupport, &rv); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr srcDB; srcFolder->GetMsgDatabase(getter_AddRefs(srcDB)); if (srcDB) { nsCOMPtr 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(nsIArray *messages, nsTArray &keyArray, nsIMsgWindow *aMsgWindow, nsIMsgFolder *dstFolder, bool isMove) { if (!mCopyState) return NS_ERROR_OUT_OF_MEMORY; nsresult rv; nsCOMPtr copyStreamListener = do_CreateInstance(NS_COPYMESSAGESTREAMLISTENER_CONTRACTID, &rv); NS_ENSURE_SUCCESS(rv,rv); nsCOMPtr copyListener(do_QueryInterface(dstFolder, &rv)); NS_ENSURE_SUCCESS(rv, NS_ERROR_NO_INTERFACE); nsCOMPtr srcFolder(do_QueryInterface(mCopyState->m_srcSupport, &rv)); NS_ENSURE_SUCCESS(rv, NS_ERROR_NO_INTERFACE); rv = copyStreamListener->Init(srcFolder, copyListener, nullptr); if (NS_FAILED(rv)) return 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 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 srcLocalFolder = do_QueryInterface(srcFolder); if (srcLocalFolder) StartMessage(); nsCOMPtr dummyNull; rv = mCopyState->m_messageService->CopyMessages(keyArray.Length(), keyArray.Elements(), srcFolder, streamListener, isMove, nullptr, aMsgWindow, getter_AddRefs(dummyNull)); } return rv; } nsresult nsMsgLocalMailFolder::CopyMessageTo(nsISupports *message, nsIMsgFolder *dstFolder /* dst same as "this" */, nsIMsgWindow *aMsgWindow, bool isMove) { if (!mCopyState) return NS_ERROR_OUT_OF_MEMORY; nsresult rv; nsCOMPtr msgHdr(do_QueryInterface(message, &rv)); NS_ENSURE_SUCCESS(rv, NS_ERROR_NO_INTERFACE); mCopyState->m_message = do_QueryInterface(msgHdr, &rv); nsCOMPtr srcFolder(do_QueryInterface(mCopyState->m_srcSupport, &rv)); NS_ENSURE_SUCCESS(rv, NS_ERROR_NO_INTERFACE); nsCString uri; srcFolder->GetUriForMsg(msgHdr, uri); nsCOMPtr copyStreamListener = do_CreateInstance(NS_COPYMESSAGESTREAMLISTENER_CONTRACTID, &rv); NS_ENSURE_SUCCESS(rv,rv); nsCOMPtr copyListener(do_QueryInterface(dstFolder, &rv)); NS_ENSURE_SUCCESS(rv, NS_ERROR_NO_INTERFACE); rv = copyStreamListener->Init(srcFolder, copyListener, nullptr); 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 streamListener(do_QueryInterface(copyStreamListener, &rv)); NS_ENSURE_SUCCESS(rv, NS_ERROR_NO_INTERFACE); nsCOMPtr dummyNull; rv = mCopyState->m_messageService->CopyMessage(uri.get(), streamListener, isMove, nullptr, aMsgWindow, getter_AddRefs(dummyNull)); } 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(nsIArray *aMessages, int32_t aMark) { nsLocalFolderScanState folderScanState; nsCOMPtr curFolderPop3MailServer; nsCOMArray pop3Servers; // servers with msgs deleted... nsCOMPtr incomingServer; nsresult rv = GetServer(getter_AddRefs(incomingServer)); NS_ENSURE_SUCCESS(rv, NS_MSG_INVALID_OR_MISSING_SERVER); nsCOMPtr accountManager = do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &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); uint32_t srcCount; aMessages->GetLength(&srcCount); // 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 (uint32_t i = 0; i < srcCount; i++) { /* get uidl for this message */ nsCOMPtr msgDBHdr (do_QueryElementAt(aMessages, i, &rv)); uint32_t flags = 0; if (msgDBHdr) { msgDBHdr->GetFlags(&flags); nsCOMPtr 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 account; rv = accountManager->GetAccount(folderScanState.m_accountKey, getter_AddRefs(account)); if (NS_SUCCEEDED(rv) && account) { account->GetIncomingServer(getter_AddRefs(incomingServer)); nsCOMPtr 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, bool *aDoSelect) { uint32_t numMsgs; char *newMsgId; // This method is only invoked thru 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 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); #if DOWNLOAD_NOTIFY_STYLE == DOWNLOAD_NOTIFY_LAST msgDBHdr->GetMessageKey(&mDownloadOldKey); msgDBHdr->GetThreadParent(&mDownloadOldParent); msgDBHdr->GetFlags(&mDownloadOldFlags); mDatabase->DeleteHeader(msgDBHdr, nullptr, false, false); // Tell caller we want to select this message if (aDoSelect) *aDoSelect = true; #else mDatabase->DeleteHeader(msgDBHdr, nullptr, false, true); // Tell caller we want to select this message if (aDoSelect && mDownloadState == DOWNLOAD_STATE_GOTMSG) *aDoSelect = true; #endif mDownloadMessages.RemoveElementAt(i); break; } } } return NS_OK; } NS_IMETHODIMP nsMsgLocalMailFolder::SelectDownloadMsg() { #if DOWNLOAD_NOTIFY_STYLE == DOWNLOAD_NOTIFY_LAST if (mDownloadState >= DOWNLOAD_STATE_GOTMSG) { nsresult rv = GetDatabase(); if (!mDatabase) return rv; } mDatabase->NotifyKeyDeletedAll(mDownloadOldKey, mDownloadOldParent, mDownloadOldFlags, nullptr); } #endif if (mDownloadState == DOWNLOAD_STATE_GOTMSG && mDownloadWindow) { nsAutoCString newuri; nsBuildLocalMessageURI(mBaseMessageURI.get(), mDownloadSelectKey, newuri); nsCOMPtr windowCommands; mDownloadWindow->GetWindowCommands(getter_AddRefs(windowCommands)); if (windowCommands) windowCommands->SelectMessage(newuri); mDownloadState = DOWNLOAD_STATE_DIDSEL; } return NS_OK; } NS_IMETHODIMP nsMsgLocalMailFolder::DownloadMessagesForOffline(nsIArray *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 uint32_t srcCount; aMessages->GetLength(&srcCount); nsresult rv; for (uint32_t i = 0; i < srcCount; i++) { nsCOMPtr msgDBHdr (do_QueryElementAt(aMessages, i, &rv)); if (NS_SUCCEEDED(rv)) { uint32_t flags = 0; msgDBHdr->GetFlags(&flags); if (flags & nsMsgMessageFlags::Partial) mDownloadMessages.AppendElement(msgDBHdr); } } mDownloadWindow = aWindow; nsCOMPtr server; rv = GetServer(getter_AddRefs(server)); NS_ENSURE_SUCCESS(rv, NS_MSG_INVALID_OR_MISSING_SERVER); nsCOMPtr localMailServer = do_QueryInterface(server, &rv); NS_ENSURE_SUCCESS(rv, NS_MSG_INVALID_OR_MISSING_SERVER); return localMailServer->GetNewMail(aWindow, this, this, nullptr); } NS_IMETHODIMP nsMsgLocalMailFolder::NotifyDelete() { NotifyFolderEvent(mDeleteOrMoveMsgCompletedAtom); 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 url = do_CreateInstance(NS_STANDARDURL_CONTRACTID, &rv); if (NS_FAILED(rv)) return rv; rv = url->SetSpec(mURI); if (NS_FAILED(rv)) return rv; nsCOMPtr accountManager = do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv); if (NS_FAILED(rv)) return rv; nsCOMPtr server; // try "none" first url->SetScheme(NS_LITERAL_CSTRING("none")); rv = accountManager->FindServerByURI(url, false, getter_AddRefs(server)); if (NS_SUCCEEDED(rv) && server) mType.AssignLiteral("none"); else { // next try "pop3" url->SetScheme(NS_LITERAL_CSTRING("pop3")); rv = accountManager->FindServerByURI(url, false, getter_AddRefs(server)); if (NS_SUCCEEDED(rv) && server) mType.AssignLiteral("pop3"); else { // next try "rss" url->SetScheme(NS_LITERAL_CSTRING("rss")); rv = accountManager->FindServerByURI(url, false, getter_AddRefs(server)); if (NS_SUCCEEDED(rv) && server) mType.AssignLiteral("rss"); else { #ifdef HAVE_MOVEMAIL // next try "movemail" url->SetScheme(NS_LITERAL_CSTRING("movemail")); rv = accountManager->FindServerByURI(url, false, getter_AddRefs(server)); if (NS_SUCCEEDED(rv) && server) mType.AssignLiteral("movemail"); #endif /* HAVE_MOVEMAIL */ } } } } aServerType = mType; return NS_OK; } nsresult nsMsgLocalMailFolder::CreateBaseMessageURI(const nsACString& aURI) { return nsCreateLocalBaseMessageURI(aURI, mBaseMessageURI); } NS_IMETHODIMP nsMsgLocalMailFolder::OnStartRunningUrl(nsIURI * aUrl) { nsresult rv; nsCOMPtr 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 popsink; rv = popurl->GetPop3Sink(getter_AddRefs(popsink)); if (NS_SUCCEEDED(rv)) { popsink->SetBaseMessageUri(mBaseMessageURI.get()); nsCString messageuri; popurl->GetMessageUri(getter_Copies(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 mailSession = do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr msgWindow; rv = mailSession->GetTopmostMsgWindow(getter_AddRefs(msgWindow)); nsAutoCString aSpec; if (aUrl) { rv = aUrl->GetSpec(aSpec); NS_ENSURE_SUCCESS(rv, rv); } if (strstr(aSpec.get(), "uidl=")) { nsCOMPtr popurl = do_QueryInterface(aUrl, &rv); if (NS_SUCCEEDED(rv)) { nsCString messageuri; rv = popurl->GetMessageUri(getter_Copies(messageuri)); if (NS_SUCCEEDED(rv)) { NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr msgDBHdr; rv = GetMsgDBHdrFromURI(messageuri.get(), getter_AddRefs(msgDBHdr)); if (NS_SUCCEEDED(rv)) { GetDatabase(); if (mDatabase) mDatabase->DeleteHeader(msgDBHdr, nullptr, true, true); } nsCOMPtr pop3sink; nsCString newMessageUri; rv = popurl->GetPop3Sink(getter_AddRefs(pop3sink)); if (NS_SUCCEEDED(rv)) { pop3sink->GetMessageUri(getter_Copies(newMessageUri)); if (msgWindow) { nsCOMPtr windowCommands; msgWindow->GetWindowCommands(getter_AddRefs(windowCommands)); if (windowCommands) windowCommands->SelectMessage(newMessageUri); } } } } } 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 saveReparseListener = mReparseListener; mReparseListener = nullptr; saveReparseListener->OnStopRunningUrl(aUrl, aExitCode); } } if (mFlags & nsMsgFolderFlags::Inbox) { // if we are the inbox and running pop url nsCOMPtr popurl = do_QueryInterface(aUrl, &rv); if (NS_SUCCEEDED(rv)) { nsCOMPtr 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 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 bundleService = mozilla::services::GetStringBundleService(); 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; const char16_t * stringArray[] = { numMsgSoFarString.get(), totalMessagesString.get(), folderName.get() }; rv = mCopyState->m_stringBundle->FormatStringFromName( (mCopyState->m_isMove) ? u"movingMessagesStatus" : u"copyingMessagesStatus", stringArray, 3, getter_Copies(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(NS_LITERAL_STRING("Inbox"), nsMsgFolderFlags::Inbox); if (flags & nsMsgFolderFlags::SentMail) setSubfolderFlag(NS_LITERAL_STRING("Sent"), nsMsgFolderFlags::SentMail); if (flags & nsMsgFolderFlags::Drafts) setSubfolderFlag(NS_LITERAL_STRING("Drafts"), nsMsgFolderFlags::Drafts); if (flags & nsMsgFolderFlags::Templates) setSubfolderFlag(NS_LITERAL_STRING("Templates"), nsMsgFolderFlags::Templates); if (flags & nsMsgFolderFlags::Trash) setSubfolderFlag(NS_LITERAL_STRING("Trash"), nsMsgFolderFlags::Trash); if (flags & nsMsgFolderFlags::Queue) setSubfolderFlag(NS_LITERAL_STRING("Unsent Messages"), nsMsgFolderFlags::Queue); if (flags & nsMsgFolderFlags::Junk) setSubfolderFlag(NS_LITERAL_STRING("Junk"), nsMsgFolderFlags::Junk); if (flags & nsMsgFolderFlags::Archive) setSubfolderFlag(NS_LITERAL_STRING("Archives"), 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 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 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(); nsCOMPtr compactCompletedAtom; compactCompletedAtom = MsgGetAtom("CompactCompleted"); NotifyFolderEvent(compactCompletedAtom); return NS_OK; } NS_IMETHODIMP nsMsgLocalMailFolder::Shutdown(bool shutdownChildren) { mInitialized = false; return nsMsgDBFolder::Shutdown(shutdownChildren); } NS_IMETHODIMP nsMsgLocalMailFolder::OnMessageClassified(const char *aMsgURI, nsMsgJunkStatus aClassification, uint32_t aJunkPercent) { nsCOMPtr server; nsresult rv = GetServer(getter_AddRefs(server)); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr spamSettings; rv = server->GetSpamSettings(getter_AddRefs(spamSettings)); NS_ENSURE_SUCCESS(rv, rv); nsCString spamFolderURI; rv = spamSettings->GetSpamFolderURI(getter_Copies(spamFolderURI)); NS_ENSURE_SUCCESS(rv,rv); if (aMsgURI) // not end of batch { nsCOMPtr 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 folder; rv = GetExistingFolder(spamFolderURI, getter_AddRefs(folder)); if (NS_SUCCEEDED(rv) && 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 = GetOrCreateFolder(spamFolderURI, nullptr /* aListener */); NS_ASSERTION(NS_SUCCEEDED(rv), "GetOrCreateFolder failed"); } } } rv = spamSettings->LogJunkHit(msgHdr, willMoveMessage); NS_ENSURE_SUCCESS(rv,rv); } } } else // end of batch { // Parent will apply post bayes filters. nsMsgDBFolder::OnMessageClassified(nullptr, nsIJunkMailPlugin::UNCLASSIFIED, 0); nsCOMPtr messages(do_CreateInstance(NS_ARRAY_CONTRACTID)); if (!mSpamKeysToMove.IsEmpty()) { nsCOMPtr folder; if (!spamFolderURI.IsEmpty()) rv = GetExistingFolder(spamFolderURI, getter_AddRefs(folder)); 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 mailHdr; rv = GetMessageHeader(msgKey, getter_AddRefs(mailHdr)); if (NS_SUCCEEDED(rv) && mailHdr) messages->AppendElement(mailHdr, false); } else { // We don't need the processing flag any more. AndProcessingFlags(msgKey, ~nsMsgProcessingFlags::FilterToMove); } } if (folder) { nsCOMPtr copySvc = do_GetService(NS_MSGCOPYSERVICE_CONTRACTID, &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(rv), 16); spamSettings->LogJunkString(logMsg.get()); } } } int32_t numNewMessages; GetNumNewMessages(false, &numNewMessages); uint32_t length; messages->GetLength(&length); SetNumNewMessages(numNewMessages - 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, &aState->m_streamReusable, getter_AddRefs(aState->m_inputStream)); aState->m_seekableStream = do_QueryInterface(aState->m_inputStream); NS_ENSURE_SUCCESS(rv,rv); nsAutoPtr > lineBuffer(new nsLineBuffer); NS_ENSURE_TRUE(lineBuffer, NS_ERROR_OUT_OF_MEMORY); 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; } } } } if (!aState->m_streamReusable) { aState->m_inputStream->Close(); aState->m_inputStream = nullptr; } lineBuffer = 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) { const char *aMessages[] = {aMessage}; nsCOMPtr hdrs; nsresult rv = AddMessageBatch(1, aMessages, getter_AddRefs(hdrs)); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr hdr(do_QueryElementAt(hdrs, 0, &rv)); NS_ENSURE_SUCCESS(rv, rv); hdr.forget(aHdr); return rv; } NS_IMETHODIMP nsMsgLocalMailFolder::AddMessageBatch(uint32_t aMessageCount, const char **aMessages, nsIArray **aHdrArray) { NS_ENSURE_ARG_POINTER(aHdrArray); nsCOMPtr server; nsresult rv = GetServer(getter_AddRefs(server)); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr msgStore; nsCOMPtr outFileStream; nsCOMPtr newHdr; rv = server->GetMsgStore(getter_AddRefs(msgStore)); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr 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(this)); if (NS_SUCCEEDED(rv)) { nsCOMPtr hdrArray = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv); NS_ENSURE_SUCCESS(rv, rv); for (uint32_t i = 0; i < aMessageCount; i++) { RefPtr newMailParser = new nsParseNewMailState; NS_ENSURE_TRUE(newMailParser, NS_ERROR_OUT_OF_MEMORY); if (!mGettingNewMessages) newMailParser->DisableFilters(); bool reusable; rv = msgStore->GetNewMsgOutputStream(this, getter_AddRefs(newHdr), &reusable, 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 msgWindow; nsCOMPtr mailSession = do_GetService(NS_MSGMAILSESSION_CONTRACTID, &rv); if (NS_SUCCEEDED(rv)) mailSession->GetTopmostMsgWindow(getter_AddRefs(msgWindow)); rv = newMailParser->Init(rootFolder, this, msgWindow, newHdr, outFileStream); uint32_t bytesWritten, messageLen = strlen(aMessages[i]); outFileStream->Write(aMessages[i], messageLen, &bytesWritten); newMailParser->BufferInput(aMessages[i], messageLen); FinishNewLocalMessage(outFileStream, newHdr, msgStore, newMailParser); outFileStream->Close(); outFileStream = nullptr; newMailParser->OnStopRequest(nullptr, nullptr, NS_OK); newMailParser->EndMsgDownload(); hdrArray->AppendElement(newHdr, false); } NS_ADDREF(*aHdrArray = hdrArray); } ReleaseSemaphore(static_cast(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 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(nsMsgKey *aKeysToFetch, uint32_t aNumKeys, bool aLocalOnly, nsIUrlListener *aUrlListener, bool *aAsyncResults) { NS_ENSURE_ARG_POINTER(aKeysToFetch); NS_ENSURE_ARG_POINTER(aAsyncResults); *aAsyncResults = false; nsCOMPtr inputStream; nsCOMPtr msgStore; nsresult rv = GetMsgStore(getter_AddRefs(msgStore)); NS_ENSURE_SUCCESS(rv, rv); for (uint32_t i = 0; i < aNumKeys; i++) { nsCOMPtr msgHdr; nsCString prevBody; rv = GetMessageHeader(aKeysToFetch[i], getter_AddRefs(msgHdr)); NS_ENSURE_SUCCESS(rv, rv); // ignore messages that already have a preview body. msgHdr->GetStringProperty("preview", getter_Copies(prevBody)); if (!prevBody.IsEmpty()) continue; bool reusable; rv = GetMsgInputStream(msgHdr, &reusable, getter_AddRefs(inputStream)); NS_ENSURE_SUCCESS(rv,rv); rv = GetMsgPreviewTextFromStream(msgHdr, inputStream); } return rv; } NS_IMETHODIMP nsMsgLocalMailFolder::AddKeywordsToMessages(nsIArray *aMessages, const nsACString& aKeywords) { return ChangeKeywordForMessages(aMessages, aKeywords, true /* add */); } nsresult nsMsgLocalMailFolder::ChangeKeywordForMessages(nsIArray *aMessages, const nsACString& aKeywords, bool add) { nsresult rv = (add) ? nsMsgDBFolder::AddKeywordsToMessages(aMessages, aKeywords) : nsMsgDBFolder::RemoveKeywordsFromMessages(aMessages, aKeywords); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr msgStore; GetMsgStore(getter_AddRefs(msgStore)); NS_ENSURE_SUCCESS(rv, rv); return msgStore->ChangeKeywords(aMessages, aKeywords, add); } NS_IMETHODIMP nsMsgLocalMailFolder::RemoveKeywordsFromMessages(nsIArray *aMessages, const nsACString& aKeywords) { return ChangeKeywordForMessages(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", getter_Copies(keywordString)); aNewHdr->SetStringProperty("keywords", keywordString.get()); // 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", getter_Copies(junkScoreOrigin)); if (junkScoreOrigin.EqualsLiteral("plugin")) aNewHdr->SetStringProperty("junkscore", ""); return NS_OK; }